<?php error_reporting(E_ALL | E_STRICT); /** * phpMoAdmin - built on a stripped-down version of the high-performance Vork Enterprise Framework * * www.phpMoAdmin.com * www.Vork.us * www.MongoDB.org * * @version 1.1.5 * @author Eric David Benari, Chief Architect, phpMoAdmin * @license GPL v3 - https://www.gnu.org/licenses/gpl-3.0.en.html */ /** * To enable password protection, uncomment below and then change the username => 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 '<h1>Cannot connect to the MongoDB database.</h1> ' . PHP_EOL . 'If Mongo is installed then be sure that' . ' an instance of the "mongod" server, not "mongo" shell, is running. <br />' . PHP_EOL . 'Instructions and database download: <a href="http://vork.us/go/fhk4">http://vork.us/go/fhk4</a>'; } } /** * Thrown when the mongo extension for PHP is not installed */ class mongoExtensionNotInstalled extends Exception { public function __toString() { return '<h1>PHP cannot access MongoDB, you need to install the Mongo extension for PHP.</h1> ' . PHP_EOL . 'Instructions and driver download: ' . '<a href="http://vork.us/go/tv27">http://vork.us/go/tv27</a>'; } } /** * 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 <head> * 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 = '<link rel="stylesheet" type="text/css" href="' . $arg . '"' . ' media="' . (isset($args[1]) ? $args[1] : 'all') . '" />'; } else { $return = '<script type="text/javascript" src="' . $arg . '"></script>'; } $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 = '<style type="text/css" media="' . (isset($args[1]) ? $args[1] : 'all') . '">' . PHP_EOL . '/*<![CDATA[*/' . PHP_EOL . '<!--' . PHP_EOL . $arg . PHP_EOL . '//-->' . PHP_EOL . '/*]]>*/' . PHP_EOL . '</style>'; break; case 'jsInline': $return = '<script type="text/javascript">' . PHP_EOL . '//<![CDATA[' . PHP_EOL . '<!--' . PHP_EOL . $arg . PHP_EOL . '//-->' . PHP_EOL . '//]]>' . PHP_EOL . '</script>'; 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 = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">'; /** * 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' => '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" ' . '"http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">', 'mobile1.1' => '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.1//EN ' . '"http://www.openmobilealliance.org/tech/DTD/xhtml-mobile11.dtd">', 'mobile1.0' => '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.0//EN" ' . '"http://www.wapforum.org/DTD/xhtml-mobile10.dtd">', 'strict' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ' . '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">', 'transitional' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ' . '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">', 'frameset' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" ' . '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">', 'html4.01' => '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" ' . '"http://www.w3.org/TR/html4/strict.dtd">', 'html5' => '<!DOCTYPE html>' ); $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 . '<html xmlns="http://www.w3.org/1999/xhtml">' . PHP_EOL . '<head>' . PHP_EOL . '<title>' . $title . '</title>'; if (!isset($metaheader['Content-Type'])) { $metaheader['Content-Type'] = 'text/html; charset=utf-8'; } foreach ($metaheader as $name => $content) { $return .= PHP_EOL . '<meta http-equiv="' . $name . '" content="' . $content . '" />'; } $meta['generator'] = 'Vork 2.00'; foreach ($meta as $name => $content) { $return .= PHP_EOL . '<meta name="' . $name . '" content="' . $content . '" />'; } if (isset($favicon)) { $return .= PHP_EOL . '<link rel="shortcut icon" href="' . $favicon . '" type="image/x-icon" />'; } if (isset($animatedFavicon)) { $return .= PHP_EOL . '<link rel="icon" href="' . $animatedFavicon . '" type="image/gif" />'; } $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 . '</head>' . PHP_EOL . '<body>'; 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 '</body></html>'; } 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 . '<a ' . self::formatProperties($args) . '>' . $text . '</a>'; } /** * 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 '<code>' . str_replace(' ', ' ', nl2br(get::htmlentities($str))) . '</code>'; } /** * 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 '<div style="background: ' . $colors[$html->alternator()] . ';">...</div>'; //gray background * echo '<div style="background: ' . $colors[$html->alternator()] . ';">...</div>'; //white background * echo '<div style="background: ' . $colors[$html->alternator()] . ';">...</div>'; //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 '<div class="' . $class . '">' . (is_array($messages) ? implode('<br />', $messages) : $messages) . '</div>'; } } } /** * Vork form-helper */ class formHelper { /** * Internal flag to keep track if a form tag has been opened and not yet closed * @var boolean */ private $_formOpen = false; /** * Internal form element counter * @var int */ private $_inputCounter = array(); /** * Converts dynamically-assigned array indecies to use an explicitely defined index * * @param string $name * @return string */ protected function _indexDynamicArray($name) { $dynamicArrayStart = strpos($name, '[]'); if ($dynamicArrayStart) { $prefix = substr($name, 0, $dynamicArrayStart); if (!isset($this->_inputCounter[$prefix])) { $this->_inputCounter[$prefix] = -1; } $name = $prefix . '[' . ++$this->_inputCounter[$prefix] . substr($name, ($dynamicArrayStart + 1)); } return $name; } /** * Form types that do not change value with user input * @var array */ protected $_staticTypes = array('hidden', 'submit', 'button', 'image'); /** * Sets the standard properties available to all input elements in addition to user-defined properties * Standard properties are: name, value, class, style, id * * @param array $args * @param array $propertyNames Optional, an array of user-defined properties * @return array */ protected function _getProperties(array $args, array $propertyNames = array()) { $method = (isset($this->_formOpen['method']) && $this->_formOpen['method'] == 'get' ? $_GET : $_POST); if (isset($args['name']) && (!isset($args['type']) || !in_array($args['type'], $this->_staticTypes))) { $arrayStart = strpos($args['name'], '['); if (!$arrayStart) { if (isset($method[$args['name']])) { $args['value'] = $method[$args['name']]; } } else { $name = $this->_indexDynamicArray($args['name']); if (preg_match_all('/\[(.*)\]/', $name, $arrayIndex)) { array_shift($arrayIndex); //dump the 0 index element containing full match string } $name = substr($name, 0, $arrayStart); if (isset($method[$name])) { $args['value'] = $method[$name]; if (!isset($args['type']) || $args['type'] != 'checkbox') { foreach ($arrayIndex as $idx) { if (isset($args['value'][current($idx)])) { $args['value'] = $args['value'][current($idx)]; } else { unset($args['value']); break; } } } } } } $return = array(); $validProperties = array_merge($propertyNames, array('name', 'value', 'class', 'style', 'id')); foreach ($validProperties as $propertyName) { if (isset($args[$propertyName])) { $return[$propertyName] = $args[$propertyName]; } } return $return; } /** * Begins a form * Includes a safety mechanism to prevent re-opening an already-open form * * @param array $args * @return string */ public function open(array $args = array()) { if (!$this->_formOpen) { if (!isset($args['method'])) { $args['method'] = 'post'; } $this->_formOpen = array('id' => (isset($args['id']) ? $args['id'] : true), 'method' => $args['method']); if (!isset($args['action'])) { $args['action'] = $_SERVER['REQUEST_URI']; } if (isset($args['upload']) && $args['upload'] && !isset($args['enctype'])) { $args['enctype'] = 'multipart/form-data'; } if (isset($args['legend'])) { $legend = $args['legend']; unset($args['legend']); if (!isset($args['title'])) { $args['title'] = $legend; } } else if (isset($args['title'])) { $legend = $args['title']; } if (isset($args['alert'])) { if ($args['alert']) { $alert = (is_array($args['alert']) ? implode('<br />', $args['alert']) : $args['alert']); } unset($args['alert']); } $return = '<form ' . htmlHelper::formatProperties($args) . '>' . PHP_EOL . '<fieldset>' . PHP_EOL; if (isset($legend)) { $return .= '<legend>' . $legend . '</legend>' . PHP_EOL; } if (isset($alert)) { $return .= $this->getErrorMessageContainer((isset($args['id']) ? $args['id'] : 'form'), $alert); } return $return; } else if (DEBUG_MODE) { $errorMsg = 'Invalid usage of ' . __METHOD__ . '() - a form is already open'; trigger_error($errorMsg, E_USER_NOTICE); } } /** * Closes a form if one is open * * @return string */ public function close() { if ($this->_formOpen) { $this->_formOpen = false; return '</fieldset></form>'; } else if (DEBUG_MODE) { $errorMsg = 'Invalid usage of ' . __METHOD__ . '() - there is no open form to close'; trigger_error($errorMsg, E_USER_NOTICE); } } /** * Adds label tags to a form element * * @param array $args * @param str $formElement * @return str */ protected function _getLabel(array $args, $formElement) { if (!isset($args['label']) && isset($args['name']) && (!isset($args['type']) || !in_array($args['type'], $this->_staticTypes))) { $args['label'] = ucfirst($args['name']); } if (isset($args['label'])) { $label = get::xhtmlentities($args['label']); if (isset($_POST['errors']) && isset($args['name']) && isset($_POST['errors'][$args['name']])) { $label .= ' ' . $this->getErrorMessageContainer($args['name'], $_POST['errors'][$args['name']]); } $labelFirst = (!isset($args['labelFirst']) || $args['labelFirst']); if (isset($args['id'])) { $label = '<label for="' . $args['id'] . '" id="' . $args['id'] . 'label">' . $label . '</label>'; } if (isset($args['addBreak']) && $args['addBreak']) { $label = ($labelFirst ? $label . '<br />' : '<br />' . $label); } $formElement = ($labelFirst ? $label . $formElement : $formElement . $label); if (!isset($args['id'])) { $formElement = '<label>' . $formElement . '</label>'; } } return $formElement; } /** * Returns a standardized container to wrap an error message * * @param string $id * @param string $errorMessage Optional, you may want to leave this blank and populate dynamically via JavaScript * @return string */ public function getErrorMessageContainer($id, $errorMessage = '') { return '<span class="errormessage" id="' . $id . 'errorwrapper">' . get::htmlentities($errorMessage) . '</span>'; } /** * Used for text, textarea, hidden, password, file, button, image and submit * * Valid args are any properties valid within an HTML input as well as label * * @param array $args * @return string */ public function input(array $args) { $args['type'] = (isset($args['type']) ? $args['type'] : 'text'); switch ($args['type']) { case 'select': return $this->select($args); break; case 'checkbox': return $this->checkboxes($args); break; case 'radio': return $this->radios($args); break; } if ($args['type'] == 'textarea' && isset($args['maxlength'])) { if (!isset($args['id']) && isset($args['name'])) { $args['id'] = $args['name']; } if (isset($args['id'])) { $maxlength = $args['maxlength']; } unset($args['maxlength']); } if ($args['type'] == 'submit' && !isset($args['class'])) { $args['class'] = $args['type']; } $takeFocus = (isset($args['focus']) && $args['focus'] && $args['type'] != 'hidden'); if ($takeFocus && !isset($args['id'])) { if (isset($args['name'])) { $args['id'] = $args['name']; } else { $takeFocus = false; if (DEBUG_MODE) { $errorMsg = 'Either name or id is required to use the focus option on a form input'; trigger_error($errorMsg, E_USER_NOTICE); } } } $properties = $this->_getProperties($args, array('type', 'maxlength')); if ($args['type'] == 'image') { $properties['src'] = $args['src']; $properties['alt'] = (isset($args['alt']) ? $args['alt'] : ''); $optionalProperties = array('height', 'width'); foreach ($optionalProperties as $optionalProperty) { if (isset($args[$optionalProperty])) { $properties[$optionalProperty] = $args[$optionalProperty]; } } } if ($args['type'] != 'textarea') { $return[] = '<input ' . htmlHelper::formatProperties($properties) . ' />'; } else { unset($properties['type']); if (isset($properties['value'])) { $value = $properties['value']; unset($properties['value']); } if (isset($args['preview']) && $args['preview'] && !isset($properties['id'])) { $properties['id'] = 'textarea_' . rand(100, 999); } $properties['rows'] = (isset($args['rows']) ? $args['rows'] : 13); $properties['cols'] = (isset($args['cols']) ? $args['cols'] : 55); $return[] = '<textarea ' . htmlHelper::formatProperties($properties); if (isset($maxlength)) { $return[] = ' onkeyup="document.getElementById(\'' . $properties['id'] . 'errorwrapper\').innerHTML = (this.value.length > ' . $maxlength . ' ? \'Form content exceeds maximum length of ' . $maxlength . ' characters\' : \'Length: \' + this.value.length + \' (maximum: ' . $maxlength . ' characters)\')"'; } $return[] = '>'; if (isset($value)) { $return[] = get::htmlentities($value, null, null, true); //double-encode allowed } $return[] = '</textarea>'; if (isset($maxlength) && (!isset($args['validatedInput']) || !$args['validatedInput'])) { $return[] = $this->getErrorMessageContainer($properties['id']); } } if (!isset($args['addBreak'])) { $args['addBreak'] = true; } if ($takeFocus) { $html = get::helper('html'); $return[] = $html->jsInline($html->jsTools(true) . 'dom("' . $args['id'] . '").focus();'); } if (isset($args['preview']) && $args['preview']) { $js = 'document.writeln(\'<div class="htmlpreviewlabel">' . '<label for="livepreview_' . $properties['id'] . '">Preview:</label></div>' . '<div id="livepreview_' . $properties['id'] . '" class="htmlpreview"></div>\');' . PHP_EOL . 'if (dom("livepreview_' . $properties['id'] . '")) {' . PHP_EOL . ' var updateLivePreview_' . $properties['id'] . ' = ' . 'function() {dom("livepreview_' . $properties['id'] . '").innerHTML = ' . 'dom("' . $properties['id'] . '").value};' . PHP_EOL . ' dom("' . $properties['id'] . '").onkeyup = updateLivePreview_' . $properties['id'] . ';' . ' updateLivePreview_' . $properties['id'] . '();' . PHP_EOL . '}'; if (!isset($html)) { $html = get::helper('html'); } $return[] = $html->jsInline($html->jsTools(true) . $js); } return $this->_getLabel($args, implode($return)); } /** * Returns a form select element * * $args = array( * 'name' => '', * 'multiple' => true, * 'leadingOptions' => array(), * 'optgroups' => array('group 1' => array('label' => 'g1o1', 'value' => 'grp 1 opt 1'), * 'group 2' => array(),), * 'options' => array('value1' => 'text1', 'value2' => 'text2', 'value3' => 'text3'), * 'value' => array('value2', 'value3') //if (multiple==false) 'value' => (str) 'value3' * ); * * @param array $args * @return str */ public function select(array $args) { if (!isset($args['id'])) { $args['id'] = $args['name']; } if (isset($args['multiple']) && $args['multiple']) { $args['multiple'] = 'multiple'; if (substr($args['name'], -2) != '[]') { $args['name'] .= '[]'; } } $properties = $this->_getProperties($args, array('multiple')); $values = (isset($properties['value']) ? $properties['value'] : null); unset($properties['value']); if (!is_array($values)) { $values = ($values != '' ? array($values) : array()); } $return = '<select ' . htmlHelper::formatProperties($properties) . '>'; if (isset($args['prependBlank']) && $args['prependBlank']) { $return .= '<option value=""></option>'; } if (isset($args['leadingOptions'])) { $useValues = (key($args['leadingOptions']) !== 0 || (isset($args['useValue']) && $args['useValue'])); foreach ($args['leadingOptions'] as $value => $text) { if (!$useValues) { $value = $text; } $return .= '<option value="' . get::htmlentities($value) . '"'; if (in_array((string) $value, $values)) { $return .= ' selected="selected"'; } $return .= '>' . get::htmlentities($text) . '</option>'; } } if (isset($args['optgroups'])) { foreach ($args['optgroups'] as $groupLabel => $optgroup) { $return .= '<optgroup label="' . get::htmlentities($groupLabel) . '">'; foreach ($optgroup as $value => $label) { $return .= '<option value="' . get::htmlentities($value) . '"'; if (isset($label)) { $return .= ' label="' . get::htmlentities($label) . '"'; } if (in_array((string) $value, $values)) { $return .= ' selected="selected"'; } $return .= '>' . get::htmlentities($label) . '</option>'; } $return .= '</optgroup>'; } } if (isset($args['options'])) { $useValues = (key($args['options']) !== 0 || (isset($args['useValue']) && $args['useValue'])); foreach ($args['options'] as $value => $text) { if (!$useValues) { $value = $text; } $return .= '<option value="' . get::htmlentities($value) . '"'; if (in_array((string) $value, $values)) { $return .= ' selected="selected"'; } $return .= '>' . get::htmlentities($text) . '</option>'; } } $return .= '</select>'; if (!isset($args['addBreak'])) { $args['addBreak'] = true; } $return = $this->_getLabel($args, $return); if (isset($args['error'])) { $return .= $this->getErrorMessageContainer($args['id'], '<br />' . $args['error']); } return $return; } /** * Cache containing individual radio or checkbox elements in an array * @var array */ public $radios = array(), $checkboxes = array(); /** * Returns a set of radio form elements * * array( * 'name' => '', * 'value' => '', * 'id' => '', * 'legend' => '', * 'options' => array('value1' => 'text1', 'value2' => 'text2', 'value3' => 'text3'), * 'options' => array('text1', 'text2', 'text3'), //also acceptable (cannot do half this, half above syntax) * ) * * @param array $args * @return str */ public function radios(array $args) { $id = (isset($args['id']) ? $args['id'] : $args['name']); $properties = $this->_getProperties($args); if (isset($properties['value'])) { $checked = $properties['value']; unset($properties['value']); } $properties['type'] = (isset($args['type']) ? $args['type'] : 'radio'); $useValues = (key($args['options']) !== 0 || (isset($args['useValue']) && $args['useValue'])); foreach ($args['options'] as $value => $text) { if (!$useValues) { $value = $text; } $properties['id'] = $id . '_' . preg_replace('/\W/', '', $value); $properties['value'] = $value; if (isset($checked) && ((($properties['type'] == 'radio' || !is_array($checked)) && $value == $checked) || ($properties['type'] == 'checkbox' && is_array($checked) && in_array((string) $value, $checked)))) { $properties['checked'] = 'checked'; $rowClass = (!isset($properties['class']) ? 'checked' : $properties['class'] . ' checked'); } $labelFirst = (isset($args['labelFirst']) ? $args['labelFirst'] : false); $labelArgs = array('label' => $text, 'id' => $properties['id'], 'labelFirst' => $labelFirst); $input = '<input ' . htmlHelper::formatProperties($properties) . ' />'; $row = $this->_getLabel($labelArgs, $input); if (isset($rowClass)) { $row = '<span class="' . $rowClass . '">' . $row . '</span>'; } $radios[] = $row; unset($properties['checked'], $rowClass); } $this->{$properties['type'] == 'radio' ? 'radios' : 'checkboxes'} = $radios; $break = (!isset($args['optionBreak']) ? '<br />' : $args['optionBreak']); $addFieldset = (isset($args['addFieldset']) ? $args['addFieldset'] : ((isset($args['label']) && $args['label']) || count($args['options']) > 1)); if ($addFieldset) { $return = '<fieldset id="' . $id . '">'; if (isset($args['label'])) { $return .= '<legend>' . get::htmlentities($args['label']) . '</legend>'; } $return .= implode($break, $radios) . '</fieldset>'; } else { $return = implode($break, $radios); } if (isset($_POST['errors']) && isset($_POST['errors'][$id])) { $return = $this->getErrorMessageContainer($id, $_POST['errors'][$id]) . $return; } return $return; } /** * Returns a set of checkbox form elements * * This method essentially extends the radios method and uses an identical signature except * that $args['value'] can also accept an array of values to be checked. * * @param array $args * @return str */ public function checkboxes(array $args) { $args['type'] = 'checkbox'; if (isset($args['value']) && !is_array($args['value'])) { $args['value'] = array($args['value']); } $nameParts = explode('[', $args['name']); if (!isset($args['id'])) { $args['id'] = $nameParts[0]; } if (!isset($nameParts[1]) && count($args['options']) > 1) { $args['name'] .= '[]'; } return $this->radios($args); } /** * Opens up shorthand usage of form elements like $form->file() and $form->submit() * * @param string $name * @param array $args * @return mixed */ public function __call($name, array $args) { $inputShorthand = array('text', 'textarea', 'password', 'file', 'hidden', 'submit', 'button', 'image'); if (in_array($name, $inputShorthand)) { $args[0]['type'] = $name; return $this->input($args[0]); } trigger_error('Call to undefined method ' . __CLASS__ . '::' . $name . '()', E_USER_ERROR); } } class jsonHelper { /** * Outputs content in JSON format * @param mixed $content Can be a JSON string or an array of any data that will automatically be converted to JSON * @param string $filename Default filename within the user-prompt for saving the JSON file */ public function echoJson($content, $filename = null) { header('Cache-Control: no-cache, must-revalidate'); header('Expires: Mon, 01 Jan 2000 01:00:00 GMT'); header('Content-type: application/json'); if ($filename) { header('Content-Disposition: attachment; filename=' . $filename); } echo (!is_array($content) && !is_object($content) ? $content : json_encode($content)); } } /** * phpMoAdmin specific functionality */ class phpMoAdmin { /** * Sets the depth limit for phpMoAdmin::getArrayKeys (and prevents an endless loop with self-referencing objects) */ const DRILL_DOWN_DEPTH_LIMIT = 8; /** * Retrieves all the keys & subkeys of an array recursively drilling down * * @param array $array * @param string $path * @param int $drillDownDepthCount * @return array */ public static function getArrayKeys(array $array, $path = '', $drillDownDepthCount = 0) { $return = array(); if ($drillDownDepthCount) { $path .= '.'; } if (++$drillDownDepthCount < self::DRILL_DOWN_DEPTH_LIMIT) { foreach ($array as $key => $val) { $return[$id] = $id = $path . $key; if (is_array($val)) { $return = array_merge($return, self::getArrayKeys($val, $id, $drillDownDepthCount)); } } } return $return; } /** * Strip slashes recursively - used only when magic quotes is enabled (this reverses magic quotes) * * @param mixed $val * @return mixed */ public static function stripslashes($val) { return (is_array($val) ? array_map(array('self', 'stripslashes'), $val) : stripslashes($val)); } } /** * phpMoAdmin bootstrap */ session_start(); if (get_magic_quotes_gpc()) { $_GET = phpMoAdmin::stripslashes($_GET); $_POST = phpMoAdmin::stripslashes($_POST); } if (isset($accessControl) && !isset($_SESSION['user']) && isset($_POST['username'])) { $_POST = array_map('trim', $_POST); if (isset($accessControl[$_POST['username']]) && $accessControl[$_POST['username']] == $_POST['password']) { $_SESSION['user'] = $_POST['username']; } else { $_POST['errors']['username'] = 'Incorrect username or password'; } } $isAuthenticated = (!isset($accessControl) || isset($_SESSION['user'])); $html = get::helper('html'); $ver = explode('.', phpversion()); get::$isPhp523orNewer = ($ver[0] >= 5 && ($ver[1] > 2 || ($ver[1] == 2 && $ver[2] >= 3))); $form = new formHelper; if ($isAuthenticated) { if (!isset($_GET['db'])) { $_GET['db'] = moadminModel::$dbName; } else if (strpos($_GET['db'], '.') !== false) { $_GET['db'] = $_GET['newdb']; } try { moadminComponent::$model = new moadminModel($_GET['db']); } catch(Exception $e) { echo $e; exit(0); } $mo = new moadminComponent; if (isset($_GET['export']) && isset($mo->mongo['listRows'])) { $rows = array(); foreach ($mo->mongo['listRows'] as $row) { $rows[] = serialize($row); } $filename = get::htmlentities($_GET['db']); if (isset($_GET['collection'])) { $filename .= '~' . get::htmlentities($_GET['collection']); } $filename .= '.json'; get::helper('json')->echoJson($rows, $filename); exit(0); } } /** * phpMoAdmin front-end view-element */ $headerArgs['title'] = (isset($_GET['action']) ? 'phpMoAdmin - ' . get::htmlentities($_GET['action']) : 'phpMoAdmin'); if (THEME != 'classic') { $headerArgs['jqueryTheme'] = (in_array(THEME, array('swanky-purse', 'trontastic', 'simple-gray')) ? THEME : 'classic'); } $headerArgs['cssInline'] = ' /* reset */ html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td { margin: 0; padding: 0; border: 0; outline: 0; font-size: 100%; vertical-align: baseline; background: transparent;} input, textarea {margin: 0; padding: 0;} body {line-height: 1;} blockquote, q {quotes: none;} blockquote:before, blockquote:after, q:before, q:after {content: ""; content: none;} :focus {outline: 0;} ins {text-decoration: none;} del {text-decoration: line-through;} table {border-collapse: collapse; border-spacing: 0;} html{color:#000;} caption,th{text-align:left;} h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;} abbr,acronym{font-variant:normal;} sup{vertical-align:text-top;} sub{vertical-align:text-bottom;} input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;} input,textarea,select{*font-size:100%;} legend{color:#000;} /* \*/ html, body{height:100%;} /* */ /* initialize */ html {background: #74736d;} body {margin: auto; width: 990px; font-family: "Arial"; font-size: small; background: #000000; color: #ffffff;} #bodycontent {padding: 10px; border: 0px solid;} textarea {width: 640px; height: 70px;} a, .textLink {text-decoration: none; color: #96f226; font-weight: bold;} a:hover, .textLink:hover {text-decoration: underline; color: #9fda58;} a:hover pre, h1 a:hover {text-decoration: none;} h1, h2, h3, h4 {margin-bottom: 3px;} h1, h2 {margin-left: -1px;} h1 {font-family: "Arial Black"; font-size: 27px; color: #b8ec79;} h1.midpageh1 {margin-top: 10px;} h2 {font-size: large; font-weight: bold; margin-top: 10px; color: #660000;} h3 {font-weight: bold; color: #687d1c;} h4 {font-weight: bold; color: #10478b;} p {margin-bottom: 10px; line-height: 1.75;} li {line-height: 1.5; margin-left: 15px;} .errormessage {color: #990000; font-weight: bold; background: #ffffff; border: 1px solid #ff0000; padding: 2px;} .rownumber {float: right; padding: 0px 5px 0px 5px; border-left: 1px dotted; border-bottom: 1px dotted; color: #ffffff; margin-top: 4px; margin-right: -1px;} .ui-widget-header .rownumber {margin-top: 2px; margin-right: 0px;} pre {border: 1px solid; margin: 1px; padding-left: 5px;} li .ui-widget-content {margin: 1px 1px 3px 1px;} #mongo_rows {padding-top: 10px;} #moadminlogo {color: #96f226; border: 0px solid; padding-left: 10px; font-size: 4px!important; width: 265px; height: 63px; overflow: hidden;}'; switch (THEME) { case 'swanky-purse': $headerArgs['cssInline'] .= ' html {background: #261803;} h1, .rownumber {color: #baaa5a;} body {background: #4c3a1d url(//jquery-ui.googlecode.com/svn/tags/1.7.2/themes/swanky-purse/images/ui-bg_diamond_25_675423_10x8.png) 50% 50% repeat;} #moadminlogo {color: #baaa5a;} li .ui-widget-header {margin: 0px 1px 0px 1px;} .ui-widget-header .rownumber {margin-top: 2px; margin-right: -1px;}'; break; case 'classic': $headerArgs['cssInline'] .= ' html, .ui-widget-header, button {background: #ccc78c;} .ui-widget-content, input.ui-state-hover {background: #edf2ed;} h1, .rownumber {color: #796f54;} body {background: #ffffcc; color: #000000;} #bodycontent {background: #ffffcc;} #moadminlogo, button {color: #bb0022;} a, .textLink {color: #990000;} a:hover, .textLink:hover {color: #7987ae;} li .ui-widget-header {margin: 0px 1px 0px 1px;} .rownumber {margin-top: 2px; margin-right: 0px;} .ui-dialog {border: 3px outset;} .ui-dialog .ui-dialog-titlebar {padding: 3px; margin: 1px; border: 3px ridge;} .ui-dialog #confirm {padding: 10px;} .ui-dialog .ui-icon-closethick, .ui-dialog button {float: right; margin: 4px;} .ui-dialog .ui-icon-closethick {margin-top: -13px;} body:first-of-type .ui-dialog .ui-icon-closethick {margin-top: -2px;} /*Chrome/Safari*/ .ui-resizable {position: relative;} .ui-resizable-handle {position: absolute;font-size: 0.1px;z-index: 99999; display: block;} .ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle {display: none;} .ui-resizable-n {cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0px;} .ui-resizable-s {cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0px;} .ui-resizable-e {cursor: e-resize; width: 7px; right: -5px; top: 0px; height: 100%;} .ui-resizable-w {cursor: w-resize; width: 7px; left: -5px; top: 0px; height: 100%;} .ui-resizable-se {cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px;} .ui-resizable-sw {cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px;} .ui-resizable-nw {cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px;} .ui-resizable-ne {cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}'; break; case 'simple-gray': $headerArgs['cssInline'] .= ' html, body {background: #eee; color: #333;} body {font-family: sans-serif, Arial;} pre {font-size: 13px; padding: 10px; margin: 0; background: #fff; border: none;} #moadminlogo {background: none;} a, .textLink {font-weight: normal;} a, .textLink, #moadminlogo, h1 {color: #0088cc;} a:hover, .textLink:hover {color: #005580;} li .ui-widget-content, li .ui-widget-header {background: #ffe; line-height: 2em; margin: 0 !important; border-bottom: 1px solid #eee;} ol a {font-weight: bold;} #mongo_rows ol {margin: 1em 0;} .ui-dialog, #mongo_rows ol li {border-radius: 4px; background: #f7f7f7; margin-bottom: .5em; padding: .5em; border: 1px solid #ddd;} .ui-dialog .ui-dialog-titlebar {padding: 3px; margin: 1px; border: 3px ridge;} .ui-dialog #confirm {padding: 10px;} .ui-dialog .ui-icon-closethick, .ui-dialog button {margin: 4px;} .ui-dialog .ui-icon-closethick {float: right; margin-top: -4px; font-size: large;} '; break; } echo $html->header($headerArgs); echo $html->jsLoad(array('jquery', 'jqueryui')); $baseUrl = $_SERVER['SCRIPT_NAME']; $db = (isset($_GET['db']) ? $_GET['db'] : (isset($_POST['db']) ? $_POST['db'] : 'admin')); //admin is in every Mongo DB $dbUrl = urlencode($db); $phpmoadmin = '<pre id="moadminlogo"> ,ggg, ,ggg,_,ggg, ,ggg, ,dPYb, dP""Y8dP""Y88P""Y8b dP""8I 8I IP\'`Yb Yb, `88\' `88\' `88 dP 88 8I I8 8I `" 88 88 88 dP 88 8I gg I8 8\' 88 88 88 ,8\' 88 8I "" gg,gggg, I8 dPgg, gg,gggg, 88 88 88 ,ggggg, d88888888 ,gggg,8I ,ggg,,ggg,,ggg, gg ,ggg,,ggg, I8P" "Yb I8dP" "8I I8P" "Yb 88 88 88 dP" "Y8 ,8" 88 dP" "Y8I ,8" "8P" "8P" "8, 88 ,8" "8P" "8, I8\' ,8i I8P I8 I8\' ,8i 88 88 88 i8\' ,8 dP ,8P Y8 i8\' ,8I I8 8I 8I 8I 88 I8 8I 8I ,I8 _ ,d8\' ,d8 I8,,I8 _ ,d8\' 88 88 Y8,,d8, ,d8 Yb,_,dP `8b,,d8, ,d8b,,dP 8I 8I Yb,_,88,_,dP 8I Yb, PI8 YY88888P88P `Y8PI8 YY88 88 88 `Y8P"Y8888P" "Y8P" `Y8P"Y8888P"`Y88P\' 8I 8I `Y88P""Y88P\' 8I `Y8 I8 I8 I8 I8 I8 I8 I8 I8 I8 I8 I8 I8 </pre>'; echo '<div id="bodycontent" class="ui-widget-content"><h1 style="float: right;">' . $html->link('http://www.phpmoadmin.com', $phpmoadmin, array('title' => 'phpMoAdmin')) . '</h1>'; if (isset($accessControl) && !isset($_SESSION['user'])) { echo $form->open(); echo $html->div($form->input(array('name' => 'username', 'focus' => true))); echo $html->div($form->password(array('name' => 'password'))); echo $html->div($form->submit(array('value' => 'Login', 'class' => 'ui-state-hover'))); echo $form->close(); exit(0); } echo '<div id="dbcollnav">'; $formArgs = array('method' => 'get'); if (isset($mo->mongo['repairDb'])) { $formArgs['alert'] = (isset($mo->mongo['repairDb']['ok']) && $mo->mongo['repairDb']['ok'] ? 'Database has been repaired and compacted' : 'Database could not be repaired'); } echo $form->open($formArgs); echo $html->div($form->select(array('name' => 'db', 'options' => $mo->mongo['dbs'], 'label' => '', 'value' => $db, 'addBreak' => false)) . $form->submit(array('value' => 'Change database', 'class' => 'ui-state-hover')) . ' <span style="font-size: xx-large;">' . get::htmlentities($db) . '</span> [' . $html->link("javascript: mo.repairDatabase('" . get::htmlentities($db) . "'); void(0);", 'repair database') . '] [' . $html->link("javascript: mo.dropDatabase('" . get::htmlentities($db) . "'); void(0);", 'drop database') . ']'); echo $form->close(); $js = 'var mo = {} mo.urlEncode = function(str) { return escape(str)' . '.replace(/\+/g, "%2B").replace(/%20/g, "+").replace(/\*/g, "%2A").replace(/\//g, "%2F").replace(/@/g, "%40"); } mo.repairDatabase = function(db) { mo.confirm("Are you sure that you want to repair and compact the " + db + " database?", function() { window.location.replace("' . $baseUrl . '?db=' . $dbUrl . '&action=repairDb"); }); } mo.dropDatabase = function(db) { mo.confirm("Are you sure that you want to drop the " + db + " database?", function() { mo.confirm("All the collections in the " + db + " database will be lost along with all the data within them!' . '\n\nAre you 100% sure that you want to drop this database?' . '\n\nLast chance to cancel!", function() { window.location.replace("' . $baseUrl . '?db=' . $dbUrl . '&action=dropDb"); }); }); }'; if (!moadminModel::$databaseWhitelist) { $js .= ' $("select[name=db]").prepend(\'<option value="new.database">Use new ==></option>\')' . '.after(\'<input type="text" name="newdb" name style="display: none;" />\').change(function() { ($(this).val() == "new.database" ? $("input[name=newdb]").show() : $("input[name=newdb]").hide()); });'; } $js .= ' mo.confirm = function(dialog, func, title) { if (typeof title == "undefined") { title = "Please confirm:"; } if (!$("#confirm").length) { $("#dbcollnav").append(\'<div id="confirm" style="display: none;"></div>\'); } mo.userFunc = func; //overcomes JS scope issues $("#confirm").html(dialog).attr("title", title).dialog({modal: true, buttons: { "Yes": function() {$(this).dialog("close"); mo.userFunc();}, Cancel: function() {$(this).dialog("close");} }}).dialog("open"); } '; echo $html->jsInline($js); if (isset($_GET['collection'])) { $collection = get::htmlentities($_GET['collection']); unset($_GET['collection']); } if (isset($mo->mongo['listCollections'])) { echo '<div id="mongo_collections">'; echo $form->open(array('method' => 'get')); echo $html->div($form->input(array('name' => 'collection', 'label' => '', 'addBreak' => false)) . $form->hidden(array('name' => 'action', 'value' => 'createCollection')) . $form->submit(array('value' => 'Add new collection', 'class' => 'ui-state-hover')) . $form->hidden(array('name' => 'db', 'value' => get::htmlentities($db))) . ' [' . $html->link($baseUrl . '?action=getStats', 'stats') . ']'); echo $form->close(); if (!$mo->mongo['listCollections']) { echo $html->div('No collections exist'); } else { echo '<ol>'; foreach ($mo->mongo['listCollections'] as $col => $rowCount) { echo $html->li($html->link($baseUrl . '?db=' . $dbUrl . '&action=listRows&collection=' . urlencode($col), $col) . ' <span title="' . $rowCount . ' objects">(' . number_format($rowCount) . ')</span>'); } echo '</ol>'; echo $html->jsInline('mo.collectionDrop = function(collection) { mo.confirm.collection = collection; mo.confirm("Are you sure that you want to drop " + collection + "?", function() { mo.confirm("All the data in the " + mo.confirm.collection + " collection will be lost;' . ' are you 100% sure that you want to drop it?\n\nLast chance to cancel!", function() { window.location.replace("' . $baseUrl . '?db=' . $dbUrl . '&action=dropCollection&collection=" + mo.urlEncode(mo.confirm.collection)); } ); } ); } $(document).ready(function() { $("#mongo_collections li").each(function() { $(this).prepend("[<a href=\"javascript: mo.collectionDrop(\'" + $(this).find("a").html() + "\'); void(0);\"' . ' title=\"drop this collection\">X</a>] "); }); }); '); } $url = $baseUrl . '?' . http_build_query($_GET); if (isset($collection)) { $url .= '&collection=' . urlencode($collection); } echo $form->open(array('action' => $url, 'style' => 'width: 80px; height: 20px;')) . $form->input(array('name' => 'limit', 'value' => $_SESSION['limit'], 'label' => '', 'addBreak' => false, 'style' => 'width: 40px;')) . $form->submit(array('value' => 'limit', 'class' => 'ui-state-hover')) . $form->close(); echo '</div>'; } echo '</div>'; //end of dbcollnav $dbcollnavJs = '$("#dbcollnav").after(\'<a id="dbcollnavlink" href="javascript: $(\\\'#dbcollnav\\\').show();' . ' $(\\\'#dbcollnavlink\\\').hide(); void(0);">[Show Database & Collection selection]</a>\').hide();'; if (isset($mo->mongo['listRows'])) { echo $form->open(array('action' => $baseUrl . '?db=' . $dbUrl . '&action=renameCollection', 'style' => 'width: 600px; display: none;', 'id' => 'renamecollectionform')) . $form->hidden(array('name' => 'collectionfrom', 'value' => $collection)) . $form->input(array('name' => 'collectionto', 'value' => $collection, 'label' => '', 'addBreak' => false)) . $form->submit(array('value' => 'Rename Collection', 'class' => 'ui-state-hover')) . $form->close(); $js = "$('#collectionname').hide(); $('#renamecollectionform').show(); void(0);"; echo '<h1 id="collectionname">' . $html->link('javascript: ' . $js, $collection) . '</h1>'; if (isset($mo->mongo['listIndexes'])) { echo '<ol id="indexes" style="display: none; margin-bottom: 10px;">'; echo $form->open(array('method' => 'get')); echo '<div id="indexInput">' . $form->input(array('name' => 'index[]', 'label' => '', 'addBreak' => false)) . $form->checkboxes(array('name' => 'isdescending[]', 'options' => array('Descending'))) . '</div>' . '<a id="addindexcolumn" style="margin-left: 160px;" href="javascript: ' . "$('#addindexcolumn').before('<div>' + $('#indexInput').html().replace(/isdescending_Descending/g, " . "'isdescending_Descending' + mo.indexCount++) + '</div>'); void(0);" . '">[Add another index field]</a>' . $form->radios(array('name' => 'unique', 'options' => array('Index', 'Unique'), 'value' => 'Index')) . $form->submit(array('value' => 'Add new index', 'class' => 'ui-state-hover')) . $form->hidden(array('name' => 'action', 'value' => 'ensureIndex')) . $form->hidden(array('name' => 'db', 'value' => get::htmlentities($db))) . $form->hidden(array('name' => 'collection', 'value' => $collection)) . $form->close(); foreach ($mo->mongo['listIndexes'] as $indexArray) { $index = ''; foreach ($indexArray['key'] as $key => $direction) { $index .= (!$index ? $key : ', ' . $key); if (!is_object($direction)) { $index .= ' [' . ($direction == -1 ? 'desc' : 'asc') . ']'; } } if (isset($indexArray['unique']) && $indexArray['unique']) { $index .= ' [unique]'; } if (key($indexArray['key']) != '_id' || count($indexArray['key']) !== 1) { $index = '[' . $html->link($baseUrl . '?db=' . $dbUrl . '&collection=' . urlencode($collection) . '&action=deleteIndex&index=' . serialize($indexArray['key']), 'X', array('title' => 'Drop Index', 'onclick' => "mo.confirm.href=this.href; " . "mo.confirm('Are you sure that you want to drop this index?', " . "function() {window.location.replace(mo.confirm.href);}); return false;") ) . '] ' . $index; } echo '<li>' . $index . '</li>'; } echo '</ol>'; } echo '<ul id="export" style="display: none; margin-bottom: 10px;">'; 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 '</ul>'; echo '<div id="import" style="display: none; margin-bottom: 10px;">'; 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 '</div>'; $objCount = $mo->mongo['listRows']->count(true); //count of rows returned $paginator = number_format($mo->mongo['count']) . ' objects'; //count of rows in collection if ($objCount && $mo->mongo['count'] != $objCount) { $skip = (isset($_GET['skip']) ? $_GET['skip'] : 0); $get = $_GET; unset($get['skip']); $url = $baseUrl . '?' . http_build_query($get) . '&collection=' . urlencode($collection) . '&skip='; $paginator = number_format($skip + 1) . '-' . number_format(min($skip + $objCount, $mo->mongo['count'])) . ' of ' . $paginator; $remainder = ($mo->mongo['count'] % $_SESSION['limit']); $lastPage = ($mo->mongo['count'] - ($remainder ? $remainder : $_SESSION['limit'])); $isLastPage = ($mo->mongo['count'] <= ($objCount + $skip)); if ($skip) { //back $backPage = (!$isLastPage ? max($skip - $objCount, 0) : ($lastPage - $_SESSION['limit'])); $backLinks = $html->link($url . 0, '{{', array('title' => 'First')) . ' ' . $html->link($url . $backPage, '<<<', array('title' => 'Previous')); $paginator = addslashes($backLinks) . ' ' . $paginator; } if (!$isLastPage) { //forward $forwardLinks = $html->link($url . ($skip + $objCount), '>>>', array('title' => 'Next')) . ' ' . $html->link($url . $lastPage, '}}', array('title' => 'Last')); $paginator .= ' ' . addslashes($forwardLinks); } } $get = $_GET; $get['collection'] = urlencode($collection); $queryGet = $searchGet = $sortGet = $get; unset($sortGet['sort'], $sortGet['sortdir']); unset($searchGet['search'], $searchGet['searchField']); unset($queryGet['find']); echo $html->jsInline('mo.indexCount = 1; $(document).ready(function() { $("#mongo_rows").prepend("<div style=\"float: right; line-height: 1.5; margin-top: -45px\">' . '[<a href=\"javascript: $(\'#mongo_rows\').find(\'pre\').height(\'100px\').css(\'overflow\', \'auto\');' . ' void(0);\" title=\"display compact view of row content\">Compact</a>] ' . '[<a href=\"javascript: $(\'#mongo_rows\').find(\'pre\').height(\'300px\').css(\'overflow\', \'auto\');' . ' void(0);\" title=\"display uniform-view row content\">Uniform</a>] ' . '[<a href=\"javascript: $(\'#mongo_rows\').find(\'pre\').height(\'auto\').css(\'overflow\', \'hidden\');' . ' void(0);\" title=\"display full row content\">Full</a>]' . '<div class=\"ui-widget-header\" style=\"padding-left: 5px;\">' . $paginator . '</div></div>"); }); mo.removeObject = function(_id, idType) { mo.confirm("Are you sure that you want to delete this " + _id + " object?", function() { window.location.replace("' . $baseUrl . '?db=' . $dbUrl . '&collection=' . urlencode($collection) . '&action=removeObject&_id=" + mo.urlEncode(_id) + "&idtype=" + idType); }); } ' . $dbcollnavJs . " mo.submitSort = function() { document.location = '" . $baseUrl . '?' . http_build_query($sortGet) . "&sort=' + $('#sort').val() + '&sortdir=' + $('#sortdir').val(); } mo.submitSearch = function() { document.location = '" . $baseUrl . '?' . http_build_query($searchGet) . "&search=' + encodeURIComponent($('#search').val()) + '&searchField=' + $('#searchField').val(); } mo.submitQuery = function() { document.location = '" . $baseUrl . '?' . http_build_query($queryGet) . "&find=' + $('#find').val(); } "); echo '<div id="mongo_rows">'; echo $form->open(array('method' => 'get', 'onsubmit' => 'mo.submitSearch(); return false;')); echo '[' . $html->link($baseUrl . '?db=' . $dbUrl . '&collection=' . urlencode($collection) . '&action=editObject', 'insert new object') . '] '; if (isset($index)) { $jsShowIndexes = "javascript: $('#indexeslink').hide(); $('#indexes').show(); void(0);"; echo $html->link($jsShowIndexes, '[show indexes]', array('id' => 'indexeslink')) . ' '; } $jsShowExport = "javascript: $('#exportlink').hide(); $('#export').show(); void(0);"; echo $html->link($jsShowExport, '[export]', array('id' => 'exportlink')) . ' '; $jsShowImport = "javascript: $('#importlink').hide(); $('#import').show(); void(0);"; echo $html->link($jsShowImport, '[import]', array('id' => 'importlink')) . ' '; $linkSubmitArgs = array('class' => 'ui-state-hover', 'style' => 'padding: 3px 8px 3px 8px;'); $inlineFormArgs = array('label' => '', 'addBreak' => false); if ($mo->mongo['colKeys']) { $colKeys = $mo->mongo['colKeys']; unset($colKeys['_id']); natcasesort($colKeys); $sort = array('name' => 'sort', 'id' => 'sort', 'options' => $colKeys, 'label' => '', 'leadingOptions' => array('_id' => '_id', '$natural' => '$natural'), 'addBreak' => false); $sortdir = array('name' => 'sortdir', 'id' => 'sortdir', 'options' => array(1 => 'asc', -1 => 'desc')); $sortdir = array_merge($sortdir, $inlineFormArgs); $formInputs = $form->select($sort) . $form->select($sortdir) . ' ' . $html->link("javascript: mo.submitSort(); void(0);", 'Sort', $linkSubmitArgs); if (!isset($_GET['sort']) || !$_GET['sort']) { $jsLink = "javascript: $('#sortlink').hide(); $('#sortform').show(); void(0);"; $formInputs = $html->link($jsLink, '[sort]', array('id' => 'sortlink')) . ' ' . '<div id="sortform" style="display: none;">' . $formInputs . '</div>'; } else { $formInputs = $html->div($formInputs); } echo $formInputs; $search = array('name' => 'search', 'id' => 'search', 'style' => 'width: 300px;'); $search = array_merge($search, $inlineFormArgs); $searchField = array('name' => 'searchField', 'id' => 'searchField', 'options' => $colKeys, 'leadingOptions' => array('_id' => '_id')); $searchField = array_merge($searchField, $inlineFormArgs); $linkSubmitArgs['title'] = 'Search may be a exact-text, (type-casted) value, (mongoid) 4c6...80c,' . ' text with * wildcards, regex or JSON (with Mongo-operators enabled)'; $formInputs = $form->select($searchField) . $form->input($search) . ' ' . $html->link("javascript: mo.submitSearch(); void(0);", 'Search', $linkSubmitArgs); if (!isset($_GET['search']) || !$_GET['search']) { $jsLink = "javascript: $('#searchlink').hide(); $('#searchform').show(); void(0);"; $formInputs = $html->link($jsLink, '[search]', array('id' => 'searchlink')) . ' ' . '<div id="searchform" style="display: none;">' . $formInputs . '</div>'; } else { $formInputs = $html->div($formInputs); } echo $formInputs; } $linkSubmitArgs['title'] = 'Query may be a JSON object or a PHP array'; $query = array('name' => 'find', 'id' => 'find', 'style' => 'width: 600px;'); $query = array_merge($query, $inlineFormArgs); $formInputs = $form->textarea($query) . ' ' . $html->link("javascript: mo.submitQuery(); void(0);", 'Query', $linkSubmitArgs); if (!isset($_GET['find']) || !$_GET['find']) { $jsLink = "javascript: $('#querylink').hide(); $('#queryform').show(); void(0);"; $formInputs = $html->link($jsLink, '[query]', array('id' => 'querylink')) . ' ' . '<div id="queryform" style="display: none;">' . $formInputs . '</div>'; } else { $formInputs = $html->div($formInputs); } echo $formInputs; echo $form->close(); echo '<ol style="list-style: none; margin-left: -15px;">'; $rowCount = (!isset($skip) ? 0 : $skip); $isChunksTable = (substr($collection, -7) == '.chunks'); if ($isChunksTable) { $chunkUrl = $baseUrl . '?db=' . $dbUrl . '&action=listRows&collection=' . urlencode(substr($collection, 0, -7)) . '.files#'; } foreach ($mo->mongo['listRows'] as $row) { $showEdit = true; $id = $idString = $row['_id']; if (is_object($idString)) { $idString = '(' . get_class($idString) . ') ' . $idString; $idForUrl = serialize($id); } else if (is_array($idString)) { $idString = '(array) ' . json_encode($idString); $idForUrl = serialize($id); } else { $idForUrl = urlencode($id); } $idType = gettype($row['_id']); if ($isChunksTable && isset($row['data']) && is_object($row['data']) && get_class($row['data']) == 'MongoBinData') { $showEdit = false; $row['data'] = $html->link($chunkUrl . $row['files_id'], 'MongoBinData Object', array('class' => 'MoAdmin_Reference')); } $data = explode("\n", substr(print_r($row, true), 8, -2)); $binData = 0; foreach ($data as $id => $rowData) { $raw = trim($rowData); if ($binData) { if (strpos($rowData, '] => ') !== false) { ++$binData; } unset($data[$id]); continue; } if ($raw === '') { unset($data[$id]); } else if ($raw === '(') { //one-true-brace $data[($id - 1)] .= ' ('; unset($data[$id]); } else { if (strpos($data[$id], 'MongoBinData Object') !== false) { $showEdit = false; $binData = -2; } $data[$id] = str_replace(' ', ' ', (substr($rowData, 0, 4) === ' ' ? substr($rowData, 4) : $rowData)); if ($raw === ')') { $data[$id] = substr($data[$id], 4); } if (strpos($data[$id], 'MoAdmin_Reference') === false) { $data[$id] = get::htmlentities($data[$id]); } } } echo $html->li('<div style="margin-top: 5px; padding-left: 5px;" class="' . ($html->alternator() ? 'ui-widget-header' : 'ui-widget-content') . '" id="' . $row['_id'] . '">' . '[' . $html->link("javascript: mo.removeObject('" . $idForUrl . "', '" . $idType . "'); void(0);", 'X', array('title' => 'Delete')) . '] ' . ($showEdit ? '[' . $html->link($baseUrl . '?db=' . $dbUrl . '&collection=' . urlencode($collection) . '&action=editObject&_id=' . $idForUrl . '&idtype=' . $idType, 'E', array('title' => 'Edit')) . '] ' : ' [<span title="Cannot edit objects containing MongoBinData">N/A</span>] ') . $idString . '<div class="rownumber">' . number_format(++$rowCount) . '</div></div><pre>' . wordwrap(implode("\n", $data), 136, "\n", true) . '</pre>'); } echo '</ol>'; if (!isset($idString)) { echo '<div class="errormessage">No records in this collection</div>'; } echo '</div>'; } else if (isset($mo->mongo['editObject'])) { echo $form->open(array('action' => $baseUrl . '?db=' . $dbUrl . '&collection=' . urlencode($collection))); if (isset($_GET['_id']) && $_GET['_id'] && ($_GET['idtype'] == 'object' || $_GET['idtype'] == 'array')) { $_GET['_id'] = unserialize($_GET['_id']); if (is_array($_GET['_id'])) { $_GET['_id'] = json_encode($_GET['_id']); } } echo $html->h1(isset($_GET['_id']) && $_GET['_id'] ? get::htmlentities($_GET['_id']) : '[New Object]'); echo $html->div($form->submit(array('value' => 'Save Changes', 'class' => 'ui-state-hover'))); $textarea = array('name' => 'object', 'label' => ''); $textarea['value'] = ($mo->mongo['editObject'] !== '' ? var_export($mo->mongo['editObject'], true) : 'array (' . PHP_EOL . PHP_EOL . ')'); //MongoID as _id $textarea['value'] = preg_replace('/\'_id\' => \s*MongoId::__set_state\(array\(\s*\)\)/', '\'_id\' => new MongoId("' . (isset($_GET['_id']) ? $_GET['_id'] : '') . '")', $textarea['value']); //MongoID in all other occurrences, original ID is not maintained $textarea['value'] = preg_replace('/MongoId::__set_state\(array\(\s*\)\)/', 'new MongoId()', $textarea['value']); //MongoDate $textarea['value'] = preg_replace('/MongoDate::__set_state\(array\(\s*\'sec\' => (\d+),\s*\'usec\' => \d+,\s*\)\)/m', 'new MongoDate($1)', $textarea['value']); echo $html->div($form->textarea($textarea) . $form->hidden(array('name' => 'action', 'value' => 'editObject'))); echo $html->div($form->hidden(array('name' => 'db', 'value' => get::htmlentities($db))) . $form->submit(array('value' => 'Save Changes', 'class' => 'ui-state-hover'))); echo $form->close(); echo $html->jsInline('$("textarea[name=object]").css({"min-width": "750px", "max-width": "1250px", ' . '"min-height": "450px", "max-height": "2000px", "width": "auto", "height": "auto"}).resizable(); ' . $dbcollnavJs); } else if (isset($mo->mongo['getStats'])) { echo $html->drillDownList($mo->mongo['getStats']); } echo '</div>'; //end of bodycontent echo $html->footer();