password
* You can add as many users as needed, eg.: array('scott' => 'tiger', 'samantha' => 'goldfish', 'gene' => 'alpaca')
*/
//$accessControl = array('scott' => 'tiger');
/**
* Uncomment to restrict databases-access to just the databases added to the array below
* uncommenting will also remove the ability to create a new database
*/
//moadminModel::$databaseWhitelist = array('admin');
/**
* Sets the design theme - themes options are: swanky-purse, trontastic, simple-gray and classic
*/
define('THEME', 'trontastic');
/**
* To connect to a remote or authenticated Mongo instance, define the connection string in the MONGO_CONNECTION constant
* mongodb://[username:password@]host1[:port1][,host2[:port2:],...]
* If you do not know what this means then it is not relevant to your application and you can safely leave it as-is
*/
define('MONGO_CONNECTION', '');
/**
* Set to true when connecting to a Mongo replicaSet
* If you do not know what this means then it is not relevant to your application and you can safely leave it as-is
*/
define('REPLICA_SET', false);
/**
* Default limit for number of objects to display per page - set to 0 for no limit
*/
define('OBJECT_LIMIT', 100);
/**
* Contributing-developers of the phpMoAdmin project should set this to true, everyone else can leave this as false
*/
define('DEBUG_MODE', false);
/**
* Vork core-functionality tools
*/
class get {
/**
* Opens up public access to config constants and variables and the cache object
* @var object
*/
public static $config;
/**
* Index of objects loaded, used to maintain uniqueness of singletons
* @var array
*/
public static $loadedObjects = array();
/**
* Is PHP Version 5.2.3 or better when htmlentities() added its fourth argument
* @var Boolean
*/
public static $isPhp523orNewer = true;
/**
* Gets the current URL
*
* @param array Optional, keys:
* get - Boolean Default: false - include GET URL if it exists
* abs - Boolean Default: false - true=absolute URL (aka. FQDN), false=just the path for relative links
* ssl - Boolean Default: null - true=https, false=http, unset/null=auto-selects the current protocol
* a true or false value implies abs=true
* @return string
*/
public static function url(array $args = array()) {
$ssl = null;
$get = false;
$abs = false;
extract($args);
if (!isset($_SERVER['HTTP_HOST']) && PHP_SAPI == 'cli') {
$_SERVER['HTTP_HOST'] = trim(`hostname`);
$argv = $_SERVER['argv'];
array_shift($argv);
$_SERVER['REDIRECT_URL'] = '/' . implode('/', $argv);
$get = false; // command-line has no GET
}
$url = (isset($_SERVER['REDIRECT_URL']) ? $_SERVER['REDIRECT_URL'] : $_SERVER['SCRIPT_NAME']);
if (substr($url, -1) == '/') { //strip trailing slash for URL consistency
$url = substr($url, 0, -1);
}
if (is_null($ssl) && $abs == true) {
$ssl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on');
}
if ($abs || !is_null($ssl)) {
$url = (!$ssl ? 'http://' : 'https://') . $_SERVER['HTTP_HOST'] . $url;
}
if ($get && isset($_SERVER['QUERY_STRING']) && $_SERVER['QUERY_STRING']) {
$url .= '?' . $_SERVER['QUERY_STRING'];
}
return ($url ? $url : '/');
}
/**
* Overloads the php function htmlentities and changes the default charset to UTF-8 and the default value for the
* fourth parameter $doubleEncode to false. Also adds ability to pass a null value to get the default $quoteStyle
* and $charset (removes need to repeatedly define ENT_COMPAT, 'UTF-8', just to access the $doubleEncode argument)
*
* If you are using a PHP version prior to 5.2.3 the $doubleEncode parameter is not available and won't do anything
*
* @param string $string
* @param int $quoteStyle Uses ENT_COMPAT if null or omitted
* @param string $charset Uses UTF-8 if null or omitted
* @param boolean $doubleEncode This is ignored in old versions of PHP before 5.2.3
* @return string
*/
public static function htmlentities($string, $quoteStyle = ENT_COMPAT, $charset = 'UTF-8', $doubleEncode = false) {
$quoteStyle = (!is_null($quoteStyle) ? $quoteStyle : ENT_COMPAT);
$charset = (!is_null($charset) ? $charset : 'UTF-8');
return (self::$isPhp523orNewer ? htmlentities($string, $quoteStyle, $charset, $doubleEncode)
: htmlentities($string, $quoteStyle, $charset));
}
/**
* Initialize the character maps needed for the xhtmlentities() method and verifies the argument values
* passed to it are valid.
*
* @param int $quoteStyle
* @param string $charset Only valid options are UTF-8 and ISO-8859-1 (Latin-1)
* @param boolean $doubleEncode
*/
protected static function initXhtmlentities($quoteStyle, $charset, $doubleEncode) {
$chars = get_html_translation_table(HTML_ENTITIES, $quoteStyle);
if (isset($chars)) {
unset($chars['<'], $chars['>']);
$charMaps[$quoteStyle]['ISO-8859-1'][true] = $chars;
$charMaps[$quoteStyle]['ISO-8859-1'][false] = array_combine(array_values($chars), $chars);
$charMaps[$quoteStyle]['UTF-8'][true] = array_combine(array_map('utf8_encode', array_keys($chars)), $chars);
$charMaps[$quoteStyle]['UTF-8'][false] = array_merge($charMaps[$quoteStyle]['ISO-8859-1'][false],
$charMaps[$quoteStyle]['UTF-8'][true]);
self::$loadedObjects['xhtmlEntities'] = $charMaps;
}
if (!isset($charMaps[$quoteStyle][$charset][$doubleEncode])) {
if (!isset($chars)) {
$invalidArgument = 'quoteStyle = ' . $quoteStyle;
} else if (!isset($charMaps[$quoteStyle][$charset])) {
$invalidArgument = 'charset = ' . $charset;
} else {
$invalidArgument = 'doubleEncode = ' . (string) $doubleEncode;
}
trigger_error('Undefined argument sent to xhtmlentities() method: ' . $invalidArgument, E_USER_NOTICE);
}
}
/**
* Converts special characters in a string to XHTML-valid ASCII encoding the same as htmlentities except
* this method allows the use of HTML tags within your string. Signature is the same as htmlentities except
* that the only character sets available (third argument) are UTF-8 (default) and ISO-8859-1 (Latin-1).
*
* @param string $string
* @param int $quoteStyle Constants available are ENT_NOQUOTES (default), ENT_QUOTES, ENT_COMPAT
* @param string $charset Only valid options are UTF-8 (default) and ISO-8859-1 (Latin-1)
* @param boolean $doubleEncode Default is false
* @return string
*/
public static function xhtmlentities($string, $quoteStyle = ENT_NOQUOTES, $charset = 'UTF-8',
$doubleEncode = false) {
$quoteStyles = array(ENT_NOQUOTES, ENT_QUOTES, ENT_COMPAT);
$quoteStyle = (!in_array($quoteStyle, $quoteStyles) ? current($quoteStyles) : $quoteStyle);
$charset = ($charset != 'ISO-8859-1' ? 'UTF-8' : $charset);
$doubleEncode = (Boolean) $doubleEncode;
if (!isset(self::$loadedObjects['xhtmlEntities'][$quoteStyle][$charset][$doubleEncode])) {
self::initXhtmlentities($quoteStyle, $charset, $doubleEncode);
}
return strtr($string, self::$loadedObjects['xhtmlEntities'][$quoteStyle][$charset][$doubleEncode]);
}
/**
* Loads an object as a singleton
*
* @param string $objectType
* @param string $objectName
* @return object
*/
protected static function _loadObject($objectType, $objectName) {
if (isset(self::$loadedObjects[$objectType][$objectName])) {
return self::$loadedObjects[$objectType][$objectName];
}
$objectClassName = $objectName . ucfirst($objectType);
if (class_exists($objectClassName)) {
$objectObject = new $objectClassName;
self::$loadedObjects[$objectType][$objectName] = $objectObject;
return $objectObject;
} else {
$errorMsg = 'Class for ' . $objectType . ' ' . $objectName . ' could not be found';
}
trigger_error($errorMsg, E_USER_WARNING);
}
/**
* Returns a helper object
*
* @param string $model
* @return object
*/
public static function helper($helper) {
if (is_array($helper)) {
array_walk($helper, array('self', __METHOD__));
return;
}
if (!isset(self::$config['helpers']) || !in_array($helper, self::$config['helpers'])) {
self::$config['helpers'][] = $helper;
}
return self::_loadObject('helper', $helper);
}
}
/**
* Public interface to load elements and cause redirects
*/
class load {
/**
* Sends a redirects header and disables view rendering
* This redirects via a browser command, this is not the same as changing controllers which is handled within MVC
*
* @param string $url Optional, if undefined this will refresh the page (mostly useful for dumping post values)
*/
public static function redirect($url = null) {
header('Location: ' . ($url ? $url : get::url(array('get' => true))));
}
}
/**
* Thrown when the mongod server is not accessible
*/
class cannotConnectToMongoServer extends Exception {
public function __toString() {
return '
Cannot connect to the MongoDB database.
' . PHP_EOL . 'If Mongo is installed then be sure that'
. ' an instance of the "mongod" server, not "mongo" shell, is running. ' . PHP_EOL
. 'Instructions and database download: http://vork.us/go/fhk4';
}
}
/**
* Thrown when the mongo extension for PHP is not installed
*/
class mongoExtensionNotInstalled extends Exception {
public function __toString() {
return '
PHP cannot access MongoDB, you need to install the Mongo extension for PHP.
'
. PHP_EOL . 'Instructions and driver download: '
. 'http://vork.us/go/tv27';
}
}
/**
* phpMoAdmin data model
*/
class moadminModel {
/**
* mongo connection - if a MongoDB object already exists (from a previous script) then only DB operations use this
* @var Mongo
*/
protected $_db;
/**
* Name of last selected DB
* @var string Defaults to admin as that is available in all Mongo instances
*/
public static $dbName = 'admin';
/**
* MongoDB
* @var MongoDB
*/
public $mongo;
/**
* Returns a new Mongo connection
* @return Mongo
*/
protected function _mongo() {
$connection = (!MONGO_CONNECTION ? 'mongodb://localhost:27017' : MONGO_CONNECTION);
$Mongo = (class_exists('MongoClient') === true ? 'MongoClient' : 'Mongo');
return (!REPLICA_SET ? new $Mongo($connection) : new $Mongo($connection, array('replicaSet' => true)));
}
/**
* Connects to a Mongo database if the name of one is supplied as an argument
* @param string $db
*/
public function __construct($db = null) {
if (self::$databaseWhitelist && !in_array($db, self::$databaseWhitelist)) {
$db = self::$dbName = $_GET['db'] = current(self::$databaseWhitelist);
}
if ($db) {
if (!extension_loaded('mongo')) {
throw new mongoExtensionNotInstalled();
}
try {
$this->_db = $this->_mongo();
$this->mongo = $this->_db->selectDB($db);
} catch (MongoConnectionException $e) {
throw new cannotConnectToMongoServer();
}
}
}
/**
* Executes a native JS MongoDB command
* This method is not currently used for anything
* @param string $cmd
* @return mixed
*/
protected function _exec($cmd) {
$exec = $this->mongo->execute($cmd);
return $exec['retval'];
}
/**
* Change the DB connection
* @param string $db
*/
public function setDb($db) {
if (self::$databaseWhitelist && !in_array($db, self::$databaseWhitelist)) {
$db = current(self::$databaseWhitelist);
}
if (!isset($this->_db)) {
$this->_db = $this->_mongo();
}
$this->mongo = $this->_db->selectDB($db);
self::$dbName = $db;
}
/**
* Total size of all the databases
* @var int
*/
public $totalDbSize = 0;
/**
* Adds ability to restrict databases-access to those on the whitelist
* @var array
*/
public static $databaseWhitelist = array();
/**
* Gets list of databases
* @return array
*/
public function listDbs() {
$return = array();
$restrictDbs = (bool) self::$databaseWhitelist;
$dbs = $this->_db->selectDB('admin')->command(array('listDatabases' => 1));
$this->totalDbSize = $dbs['totalSize'];
foreach ($dbs['databases'] as $db) {
if (!$restrictDbs || in_array($db['name'], self::$databaseWhitelist)) {
$return[$db['name']] = $db['name'] . ' ('
. (!$db['empty'] ? round($db['sizeOnDisk'] / 1000000) . 'mb' : 'empty') . ')';
}
}
ksort($return);
$dbCount = 0;
foreach ($return as $key => $val) {
$return[$key] = ++$dbCount . '. ' . $val;
}
return $return;
}
/**
* Generate system info and stats
* @return array
*/
public function getStats() {
$admin = $this->_db->selectDB('admin');
$return = $admin->command(array('buildinfo' => 1));
try {
$return = array_merge($return, $admin->command(array('serverStatus' => 1)));
} catch (MongoCursorException $e) {}
$profile = $admin->command(array('profile' => -1));
$return['profilingLevel'] = $profile['was'];
$return['mongoDbTotalSize'] = round($this->totalDbSize / 1000000) . 'mb';
$prevError = $admin->command(array('getpreverror' => 1));
if (!$prevError['n']) {
$return['previousDbErrors'] = 'None';
} else {
$return['previousDbErrors']['error'] = $prevError['err'];
$return['previousDbErrors']['numberOfOperationsAgo'] = $prevError['nPrev'];
}
if (isset($return['globalLock']['totalTime'])) {
$return['globalLock']['totalTime'] .= ' µSec';
}
if (isset($return['uptime'])) {
$return['uptime'] = round($return['uptime'] / 60) . ':' . str_pad($return['uptime'] % 60, 2, '0', STR_PAD_LEFT)
. ' minutes';
}
$unshift['mongo'] = $return['version'] . ' (' . $return['bits'] . '-bit)';
$unshift['mongoPhpDriver'] = Mongo::VERSION;
$unshift['phpMoAdmin'] = '1.1.4';
$unshift['php'] = PHP_VERSION . ' (' . (PHP_INT_MAX > 2200000000 ? 64 : 32) . '-bit)';
$unshift['gitVersion'] = $return['gitVersion'];
unset($return['ok'], $return['version'], $return['gitVersion'], $return['bits']);
$return = array_merge(array('version' => $unshift), $return);
$iniIndex = array(-1 => 'Unlimited', 'Off', 'On');
$phpIni = array('allow_persistent', 'auto_reconnect', 'chunk_size', 'cmd', 'default_host', 'default_port',
'max_connections', 'max_persistent');
foreach ($phpIni as $ini) {
$key = 'php_' . $ini;
$return[$key] = ini_get('mongo.' . $ini);
if (isset($iniIndex[$return[$key]])) {
$return[$key] = $iniIndex[$return[$key]];
}
}
return $return;
}
/**
* Repairs a database
* @return array Success status
*/
public function repairDb() {
return $this->mongo->repair();
}
/**
* Drops a database
*/
public function dropDb() {
$this->mongo->drop();
return;
if (!isset($this->_db)) {
$this->_db = $this->_mongo();
}
$this->_db->dropDB($this->mongo);
}
/**
* Gets a list of database collections
* @return array
*/
public function listCollections() {
$collections = array();
$MongoCollectionObjects = $this->mongo->listCollections();
foreach ($MongoCollectionObjects as $collection) {
$collection = substr(strstr((string) $collection, '.'), 1);
$collections[$collection] = $this->mongo->selectCollection($collection)->count();
}
ksort($collections);
return $collections;
}
/**
* Drops a collection
* @param string $collection
*/
public function dropCollection($collection) {
$this->mongo->selectCollection($collection)->drop();
}
/**
* Creates a collection
* @param string $collection
*/
public function createCollection($collection) {
if ($collection) {
$this->mongo->createCollection($collection);
}
}
/**
* Renames a collection
*
* @param string $from
* @param string $to
*/
public function renameCollection($from, $to) {
$result = $this->_db->selectDB('admin')->command(array(
'renameCollection' => self::$dbName . '.' . $from,
'to' => self::$dbName . '.' . $to,
));
}
/**
* Gets a list of the indexes on a collection
*
* @param string $collection
* @return array
*/
public function listIndexes($collection) {
return $this->mongo->selectCollection($collection)->getIndexInfo();
}
/**
* Ensures an index
*
* @param string $collection
* @param array $indexes
* @param array $unique
*/
public function ensureIndex($collection, array $indexes, array $unique) {
$unique = ($unique ? true : false); //signature requires a bool in both Mongo v. 1.0.1 and 1.2.0
$this->mongo->selectCollection($collection)->ensureIndex($indexes, $unique);
}
/**
* Removes an index
*
* @param string $collection
* @param array $index Must match the array signature of the index
*/
public function deleteIndex($collection, array $index) {
$this->mongo->selectCollection($collection)->deleteIndex($index);
}
/**
* Sort array - currently only used for collections
* @var array
*/
public $sort = array('_id' => 1);
/**
* Number of rows in the entire resultset (before limit-clause is applied)
* @var int
*/
public $count;
/**
* Array keys in the first and last object in a collection merged together (used to build sort-by options)
* @var array
*/
public $colKeys = array();
/**
* Get the records in a collection
*
* @param string $collection
* @return array
*/
public function listRows($collection) {
foreach ($this->sort as $key => $val) { //cast vals to int
$sort[$key] = (int) $val;
}
$col = $this->mongo->selectCollection($collection);
$find = array();
if (isset($_GET['find']) && $_GET['find']) {
$_GET['find'] = trim($_GET['find']);
if (strpos($_GET['find'], 'array') === 0) {
eval('$find = ' . $_GET['find'] . ';');
} else if (is_string($_GET['find'])) {
if ($findArr = json_decode($_GET['find'], true)) {
$find = $findArr;
}
}
}
if (isset($_GET['search']) && $_GET['search']) {
switch (substr(trim($_GET['search']), 0, 1)) { //first character
case '/': //regex
$find[$_GET['searchField']] = new mongoRegex($_GET['search']);
break;
case '{': //JSON
if ($search = json_decode($_GET['search'], true)) {
$find[$_GET['searchField']] = $search;
}
break;
case '(':
$types = array('bool', 'boolean', 'int', 'integer', 'float', 'double', 'string', 'array', 'object',
'null', 'mongoid');
$closeParentheses = strpos($_GET['search'], ')');
if ($closeParentheses) {
$cast = strtolower(substr($_GET['search'], 1, ($closeParentheses - 1)));
if (in_array($cast, $types)) {
$search = trim(substr($_GET['search'], ($closeParentheses + 1)));
if ($cast == 'mongoid') {
$search = new MongoID($search);
} else {
settype($search, $cast);
}
$find[$_GET['searchField']] = $search;
break;
}
} //else no-break
default: //text-search
if (strpos($_GET['search'], '*') === false) {
if (!is_numeric($_GET['search'])) {
$find[$_GET['searchField']] = $_GET['search'];
} else { //$_GET is always a string-type
$in = array((string) $_GET['search'], (int) $_GET['search'], (float) $_GET['search']);
$find[$_GET['searchField']] = array('$in' => $in);
}
} else { //text with wildcards
$regex = '/' . str_replace('\*', '.*', preg_quote($_GET['search'])) . '/i';
$find[$_GET['searchField']] = new mongoRegex($regex);
}
break;
}
}
$cols = (!isset($_GET['cols']) ? array() : array_fill_keys($_GET['cols'], true));
$cur = $col->find($find, $cols)->sort($sort);
$this->count = $cur->count();
//get keys of first object
if ($_SESSION['limit'] && $this->count > $_SESSION['limit'] //more results than per-page limit
&& (!isset($_GET['export']) || $_GET['export'] != 'nolimit')) {
if ($this->count > 1) {
$this->colKeys = phpMoAdmin::getArrayKeys($col->findOne());
}
$cur->limit($_SESSION['limit']);
if (isset($_GET['skip'])) {
if ($this->count <= $_GET['skip']) {
$_GET['skip'] = ($this->count - $_SESSION['limit']);
}
$cur->skip($_GET['skip']);
}
} else if ($this->count) { // results exist but are fewer than per-page limit
$this->colKeys = phpMoAdmin::getArrayKeys($cur->getNext());
} else if ($find && $col->count()) { //query is not returning anything, get cols from first obj in collection
$this->colKeys = phpMoAdmin::getArrayKeys($col->findOne());
}
//get keys of last or much-later object
if ($this->count > 1) {
$curLast = $col->find()->sort($sort);
if ($this->count > 2) {
$curLast->skip(min($this->count, 100) - 1);
}
$this->colKeys = array_merge($this->colKeys, phpMoAdmin::getArrayKeys($curLast->getNext()));
ksort($this->colKeys);
}
return $cur;
}
/**
* Returns a serialized element back to its native PHP form
*
* @param string $_id
* @param string $idtype
* @return mixed
*/
protected function _unserialize($_id, $idtype) {
if ($idtype == 'object' || $idtype == 'array') {
$errLevel = error_reporting();
error_reporting(0); //unserializing an object that is not serialized throws a warning
$_idObj = unserialize($_id);
error_reporting($errLevel);
if ($_idObj !== false) {
$_id = $_idObj;
}
} else if (gettype($_id) != $idtype) {
settype($_id, $idtype);
}
return $_id;
}
/**
* Removes an object from a collection
*
* @param string $collection
* @param string $_id
* @param string $idtype
*/
public function removeObject($collection, $_id, $idtype) {
$this->mongo->selectCollection($collection)->remove(array('_id' => $this->_unserialize($_id, $idtype)));
}
/**
* Retieves an object for editing
*
* @param string $collection
* @param string $_id
* @param string $idtype
* @return array
*/
public function editObject($collection, $_id, $idtype) {
return $this->mongo->selectCollection($collection)->findOne(array('_id' => $this->_unserialize($_id, $idtype)));
}
/**
* Saves an object
*
* @param string $collection
* @param string $obj
* @return array
*/
public function saveObject($collection, $obj) {
eval('$obj=' . $obj . ';'); //cast from string to array
return $this->mongo->selectCollection($collection)->save($obj);
}
/**
* Imports data into the current collection
*
* @param string $collection
* @param array $data
* @param string $importMethod Valid options are batchInsert, save, insert, update
*/
public function import($collection, array $data, $importMethod) {
$coll = $this->mongo->selectCollection($collection);
switch ($importMethod) {
case 'batchInsert':
foreach ($data as &$obj) {
$obj = unserialize($obj);
}
$coll->$importMethod($data);
break;
case 'update':
foreach ($data as $obj) {
$obj = unserialize($obj);
if (is_object($obj) && property_exists($obj, '_id')) {
$_id = $obj->_id;
} else if (is_array($obj) && isset($obj['_id'])) {
$_id = $obj['_id'];
} else {
continue;
}
$coll->$importMethod(array('_id' => $_id), $obj);
}
break;
default: //insert & save
foreach ($data as $obj) {
$coll->$importMethod(unserialize($obj));
}
break;
}
}
}
/**
* phpMoAdmin application control
*/
class moadminComponent {
/**
* $this->mongo is used to pass properties from component to view without relying on a controller to return them
* @var array
*/
public $mongo = array();
/**
* Model object
* @var moadminModel
*/
public static $model;
/**
* Removes the POST/GET params
*/
protected function _dumpFormVals() {
load::redirect(get::url() . '?action=listRows&db=' . urlencode($_GET['db'])
. '&collection=' . urlencode($_GET['collection']));
}
/**
* Routes requests and sets return data
*/
public function __construct() {
if (class_exists('mvc')) {
mvc::$view = '#moadmin';
}
$this->mongo['dbs'] = self::$model->listDbs();
if (isset($_GET['db'])) {
if (strpos($_GET['db'], '.') !== false) {
$_GET['db'] = $_GET['newdb'];
}
self::$model->setDb($_GET['db']);
}
if (isset($_POST['limit'])) {
$_SESSION['limit'] = (int) $_POST['limit'];
} else if (!isset($_SESSION['limit'])) {
$_SESSION['limit'] = OBJECT_LIMIT;
}
if (isset($_FILES['import']) && is_uploaded_file($_FILES['import']['tmp_name']) && isset($_GET['collection'])) {
$data = json_decode(file_get_contents($_FILES['import']['tmp_name']));
self::$model->import($_GET['collection'], $data, $_POST['importmethod']);
}
$action = (isset($_GET['action']) ? $_GET['action'] : 'listCollections');
if (isset($_POST['object'])) {
if (self::$model->saveObject($_GET['collection'], $_POST['object'])) {
return $this->_dumpFormVals();
} else {
$action = 'editObject';
$_POST['errors']['object'] = 'Error: object could not be saved - check your array syntax.';
}
} else if ($action == 'createCollection') {
self::$model->$action($_GET['collection']);
} else if ($action == 'renameCollection'
&& isset($_POST['collectionto']) && $_POST['collectionto'] != $_POST['collectionfrom']) {
self::$model->$action($_POST['collectionfrom'], $_POST['collectionto']);
$_GET['collection'] = $_POST['collectionto'];
$action = 'listRows';
}
if (isset($_GET['sort'])) {
self::$model->sort = array($_GET['sort'] => $_GET['sortdir']);
}
$this->mongo['listCollections'] = self::$model->listCollections();
if ($action == 'editObject') {
$this->mongo[$action] = (isset($_GET['_id'])
? self::$model->$action($_GET['collection'], $_GET['_id'], $_GET['idtype']) : '');
return;
} else if ($action == 'removeObject') {
self::$model->$action($_GET['collection'], $_GET['_id'], $_GET['idtype']);
return $this->_dumpFormVals();
} else if ($action == 'ensureIndex') {
foreach ($_GET['index'] as $key => $field) {
$indexes[$field] = (isset($_GET['isdescending'][$key]) && $_GET['isdescending'][$key] ? -1 : 1);
}
self::$model->$action($_GET['collection'], $indexes, ($_GET['unique'] == 'Unique' ? array('unique' => true)
: array()));
$action = 'listCollections';
} else if ($action == 'deleteIndex') {
self::$model->$action($_GET['collection'], unserialize($_GET['index']));
return $this->_dumpFormVals();
} else if ($action == 'getStats') {
$this->mongo[$action] = self::$model->$action();
unset($this->mongo['listCollections']);
} else if ($action == 'repairDb' || $action == 'getStats') {
$this->mongo[$action] = self::$model->$action();
$action = 'listCollections';
} else if ($action == 'dropDb') {
self::$model->$action();
load::redirect(get::url());
return;
}
if (isset($_GET['collection']) && $action != 'listCollections' && method_exists(self::$model, $action)) {
$this->mongo[$action] = self::$model->$action($_GET['collection']);
$this->mongo['count'] = self::$model->count;
$this->mongo['colKeys'] = self::$model->colKeys;
}
if ($action == 'listRows') {
$this->mongo['listIndexes'] = self::$model->listIndexes($_GET['collection']);
} else if ($action == 'dropCollection') {
return load::redirect(get::url() . '?db=' . urlencode($_GET['db']));
}
}
}
/**
* HTML helper tools
*/
class htmlHelper {
/**
* Internal storage of the link-prefix and hypertext protocol values
* @var string
*/
protected $_linkPrefix, $_protocol;
/**
* Internal list of included CSS & JS files used by $this->_tagBuilder() to assure that files are not included twice
* @var array
*/
protected $_includedFiles = array();
/**
* Flag array to avoid defining singleton JavaScript & CSS snippets more than once
* @var array
*/
protected $_jsSingleton = array(), $_cssSingleton = array();
/**
* Sets the protocol (http/https) - this is modified from the original Vork version for phpMoAdmin usage
*/
public function __construct() {
$this->_protocol = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ? 'https://' : 'http://');
}
/**
* Creates simple HTML wrappers, accessed via $this->__call()
*
* JS and CSS files are never included more than once even if requested twice. If DEBUG mode is enabled than the
* second request will be added to the debug log as a duplicate. The jsSingleton and cssSingleton methods operate
* the same as the js & css methods except that they will silently skip duplicate requests instead of logging them.
*
* jsInlineSingleton and cssInlineSingleton makes sure a JavaScript or CSS snippet will only be output once, even
* if echoed out multiple times and this method will attempt to place the JS code into the head section, if
* has already been echoed out then it will return the JS code inline the same as jsInline. Eg.:
* $helloJs = "function helloWorld() {alert('Hello World');}";
* echo $html->jsInlineSingleton($helloJs);
*
* Adding an optional extra argument to jsInlineSingleton/cssInlineSingleton will return the inline code bare (plus
* a trailing linebreak) if it cannot place it into the head section, this is used for joint JS/CSS statements:
* echo $html->jsInline($html->jsInlineSingleton($helloJs, true) . 'helloWorld();');
*
* @param string $tagType
* @param array $args
* @return string
*/
protected function _tagBuilder($tagType, $args = array()) {
$arg = current($args);
if (empty($arg) || $arg === '') {
$errorMsg = 'Missing argument for ' . __CLASS__ . '::' . $tagType . '()';
trigger_error($errorMsg, E_USER_WARNING);
}
if (is_array($arg)) {
foreach ($arg as $thisArg) {
$return[] = $this->_tagBuilder($tagType, array($thisArg));
}
$return = implode(PHP_EOL, $return);
} else {
switch ($tagType) {
case 'js':
case 'jsSingleton':
case 'css': //Optional extra argument to define CSS media type
case 'cssSingleton':
case 'jqueryTheme':
if ($tagType == 'jqueryTheme') {
$arg = $this->_protocol . 'ajax.googleapis.com/ajax/libs/jqueryui/1/themes/'
. str_replace(' ', '-', strtolower($arg)) . '/jquery-ui.css';
$tagType = 'css';
}
if (!isset($this->_includedFiles[$tagType][$arg])) {
if ($tagType == 'css' || $tagType == 'cssSingleton') {
$return = '';
} else {
$return = '';
}
$this->_includedFiles[$tagType][$arg] = true;
} else {
$return = null;
if (DEBUG_MODE && ($tagType == 'js' || $tagType == 'css')) {
debug::log($arg . $tagType . ' file has already been included', 'warn');
}
}
break;
case 'cssInline': //Optional extra argument to define CSS media type
$return = '';
break;
case 'jsInline':
$return = '';
break;
case 'jsInlineSingleton': //Optional extra argument to supress adding of inline JS/CSS wrapper
case 'cssInlineSingleton':
$tagTypeBase = substr($tagType, 0, -15);
$return = null;
$md5 = md5($arg);
if (!isset($this->{'_' . $tagTypeBase . 'Singleton'}[$md5])) {
$this->{'_' . $tagTypeBase . 'Singleton'}[$md5] = true;
if (!$this->_bodyOpen) {
$this->vorkHead[$tagTypeBase . 'Inline'][] = $arg;
} else {
$return = (!isset($args[1]) || !$args[1] ? $this->{$tagTypeBase . 'Inline'}($arg)
: $arg . PHP_EOL);
}
}
break;
case 'div':
case 'li':
case 'p':
case 'h1':
case 'h2':
case 'h3':
case 'h4':
$return = '<' . $tagType . '>' . $arg . '' . $tagType . '>';
break;
default:
$errorMsg = 'TagType ' . $tagType . ' not valid in ' . __CLASS__ . '::' . __METHOD__;
throw new Exception($errorMsg);
break;
}
}
return $return;
}
/**
* Creates virtual wrapper methods via $this->_tagBuilder() for the simple wrapper functions including:
* $html->css, js, cssInline, jsInline, div, li, p and h1-h4
*
* @param string $method
* @param array $arg
* @return string
*/
public function __call($method, $args) {
$validTags = array('css', 'js', 'cssSingleton', 'jsSingleton', 'jqueryTheme',
'cssInline', 'jsInline', 'jsInlineSingleton', 'cssInlineSingleton',
'div', 'li', 'p', 'h1', 'h2', 'h3', 'h4');
if (in_array($method, $validTags)) {
return $this->_tagBuilder($method, $args);
} else {
$errorMsg = 'Call to undefined method ' . __CLASS__ . '::' . $method . '()';
trigger_error($errorMsg, E_USER_ERROR);
}
}
/**
* Flag to make sure that header() can only be opened one-at-a-time and footer() can only be used after header()
* @var boolean
*/
private $_bodyOpen = false;
/**
* Sets the default doctype to XHTML 1.1
* @var string
*/
protected $_docType = '';
/**
* Allows modification of the docType
*
* Can either set to an actual doctype definition or to one of the presets (case-insensitive):
* XHTML Mobile 1.2
* XHTML Mobile 1.1
* XHTML Mobile 1.0
* Mobile 1.2 (alias for XHTML Mobile 1.2)
* Mobile 1.1 (alias for XHTML Mobile 1.1)
* Mobile 1.0 (alias for XHTML Mobile 1.0)
* Mobile (alias for the most-strict Mobile DTD, currently 1.2)
* XHTML 1.1 (this is the default DTD, there is no need to apply this method for an XHTML 1.1 doctype)
* XHTML (Alias for XHTML 1.1)
* XHTML 1.0 Strict
* XHTML 1.0 Transitional
* XHTML 1.0 Frameset
* XHTML 1.0 (Alias for XHTML 1.0 Strict)
* HTML 5
* HTML 4.01
* HTML (Alias for HTML 4.01)
*
* @param string $docType
*/
public function setDocType($docType) {
$docType = str_replace(' ', '', strtolower($docType));
if ($docType == 'xhtml1.1' || $docType == 'xhtml') {
return; //XHTML 1.1 is the default
} else if ($docType == 'xhtml1.0') {
$docType = 'strict';
}
$docType = str_replace(array('xhtml mobile', 'xhtml1.0'), array('mobile', ''), $docType);
$docTypes = array(
'mobile1.2' => '',
'mobile1.1' => '',
'mobile1.0' => '',
'strict' => '',
'transitional' => '',
'frameset' => '',
'html4.01' => '',
'html5' => ''
);
$docTypes['mobile'] = $docTypes['mobile1.2'];
$docTypes['html'] = $docTypes['html4.01'];
$this->_docType = (isset($docTypes[$docType]) ? $docTypes[$docType] : $docType);
}
/**
* Array used internally by Vork to cache JavaScript and CSS snippets and place them in the head section
* Changing the contents of this property may cause Vork components to be rendered incorrectly.
* @var array
*/
public $vorkHead = array();
/**
* Returns an HTML header and opens the body container
* This method will trigger an error if executed more than once without first calling
* the footer() method on the prior usage
* This is meant to be utilized within layouts, not views (but will work in either)
*
* @param array $args
* @return string
*/
public function header(array $args) {
if (!$this->_bodyOpen) {
$this->_bodyOpen = true;
extract($args);
$return = $this->_docType
. PHP_EOL . ''
. PHP_EOL . ''
. PHP_EOL . '' . $title . '';
if (!isset($metaheader['Content-Type'])) {
$metaheader['Content-Type'] = 'text/html; charset=utf-8';
}
foreach ($metaheader as $name => $content) {
$return .= PHP_EOL . '';
}
$meta['generator'] = 'Vork 2.00';
foreach ($meta as $name => $content) {
$return .= PHP_EOL . '';
}
if (isset($favicon)) {
$return .= PHP_EOL . '';
}
if (isset($animatedFavicon)) {
$return .= PHP_EOL . '';
}
$containers = array('css', 'cssInline', 'js', 'jsInline', 'jqueryTheme');
foreach ($containers as $container) {
if (isset($$container)) {
$return .= PHP_EOL . $this->$container($$container);
}
}
if ($this->vorkHead) { //used internally by Vork tools
foreach ($this->vorkHead as $container => $objArray) { //works only for inline code, not external files
$return .= PHP_EOL . $this->$container(implode(PHP_EOL, $objArray));
}
}
if (isset($head)) {
$return .= PHP_EOL . (is_array($head) ? implode(PHP_EOL, $head) : $head);
}
$return .= PHP_EOL . '' . PHP_EOL . '';
return $return;
} else {
$errorMsg = 'Invalid usage of ' . __METHOD__ . '() - the header has already been returned';
trigger_error($errorMsg, E_USER_NOTICE);
}
}
/**
* Returns an HTML footer and optional Google Analytics
* This method will trigger an error if executed without first calling the header() method
* This is meant to be utilized within layouts, not views (but will work in either)
*
* @param array $args
* @return string
*/
public function footer(array $args = array()) {
if ($this->_bodyOpen) {
$this->_bodyOpen = false;
return '';
} else {
$errorMsg = 'Invalid usage of ' . __METHOD__ . '() - header() has not been called';
trigger_error($errorMsg, E_USER_NOTICE);
}
}
/**
* Establishes a basic set of JavaScript tools, just echo $html->jsTools() before any JavaScript code that
* will use the tools.
*
* This method will only operate from the first occurrence in your code, subsequent calls will not output anything
* but you should add it anyway as it will make sure that your code continues to work if you later remove a
* previous call to jsTools.
*
* Tools provided:
*
* dom() method is a direct replacement for document.getElementById() that works in all JS-capable
* browsers Y2k and newer.
*
* vork object - defines a global vork storage space; use by appending your own properties, eg.: vork.widgetCount
*
* @param Boolean $noJsWrapper set to True if calling from within a $html->jsInline() wrapper
* @return string
*/
public function jsTools($noJsWrapper = false) {
return $this->jsInlineSingleton("var vork = function() {}
var dom = function(id) {
if (typeof document.getElementById != 'undefined') {
dom = function(id) {return document.getElementById(id);}
} else if (typeof document.all != 'undefined') {
dom = function(id) {return document.all[id];}
} else {
return false;
}
return dom(id);
}", $noJsWrapper);
}
/**
* Load a JavaScript library via Google's AJAX API
* http://code.google.com/apis/ajaxlibs/documentation/
*
* Version is optional and can be exact (1.8.2) or just version-major (1 or 1.8)
*
* Usage:
* echo $html->jsLoad('jquery');
* echo $html->jsLoad(array('yui', 'mootools'));
* echo $html->jsLoad(array('yui' => 2.7, 'jquery', 'dojo' => '1.3.1', 'scriptaculous'));
*
* //You can also use the Google API format JSON-decoded in which case version is required & name must be lowercase
* $jsLibs = array(array('name' => 'mootools', 'version' => 1.2, 'base_domain' => 'ditu.google.cn'), array(...));
* echo $html->jsLoad($jsLibs);
*
* @param mixed $library Can be a string, array(str1, str2...) or , array(name1 => version1, name2 => version2...)
* or JSON-decoded Google API syntax array(array('name' => 'yui', 'version' => 2), array(...))
* @param mixed $version Optional, int or str, this is only used if $library is a string
* @param array $options Optional, passed to Google "optionalSettings" argument, only used if $library == str
* @return str
*/
public function jsLoad($library, $version = null, array $options = array()) {
$versionDefaults = array('swfobject' => 2, 'yui' => 2, 'ext-core' => 3, 'mootools' => 1.2);
if (!is_array($library)) { //jsLoad('yui')
$library = strtolower($library);
if (!$version) {
$version = (!isset($versionDefaults[$library]) ? 1 : $versionDefaults[$library]);
}
$library = array('name' => $library, 'version' => $version);
$library = array(!$options ? $library : array_merge($library, $options));
} else {
foreach ($library as $key => $val) {
if (!is_array($val)) {
if (is_int($key)) { //jsLoad(array('yui', 'prototype'))
$val = strtolower($val);
$version = (!isset($versionDefaults[$val]) ? 1 : $versionDefaults[$val]);
$library[$key] = array('name' => $val, 'version' => $version);
} else if (!is_array($val)) { // //jsLoad(array('yui' => '2.8.0r4', 'prototype' => 1.6))
$library[$key] = array('name' => strtolower($key), 'version' => $val);
}
}
}
}
$url = $this->_protocol . 'www.google.com/jsapi';
if (!isset($this->_includedFiles['js'][$url])) { //autoload library
$this->_includedFiles['js'][$url] = true;
$url .= '?autoload=' . urlencode(json_encode(array('modules' => array_values($library))));
$return = $this->js($url);
} else { //load inline
foreach ($library as $lib) {
$js = 'google.load("' . $lib['name'] . '", "' . $lib['version'] . '"';
if (count($lib) > 2) {
unset($lib['name'], $lib['version']);
$js .= ', ' . json_encode($lib);
}
$jsLoads[] = $js . ');';
}
$return = $this->jsInline(implode(PHP_EOL, $jsLoads));
}
return $return;
}
/**
* Takes an array of key-value pairs and formats them in the syntax of HTML-container properties
*
* @param array $properties
* @return string
*/
public static function formatProperties(array $properties) {
$return = array();
foreach ($properties as $name => $value) {
$return[] = $name . '="' . get::htmlentities($value) . '"';
}
return implode(' ', $return);
}
/**
* Creates an anchor or link container
*
* @param array $args
* @return string
*/
public function anchor(array $args) {
if (!isset($args['text']) && isset($args['href'])) {
$args['text'] = $args['href'];
}
if (!isset($args['title']) && isset($args['text'])) {
$args['title'] = str_replace(array("\n", "\r"), ' ', strip_tags($args['text']));
}
$return = '';
if (isset($args['ajaxload'])) {
$return = $this->jsSingleton('/js/ajax.js');
$onclick = "return ajax.load('" . $args['ajaxload'] . "', this.href);";
$args['onclick'] = (!isset($args['onclick']) ? $onclick : $args['onclick'] . '; ' . $onclick);
unset($args['ajaxload']);
}
$text = (isset($args['text']) ? $args['text'] : null);
unset($args['text']);
return $return . '' . $text . '';
}
/**
* Shortcut to access the anchor method
*
* @param str $href
* @param str $text
* @param array $args
* @return str
*/
public function link($href, $text = null, array $args = array()) {
if (strpos($href, 'http') !== 0) {
$href = $this->_linkPrefix . $href;
}
$args['href'] = $href;
if ($text !== null) {
$args['text'] = $text;
}
return $this->anchor($args);
}
/**
* Wrapper display computer-code samples
*
* @param str $str
* @return str
*/
public function code($str) {
return '' . str_replace(' ', ' ', nl2br(get::htmlentities($str))) . '';
}
/**
* Will return true if the number passed in is even, false if odd.
*
* @param int $number
* @return boolean
*/
public function isEven($number) {
return (Boolean) ($number % 2 == 0);
}
/**
* Internal incrementing integar for the alternator() method
* @var int
*/
private $alternator = 1;
/**
* Returns an alternating Boolean, useful to generate alternating background colors
* Eg.:
* $colors = array(true => 'gray', false => 'white');
* echo '
...
'; //gray background
* echo '
...
'; //white background
* echo '
...
'; //gray background
*
* @return Boolean
*/
public function alternator() {
return $this->isEven(++$this->alternator);
}
/**
* Creates a list from an array with automatic nesting
*
* @param array $list
* @param string $kvDelimiter Optional, sets delimiter to appear between keys and values
* @param string $listType Optional, must be a valid HTML list type, either "ul" (default) or "ol"
* @return string
*/
public function drillDownList(array $list, $kvDelimiter = ': ', $listType = 'ul') {
foreach ($list as $key => $val) {
$val = (is_array($val) ? $this->drillDownList($val, $kvDelimiter, $listType) : $val);
$str = trim($key && !is_int($key) ? $key . $kvDelimiter . $val : $val);
if ($str) {
$return[] = $this->li($str);
}
}
if (isset($return)) {
return ($listType ? '<' . $listType . '>' : '')
. implode(PHP_EOL, $return)
. ($listType ? '' . $listType . '>' : '');
}
}
/**
* Returns a list of notifications if there are any - similar to the Flash feature of Ruby on Rails
*
* @param mixed $messages String or an array of strings
* @param string $class
* @return string Returns null if there are no notifications to return
*/
public function getNotifications($messages, $class = 'errormessage') {
if (isset($messages) && $messages) {
return '
';
echo $html->li($html->link(get::url(array('get' => true)) . '&export=nolimit',
'Export full results of this query (ignoring limit and skip clauses)'));
echo $html->li($html->link(get::url(array('get' => true)) . '&export=limited',
'Export exactly the results visible on this page'));
echo '
';
echo '
';
echo $form->open(array('upload' => true));
echo $form->file(array('name' => 'import'));
echo $form->radios(array('name' => 'importmethod', 'value' => 'insert', 'options' => array(
'insert' => 'Insert: skips over duplicate records',
'save' => 'Save: overwrites duplicate records',
'update' => 'Update: overwrites only records that currently exist (skips new objects)',
'batchInsert' => 'Batch-Insert: Halt upon reaching first duplicate record (may result in partial dataset)',
)));
echo $form->submit(array('value' => 'Import records into this collection'));
echo $form->close();
echo '