├── LICENSE ├── README.textile ├── change.log └── moadmin.php /LICENSE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MongoDB-Rox/phpMoAdmin-MongoDB-Admin-Tool-for-PHP/4b1a16eaec0a22b76ebf0d6a86c73b1ffa6ff785/LICENSE -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | h1. phpMoAdmin 2 | 3 | h2. MongoDB administration tool for PHP 4 | 5 | h3. Built on a stripped-down version of the high-performance Vork Enterprise Framework 6 | 7 | "phpMoAdmin":http://www.phpMoAdmin.com - "MongoDB":http://www.MongoDB.org - "Vork":http://www.Vork.us 8 | 9 | * *Nothing to configure* - place the moadmin.php file anywhere on your web site and *it just works!* 10 | * Fast *AJAX-driven XHTML 1.1* interface operates consistently in every browser! 11 | * *Self-contained in a single 110kb file!* 12 | * Works on any version of PHP5 or PHP7 with the MongoDB NoSQL database & Mongo PHP driver installed and running. 13 | * Instructional error messages - *phpMoAdmin can be used as a PHP-Mongo connection debugging tool* 14 | * Option to query MongoDB using JSON syntax or a PHP-array 15 | * Super flexible search tool - you can search for: 16 | ** exact-text 17 | ** (type-casted) values 18 | ** text with * wildcards 19 | ** Regular Expressions (regex) 20 | ** JSON (with Mongo-operators enabled) 21 | * Import/export data in JSON format 22 | ** Export full collections or a recordset limited to the results of a query 23 | *** Option to export entire query results (ignoring any limit or skip values) or just the contents of the current page 24 | ** Import can specify: 25 | *** Insert new records only 26 | *** Save / upsert (adds new records, overwrites existing records that have the same _id key) 27 | *** Update only pre-existing records (skips any new records) 28 | *** Insert until a duplicate record is found and then halt import 29 | * Option to enable password-protection for one or more users - to activate protection: 30 | ** Uncomment the *$accessControl* array at the top of the file on *line 18* 31 | ** Change the default username (*scott*) & password (*tiger*) 32 | ** (optional) Add additional sets of username-password(s) to the array 33 | * E_STRICT PHP code is formatted to the Zend Framework coding standards 34 | ** Fully-documented in the phpDocumentor DocBlock standard. 35 | * All form textareas can be resized by dragging/stretching the lower-right corner 36 | * Multiple design themes to choose from 37 | ** change themes easily by setting the *THEME* constant-variable on *line 29* to one of the following: 38 | *** swanky-purse 39 | *** trontastic 40 | *** simple-gray 41 | *** classic 42 | * "Stats" tool displays helpful data including version numbers and configuration settings for PHP, MongoDB and the Mongo-PHP driver 43 | * *Free & open-source!* Released under the GPLv3 FOSS license! 44 | 45 | "www.phpMoAdmin.com":http://www.phpMoAdmin.com 46 | 47 | h2. Screenshots 48 | 49 | h3. Object display (Full-mode) 50 | !http://www.phpmoadmin.com/file/swankypurse.png?t!:http://www.phpmoadmin.com/screenshot?screenshot=swankypurse 51 | 52 | h3. Object display (Compact-mode) 53 | !http://www.phpmoadmin.com/file/compact.png?t!:http://www.phpmoadmin.com/screenshot?screenshot=compact 54 | 55 | h3. Object editor - Drag-corner to resize textarea 56 | !http://www.phpmoadmin.com/file/editor.png?t!:http://www.phpmoadmin.com/screenshot?screenshot=editor 57 | 58 | h3. Mongo Statistics - Stats, error log, versions & system info 59 | !http://www.phpmoadmin.com/file/stats.png?t!:http://www.phpmoadmin.com/screenshot?screenshot=stats 60 | 61 | h2. Features 62 | 63 | * Database 64 | ** List with data sizes 65 | ** Create/drop 66 | ** Repair 67 | * Collection 68 | ** Show collections 69 | ** Create/drop collection 70 | ** Rename collection 71 | ** List indexes 72 | ** Create/drop indexes 73 | *** multiple keys 74 | *** ascending/descending 75 | *** unique index 76 | * Data objects 77 | ** Search for exact-text, (type-casted) values, text with * wildcards, regex or JSON (with Mongo-operators enabled) 78 | ** Show objects with 3-different viewing options (full, compact & uniform) 79 | ** Option to query MongoDB using JSON syntax or a PHP array 80 | ** Sort by any key within your data object (even nested sub-keys!) with ascending/descending option 81 | *** Option to sort by $natural for rapid browsing of large datasets 82 | ** Create/delete objects 83 | ** Edit object data using standard PHP object/array syntax 84 | ** Export to JSON format 85 | *** Full collections or limited recordset based on the results of a query 86 | *** Export exact current results-page contents or the entire query results (ignoring any limit or skip values) 87 | ** Import from JSON format 88 | *** Insert new records only 89 | *** Save / upsert (adds new records, and overwrites existing records with same _id) 90 | *** Update only pre-existing records (ignores new records) 91 | *** Insert until a duplicate is found and then halt import 92 | * Mongo GridFS 93 | ** GridFS objects automatically link GridFS chunks to GridFS files 94 | * MongoDB stats 95 | ** Uptime, memory, etc. 96 | ** Log of previous errors 97 | ** Mongo-PHP settings 98 | ** Version & bit-depth of PHP, MongoDB, Mongo PHP driver, phpMoAdmin, etc. 99 | * Optional password-protection for one or more users 100 | 101 | h2. Themes 102 | 103 | !http://www.phpmoadmin.com/file/swankypurse.png?t!:http://www.phpmoadmin.com/screenshot?screenshot=swankypurse 104 | 105 | !http://www.phpmoadmin.com/file/trontastic.png?t!:http://www.phpmoadmin.com/screenshot?screenshot=trontastic 106 | 107 | !http://www.phpmoadmin.com/file/classic.png?t!:http://www.phpmoadmin.com/screenshot?screenshot=classic 108 | 109 | !http://www.phpmoadmin.com/file/swankypurse-confirm.png! 110 | 111 | !http://www.phpmoadmin.com/file/trontastic-confirm.png! 112 | 113 | !http://www.phpmoadmin.com/file/classic-confirm.png! 114 | 115 | h2. Contact phpMoAdmin 116 | 117 | "www.phpMoAdmin.com":http://www.phpMoAdmin.com 118 | 119 | !http://www.phpmoadmin.com/file/phpMoAdmin-MongoDB-Admin-GUI.jpg! 120 | -------------------------------------------------------------------------------- /change.log: -------------------------------------------------------------------------------- 1 | Version 1.1.5 2 | Bugfix: stats() now works correctly on Windows 3 | Bugfix: removed usage of escapeshellarg() (added in 1.1.4) as it was causing queries to fail in certain situations 4 | 5 | Version 1.1.4 6 | Added pagination option to skip to first/last 7 | Bugfix: corrected security vulnerabilities 8 | Bugfix: all stats() results now display correctly 9 | Bugfix: enabled searching for URI-reserved chars 10 | Removed GoChat (project no longer active) 11 | 12 | Version 1.1.3 13 | Added support for MongoClient connection manager 14 | Made phpMoAdmin SSL-friendly 15 | 16 | Version 1.1.2 17 | Added GoChat 18 | 19 | Version 1.1.1 20 | Added simple-gray theme (thank you Xeoncross) 21 | 22 | Version 1.1.0 23 | Added import/export of data in JSON format 24 | Updated all formHelper methods and get::url() to the improved versions in the current Vork core 25 | 26 | Version 1.0.9 27 | Added ability to limit usage to a whitelist of databases (ignoring any others on the same server) 28 | Added REPLICA_SET option 29 | Switched default theme to trontastic (in response to user-feedback) 30 | 31 | Version 1.0.8 32 | Added explicit typecasting to search and automated numeric-search typecasting 33 | Added $natural sorting option 34 | 35 | Version 1.0.7 36 | Added search for exact-text, text with * wildcards, regex or JSON (with Mongo-operators enabled) 37 | Added query via JSON or PHP-array 38 | Added rename collection 39 | Set destructive actions to not repeat upon page-refresh 40 | Added application bit-depths to stats 41 | 42 | Version 1.0.6 43 | Added object-result pagination support with adjustable limit 44 | Added display of object counts to list of collections 45 | 46 | Version 1.0.5 47 | Minor enhancements to increase universality of configuration-free operation 48 | 49 | Version 1.0.4 50 | Added design themes and set original design into classic theme 51 | Replaced all JavaScript confirm() boxes with on-page modal confirms 52 | 53 | Version 1.0.3 54 | Added sorting of data objects 55 | Added auto-focus to login box in login form 56 | Added display of the number of data objects in a collection 57 | Added row/result-numbers to lists of databases, collections, indexes and collection-data objects. 58 | Improved stats output 59 | 60 | Version 1.0.2 61 | Added stats feature 62 | Added display of database sizes 63 | 64 | Version 1.0.1 65 | Added support for remote & authenticated connections 66 | Added exceptions-to-be-thrown when connection to Mongo cannot be established (includes instructions on how to fix issue) 67 | 68 | Version 1.0.0 69 | Initial stable release 70 | -------------------------------------------------------------------------------- /moadmin.php: -------------------------------------------------------------------------------- 1 | password 16 | * You can add as many users as needed, eg.: array('scott' => 'tiger', 'samantha' => 'goldfish', 'gene' => 'alpaca') 17 | */ 18 | //$accessControl = array('scott' => 'tiger'); 19 | 20 | /** 21 | * Uncomment to restrict databases-access to just the databases added to the array below 22 | * uncommenting will also remove the ability to create a new database 23 | */ 24 | //moadminModel::$databaseWhitelist = array('admin'); 25 | 26 | /** 27 | * Sets the design theme - themes options are: swanky-purse, trontastic, simple-gray and classic 28 | */ 29 | define('THEME', 'trontastic'); 30 | 31 | /** 32 | * To connect to a remote or authenticated Mongo instance, define the connection string in the MONGO_CONNECTION constant 33 | * mongodb://[username:password@]host1[:port1][,host2[:port2:],...] 34 | * If you do not know what this means then it is not relevant to your application and you can safely leave it as-is 35 | */ 36 | define('MONGO_CONNECTION', ''); 37 | 38 | /** 39 | * Set to true when connecting to a Mongo replicaSet 40 | * If you do not know what this means then it is not relevant to your application and you can safely leave it as-is 41 | */ 42 | define('REPLICA_SET', false); 43 | 44 | /** 45 | * Default limit for number of objects to display per page - set to 0 for no limit 46 | */ 47 | define('OBJECT_LIMIT', 100); 48 | 49 | /** 50 | * Contributing-developers of the phpMoAdmin project should set this to true, everyone else can leave this as false 51 | */ 52 | define('DEBUG_MODE', false); 53 | 54 | /** 55 | * Vork core-functionality tools 56 | */ 57 | class get { 58 | /** 59 | * Opens up public access to config constants and variables and the cache object 60 | * @var object 61 | */ 62 | public static $config; 63 | 64 | /** 65 | * Index of objects loaded, used to maintain uniqueness of singletons 66 | * @var array 67 | */ 68 | public static $loadedObjects = array(); 69 | 70 | /** 71 | * Is PHP Version 5.2.3 or better when htmlentities() added its fourth argument 72 | * @var Boolean 73 | */ 74 | public static $isPhp523orNewer = true; 75 | 76 | /** 77 | * Gets the current URL 78 | * 79 | * @param array Optional, keys: 80 | * get - Boolean Default: false - include GET URL if it exists 81 | * abs - Boolean Default: false - true=absolute URL (aka. FQDN), false=just the path for relative links 82 | * ssl - Boolean Default: null - true=https, false=http, unset/null=auto-selects the current protocol 83 | * a true or false value implies abs=true 84 | * @return string 85 | */ 86 | public static function url(array $args = array()) { 87 | $ssl = null; 88 | $get = false; 89 | $abs = false; 90 | extract($args); 91 | 92 | if (!isset($_SERVER['HTTP_HOST']) && PHP_SAPI == 'cli') { 93 | $_SERVER['HTTP_HOST'] = trim(`hostname`); 94 | $argv = $_SERVER['argv']; 95 | array_shift($argv); 96 | $_SERVER['REDIRECT_URL'] = '/' . implode('/', $argv); 97 | $get = false; // command-line has no GET 98 | } 99 | 100 | $url = (isset($_SERVER['REDIRECT_URL']) ? $_SERVER['REDIRECT_URL'] : $_SERVER['SCRIPT_NAME']); 101 | if (substr($url, -1) == '/') { //strip trailing slash for URL consistency 102 | $url = substr($url, 0, -1); 103 | } 104 | 105 | if (is_null($ssl) && $abs == true) { 106 | $ssl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on'); 107 | } 108 | if ($abs || !is_null($ssl)) { 109 | $url = (!$ssl ? 'http://' : 'https://') . $_SERVER['HTTP_HOST'] . $url; 110 | } 111 | 112 | if ($get && isset($_SERVER['QUERY_STRING']) && $_SERVER['QUERY_STRING']) { 113 | $url .= '?' . $_SERVER['QUERY_STRING']; 114 | } 115 | return ($url ? $url : '/'); 116 | } 117 | 118 | /** 119 | * Overloads the php function htmlentities and changes the default charset to UTF-8 and the default value for the 120 | * fourth parameter $doubleEncode to false. Also adds ability to pass a null value to get the default $quoteStyle 121 | * and $charset (removes need to repeatedly define ENT_COMPAT, 'UTF-8', just to access the $doubleEncode argument) 122 | * 123 | * If you are using a PHP version prior to 5.2.3 the $doubleEncode parameter is not available and won't do anything 124 | * 125 | * @param string $string 126 | * @param int $quoteStyle Uses ENT_COMPAT if null or omitted 127 | * @param string $charset Uses UTF-8 if null or omitted 128 | * @param boolean $doubleEncode This is ignored in old versions of PHP before 5.2.3 129 | * @return string 130 | */ 131 | public static function htmlentities($string, $quoteStyle = ENT_COMPAT, $charset = 'UTF-8', $doubleEncode = false) { 132 | $quoteStyle = (!is_null($quoteStyle) ? $quoteStyle : ENT_COMPAT); 133 | $charset = (!is_null($charset) ? $charset : 'UTF-8'); 134 | return (self::$isPhp523orNewer ? htmlentities($string, $quoteStyle, $charset, $doubleEncode) 135 | : htmlentities($string, $quoteStyle, $charset)); 136 | } 137 | 138 | /** 139 | * Initialize the character maps needed for the xhtmlentities() method and verifies the argument values 140 | * passed to it are valid. 141 | * 142 | * @param int $quoteStyle 143 | * @param string $charset Only valid options are UTF-8 and ISO-8859-1 (Latin-1) 144 | * @param boolean $doubleEncode 145 | */ 146 | protected static function initXhtmlentities($quoteStyle, $charset, $doubleEncode) { 147 | $chars = get_html_translation_table(HTML_ENTITIES, $quoteStyle); 148 | if (isset($chars)) { 149 | unset($chars['<'], $chars['>']); 150 | $charMaps[$quoteStyle]['ISO-8859-1'][true] = $chars; 151 | $charMaps[$quoteStyle]['ISO-8859-1'][false] = array_combine(array_values($chars), $chars); 152 | $charMaps[$quoteStyle]['UTF-8'][true] = array_combine(array_map('utf8_encode', array_keys($chars)), $chars); 153 | $charMaps[$quoteStyle]['UTF-8'][false] = array_merge($charMaps[$quoteStyle]['ISO-8859-1'][false], 154 | $charMaps[$quoteStyle]['UTF-8'][true]); 155 | self::$loadedObjects['xhtmlEntities'] = $charMaps; 156 | } 157 | if (!isset($charMaps[$quoteStyle][$charset][$doubleEncode])) { 158 | if (!isset($chars)) { 159 | $invalidArgument = 'quoteStyle = ' . $quoteStyle; 160 | } else if (!isset($charMaps[$quoteStyle][$charset])) { 161 | $invalidArgument = 'charset = ' . $charset; 162 | } else { 163 | $invalidArgument = 'doubleEncode = ' . (string) $doubleEncode; 164 | } 165 | trigger_error('Undefined argument sent to xhtmlentities() method: ' . $invalidArgument, E_USER_NOTICE); 166 | } 167 | } 168 | 169 | /** 170 | * Converts special characters in a string to XHTML-valid ASCII encoding the same as htmlentities except 171 | * this method allows the use of HTML tags within your string. Signature is the same as htmlentities except 172 | * that the only character sets available (third argument) are UTF-8 (default) and ISO-8859-1 (Latin-1). 173 | * 174 | * @param string $string 175 | * @param int $quoteStyle Constants available are ENT_NOQUOTES (default), ENT_QUOTES, ENT_COMPAT 176 | * @param string $charset Only valid options are UTF-8 (default) and ISO-8859-1 (Latin-1) 177 | * @param boolean $doubleEncode Default is false 178 | * @return string 179 | */ 180 | public static function xhtmlentities($string, $quoteStyle = ENT_NOQUOTES, $charset = 'UTF-8', 181 | $doubleEncode = false) { 182 | $quoteStyles = array(ENT_NOQUOTES, ENT_QUOTES, ENT_COMPAT); 183 | $quoteStyle = (!in_array($quoteStyle, $quoteStyles) ? current($quoteStyles) : $quoteStyle); 184 | $charset = ($charset != 'ISO-8859-1' ? 'UTF-8' : $charset); 185 | $doubleEncode = (Boolean) $doubleEncode; 186 | if (!isset(self::$loadedObjects['xhtmlEntities'][$quoteStyle][$charset][$doubleEncode])) { 187 | self::initXhtmlentities($quoteStyle, $charset, $doubleEncode); 188 | } 189 | return strtr($string, self::$loadedObjects['xhtmlEntities'][$quoteStyle][$charset][$doubleEncode]); 190 | } 191 | 192 | /** 193 | * Loads an object as a singleton 194 | * 195 | * @param string $objectType 196 | * @param string $objectName 197 | * @return object 198 | */ 199 | protected static function _loadObject($objectType, $objectName) { 200 | if (isset(self::$loadedObjects[$objectType][$objectName])) { 201 | return self::$loadedObjects[$objectType][$objectName]; 202 | } 203 | $objectClassName = $objectName . ucfirst($objectType); 204 | if (class_exists($objectClassName)) { 205 | $objectObject = new $objectClassName; 206 | self::$loadedObjects[$objectType][$objectName] = $objectObject; 207 | return $objectObject; 208 | } else { 209 | $errorMsg = 'Class for ' . $objectType . ' ' . $objectName . ' could not be found'; 210 | } 211 | trigger_error($errorMsg, E_USER_WARNING); 212 | } 213 | 214 | /** 215 | * Returns a helper object 216 | * 217 | * @param string $model 218 | * @return object 219 | */ 220 | public static function helper($helper) { 221 | if (is_array($helper)) { 222 | array_walk($helper, array('self', __METHOD__)); 223 | return; 224 | } 225 | if (!isset(self::$config['helpers']) || !in_array($helper, self::$config['helpers'])) { 226 | self::$config['helpers'][] = $helper; 227 | } 228 | return self::_loadObject('helper', $helper); 229 | } 230 | } 231 | 232 | /** 233 | * Public interface to load elements and cause redirects 234 | */ 235 | class load { 236 | /** 237 | * Sends a redirects header and disables view rendering 238 | * This redirects via a browser command, this is not the same as changing controllers which is handled within MVC 239 | * 240 | * @param string $url Optional, if undefined this will refresh the page (mostly useful for dumping post values) 241 | */ 242 | public static function redirect($url = null) { 243 | header('Location: ' . ($url ? $url : get::url(array('get' => true)))); 244 | } 245 | } 246 | 247 | /** 248 | * Thrown when the mongod server is not accessible 249 | */ 250 | class cannotConnectToMongoServer extends Exception { 251 | public function __toString() { 252 | return '

Cannot connect to the MongoDB database.

' . PHP_EOL . 'If Mongo is installed then be sure that' 253 | . ' an instance of the "mongod" server, not "mongo" shell, is running.
' . PHP_EOL 254 | . 'Instructions and database download: http://vork.us/go/fhk4'; 255 | } 256 | } 257 | 258 | /** 259 | * Thrown when the mongo extension for PHP is not installed 260 | */ 261 | class mongoExtensionNotInstalled extends Exception { 262 | public function __toString() { 263 | return '

PHP cannot access MongoDB, you need to install the Mongo extension for PHP.

' 264 | . PHP_EOL . 'Instructions and driver download: ' 265 | . 'http://vork.us/go/tv27'; 266 | } 267 | } 268 | 269 | /** 270 | * phpMoAdmin data model 271 | */ 272 | class moadminModel { 273 | /** 274 | * mongo connection - if a MongoDB object already exists (from a previous script) then only DB operations use this 275 | * @var Mongo 276 | */ 277 | protected $_db; 278 | 279 | /** 280 | * Name of last selected DB 281 | * @var string Defaults to admin as that is available in all Mongo instances 282 | */ 283 | public static $dbName = 'admin'; 284 | 285 | /** 286 | * MongoDB 287 | * @var MongoDB 288 | */ 289 | public $mongo; 290 | 291 | /** 292 | * Returns a new Mongo connection 293 | * @return Mongo 294 | */ 295 | protected function _mongo() { 296 | $connection = (!MONGO_CONNECTION ? 'mongodb://localhost:27017' : MONGO_CONNECTION); 297 | $Mongo = (class_exists('MongoClient') === true ? 'MongoClient' : 'Mongo'); 298 | return (!REPLICA_SET ? new $Mongo($connection) : new $Mongo($connection, array('replicaSet' => true))); 299 | } 300 | 301 | /** 302 | * Connects to a Mongo database if the name of one is supplied as an argument 303 | * @param string $db 304 | */ 305 | public function __construct($db = null) { 306 | if (self::$databaseWhitelist && !in_array($db, self::$databaseWhitelist)) { 307 | $db = self::$dbName = $_GET['db'] = current(self::$databaseWhitelist); 308 | } 309 | if ($db) { 310 | if (!extension_loaded('mongo')) { 311 | throw new mongoExtensionNotInstalled(); 312 | } 313 | try { 314 | $this->_db = $this->_mongo(); 315 | $this->mongo = $this->_db->selectDB($db); 316 | } catch (MongoConnectionException $e) { 317 | throw new cannotConnectToMongoServer(); 318 | } 319 | } 320 | } 321 | 322 | /** 323 | * Executes a native JS MongoDB command 324 | * This method is not currently used for anything 325 | * @param string $cmd 326 | * @return mixed 327 | */ 328 | protected function _exec($cmd) { 329 | $exec = $this->mongo->execute($cmd); 330 | return $exec['retval']; 331 | } 332 | 333 | /** 334 | * Change the DB connection 335 | * @param string $db 336 | */ 337 | public function setDb($db) { 338 | if (self::$databaseWhitelist && !in_array($db, self::$databaseWhitelist)) { 339 | $db = current(self::$databaseWhitelist); 340 | } 341 | if (!isset($this->_db)) { 342 | $this->_db = $this->_mongo(); 343 | } 344 | $this->mongo = $this->_db->selectDB($db); 345 | self::$dbName = $db; 346 | } 347 | 348 | /** 349 | * Total size of all the databases 350 | * @var int 351 | */ 352 | public $totalDbSize = 0; 353 | 354 | /** 355 | * Adds ability to restrict databases-access to those on the whitelist 356 | * @var array 357 | */ 358 | public static $databaseWhitelist = array(); 359 | 360 | /** 361 | * Gets list of databases 362 | * @return array 363 | */ 364 | public function listDbs() { 365 | $return = array(); 366 | $restrictDbs = (bool) self::$databaseWhitelist; 367 | $dbs = $this->_db->selectDB('admin')->command(array('listDatabases' => 1)); 368 | $this->totalDbSize = $dbs['totalSize']; 369 | foreach ($dbs['databases'] as $db) { 370 | if (!$restrictDbs || in_array($db['name'], self::$databaseWhitelist)) { 371 | $return[$db['name']] = $db['name'] . ' (' 372 | . (!$db['empty'] ? round($db['sizeOnDisk'] / 1000000) . 'mb' : 'empty') . ')'; 373 | } 374 | } 375 | ksort($return); 376 | $dbCount = 0; 377 | foreach ($return as $key => $val) { 378 | $return[$key] = ++$dbCount . '. ' . $val; 379 | } 380 | return $return; 381 | } 382 | 383 | /** 384 | * Generate system info and stats 385 | * @return array 386 | */ 387 | public function getStats() { 388 | $admin = $this->_db->selectDB('admin'); 389 | $return = $admin->command(array('buildinfo' => 1)); 390 | try { 391 | $return = array_merge($return, $admin->command(array('serverStatus' => 1))); 392 | } catch (MongoCursorException $e) {} 393 | $profile = $admin->command(array('profile' => -1)); 394 | $return['profilingLevel'] = $profile['was']; 395 | $return['mongoDbTotalSize'] = round($this->totalDbSize / 1000000) . 'mb'; 396 | $prevError = $admin->command(array('getpreverror' => 1)); 397 | if (!$prevError['n']) { 398 | $return['previousDbErrors'] = 'None'; 399 | } else { 400 | $return['previousDbErrors']['error'] = $prevError['err']; 401 | $return['previousDbErrors']['numberOfOperationsAgo'] = $prevError['nPrev']; 402 | } 403 | if (isset($return['globalLock']['totalTime'])) { 404 | $return['globalLock']['totalTime'] .= ' µSec'; 405 | } 406 | if (isset($return['uptime'])) { 407 | $return['uptime'] = round($return['uptime'] / 60) . ':' . str_pad($return['uptime'] % 60, 2, '0', STR_PAD_LEFT) 408 | . ' minutes'; 409 | } 410 | $unshift['mongo'] = $return['version'] . ' (' . $return['bits'] . '-bit)'; 411 | $unshift['mongoPhpDriver'] = Mongo::VERSION; 412 | $unshift['phpMoAdmin'] = '1.1.4'; 413 | $unshift['php'] = PHP_VERSION . ' (' . (PHP_INT_MAX > 2200000000 ? 64 : 32) . '-bit)'; 414 | $unshift['gitVersion'] = $return['gitVersion']; 415 | unset($return['ok'], $return['version'], $return['gitVersion'], $return['bits']); 416 | $return = array_merge(array('version' => $unshift), $return); 417 | $iniIndex = array(-1 => 'Unlimited', 'Off', 'On'); 418 | $phpIni = array('allow_persistent', 'auto_reconnect', 'chunk_size', 'cmd', 'default_host', 'default_port', 419 | 'max_connections', 'max_persistent'); 420 | foreach ($phpIni as $ini) { 421 | $key = 'php_' . $ini; 422 | $return[$key] = ini_get('mongo.' . $ini); 423 | if (isset($iniIndex[$return[$key]])) { 424 | $return[$key] = $iniIndex[$return[$key]]; 425 | } 426 | } 427 | return $return; 428 | } 429 | 430 | /** 431 | * Repairs a database 432 | * @return array Success status 433 | */ 434 | public function repairDb() { 435 | return $this->mongo->repair(); 436 | } 437 | 438 | /** 439 | * Drops a database 440 | */ 441 | public function dropDb() { 442 | $this->mongo->drop(); 443 | return; 444 | if (!isset($this->_db)) { 445 | $this->_db = $this->_mongo(); 446 | } 447 | $this->_db->dropDB($this->mongo); 448 | } 449 | 450 | /** 451 | * Gets a list of database collections 452 | * @return array 453 | */ 454 | public function listCollections() { 455 | $collections = array(); 456 | $MongoCollectionObjects = $this->mongo->listCollections(); 457 | foreach ($MongoCollectionObjects as $collection) { 458 | $collection = substr(strstr((string) $collection, '.'), 1); 459 | $collections[$collection] = $this->mongo->selectCollection($collection)->count(); 460 | } 461 | ksort($collections); 462 | return $collections; 463 | } 464 | 465 | /** 466 | * Drops a collection 467 | * @param string $collection 468 | */ 469 | public function dropCollection($collection) { 470 | $this->mongo->selectCollection($collection)->drop(); 471 | } 472 | 473 | /** 474 | * Creates a collection 475 | * @param string $collection 476 | */ 477 | public function createCollection($collection) { 478 | if ($collection) { 479 | $this->mongo->createCollection($collection); 480 | } 481 | } 482 | 483 | /** 484 | * Renames a collection 485 | * 486 | * @param string $from 487 | * @param string $to 488 | */ 489 | public function renameCollection($from, $to) { 490 | $result = $this->_db->selectDB('admin')->command(array( 491 | 'renameCollection' => self::$dbName . '.' . $from, 492 | 'to' => self::$dbName . '.' . $to, 493 | )); 494 | } 495 | 496 | /** 497 | * Gets a list of the indexes on a collection 498 | * 499 | * @param string $collection 500 | * @return array 501 | */ 502 | public function listIndexes($collection) { 503 | return $this->mongo->selectCollection($collection)->getIndexInfo(); 504 | } 505 | 506 | /** 507 | * Ensures an index 508 | * 509 | * @param string $collection 510 | * @param array $indexes 511 | * @param array $unique 512 | */ 513 | public function ensureIndex($collection, array $indexes, array $unique) { 514 | $unique = ($unique ? true : false); //signature requires a bool in both Mongo v. 1.0.1 and 1.2.0 515 | $this->mongo->selectCollection($collection)->ensureIndex($indexes, $unique); 516 | } 517 | 518 | /** 519 | * Removes an index 520 | * 521 | * @param string $collection 522 | * @param array $index Must match the array signature of the index 523 | */ 524 | public function deleteIndex($collection, array $index) { 525 | $this->mongo->selectCollection($collection)->deleteIndex($index); 526 | } 527 | 528 | /** 529 | * Sort array - currently only used for collections 530 | * @var array 531 | */ 532 | public $sort = array('_id' => 1); 533 | 534 | /** 535 | * Number of rows in the entire resultset (before limit-clause is applied) 536 | * @var int 537 | */ 538 | public $count; 539 | 540 | /** 541 | * Array keys in the first and last object in a collection merged together (used to build sort-by options) 542 | * @var array 543 | */ 544 | public $colKeys = array(); 545 | 546 | /** 547 | * Get the records in a collection 548 | * 549 | * @param string $collection 550 | * @return array 551 | */ 552 | public function listRows($collection) { 553 | foreach ($this->sort as $key => $val) { //cast vals to int 554 | $sort[$key] = (int) $val; 555 | } 556 | $col = $this->mongo->selectCollection($collection); 557 | 558 | $find = array(); 559 | if (isset($_GET['find']) && $_GET['find']) { 560 | $_GET['find'] = trim($_GET['find']); 561 | if (strpos($_GET['find'], 'array') === 0) { 562 | eval('$find = ' . $_GET['find'] . ';'); 563 | } else if (is_string($_GET['find'])) { 564 | if ($findArr = json_decode($_GET['find'], true)) { 565 | $find = $findArr; 566 | } 567 | } 568 | } 569 | if (isset($_GET['search']) && $_GET['search']) { 570 | switch (substr(trim($_GET['search']), 0, 1)) { //first character 571 | case '/': //regex 572 | $find[$_GET['searchField']] = new mongoRegex($_GET['search']); 573 | break; 574 | case '{': //JSON 575 | if ($search = json_decode($_GET['search'], true)) { 576 | $find[$_GET['searchField']] = $search; 577 | } 578 | break; 579 | case '(': 580 | $types = array('bool', 'boolean', 'int', 'integer', 'float', 'double', 'string', 'array', 'object', 581 | 'null', 'mongoid'); 582 | $closeParentheses = strpos($_GET['search'], ')'); 583 | if ($closeParentheses) { 584 | $cast = strtolower(substr($_GET['search'], 1, ($closeParentheses - 1))); 585 | if (in_array($cast, $types)) { 586 | $search = trim(substr($_GET['search'], ($closeParentheses + 1))); 587 | if ($cast == 'mongoid') { 588 | $search = new MongoID($search); 589 | } else { 590 | settype($search, $cast); 591 | } 592 | $find[$_GET['searchField']] = $search; 593 | break; 594 | } 595 | } //else no-break 596 | default: //text-search 597 | if (strpos($_GET['search'], '*') === false) { 598 | if (!is_numeric($_GET['search'])) { 599 | $find[$_GET['searchField']] = $_GET['search']; 600 | } else { //$_GET is always a string-type 601 | $in = array((string) $_GET['search'], (int) $_GET['search'], (float) $_GET['search']); 602 | $find[$_GET['searchField']] = array('$in' => $in); 603 | } 604 | } else { //text with wildcards 605 | $regex = '/' . str_replace('\*', '.*', preg_quote($_GET['search'])) . '/i'; 606 | $find[$_GET['searchField']] = new mongoRegex($regex); 607 | } 608 | break; 609 | } 610 | } 611 | 612 | $cols = (!isset($_GET['cols']) ? array() : array_fill_keys($_GET['cols'], true)); 613 | $cur = $col->find($find, $cols)->sort($sort); 614 | $this->count = $cur->count(); 615 | 616 | //get keys of first object 617 | if ($_SESSION['limit'] && $this->count > $_SESSION['limit'] //more results than per-page limit 618 | && (!isset($_GET['export']) || $_GET['export'] != 'nolimit')) { 619 | if ($this->count > 1) { 620 | $this->colKeys = phpMoAdmin::getArrayKeys($col->findOne()); 621 | } 622 | $cur->limit($_SESSION['limit']); 623 | if (isset($_GET['skip'])) { 624 | if ($this->count <= $_GET['skip']) { 625 | $_GET['skip'] = ($this->count - $_SESSION['limit']); 626 | } 627 | $cur->skip($_GET['skip']); 628 | } 629 | } else if ($this->count) { // results exist but are fewer than per-page limit 630 | $this->colKeys = phpMoAdmin::getArrayKeys($cur->getNext()); 631 | } else if ($find && $col->count()) { //query is not returning anything, get cols from first obj in collection 632 | $this->colKeys = phpMoAdmin::getArrayKeys($col->findOne()); 633 | } 634 | 635 | //get keys of last or much-later object 636 | if ($this->count > 1) { 637 | $curLast = $col->find()->sort($sort); 638 | if ($this->count > 2) { 639 | $curLast->skip(min($this->count, 100) - 1); 640 | } 641 | $this->colKeys = array_merge($this->colKeys, phpMoAdmin::getArrayKeys($curLast->getNext())); 642 | ksort($this->colKeys); 643 | } 644 | return $cur; 645 | } 646 | 647 | /** 648 | * Returns a serialized element back to its native PHP form 649 | * 650 | * @param string $_id 651 | * @param string $idtype 652 | * @return mixed 653 | */ 654 | protected function _unserialize($_id, $idtype) { 655 | if ($idtype == 'object' || $idtype == 'array') { 656 | $errLevel = error_reporting(); 657 | error_reporting(0); //unserializing an object that is not serialized throws a warning 658 | $_idObj = unserialize($_id); 659 | error_reporting($errLevel); 660 | if ($_idObj !== false) { 661 | $_id = $_idObj; 662 | } 663 | } else if (gettype($_id) != $idtype) { 664 | settype($_id, $idtype); 665 | } 666 | return $_id; 667 | } 668 | 669 | /** 670 | * Removes an object from a collection 671 | * 672 | * @param string $collection 673 | * @param string $_id 674 | * @param string $idtype 675 | */ 676 | public function removeObject($collection, $_id, $idtype) { 677 | $this->mongo->selectCollection($collection)->remove(array('_id' => $this->_unserialize($_id, $idtype))); 678 | } 679 | 680 | /** 681 | * Retieves an object for editing 682 | * 683 | * @param string $collection 684 | * @param string $_id 685 | * @param string $idtype 686 | * @return array 687 | */ 688 | public function editObject($collection, $_id, $idtype) { 689 | return $this->mongo->selectCollection($collection)->findOne(array('_id' => $this->_unserialize($_id, $idtype))); 690 | } 691 | 692 | /** 693 | * Saves an object 694 | * 695 | * @param string $collection 696 | * @param string $obj 697 | * @return array 698 | */ 699 | public function saveObject($collection, $obj) { 700 | eval('$obj=' . $obj . ';'); //cast from string to array 701 | return $this->mongo->selectCollection($collection)->save($obj); 702 | } 703 | 704 | /** 705 | * Imports data into the current collection 706 | * 707 | * @param string $collection 708 | * @param array $data 709 | * @param string $importMethod Valid options are batchInsert, save, insert, update 710 | */ 711 | public function import($collection, array $data, $importMethod) { 712 | $coll = $this->mongo->selectCollection($collection); 713 | switch ($importMethod) { 714 | case 'batchInsert': 715 | foreach ($data as &$obj) { 716 | $obj = unserialize($obj); 717 | } 718 | $coll->$importMethod($data); 719 | break; 720 | case 'update': 721 | foreach ($data as $obj) { 722 | $obj = unserialize($obj); 723 | if (is_object($obj) && property_exists($obj, '_id')) { 724 | $_id = $obj->_id; 725 | } else if (is_array($obj) && isset($obj['_id'])) { 726 | $_id = $obj['_id']; 727 | } else { 728 | continue; 729 | } 730 | $coll->$importMethod(array('_id' => $_id), $obj); 731 | } 732 | break; 733 | default: //insert & save 734 | foreach ($data as $obj) { 735 | $coll->$importMethod(unserialize($obj)); 736 | } 737 | break; 738 | } 739 | } 740 | } 741 | 742 | /** 743 | * phpMoAdmin application control 744 | */ 745 | class moadminComponent { 746 | /** 747 | * $this->mongo is used to pass properties from component to view without relying on a controller to return them 748 | * @var array 749 | */ 750 | public $mongo = array(); 751 | 752 | /** 753 | * Model object 754 | * @var moadminModel 755 | */ 756 | public static $model; 757 | 758 | /** 759 | * Removes the POST/GET params 760 | */ 761 | protected function _dumpFormVals() { 762 | load::redirect(get::url() . '?action=listRows&db=' . urlencode($_GET['db']) 763 | . '&collection=' . urlencode($_GET['collection'])); 764 | } 765 | 766 | /** 767 | * Routes requests and sets return data 768 | */ 769 | public function __construct() { 770 | if (class_exists('mvc')) { 771 | mvc::$view = '#moadmin'; 772 | } 773 | $this->mongo['dbs'] = self::$model->listDbs(); 774 | if (isset($_GET['db'])) { 775 | if (strpos($_GET['db'], '.') !== false) { 776 | $_GET['db'] = $_GET['newdb']; 777 | } 778 | self::$model->setDb($_GET['db']); 779 | } 780 | 781 | if (isset($_POST['limit'])) { 782 | $_SESSION['limit'] = (int) $_POST['limit']; 783 | } else if (!isset($_SESSION['limit'])) { 784 | $_SESSION['limit'] = OBJECT_LIMIT; 785 | } 786 | 787 | if (isset($_FILES['import']) && is_uploaded_file($_FILES['import']['tmp_name']) && isset($_GET['collection'])) { 788 | $data = json_decode(file_get_contents($_FILES['import']['tmp_name'])); 789 | self::$model->import($_GET['collection'], $data, $_POST['importmethod']); 790 | } 791 | 792 | $action = (isset($_GET['action']) ? $_GET['action'] : 'listCollections'); 793 | if (isset($_POST['object'])) { 794 | if (self::$model->saveObject($_GET['collection'], $_POST['object'])) { 795 | return $this->_dumpFormVals(); 796 | } else { 797 | $action = 'editObject'; 798 | $_POST['errors']['object'] = 'Error: object could not be saved - check your array syntax.'; 799 | } 800 | } else if ($action == 'createCollection') { 801 | self::$model->$action($_GET['collection']); 802 | } else if ($action == 'renameCollection' 803 | && isset($_POST['collectionto']) && $_POST['collectionto'] != $_POST['collectionfrom']) { 804 | self::$model->$action($_POST['collectionfrom'], $_POST['collectionto']); 805 | $_GET['collection'] = $_POST['collectionto']; 806 | $action = 'listRows'; 807 | } 808 | 809 | if (isset($_GET['sort'])) { 810 | self::$model->sort = array($_GET['sort'] => $_GET['sortdir']); 811 | } 812 | 813 | $this->mongo['listCollections'] = self::$model->listCollections(); 814 | if ($action == 'editObject') { 815 | $this->mongo[$action] = (isset($_GET['_id']) 816 | ? self::$model->$action($_GET['collection'], $_GET['_id'], $_GET['idtype']) : ''); 817 | return; 818 | } else if ($action == 'removeObject') { 819 | self::$model->$action($_GET['collection'], $_GET['_id'], $_GET['idtype']); 820 | return $this->_dumpFormVals(); 821 | } else if ($action == 'ensureIndex') { 822 | foreach ($_GET['index'] as $key => $field) { 823 | $indexes[$field] = (isset($_GET['isdescending'][$key]) && $_GET['isdescending'][$key] ? -1 : 1); 824 | } 825 | self::$model->$action($_GET['collection'], $indexes, ($_GET['unique'] == 'Unique' ? array('unique' => true) 826 | : array())); 827 | $action = 'listCollections'; 828 | } else if ($action == 'deleteIndex') { 829 | self::$model->$action($_GET['collection'], unserialize($_GET['index'])); 830 | return $this->_dumpFormVals(); 831 | } else if ($action == 'getStats') { 832 | $this->mongo[$action] = self::$model->$action(); 833 | unset($this->mongo['listCollections']); 834 | } else if ($action == 'repairDb' || $action == 'getStats') { 835 | $this->mongo[$action] = self::$model->$action(); 836 | $action = 'listCollections'; 837 | } else if ($action == 'dropDb') { 838 | self::$model->$action(); 839 | load::redirect(get::url()); 840 | return; 841 | } 842 | 843 | if (isset($_GET['collection']) && $action != 'listCollections' && method_exists(self::$model, $action)) { 844 | $this->mongo[$action] = self::$model->$action($_GET['collection']); 845 | $this->mongo['count'] = self::$model->count; 846 | $this->mongo['colKeys'] = self::$model->colKeys; 847 | } 848 | if ($action == 'listRows') { 849 | $this->mongo['listIndexes'] = self::$model->listIndexes($_GET['collection']); 850 | } else if ($action == 'dropCollection') { 851 | return load::redirect(get::url() . '?db=' . urlencode($_GET['db'])); 852 | } 853 | } 854 | } 855 | 856 | /** 857 | * HTML helper tools 858 | */ 859 | class htmlHelper { 860 | /** 861 | * Internal storage of the link-prefix and hypertext protocol values 862 | * @var string 863 | */ 864 | protected $_linkPrefix, $_protocol; 865 | 866 | /** 867 | * Internal list of included CSS & JS files used by $this->_tagBuilder() to assure that files are not included twice 868 | * @var array 869 | */ 870 | protected $_includedFiles = array(); 871 | 872 | /** 873 | * Flag array to avoid defining singleton JavaScript & CSS snippets more than once 874 | * @var array 875 | */ 876 | protected $_jsSingleton = array(), $_cssSingleton = array(); 877 | 878 | /** 879 | * Sets the protocol (http/https) - this is modified from the original Vork version for phpMoAdmin usage 880 | */ 881 | public function __construct() { 882 | $this->_protocol = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ? 'https://' : 'http://'); 883 | } 884 | 885 | /** 886 | * Creates simple HTML wrappers, accessed via $this->__call() 887 | * 888 | * JS and CSS files are never included more than once even if requested twice. If DEBUG mode is enabled than the 889 | * second request will be added to the debug log as a duplicate. The jsSingleton and cssSingleton methods operate 890 | * the same as the js & css methods except that they will silently skip duplicate requests instead of logging them. 891 | * 892 | * jsInlineSingleton and cssInlineSingleton makes sure a JavaScript or CSS snippet will only be output once, even 893 | * if echoed out multiple times and this method will attempt to place the JS code into the head section, if 894 | * has already been echoed out then it will return the JS code inline the same as jsInline. Eg.: 895 | * $helloJs = "function helloWorld() {alert('Hello World');}"; 896 | * echo $html->jsInlineSingleton($helloJs); 897 | * 898 | * Adding an optional extra argument to jsInlineSingleton/cssInlineSingleton will return the inline code bare (plus 899 | * a trailing linebreak) if it cannot place it into the head section, this is used for joint JS/CSS statements: 900 | * echo $html->jsInline($html->jsInlineSingleton($helloJs, true) . 'helloWorld();'); 901 | * 902 | * @param string $tagType 903 | * @param array $args 904 | * @return string 905 | */ 906 | protected function _tagBuilder($tagType, $args = array()) { 907 | $arg = current($args); 908 | if (empty($arg) || $arg === '') { 909 | $errorMsg = 'Missing argument for ' . __CLASS__ . '::' . $tagType . '()'; 910 | trigger_error($errorMsg, E_USER_WARNING); 911 | } 912 | 913 | if (is_array($arg)) { 914 | foreach ($arg as $thisArg) { 915 | $return[] = $this->_tagBuilder($tagType, array($thisArg)); 916 | } 917 | $return = implode(PHP_EOL, $return); 918 | } else { 919 | switch ($tagType) { 920 | case 'js': 921 | case 'jsSingleton': 922 | case 'css': //Optional extra argument to define CSS media type 923 | case 'cssSingleton': 924 | case 'jqueryTheme': 925 | if ($tagType == 'jqueryTheme') { 926 | $arg = $this->_protocol . 'ajax.googleapis.com/ajax/libs/jqueryui/1/themes/' 927 | . str_replace(' ', '-', strtolower($arg)) . '/jquery-ui.css'; 928 | $tagType = 'css'; 929 | } 930 | if (!isset($this->_includedFiles[$tagType][$arg])) { 931 | if ($tagType == 'css' || $tagType == 'cssSingleton') { 932 | $return = ''; 934 | } else { 935 | $return = ''; 936 | } 937 | $this->_includedFiles[$tagType][$arg] = true; 938 | } else { 939 | $return = null; 940 | if (DEBUG_MODE && ($tagType == 'js' || $tagType == 'css')) { 941 | debug::log($arg . $tagType . ' file has already been included', 'warn'); 942 | } 943 | } 944 | break; 945 | case 'cssInline': //Optional extra argument to define CSS media type 946 | $return = ''; 953 | break; 954 | case 'jsInline': 955 | $return = ''; 962 | break; 963 | case 'jsInlineSingleton': //Optional extra argument to supress adding of inline JS/CSS wrapper 964 | case 'cssInlineSingleton': 965 | $tagTypeBase = substr($tagType, 0, -15); 966 | $return = null; 967 | $md5 = md5($arg); 968 | if (!isset($this->{'_' . $tagTypeBase . 'Singleton'}[$md5])) { 969 | $this->{'_' . $tagTypeBase . 'Singleton'}[$md5] = true; 970 | if (!$this->_bodyOpen) { 971 | $this->vorkHead[$tagTypeBase . 'Inline'][] = $arg; 972 | } else { 973 | $return = (!isset($args[1]) || !$args[1] ? $this->{$tagTypeBase . 'Inline'}($arg) 974 | : $arg . PHP_EOL); 975 | } 976 | } 977 | break; 978 | case 'div': 979 | case 'li': 980 | case 'p': 981 | case 'h1': 982 | case 'h2': 983 | case 'h3': 984 | case 'h4': 985 | $return = '<' . $tagType . '>' . $arg . ''; 986 | break; 987 | default: 988 | $errorMsg = 'TagType ' . $tagType . ' not valid in ' . __CLASS__ . '::' . __METHOD__; 989 | throw new Exception($errorMsg); 990 | break; 991 | } 992 | } 993 | return $return; 994 | } 995 | 996 | /** 997 | * Creates virtual wrapper methods via $this->_tagBuilder() for the simple wrapper functions including: 998 | * $html->css, js, cssInline, jsInline, div, li, p and h1-h4 999 | * 1000 | * @param string $method 1001 | * @param array $arg 1002 | * @return string 1003 | */ 1004 | public function __call($method, $args) { 1005 | $validTags = array('css', 'js', 'cssSingleton', 'jsSingleton', 'jqueryTheme', 1006 | 'cssInline', 'jsInline', 'jsInlineSingleton', 'cssInlineSingleton', 1007 | 'div', 'li', 'p', 'h1', 'h2', 'h3', 'h4'); 1008 | if (in_array($method, $validTags)) { 1009 | return $this->_tagBuilder($method, $args); 1010 | } else { 1011 | $errorMsg = 'Call to undefined method ' . __CLASS__ . '::' . $method . '()'; 1012 | trigger_error($errorMsg, E_USER_ERROR); 1013 | } 1014 | } 1015 | 1016 | /** 1017 | * Flag to make sure that header() can only be opened one-at-a-time and footer() can only be used after header() 1018 | * @var boolean 1019 | */ 1020 | private $_bodyOpen = false; 1021 | 1022 | /** 1023 | * Sets the default doctype to XHTML 1.1 1024 | * @var string 1025 | */ 1026 | protected $_docType = ''; 1027 | 1028 | /** 1029 | * Allows modification of the docType 1030 | * 1031 | * Can either set to an actual doctype definition or to one of the presets (case-insensitive): 1032 | * XHTML Mobile 1.2 1033 | * XHTML Mobile 1.1 1034 | * XHTML Mobile 1.0 1035 | * Mobile 1.2 (alias for XHTML Mobile 1.2) 1036 | * Mobile 1.1 (alias for XHTML Mobile 1.1) 1037 | * Mobile 1.0 (alias for XHTML Mobile 1.0) 1038 | * Mobile (alias for the most-strict Mobile DTD, currently 1.2) 1039 | * XHTML 1.1 (this is the default DTD, there is no need to apply this method for an XHTML 1.1 doctype) 1040 | * XHTML (Alias for XHTML 1.1) 1041 | * XHTML 1.0 Strict 1042 | * XHTML 1.0 Transitional 1043 | * XHTML 1.0 Frameset 1044 | * XHTML 1.0 (Alias for XHTML 1.0 Strict) 1045 | * HTML 5 1046 | * HTML 4.01 1047 | * HTML (Alias for HTML 4.01) 1048 | * 1049 | * @param string $docType 1050 | */ 1051 | public function setDocType($docType) { 1052 | $docType = str_replace(' ', '', strtolower($docType)); 1053 | if ($docType == 'xhtml1.1' || $docType == 'xhtml') { 1054 | return; //XHTML 1.1 is the default 1055 | } else if ($docType == 'xhtml1.0') { 1056 | $docType = 'strict'; 1057 | } 1058 | $docType = str_replace(array('xhtml mobile', 'xhtml1.0'), array('mobile', ''), $docType); 1059 | $docTypes = array( 1060 | 'mobile1.2' => '', 1062 | 'mobile1.1' => '', 1064 | 'mobile1.0' => '', 1066 | 'strict' => '', 1068 | 'transitional' => '', 1070 | 'frameset' => '', 1072 | 'html4.01' => '', 1074 | 'html5' => '' 1075 | ); 1076 | $docTypes['mobile'] = $docTypes['mobile1.2']; 1077 | $docTypes['html'] = $docTypes['html4.01']; 1078 | $this->_docType = (isset($docTypes[$docType]) ? $docTypes[$docType] : $docType); 1079 | } 1080 | 1081 | /** 1082 | * Array used internally by Vork to cache JavaScript and CSS snippets and place them in the head section 1083 | * Changing the contents of this property may cause Vork components to be rendered incorrectly. 1084 | * @var array 1085 | */ 1086 | public $vorkHead = array(); 1087 | 1088 | /** 1089 | * Returns an HTML header and opens the body container 1090 | * This method will trigger an error if executed more than once without first calling 1091 | * the footer() method on the prior usage 1092 | * This is meant to be utilized within layouts, not views (but will work in either) 1093 | * 1094 | * @param array $args 1095 | * @return string 1096 | */ 1097 | public function header(array $args) { 1098 | if (!$this->_bodyOpen) { 1099 | $this->_bodyOpen = true; 1100 | extract($args); 1101 | $return = $this->_docType 1102 | . PHP_EOL . '' 1103 | . PHP_EOL . '' 1104 | . PHP_EOL . '' . $title . ''; 1105 | 1106 | if (!isset($metaheader['Content-Type'])) { 1107 | $metaheader['Content-Type'] = 'text/html; charset=utf-8'; 1108 | } 1109 | foreach ($metaheader as $name => $content) { 1110 | $return .= PHP_EOL . ''; 1111 | } 1112 | 1113 | $meta['generator'] = 'Vork 2.00'; 1114 | foreach ($meta as $name => $content) { 1115 | $return .= PHP_EOL . ''; 1116 | } 1117 | 1118 | if (isset($favicon)) { 1119 | $return .= PHP_EOL . ''; 1120 | } 1121 | if (isset($animatedFavicon)) { 1122 | $return .= PHP_EOL . ''; 1123 | } 1124 | 1125 | $containers = array('css', 'cssInline', 'js', 'jsInline', 'jqueryTheme'); 1126 | foreach ($containers as $container) { 1127 | if (isset($$container)) { 1128 | $return .= PHP_EOL . $this->$container($$container); 1129 | } 1130 | } 1131 | 1132 | if ($this->vorkHead) { //used internally by Vork tools 1133 | foreach ($this->vorkHead as $container => $objArray) { //works only for inline code, not external files 1134 | $return .= PHP_EOL . $this->$container(implode(PHP_EOL, $objArray)); 1135 | } 1136 | } 1137 | 1138 | if (isset($head)) { 1139 | $return .= PHP_EOL . (is_array($head) ? implode(PHP_EOL, $head) : $head); 1140 | } 1141 | 1142 | $return .= PHP_EOL . '' . PHP_EOL . ''; 1143 | return $return; 1144 | } else { 1145 | $errorMsg = 'Invalid usage of ' . __METHOD__ . '() - the header has already been returned'; 1146 | trigger_error($errorMsg, E_USER_NOTICE); 1147 | } 1148 | } 1149 | 1150 | /** 1151 | * Returns an HTML footer and optional Google Analytics 1152 | * This method will trigger an error if executed without first calling the header() method 1153 | * This is meant to be utilized within layouts, not views (but will work in either) 1154 | * 1155 | * @param array $args 1156 | * @return string 1157 | */ 1158 | public function footer(array $args = array()) { 1159 | if ($this->_bodyOpen) { 1160 | $this->_bodyOpen = false; 1161 | return ''; 1162 | } else { 1163 | $errorMsg = 'Invalid usage of ' . __METHOD__ . '() - header() has not been called'; 1164 | trigger_error($errorMsg, E_USER_NOTICE); 1165 | } 1166 | } 1167 | 1168 | /** 1169 | * Establishes a basic set of JavaScript tools, just echo $html->jsTools() before any JavaScript code that 1170 | * will use the tools. 1171 | * 1172 | * This method will only operate from the first occurrence in your code, subsequent calls will not output anything 1173 | * but you should add it anyway as it will make sure that your code continues to work if you later remove a 1174 | * previous call to jsTools. 1175 | * 1176 | * Tools provided: 1177 | * 1178 | * dom() method is a direct replacement for document.getElementById() that works in all JS-capable 1179 | * browsers Y2k and newer. 1180 | * 1181 | * vork object - defines a global vork storage space; use by appending your own properties, eg.: vork.widgetCount 1182 | * 1183 | * @param Boolean $noJsWrapper set to True if calling from within a $html->jsInline() wrapper 1184 | * @return string 1185 | */ 1186 | public function jsTools($noJsWrapper = false) { 1187 | return $this->jsInlineSingleton("var vork = function() {} 1188 | var dom = function(id) { 1189 | if (typeof document.getElementById != 'undefined') { 1190 | dom = function(id) {return document.getElementById(id);} 1191 | } else if (typeof document.all != 'undefined') { 1192 | dom = function(id) {return document.all[id];} 1193 | } else { 1194 | return false; 1195 | } 1196 | return dom(id); 1197 | }", $noJsWrapper); 1198 | } 1199 | 1200 | /** 1201 | * Load a JavaScript library via Google's AJAX API 1202 | * http://code.google.com/apis/ajaxlibs/documentation/ 1203 | * 1204 | * Version is optional and can be exact (1.8.2) or just version-major (1 or 1.8) 1205 | * 1206 | * Usage: 1207 | * echo $html->jsLoad('jquery'); 1208 | * echo $html->jsLoad(array('yui', 'mootools')); 1209 | * echo $html->jsLoad(array('yui' => 2.7, 'jquery', 'dojo' => '1.3.1', 'scriptaculous')); 1210 | * 1211 | * //You can also use the Google API format JSON-decoded in which case version is required & name must be lowercase 1212 | * $jsLibs = array(array('name' => 'mootools', 'version' => 1.2, 'base_domain' => 'ditu.google.cn'), array(...)); 1213 | * echo $html->jsLoad($jsLibs); 1214 | * 1215 | * @param mixed $library Can be a string, array(str1, str2...) or , array(name1 => version1, name2 => version2...) 1216 | * or JSON-decoded Google API syntax array(array('name' => 'yui', 'version' => 2), array(...)) 1217 | * @param mixed $version Optional, int or str, this is only used if $library is a string 1218 | * @param array $options Optional, passed to Google "optionalSettings" argument, only used if $library == str 1219 | * @return str 1220 | */ 1221 | public function jsLoad($library, $version = null, array $options = array()) { 1222 | $versionDefaults = array('swfobject' => 2, 'yui' => 2, 'ext-core' => 3, 'mootools' => 1.2); 1223 | if (!is_array($library)) { //jsLoad('yui') 1224 | $library = strtolower($library); 1225 | if (!$version) { 1226 | $version = (!isset($versionDefaults[$library]) ? 1 : $versionDefaults[$library]); 1227 | } 1228 | $library = array('name' => $library, 'version' => $version); 1229 | $library = array(!$options ? $library : array_merge($library, $options)); 1230 | } else { 1231 | foreach ($library as $key => $val) { 1232 | if (!is_array($val)) { 1233 | if (is_int($key)) { //jsLoad(array('yui', 'prototype')) 1234 | $val = strtolower($val); 1235 | $version = (!isset($versionDefaults[$val]) ? 1 : $versionDefaults[$val]); 1236 | $library[$key] = array('name' => $val, 'version' => $version); 1237 | } else if (!is_array($val)) { // //jsLoad(array('yui' => '2.8.0r4', 'prototype' => 1.6)) 1238 | $library[$key] = array('name' => strtolower($key), 'version' => $val); 1239 | } 1240 | } 1241 | } 1242 | } 1243 | $url = $this->_protocol . 'www.google.com/jsapi'; 1244 | if (!isset($this->_includedFiles['js'][$url])) { //autoload library 1245 | $this->_includedFiles['js'][$url] = true; 1246 | $url .= '?autoload=' . urlencode(json_encode(array('modules' => array_values($library)))); 1247 | $return = $this->js($url); 1248 | } else { //load inline 1249 | foreach ($library as $lib) { 1250 | $js = 'google.load("' . $lib['name'] . '", "' . $lib['version'] . '"'; 1251 | if (count($lib) > 2) { 1252 | unset($lib['name'], $lib['version']); 1253 | $js .= ', ' . json_encode($lib); 1254 | } 1255 | $jsLoads[] = $js . ');'; 1256 | } 1257 | $return = $this->jsInline(implode(PHP_EOL, $jsLoads)); 1258 | } 1259 | return $return; 1260 | } 1261 | 1262 | /** 1263 | * Takes an array of key-value pairs and formats them in the syntax of HTML-container properties 1264 | * 1265 | * @param array $properties 1266 | * @return string 1267 | */ 1268 | public static function formatProperties(array $properties) { 1269 | $return = array(); 1270 | foreach ($properties as $name => $value) { 1271 | $return[] = $name . '="' . get::htmlentities($value) . '"'; 1272 | } 1273 | return implode(' ', $return); 1274 | } 1275 | 1276 | /** 1277 | * Creates an anchor or link container 1278 | * 1279 | * @param array $args 1280 | * @return string 1281 | */ 1282 | public function anchor(array $args) { 1283 | if (!isset($args['text']) && isset($args['href'])) { 1284 | $args['text'] = $args['href']; 1285 | } 1286 | if (!isset($args['title']) && isset($args['text'])) { 1287 | $args['title'] = str_replace(array("\n", "\r"), ' ', strip_tags($args['text'])); 1288 | } 1289 | $return = ''; 1290 | if (isset($args['ajaxload'])) { 1291 | $return = $this->jsSingleton('/js/ajax.js'); 1292 | $onclick = "return ajax.load('" . $args['ajaxload'] . "', this.href);"; 1293 | $args['onclick'] = (!isset($args['onclick']) ? $onclick : $args['onclick'] . '; ' . $onclick); 1294 | unset($args['ajaxload']); 1295 | } 1296 | $text = (isset($args['text']) ? $args['text'] : null); 1297 | unset($args['text']); 1298 | return $return . '' . $text . ''; 1299 | } 1300 | 1301 | /** 1302 | * Shortcut to access the anchor method 1303 | * 1304 | * @param str $href 1305 | * @param str $text 1306 | * @param array $args 1307 | * @return str 1308 | */ 1309 | public function link($href, $text = null, array $args = array()) { 1310 | if (strpos($href, 'http') !== 0) { 1311 | $href = $this->_linkPrefix . $href; 1312 | } 1313 | $args['href'] = $href; 1314 | if ($text !== null) { 1315 | $args['text'] = $text; 1316 | } 1317 | return $this->anchor($args); 1318 | } 1319 | 1320 | /** 1321 | * Wrapper display computer-code samples 1322 | * 1323 | * @param str $str 1324 | * @return str 1325 | */ 1326 | public function code($str) { 1327 | return '' . str_replace(' ', '  ', nl2br(get::htmlentities($str))) . ''; 1328 | } 1329 | 1330 | /** 1331 | * Will return true if the number passed in is even, false if odd. 1332 | * 1333 | * @param int $number 1334 | * @return boolean 1335 | */ 1336 | public function isEven($number) { 1337 | return (Boolean) ($number % 2 == 0); 1338 | } 1339 | 1340 | /** 1341 | * Internal incrementing integar for the alternator() method 1342 | * @var int 1343 | */ 1344 | private $alternator = 1; 1345 | 1346 | /** 1347 | * Returns an alternating Boolean, useful to generate alternating background colors 1348 | * Eg.: 1349 | * $colors = array(true => 'gray', false => 'white'); 1350 | * echo '
...
'; //gray background 1351 | * echo '
...
'; //white background 1352 | * echo '
...
'; //gray background 1353 | * 1354 | * @return Boolean 1355 | */ 1356 | public function alternator() { 1357 | return $this->isEven(++$this->alternator); 1358 | } 1359 | 1360 | /** 1361 | * Creates a list from an array with automatic nesting 1362 | * 1363 | * @param array $list 1364 | * @param string $kvDelimiter Optional, sets delimiter to appear between keys and values 1365 | * @param string $listType Optional, must be a valid HTML list type, either "ul" (default) or "ol" 1366 | * @return string 1367 | */ 1368 | public function drillDownList(array $list, $kvDelimiter = ': ', $listType = 'ul') { 1369 | foreach ($list as $key => $val) { 1370 | $val = (is_array($val) ? $this->drillDownList($val, $kvDelimiter, $listType) : $val); 1371 | $str = trim($key && !is_int($key) ? $key . $kvDelimiter . $val : $val); 1372 | if ($str) { 1373 | $return[] = $this->li($str); 1374 | } 1375 | } 1376 | if (isset($return)) { 1377 | return ($listType ? '<' . $listType . '>' : '') 1378 | . implode(PHP_EOL, $return) 1379 | . ($listType ? '' : ''); 1380 | } 1381 | } 1382 | 1383 | /** 1384 | * Returns a list of notifications if there are any - similar to the Flash feature of Ruby on Rails 1385 | * 1386 | * @param mixed $messages String or an array of strings 1387 | * @param string $class 1388 | * @return string Returns null if there are no notifications to return 1389 | */ 1390 | public function getNotifications($messages, $class = 'errormessage') { 1391 | if (isset($messages) && $messages) { 1392 | return '
' 1393 | . (is_array($messages) ? implode('
', $messages) : $messages) . '
'; 1394 | } 1395 | } 1396 | } 1397 | 1398 | /** 1399 | * Vork form-helper 1400 | */ 1401 | class formHelper { 1402 | /** 1403 | * Internal flag to keep track if a form tag has been opened and not yet closed 1404 | * @var boolean 1405 | */ 1406 | private $_formOpen = false; 1407 | 1408 | /** 1409 | * Internal form element counter 1410 | * @var int 1411 | */ 1412 | private $_inputCounter = array(); 1413 | 1414 | /** 1415 | * Converts dynamically-assigned array indecies to use an explicitely defined index 1416 | * 1417 | * @param string $name 1418 | * @return string 1419 | */ 1420 | protected function _indexDynamicArray($name) { 1421 | $dynamicArrayStart = strpos($name, '[]'); 1422 | if ($dynamicArrayStart) { 1423 | $prefix = substr($name, 0, $dynamicArrayStart); 1424 | if (!isset($this->_inputCounter[$prefix])) { 1425 | $this->_inputCounter[$prefix] = -1; 1426 | } 1427 | $name = $prefix . '[' . ++$this->_inputCounter[$prefix] . substr($name, ($dynamicArrayStart + 1)); 1428 | } 1429 | return $name; 1430 | } 1431 | 1432 | /** 1433 | * Form types that do not change value with user input 1434 | * @var array 1435 | */ 1436 | protected $_staticTypes = array('hidden', 'submit', 'button', 'image'); 1437 | 1438 | /** 1439 | * Sets the standard properties available to all input elements in addition to user-defined properties 1440 | * Standard properties are: name, value, class, style, id 1441 | * 1442 | * @param array $args 1443 | * @param array $propertyNames Optional, an array of user-defined properties 1444 | * @return array 1445 | */ 1446 | protected function _getProperties(array $args, array $propertyNames = array()) { 1447 | $method = (isset($this->_formOpen['method']) && $this->_formOpen['method'] == 'get' ? $_GET : $_POST); 1448 | if (isset($args['name']) && (!isset($args['type']) || !in_array($args['type'], $this->_staticTypes))) { 1449 | $arrayStart = strpos($args['name'], '['); 1450 | if (!$arrayStart) { 1451 | if (isset($method[$args['name']])) { 1452 | $args['value'] = $method[$args['name']]; 1453 | } 1454 | } else { 1455 | $name = $this->_indexDynamicArray($args['name']); 1456 | if (preg_match_all('/\[(.*)\]/', $name, $arrayIndex)) { 1457 | array_shift($arrayIndex); //dump the 0 index element containing full match string 1458 | } 1459 | $name = substr($name, 0, $arrayStart); 1460 | if (isset($method[$name])) { 1461 | $args['value'] = $method[$name]; 1462 | if (!isset($args['type']) || $args['type'] != 'checkbox') { 1463 | foreach ($arrayIndex as $idx) { 1464 | if (isset($args['value'][current($idx)])) { 1465 | $args['value'] = $args['value'][current($idx)]; 1466 | } else { 1467 | unset($args['value']); 1468 | break; 1469 | } 1470 | } 1471 | } 1472 | } 1473 | } 1474 | } 1475 | $return = array(); 1476 | $validProperties = array_merge($propertyNames, array('name', 'value', 'class', 'style', 'id')); 1477 | foreach ($validProperties as $propertyName) { 1478 | if (isset($args[$propertyName])) { 1479 | $return[$propertyName] = $args[$propertyName]; 1480 | } 1481 | } 1482 | return $return; 1483 | } 1484 | 1485 | /** 1486 | * Begins a form 1487 | * Includes a safety mechanism to prevent re-opening an already-open form 1488 | * 1489 | * @param array $args 1490 | * @return string 1491 | */ 1492 | public function open(array $args = array()) { 1493 | if (!$this->_formOpen) { 1494 | if (!isset($args['method'])) { 1495 | $args['method'] = 'post'; 1496 | } 1497 | 1498 | $this->_formOpen = array('id' => (isset($args['id']) ? $args['id'] : true), 1499 | 'method' => $args['method']); 1500 | 1501 | if (!isset($args['action'])) { 1502 | $args['action'] = $_SERVER['REQUEST_URI']; 1503 | } 1504 | if (isset($args['upload']) && $args['upload'] && !isset($args['enctype'])) { 1505 | $args['enctype'] = 'multipart/form-data'; 1506 | } 1507 | if (isset($args['legend'])) { 1508 | $legend = $args['legend']; 1509 | unset($args['legend']); 1510 | if (!isset($args['title'])) { 1511 | $args['title'] = $legend; 1512 | } 1513 | } else if (isset($args['title'])) { 1514 | $legend = $args['title']; 1515 | } 1516 | if (isset($args['alert'])) { 1517 | if ($args['alert']) { 1518 | $alert = (is_array($args['alert']) ? implode('
', $args['alert']) : $args['alert']); 1519 | } 1520 | unset($args['alert']); 1521 | } 1522 | $return = '
' . PHP_EOL . '
' . PHP_EOL; 1523 | if (isset($legend)) { 1524 | $return .= '' . $legend . '' . PHP_EOL; 1525 | } 1526 | if (isset($alert)) { 1527 | $return .= $this->getErrorMessageContainer((isset($args['id']) ? $args['id'] : 'form'), $alert); 1528 | } 1529 | return $return; 1530 | } else if (DEBUG_MODE) { 1531 | $errorMsg = 'Invalid usage of ' . __METHOD__ . '() - a form is already open'; 1532 | trigger_error($errorMsg, E_USER_NOTICE); 1533 | } 1534 | } 1535 | 1536 | /** 1537 | * Closes a form if one is open 1538 | * 1539 | * @return string 1540 | */ 1541 | public function close() { 1542 | if ($this->_formOpen) { 1543 | $this->_formOpen = false; 1544 | return '
'; 1545 | } else if (DEBUG_MODE) { 1546 | $errorMsg = 'Invalid usage of ' . __METHOD__ . '() - there is no open form to close'; 1547 | trigger_error($errorMsg, E_USER_NOTICE); 1548 | } 1549 | } 1550 | 1551 | /** 1552 | * Adds label tags to a form element 1553 | * 1554 | * @param array $args 1555 | * @param str $formElement 1556 | * @return str 1557 | */ 1558 | protected function _getLabel(array $args, $formElement) { 1559 | if (!isset($args['label']) && isset($args['name']) 1560 | && (!isset($args['type']) || !in_array($args['type'], $this->_staticTypes))) { 1561 | $args['label'] = ucfirst($args['name']); 1562 | } 1563 | 1564 | if (isset($args['label'])) { 1565 | $label = get::xhtmlentities($args['label']); 1566 | if (isset($_POST['errors']) && isset($args['name']) && isset($_POST['errors'][$args['name']])) { 1567 | $label .= ' ' . $this->getErrorMessageContainer($args['name'], $_POST['errors'][$args['name']]); 1568 | } 1569 | $labelFirst = (!isset($args['labelFirst']) || $args['labelFirst']); 1570 | if (isset($args['id'])) { 1571 | $label = ''; 1573 | } 1574 | if (isset($args['addBreak']) && $args['addBreak']) { 1575 | $label = ($labelFirst ? $label . '
' : '
' . $label); 1576 | } 1577 | $formElement = ($labelFirst ? $label . $formElement : $formElement . $label); 1578 | if (!isset($args['id'])) { 1579 | $formElement = ''; 1580 | } 1581 | } 1582 | return $formElement; 1583 | } 1584 | 1585 | /** 1586 | * Returns a standardized container to wrap an error message 1587 | * 1588 | * @param string $id 1589 | * @param string $errorMessage Optional, you may want to leave this blank and populate dynamically via JavaScript 1590 | * @return string 1591 | */ 1592 | public function getErrorMessageContainer($id, $errorMessage = '') { 1593 | return '' 1594 | . get::htmlentities($errorMessage) . ''; 1595 | } 1596 | 1597 | /** 1598 | * Used for text, textarea, hidden, password, file, button, image and submit 1599 | * 1600 | * Valid args are any properties valid within an HTML input as well as label 1601 | * 1602 | * @param array $args 1603 | * @return string 1604 | */ 1605 | public function input(array $args) { 1606 | $args['type'] = (isset($args['type']) ? $args['type'] : 'text'); 1607 | 1608 | switch ($args['type']) { 1609 | case 'select': 1610 | return $this->select($args); 1611 | break; 1612 | case 'checkbox': 1613 | return $this->checkboxes($args); 1614 | break; 1615 | case 'radio': 1616 | return $this->radios($args); 1617 | break; 1618 | } 1619 | 1620 | if ($args['type'] == 'textarea' && isset($args['maxlength'])) { 1621 | if (!isset($args['id']) && isset($args['name'])) { 1622 | $args['id'] = $args['name']; 1623 | } 1624 | if (isset($args['id'])) { 1625 | $maxlength = $args['maxlength']; 1626 | } 1627 | unset($args['maxlength']); 1628 | } 1629 | 1630 | if ($args['type'] == 'submit' && !isset($args['class'])) { 1631 | $args['class'] = $args['type']; 1632 | } 1633 | 1634 | $takeFocus = (isset($args['focus']) && $args['focus'] && $args['type'] != 'hidden'); 1635 | if ($takeFocus && !isset($args['id'])) { 1636 | if (isset($args['name'])) { 1637 | $args['id'] = $args['name']; 1638 | } else { 1639 | $takeFocus = false; 1640 | if (DEBUG_MODE) { 1641 | $errorMsg = 'Either name or id is required to use the focus option on a form input'; 1642 | trigger_error($errorMsg, E_USER_NOTICE); 1643 | } 1644 | } 1645 | } 1646 | 1647 | $properties = $this->_getProperties($args, array('type', 'maxlength')); 1648 | 1649 | if ($args['type'] == 'image') { 1650 | $properties['src'] = $args['src']; 1651 | $properties['alt'] = (isset($args['alt']) ? $args['alt'] : ''); 1652 | $optionalProperties = array('height', 'width'); 1653 | foreach ($optionalProperties as $optionalProperty) { 1654 | if (isset($args[$optionalProperty])) { 1655 | $properties[$optionalProperty] = $args[$optionalProperty]; 1656 | } 1657 | } 1658 | } 1659 | 1660 | if ($args['type'] != 'textarea') { 1661 | $return[] = ''; 1662 | } else { 1663 | unset($properties['type']); 1664 | if (isset($properties['value'])) { 1665 | $value = $properties['value']; 1666 | unset($properties['value']); 1667 | } 1668 | if (isset($args['preview']) && $args['preview'] && !isset($properties['id'])) { 1669 | $properties['id'] = 'textarea_' . rand(100, 999); 1670 | } 1671 | $properties['rows'] = (isset($args['rows']) ? $args['rows'] : 13); 1672 | $properties['cols'] = (isset($args['cols']) ? $args['cols'] : 55); 1673 | $return[] = ''; 1686 | if (isset($maxlength) && (!isset($args['validatedInput']) || !$args['validatedInput'])) { 1687 | $return[] = $this->getErrorMessageContainer($properties['id']); 1688 | } 1689 | } 1690 | if (!isset($args['addBreak'])) { 1691 | $args['addBreak'] = true; 1692 | } 1693 | if ($takeFocus) { 1694 | $html = get::helper('html'); 1695 | $return[] = $html->jsInline($html->jsTools(true) . 'dom("' . $args['id'] . '").focus();'); 1696 | } 1697 | if (isset($args['preview']) && $args['preview']) { 1698 | $js = 'document.writeln(\'
' 1699 | . '
' 1700 | . '
\');' . PHP_EOL 1701 | . 'if (dom("livepreview_' . $properties['id'] . '")) {' . PHP_EOL 1702 | . ' var updateLivePreview_' . $properties['id'] . ' = ' 1703 | . 'function() {dom("livepreview_' . $properties['id'] . '").innerHTML = ' 1704 | . 'dom("' . $properties['id'] . '").value};' . PHP_EOL 1705 | . ' dom("' . $properties['id'] . '").onkeyup = updateLivePreview_' . $properties['id'] . ';' 1706 | . ' updateLivePreview_' . $properties['id'] . '();' . PHP_EOL 1707 | . '}'; 1708 | if (!isset($html)) { 1709 | $html = get::helper('html'); 1710 | } 1711 | $return[] = $html->jsInline($html->jsTools(true) . $js); 1712 | } 1713 | return $this->_getLabel($args, implode($return)); 1714 | } 1715 | 1716 | /** 1717 | * Returns a form select element 1718 | * 1719 | * $args = array( 1720 | * 'name' => '', 1721 | * 'multiple' => true, 1722 | * 'leadingOptions' => array(), 1723 | * 'optgroups' => array('group 1' => array('label' => 'g1o1', 'value' => 'grp 1 opt 1'), 1724 | * 'group 2' => array(),), 1725 | * 'options' => array('value1' => 'text1', 'value2' => 'text2', 'value3' => 'text3'), 1726 | * 'value' => array('value2', 'value3') //if (multiple==false) 'value' => (str) 'value3' 1727 | * ); 1728 | * 1729 | * @param array $args 1730 | * @return str 1731 | */ 1732 | public function select(array $args) { 1733 | if (!isset($args['id'])) { 1734 | $args['id'] = $args['name']; 1735 | } 1736 | if (isset($args['multiple']) && $args['multiple']) { 1737 | $args['multiple'] = 'multiple'; 1738 | if (substr($args['name'], -2) != '[]') { 1739 | $args['name'] .= '[]'; 1740 | } 1741 | } 1742 | $properties = $this->_getProperties($args, array('multiple')); 1743 | $values = (isset($properties['value']) ? $properties['value'] : null); 1744 | unset($properties['value']); 1745 | if (!is_array($values)) { 1746 | $values = ($values != '' ? array($values) : array()); 1747 | } 1748 | $return = ''; 1799 | if (!isset($args['addBreak'])) { 1800 | $args['addBreak'] = true; 1801 | } 1802 | $return = $this->_getLabel($args, $return); 1803 | if (isset($args['error'])) { 1804 | $return .= $this->getErrorMessageContainer($args['id'], '
' . $args['error']); 1805 | } 1806 | return $return; 1807 | } 1808 | 1809 | /** 1810 | * Cache containing individual radio or checkbox elements in an array 1811 | * @var array 1812 | */ 1813 | public $radios = array(), $checkboxes = array(); 1814 | 1815 | /** 1816 | * Returns a set of radio form elements 1817 | * 1818 | * array( 1819 | * 'name' => '', 1820 | * 'value' => '', 1821 | * 'id' => '', 1822 | * 'legend' => '', 1823 | * 'options' => array('value1' => 'text1', 'value2' => 'text2', 'value3' => 'text3'), 1824 | * 'options' => array('text1', 'text2', 'text3'), //also acceptable (cannot do half this, half above syntax) 1825 | * ) 1826 | * 1827 | * @param array $args 1828 | * @return str 1829 | */ 1830 | public function radios(array $args) { 1831 | $id = (isset($args['id']) ? $args['id'] : $args['name']); 1832 | $properties = $this->_getProperties($args); 1833 | if (isset($properties['value'])) { 1834 | $checked = $properties['value']; 1835 | unset($properties['value']); 1836 | } 1837 | $properties['type'] = (isset($args['type']) ? $args['type'] : 'radio'); 1838 | $useValues = (key($args['options']) !== 0 || (isset($args['useValue']) && $args['useValue'])); 1839 | foreach ($args['options'] as $value => $text) { 1840 | if (!$useValues) { 1841 | $value = $text; 1842 | } 1843 | $properties['id'] = $id . '_' . preg_replace('/\W/', '', $value); 1844 | $properties['value'] = $value; 1845 | if (isset($checked) && 1846 | ((($properties['type'] == 'radio' || !is_array($checked)) && $value == $checked) 1847 | || ($properties['type'] == 'checkbox' && is_array($checked) && in_array((string) $value, $checked)))) { 1848 | $properties['checked'] = 'checked'; 1849 | $rowClass = (!isset($properties['class']) ? 'checked' : $properties['class'] . ' checked'); 1850 | } 1851 | $labelFirst = (isset($args['labelFirst']) ? $args['labelFirst'] : false); 1852 | $labelArgs = array('label' => $text, 'id' => $properties['id'], 'labelFirst' => $labelFirst); 1853 | $input = ''; 1854 | $row = $this->_getLabel($labelArgs, $input); 1855 | if (isset($rowClass)) { 1856 | $row = '' . $row . ''; 1857 | } 1858 | $radios[] = $row; 1859 | unset($properties['checked'], $rowClass); 1860 | } 1861 | $this->{$properties['type'] == 'radio' ? 'radios' : 'checkboxes'} = $radios; 1862 | $break = (!isset($args['optionBreak']) ? '
' : $args['optionBreak']); 1863 | $addFieldset = (isset($args['addFieldset']) ? $args['addFieldset'] 1864 | : ((isset($args['label']) && $args['label']) || count($args['options']) > 1)); 1865 | if ($addFieldset) { 1866 | $return = '
'; 1867 | if (isset($args['label'])) { 1868 | $return .= '' . get::htmlentities($args['label']) . ''; 1869 | } 1870 | $return .= implode($break, $radios) . '
'; 1871 | } else { 1872 | $return = implode($break, $radios); 1873 | } 1874 | if (isset($_POST['errors']) && isset($_POST['errors'][$id])) { 1875 | $return = $this->getErrorMessageContainer($id, $_POST['errors'][$id]) . $return; 1876 | } 1877 | return $return; 1878 | } 1879 | 1880 | /** 1881 | * Returns a set of checkbox form elements 1882 | * 1883 | * This method essentially extends the radios method and uses an identical signature except 1884 | * that $args['value'] can also accept an array of values to be checked. 1885 | * 1886 | * @param array $args 1887 | * @return str 1888 | */ 1889 | public function checkboxes(array $args) { 1890 | $args['type'] = 'checkbox'; 1891 | if (isset($args['value']) && !is_array($args['value'])) { 1892 | $args['value'] = array($args['value']); 1893 | } 1894 | $nameParts = explode('[', $args['name']); 1895 | if (!isset($args['id'])) { 1896 | $args['id'] = $nameParts[0]; 1897 | } 1898 | if (!isset($nameParts[1]) && count($args['options']) > 1) { 1899 | $args['name'] .= '[]'; 1900 | } 1901 | return $this->radios($args); 1902 | } 1903 | 1904 | /** 1905 | * Opens up shorthand usage of form elements like $form->file() and $form->submit() 1906 | * 1907 | * @param string $name 1908 | * @param array $args 1909 | * @return mixed 1910 | */ 1911 | public function __call($name, array $args) { 1912 | $inputShorthand = array('text', 'textarea', 'password', 'file', 'hidden', 'submit', 'button', 'image'); 1913 | if (in_array($name, $inputShorthand)) { 1914 | $args[0]['type'] = $name; 1915 | return $this->input($args[0]); 1916 | } 1917 | trigger_error('Call to undefined method ' . __CLASS__ . '::' . $name . '()', E_USER_ERROR); 1918 | } 1919 | } 1920 | 1921 | class jsonHelper { 1922 | /** 1923 | * Outputs content in JSON format 1924 | * @param mixed $content Can be a JSON string or an array of any data that will automatically be converted to JSON 1925 | * @param string $filename Default filename within the user-prompt for saving the JSON file 1926 | */ 1927 | public function echoJson($content, $filename = null) { 1928 | header('Cache-Control: no-cache, must-revalidate'); 1929 | header('Expires: Mon, 01 Jan 2000 01:00:00 GMT'); 1930 | header('Content-type: application/json'); 1931 | if ($filename) { 1932 | header('Content-Disposition: attachment; filename=' . $filename); 1933 | } 1934 | echo (!is_array($content) && !is_object($content) ? $content : json_encode($content)); 1935 | } 1936 | } 1937 | 1938 | /** 1939 | * phpMoAdmin specific functionality 1940 | */ 1941 | class phpMoAdmin { 1942 | /** 1943 | * Sets the depth limit for phpMoAdmin::getArrayKeys (and prevents an endless loop with self-referencing objects) 1944 | */ 1945 | const DRILL_DOWN_DEPTH_LIMIT = 8; 1946 | 1947 | /** 1948 | * Retrieves all the keys & subkeys of an array recursively drilling down 1949 | * 1950 | * @param array $array 1951 | * @param string $path 1952 | * @param int $drillDownDepthCount 1953 | * @return array 1954 | */ 1955 | public static function getArrayKeys(array $array, $path = '', $drillDownDepthCount = 0) { 1956 | $return = array(); 1957 | if ($drillDownDepthCount) { 1958 | $path .= '.'; 1959 | } 1960 | if (++$drillDownDepthCount < self::DRILL_DOWN_DEPTH_LIMIT) { 1961 | foreach ($array as $key => $val) { 1962 | $return[$id] = $id = $path . $key; 1963 | if (is_array($val)) { 1964 | $return = array_merge($return, self::getArrayKeys($val, $id, $drillDownDepthCount)); 1965 | } 1966 | } 1967 | } 1968 | return $return; 1969 | } 1970 | 1971 | /** 1972 | * Strip slashes recursively - used only when magic quotes is enabled (this reverses magic quotes) 1973 | * 1974 | * @param mixed $val 1975 | * @return mixed 1976 | */ 1977 | public static function stripslashes($val) { 1978 | return (is_array($val) ? array_map(array('self', 'stripslashes'), $val) : stripslashes($val)); 1979 | } 1980 | } 1981 | 1982 | /** 1983 | * phpMoAdmin bootstrap 1984 | */ 1985 | session_start(); 1986 | if (get_magic_quotes_gpc()) { 1987 | $_GET = phpMoAdmin::stripslashes($_GET); 1988 | $_POST = phpMoAdmin::stripslashes($_POST); 1989 | } 1990 | 1991 | if (isset($accessControl) && !isset($_SESSION['user']) && isset($_POST['username'])) { 1992 | $_POST = array_map('trim', $_POST); 1993 | if (isset($accessControl[$_POST['username']]) && $accessControl[$_POST['username']] == $_POST['password']) { 1994 | $_SESSION['user'] = $_POST['username']; 1995 | } else { 1996 | $_POST['errors']['username'] = 'Incorrect username or password'; 1997 | } 1998 | } 1999 | $isAuthenticated = (!isset($accessControl) || isset($_SESSION['user'])); 2000 | 2001 | $html = get::helper('html'); 2002 | $ver = explode('.', phpversion()); 2003 | get::$isPhp523orNewer = ($ver[0] >= 5 && ($ver[1] > 2 || ($ver[1] == 2 && $ver[2] >= 3))); 2004 | $form = new formHelper; 2005 | 2006 | if ($isAuthenticated) { 2007 | if (!isset($_GET['db'])) { 2008 | $_GET['db'] = moadminModel::$dbName; 2009 | } else if (strpos($_GET['db'], '.') !== false) { 2010 | $_GET['db'] = $_GET['newdb']; 2011 | } 2012 | try { 2013 | moadminComponent::$model = new moadminModel($_GET['db']); 2014 | } catch(Exception $e) { 2015 | echo $e; 2016 | exit(0); 2017 | } 2018 | $mo = new moadminComponent; 2019 | 2020 | if (isset($_GET['export']) && isset($mo->mongo['listRows'])) { 2021 | $rows = array(); 2022 | foreach ($mo->mongo['listRows'] as $row) { 2023 | $rows[] = serialize($row); 2024 | } 2025 | $filename = get::htmlentities($_GET['db']); 2026 | if (isset($_GET['collection'])) { 2027 | $filename .= '~' . get::htmlentities($_GET['collection']); 2028 | } 2029 | $filename .= '.json'; 2030 | get::helper('json')->echoJson($rows, $filename); 2031 | exit(0); 2032 | } 2033 | } 2034 | 2035 | /** 2036 | * phpMoAdmin front-end view-element 2037 | */ 2038 | $headerArgs['title'] = (isset($_GET['action']) ? 'phpMoAdmin - ' . get::htmlentities($_GET['action']) : 'phpMoAdmin'); 2039 | if (THEME != 'classic') { 2040 | $headerArgs['jqueryTheme'] = (in_array(THEME, array('swanky-purse', 'trontastic', 'simple-gray')) ? THEME : 'classic'); 2041 | } 2042 | $headerArgs['cssInline'] = ' 2043 | /* reset */ 2044 | html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, 2045 | big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, 2046 | center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td { 2047 | margin: 0; padding: 0; border: 0; outline: 0; font-size: 100%; vertical-align: baseline; background: transparent;} 2048 | input, textarea {margin: 0; padding: 0;} 2049 | body {line-height: 1;} 2050 | blockquote, q {quotes: none;} 2051 | blockquote:before, blockquote:after, q:before, q:after {content: ""; content: none;} 2052 | :focus {outline: 0;} 2053 | ins {text-decoration: none;} 2054 | del {text-decoration: line-through;} 2055 | table {border-collapse: collapse; border-spacing: 0;} 2056 | 2057 | html{color:#000;} 2058 | caption,th{text-align:left;} 2059 | h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;} 2060 | abbr,acronym{font-variant:normal;} 2061 | sup{vertical-align:text-top;} 2062 | sub{vertical-align:text-bottom;} 2063 | input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;} 2064 | input,textarea,select{*font-size:100%;} 2065 | legend{color:#000;} 2066 | 2067 | /* \*/ html, body{height:100%;} /* */ 2068 | 2069 | /* initialize */ 2070 | html {background: #74736d;} 2071 | body {margin: auto; width: 990px; font-family: "Arial"; font-size: small; background: #000000; color: #ffffff;} 2072 | #bodycontent {padding: 10px; border: 0px solid;} 2073 | textarea {width: 640px; height: 70px;} 2074 | a, .textLink {text-decoration: none; color: #96f226; font-weight: bold;} 2075 | a:hover, .textLink:hover {text-decoration: underline; color: #9fda58;} 2076 | a:hover pre, h1 a:hover {text-decoration: none;} 2077 | h1, h2, h3, h4 {margin-bottom: 3px;} 2078 | h1, h2 {margin-left: -1px;} 2079 | h1 {font-family: "Arial Black"; font-size: 27px; color: #b8ec79;} 2080 | h1.midpageh1 {margin-top: 10px;} 2081 | h2 {font-size: large; font-weight: bold; margin-top: 10px; color: #660000;} 2082 | h3 {font-weight: bold; color: #687d1c;} 2083 | h4 {font-weight: bold; color: #10478b;} 2084 | p {margin-bottom: 10px; line-height: 1.75;} 2085 | li {line-height: 1.5; margin-left: 15px;} 2086 | .errormessage {color: #990000; font-weight: bold; background: #ffffff; border: 1px solid #ff0000; padding: 2px;} 2087 | .rownumber {float: right; padding: 0px 5px 0px 5px; border-left: 1px dotted; border-bottom: 1px dotted; color: #ffffff; 2088 | margin-top: 4px; margin-right: -1px;} 2089 | .ui-widget-header .rownumber {margin-top: 2px; margin-right: 0px;} 2090 | pre {border: 1px solid; margin: 1px; padding-left: 5px;} 2091 | li .ui-widget-content {margin: 1px 1px 3px 1px;} 2092 | #mongo_rows {padding-top: 10px;} 2093 | #moadminlogo {color: #96f226; border: 0px solid; padding-left: 10px; font-size: 4px!important; 2094 | width: 265px; height: 63px; overflow: hidden;}'; 2095 | 2096 | switch (THEME) { 2097 | case 'swanky-purse': 2098 | $headerArgs['cssInline'] .= ' 2099 | html {background: #261803;} 2100 | h1, .rownumber {color: #baaa5a;} 2101 | 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;} 2102 | #moadminlogo {color: #baaa5a;} 2103 | li .ui-widget-header {margin: 0px 1px 0px 1px;} 2104 | .ui-widget-header .rownumber {margin-top: 2px; margin-right: -1px;}'; 2105 | break; 2106 | case 'classic': 2107 | $headerArgs['cssInline'] .= ' 2108 | html, .ui-widget-header, button {background: #ccc78c;} 2109 | .ui-widget-content, input.ui-state-hover {background: #edf2ed;} 2110 | h1, .rownumber {color: #796f54;} 2111 | body {background: #ffffcc; color: #000000;} 2112 | #bodycontent {background: #ffffcc;} 2113 | #moadminlogo, button {color: #bb0022;} 2114 | a, .textLink {color: #990000;} 2115 | a:hover, .textLink:hover {color: #7987ae;} 2116 | li .ui-widget-header {margin: 0px 1px 0px 1px;} 2117 | .rownumber {margin-top: 2px; margin-right: 0px;} 2118 | .ui-dialog {border: 3px outset;} 2119 | .ui-dialog .ui-dialog-titlebar {padding: 3px; margin: 1px; border: 3px ridge;} 2120 | .ui-dialog #confirm {padding: 10px;} 2121 | .ui-dialog .ui-icon-closethick, .ui-dialog button {float: right; margin: 4px;} 2122 | .ui-dialog .ui-icon-closethick {margin-top: -13px;} 2123 | body:first-of-type .ui-dialog .ui-icon-closethick {margin-top: -2px;} /*Chrome/Safari*/ 2124 | .ui-resizable {position: relative;} 2125 | .ui-resizable-handle {position: absolute;font-size: 0.1px;z-index: 99999; display: block;} 2126 | .ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle {display: none;} 2127 | .ui-resizable-n {cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0px;} 2128 | .ui-resizable-s {cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0px;} 2129 | .ui-resizable-e {cursor: e-resize; width: 7px; right: -5px; top: 0px; height: 100%;} 2130 | .ui-resizable-w {cursor: w-resize; width: 7px; left: -5px; top: 0px; height: 100%;} 2131 | .ui-resizable-se {cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px;} 2132 | .ui-resizable-sw {cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px;} 2133 | .ui-resizable-nw {cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px;} 2134 | .ui-resizable-ne {cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}'; 2135 | break; 2136 | case 'simple-gray': 2137 | $headerArgs['cssInline'] .= ' 2138 | html, body {background: #eee; color: #333;} 2139 | body {font-family: sans-serif, Arial;} 2140 | pre {font-size: 13px; padding: 10px; margin: 0; background: #fff; border: none;} 2141 | #moadminlogo {background: none;} 2142 | a, .textLink {font-weight: normal;} 2143 | a, .textLink, #moadminlogo, h1 {color: #0088cc;} 2144 | a:hover, .textLink:hover {color: #005580;} 2145 | li .ui-widget-content, li .ui-widget-header {background: #ffe; line-height: 2em; margin: 0 !important; 2146 | border-bottom: 1px solid #eee;} 2147 | ol a {font-weight: bold;} 2148 | #mongo_rows ol {margin: 1em 0;} 2149 | .ui-dialog, #mongo_rows ol li {border-radius: 4px; background: #f7f7f7; margin-bottom: .5em; padding: .5em; border: 1px solid #ddd;} 2150 | .ui-dialog .ui-dialog-titlebar {padding: 3px; margin: 1px; border: 3px ridge;} 2151 | .ui-dialog #confirm {padding: 10px;} 2152 | .ui-dialog .ui-icon-closethick, .ui-dialog button {margin: 4px;} 2153 | .ui-dialog .ui-icon-closethick {float: right; margin-top: -4px; font-size: large;} 2154 | '; 2155 | break; 2156 | } 2157 | echo $html->header($headerArgs); 2158 | 2159 | echo $html->jsLoad(array('jquery', 'jqueryui')); 2160 | $baseUrl = $_SERVER['SCRIPT_NAME']; 2161 | 2162 | $db = (isset($_GET['db']) ? $_GET['db'] : (isset($_POST['db']) ? $_POST['db'] : 'admin')); //admin is in every Mongo DB 2163 | $dbUrl = urlencode($db); 2164 | 2165 | $phpmoadmin = ''; 2183 | echo '

' 2184 | . $html->link('http://www.phpmoadmin.com', $phpmoadmin, array('title' => 'phpMoAdmin')) . '

'; 2185 | 2186 | if (isset($accessControl) && !isset($_SESSION['user'])) { 2187 | echo $form->open(); 2188 | echo $html->div($form->input(array('name' => 'username', 'focus' => true))); 2189 | echo $html->div($form->password(array('name' => 'password'))); 2190 | echo $html->div($form->submit(array('value' => 'Login', 'class' => 'ui-state-hover'))); 2191 | echo $form->close(); 2192 | exit(0); 2193 | } 2194 | 2195 | echo '
'; 2196 | $formArgs = array('method' => 'get'); 2197 | if (isset($mo->mongo['repairDb'])) { 2198 | $formArgs['alert'] = (isset($mo->mongo['repairDb']['ok']) && $mo->mongo['repairDb']['ok'] 2199 | ? 'Database has been repaired and compacted' : 'Database could not be repaired'); 2200 | } 2201 | echo $form->open($formArgs); 2202 | echo $html->div($form->select(array('name' => 'db', 'options' => $mo->mongo['dbs'], 'label' => '', 'value' => $db, 2203 | 'addBreak' => false)) 2204 | . $form->submit(array('value' => 'Change database', 'class' => 'ui-state-hover')) 2205 | . ' ' . get::htmlentities($db) 2206 | . ' [' . $html->link("javascript: mo.repairDatabase('" . get::htmlentities($db) 2207 | . "'); void(0);", 'repair database') . '] [' . $html->link("javascript: mo.dropDatabase('" 2208 | . get::htmlentities($db) . "'); void(0);", 'drop database') . ']'); 2209 | echo $form->close(); 2210 | 2211 | $js = 'var mo = {} 2212 | mo.urlEncode = function(str) { 2213 | return escape(str)' 2214 | . '.replace(/\+/g, "%2B").replace(/%20/g, "+").replace(/\*/g, "%2A").replace(/\//g, "%2F").replace(/@/g, "%40"); 2215 | } 2216 | mo.repairDatabase = function(db) { 2217 | mo.confirm("Are you sure that you want to repair and compact the " + db + " database?", function() { 2218 | window.location.replace("' . $baseUrl . '?db=' . $dbUrl . '&action=repairDb"); 2219 | }); 2220 | } 2221 | mo.dropDatabase = function(db) { 2222 | mo.confirm("Are you sure that you want to drop the " + db + " database?", function() { 2223 | mo.confirm("All the collections in the " + db + " database will be lost along with all the data within them!' 2224 | . '\n\nAre you 100% sure that you want to drop this database?' 2225 | . '\n\nLast chance to cancel!", function() { 2226 | window.location.replace("' . $baseUrl . '?db=' . $dbUrl . '&action=dropDb"); 2227 | }); 2228 | }); 2229 | }'; 2230 | if (!moadminModel::$databaseWhitelist) { 2231 | $js .= ' 2232 | $("select[name=db]").prepend(\'\')' 2233 | . '.after(\'\').change(function() { 2234 | ($(this).val() == "new.database" ? $("input[name=newdb]").show() : $("input[name=newdb]").hide()); 2235 | });'; 2236 | } 2237 | $js .= ' 2238 | mo.confirm = function(dialog, func, title) { 2239 | if (typeof title == "undefined") { 2240 | title = "Please confirm:"; 2241 | } 2242 | if (!$("#confirm").length) { 2243 | $("#dbcollnav").append(\'\'); 2244 | } 2245 | mo.userFunc = func; //overcomes JS scope issues 2246 | $("#confirm").html(dialog).attr("title", title).dialog({modal: true, buttons: { 2247 | "Yes": function() {$(this).dialog("close"); mo.userFunc();}, 2248 | Cancel: function() {$(this).dialog("close");} 2249 | }}).dialog("open"); 2250 | } 2251 | '; 2252 | echo $html->jsInline($js); 2253 | 2254 | if (isset($_GET['collection'])) { 2255 | $collection = get::htmlentities($_GET['collection']); 2256 | unset($_GET['collection']); 2257 | } 2258 | if (isset($mo->mongo['listCollections'])) { 2259 | echo '
'; 2260 | 2261 | echo $form->open(array('method' => 'get')); 2262 | echo $html->div($form->input(array('name' => 'collection', 'label' => '', 'addBreak' => false)) 2263 | . $form->hidden(array('name' => 'action', 'value' => 'createCollection')) 2264 | . $form->submit(array('value' => 'Add new collection', 'class' => 'ui-state-hover')) 2265 | . $form->hidden(array('name' => 'db', 'value' => get::htmlentities($db))) 2266 | . '       [' . $html->link($baseUrl . '?action=getStats', 'stats') . ']'); 2267 | echo $form->close(); 2268 | 2269 | if (!$mo->mongo['listCollections']) { 2270 | echo $html->div('No collections exist'); 2271 | } else { 2272 | echo '
    '; 2273 | foreach ($mo->mongo['listCollections'] as $col => $rowCount) { 2274 | echo $html->li($html->link($baseUrl . '?db=' 2275 | . $dbUrl . '&action=listRows&collection=' . urlencode($col), $col) 2276 | . ' (' . number_format($rowCount) . ')'); 2277 | } 2278 | echo '
'; 2279 | echo $html->jsInline('mo.collectionDrop = function(collection) { 2280 | mo.confirm.collection = collection; 2281 | mo.confirm("Are you sure that you want to drop " + collection + "?", 2282 | function() { 2283 | mo.confirm("All the data in the " + mo.confirm.collection + " collection will be lost;' 2284 | . ' are you 100% sure that you want to drop it?\n\nLast chance to cancel!", 2285 | function() { 2286 | window.location.replace("' . $baseUrl . '?db=' . $dbUrl 2287 | . '&action=dropCollection&collection=" + mo.urlEncode(mo.confirm.collection)); 2288 | } 2289 | ); 2290 | } 2291 | ); 2292 | } 2293 | $(document).ready(function() { 2294 | $("#mongo_collections li").each(function() { 2295 | $(this).prepend("[X] "); 2297 | }); 2298 | }); 2299 | '); 2300 | } 2301 | $url = $baseUrl . '?' . http_build_query($_GET); 2302 | if (isset($collection)) { 2303 | $url .= '&collection=' . urlencode($collection); 2304 | } 2305 | echo $form->open(array('action' => $url, 'style' => 'width: 80px; height: 20px;')) 2306 | . $form->input(array('name' => 'limit', 'value' => $_SESSION['limit'], 'label' => '', 'addBreak' => false, 2307 | 'style' => 'width: 40px;')) 2308 | . $form->submit(array('value' => 'limit', 'class' => 'ui-state-hover')) 2309 | . $form->close(); 2310 | echo '
'; 2311 | } 2312 | echo '
'; //end of dbcollnav 2313 | $dbcollnavJs = '$("#dbcollnav").after(\'[Show Database & Collection selection]\').hide();'; 2315 | if (isset($mo->mongo['listRows'])) { 2316 | echo $form->open(array('action' => $baseUrl . '?db=' . $dbUrl . '&action=renameCollection', 2317 | 'style' => 'width: 600px; display: none;', 'id' => 'renamecollectionform')) 2318 | . $form->hidden(array('name' => 'collectionfrom', 'value' => $collection)) 2319 | . $form->input(array('name' => 'collectionto', 'value' => $collection, 'label' => '', 'addBreak' => false)) 2320 | . $form->submit(array('value' => 'Rename Collection', 'class' => 'ui-state-hover')) 2321 | . $form->close(); 2322 | $js = "$('#collectionname').hide(); $('#renamecollectionform').show(); void(0);"; 2323 | echo '

' . $html->link('javascript: ' . $js, $collection) . '

'; 2324 | 2325 | if (isset($mo->mongo['listIndexes'])) { 2326 | echo '
'); void(0);" 2335 | . '">[Add another index field]' 2336 | . $form->radios(array('name' => 'unique', 'options' => array('Index', 'Unique'), 'value' => 'Index')) 2337 | . $form->submit(array('value' => 'Add new index', 'class' => 'ui-state-hover')) 2338 | . $form->hidden(array('name' => 'action', 'value' => 'ensureIndex')) 2339 | . $form->hidden(array('name' => 'db', 'value' => get::htmlentities($db))) 2340 | . $form->hidden(array('name' => 'collection', 'value' => $collection)) 2341 | . $form->close(); 2342 | foreach ($mo->mongo['listIndexes'] as $indexArray) { 2343 | $index = ''; 2344 | foreach ($indexArray['key'] as $key => $direction) { 2345 | $index .= (!$index ? $key : ', ' . $key); 2346 | if (!is_object($direction)) { 2347 | $index .= ' [' . ($direction == -1 ? 'desc' : 'asc') . ']'; 2348 | } 2349 | } 2350 | if (isset($indexArray['unique']) && $indexArray['unique']) { 2351 | $index .= ' [unique]'; 2352 | } 2353 | if (key($indexArray['key']) != '_id' || count($indexArray['key']) !== 1) { 2354 | $index = '[' . $html->link($baseUrl . '?db=' . $dbUrl . '&collection=' . urlencode($collection) 2355 | . '&action=deleteIndex&index=' 2356 | . serialize($indexArray['key']), 'X', array('title' => 'Drop Index', 2357 | 'onclick' => "mo.confirm.href=this.href; " 2358 | . "mo.confirm('Are you sure that you want to drop this index?', " 2359 | . "function() {window.location.replace(mo.confirm.href);}); return false;") 2360 | ) . '] ' 2361 | . $index; 2362 | } 2363 | echo '
  • ' . $index . '
  • '; 2364 | } 2365 | echo ''; 2366 | } 2367 | 2368 | echo ''; 2374 | 2375 | echo ''; 2387 | 2388 | $objCount = $mo->mongo['listRows']->count(true); //count of rows returned 2389 | $paginator = number_format($mo->mongo['count']) . ' objects'; //count of rows in collection 2390 | if ($objCount && $mo->mongo['count'] != $objCount) { 2391 | $skip = (isset($_GET['skip']) ? $_GET['skip'] : 0); 2392 | $get = $_GET; 2393 | unset($get['skip']); 2394 | $url = $baseUrl . '?' . http_build_query($get) . '&collection=' . urlencode($collection) . '&skip='; 2395 | $paginator = number_format($skip + 1) . '-' . number_format(min($skip + $objCount, $mo->mongo['count'])) 2396 | . ' of ' . $paginator; 2397 | $remainder = ($mo->mongo['count'] % $_SESSION['limit']); 2398 | $lastPage = ($mo->mongo['count'] - ($remainder ? $remainder : $_SESSION['limit'])); 2399 | $isLastPage = ($mo->mongo['count'] <= ($objCount + $skip)); 2400 | if ($skip) { //back 2401 | $backPage = (!$isLastPage ? max($skip - $objCount, 0) : ($lastPage - $_SESSION['limit'])); 2402 | $backLinks = $html->link($url . 0, '{{', array('title' => 'First')) . ' ' 2403 | . $html->link($url . $backPage, '<<<', array('title' => 'Previous')); 2404 | $paginator = addslashes($backLinks) . ' ' . $paginator; 2405 | } 2406 | if (!$isLastPage) { //forward 2407 | $forwardLinks = $html->link($url . ($skip + $objCount), '>>>', array('title' => 'Next')) . ' ' 2408 | . $html->link($url . $lastPage, '}}', array('title' => 'Last')); 2409 | $paginator .= ' ' . addslashes($forwardLinks); 2410 | } 2411 | } 2412 | 2413 | $get = $_GET; 2414 | $get['collection'] = urlencode($collection); 2415 | $queryGet = $searchGet = $sortGet = $get; 2416 | unset($sortGet['sort'], $sortGet['sortdir']); 2417 | unset($searchGet['search'], $searchGet['searchField']); 2418 | unset($queryGet['find']); 2419 | 2420 | echo $html->jsInline('mo.indexCount = 1; 2421 | $(document).ready(function() { 2422 | $("#mongo_rows").prepend("
    ' 2423 | . '[Compact] ' 2425 | . '[Uniform] ' 2427 | . '[Full]' 2429 | . '
    ' . $paginator . '
    "); 2430 | }); 2431 | mo.removeObject = function(_id, idType) { 2432 | mo.confirm("Are you sure that you want to delete this " + _id + " object?", function() { 2433 | window.location.replace("' . $baseUrl . '?db=' . $dbUrl . '&collection=' . urlencode($collection) 2434 | . '&action=removeObject&_id=" + mo.urlEncode(_id) + "&idtype=" + idType); 2435 | }); 2436 | } 2437 | ' . $dbcollnavJs . " 2438 | mo.submitSort = function() { 2439 | document.location = '" . $baseUrl . '?' . http_build_query($sortGet) . "&sort=' 2440 | + $('#sort').val() + '&sortdir=' + $('#sortdir').val(); 2441 | } 2442 | mo.submitSearch = function() { 2443 | document.location = '" . $baseUrl . '?' . http_build_query($searchGet) . "&search=' 2444 | + encodeURIComponent($('#search').val()) + '&searchField=' + $('#searchField').val(); 2445 | } 2446 | mo.submitQuery = function() { 2447 | document.location = '" . $baseUrl . '?' . http_build_query($queryGet) . "&find=' + $('#find').val(); 2448 | } 2449 | "); 2450 | 2451 | echo '
    '; 2452 | echo $form->open(array('method' => 'get', 'onsubmit' => 'mo.submitSearch(); return false;')); 2453 | echo '[' . $html->link($baseUrl . '?db=' . $dbUrl . '&collection=' . urlencode($collection) . '&action=editObject', 2454 | 'insert new object') . '] '; 2455 | if (isset($index)) { 2456 | $jsShowIndexes = "javascript: $('#indexeslink').hide(); $('#indexes').show(); void(0);"; 2457 | echo $html->link($jsShowIndexes, '[show indexes]', array('id' => 'indexeslink')) . ' '; 2458 | } 2459 | $jsShowExport = "javascript: $('#exportlink').hide(); $('#export').show(); void(0);"; 2460 | echo $html->link($jsShowExport, '[export]', array('id' => 'exportlink')) . ' '; 2461 | $jsShowImport = "javascript: $('#importlink').hide(); $('#import').show(); void(0);"; 2462 | echo $html->link($jsShowImport, '[import]', array('id' => 'importlink')) . ' '; 2463 | 2464 | $linkSubmitArgs = array('class' => 'ui-state-hover', 'style' => 'padding: 3px 8px 3px 8px;'); 2465 | $inlineFormArgs = array('label' => '', 'addBreak' => false); 2466 | if ($mo->mongo['colKeys']) { 2467 | $colKeys = $mo->mongo['colKeys']; 2468 | unset($colKeys['_id']); 2469 | natcasesort($colKeys); 2470 | $sort = array('name' => 'sort', 'id' => 'sort', 'options' => $colKeys, 'label' => '', 2471 | 'leadingOptions' => array('_id' => '_id', '$natural' => '$natural'), 'addBreak' => false); 2472 | $sortdir = array('name' => 'sortdir', 'id' => 'sortdir', 'options' => array(1 => 'asc', -1 => 'desc')); 2473 | $sortdir = array_merge($sortdir, $inlineFormArgs); 2474 | $formInputs = $form->select($sort) . $form->select($sortdir) . ' ' 2475 | . $html->link("javascript: mo.submitSort(); void(0);", 'Sort', $linkSubmitArgs); 2476 | if (!isset($_GET['sort']) || !$_GET['sort']) { 2477 | $jsLink = "javascript: $('#sortlink').hide(); $('#sortform').show(); void(0);"; 2478 | $formInputs = $html->link($jsLink, '[sort]', array('id' => 'sortlink')) . ' ' 2479 | . ''; 2480 | } else { 2481 | $formInputs = $html->div($formInputs); 2482 | } 2483 | echo $formInputs; 2484 | 2485 | $search = array('name' => 'search', 'id' => 'search', 'style' => 'width: 300px;'); 2486 | $search = array_merge($search, $inlineFormArgs); 2487 | $searchField = array('name' => 'searchField', 'id' => 'searchField', 'options' => $colKeys, 2488 | 'leadingOptions' => array('_id' => '_id')); 2489 | $searchField = array_merge($searchField, $inlineFormArgs); 2490 | 2491 | $linkSubmitArgs['title'] = 'Search may be a exact-text, (type-casted) value, (mongoid) 4c6...80c,' 2492 | . ' text with * wildcards, regex or JSON (with Mongo-operators enabled)'; 2493 | $formInputs = $form->select($searchField) . $form->input($search) . ' ' 2494 | . $html->link("javascript: mo.submitSearch(); void(0);", 'Search', $linkSubmitArgs); 2495 | if (!isset($_GET['search']) || !$_GET['search']) { 2496 | $jsLink = "javascript: $('#searchlink').hide(); $('#searchform').show(); void(0);"; 2497 | $formInputs = $html->link($jsLink, '[search]', array('id' => 'searchlink')) . ' ' 2498 | . ''; 2499 | } else { 2500 | $formInputs = $html->div($formInputs); 2501 | } 2502 | echo $formInputs; 2503 | } 2504 | 2505 | $linkSubmitArgs['title'] = 'Query may be a JSON object or a PHP array'; 2506 | $query = array('name' => 'find', 'id' => 'find', 'style' => 'width: 600px;'); 2507 | $query = array_merge($query, $inlineFormArgs); 2508 | $formInputs = $form->textarea($query) . ' ' 2509 | . $html->link("javascript: mo.submitQuery(); void(0);", 'Query', $linkSubmitArgs); 2510 | if (!isset($_GET['find']) || !$_GET['find']) { 2511 | $jsLink = "javascript: $('#querylink').hide(); $('#queryform').show(); void(0);"; 2512 | $formInputs = $html->link($jsLink, '[query]', array('id' => 'querylink')) . ' ' 2513 | . ''; 2514 | } else { 2515 | $formInputs = $html->div($formInputs); 2516 | } 2517 | echo $formInputs; 2518 | 2519 | echo $form->close(); 2520 | 2521 | echo '
      '; 2522 | $rowCount = (!isset($skip) ? 0 : $skip); 2523 | $isChunksTable = (substr($collection, -7) == '.chunks'); 2524 | if ($isChunksTable) { 2525 | $chunkUrl = $baseUrl . '?db=' . $dbUrl . '&action=listRows&collection=' . urlencode(substr($collection, 0, -7)) 2526 | . '.files#'; 2527 | } 2528 | foreach ($mo->mongo['listRows'] as $row) { 2529 | $showEdit = true; 2530 | $id = $idString = $row['_id']; 2531 | if (is_object($idString)) { 2532 | $idString = '(' . get_class($idString) . ') ' . $idString; 2533 | $idForUrl = serialize($id); 2534 | } else if (is_array($idString)) { 2535 | $idString = '(array) ' . json_encode($idString); 2536 | $idForUrl = serialize($id); 2537 | } else { 2538 | $idForUrl = urlencode($id); 2539 | } 2540 | $idType = gettype($row['_id']); 2541 | if ($isChunksTable && isset($row['data']) && is_object($row['data']) 2542 | && get_class($row['data']) == 'MongoBinData') { 2543 | $showEdit = false; 2544 | $row['data'] = $html->link($chunkUrl . $row['files_id'], 'MongoBinData Object', 2545 | array('class' => 'MoAdmin_Reference')); 2546 | } 2547 | $data = explode("\n", substr(print_r($row, true), 8, -2)); 2548 | $binData = 0; 2549 | foreach ($data as $id => $rowData) { 2550 | $raw = trim($rowData); 2551 | if ($binData) { 2552 | if (strpos($rowData, '] => ') !== false) { 2553 | ++$binData; 2554 | } 2555 | unset($data[$id]); 2556 | continue; 2557 | } 2558 | 2559 | if ($raw === '') { 2560 | unset($data[$id]); 2561 | } else if ($raw === '(') { //one-true-brace 2562 | $data[($id - 1)] .= ' ('; 2563 | unset($data[$id]); 2564 | } else { 2565 | if (strpos($data[$id], 'MongoBinData Object') !== false) { 2566 | $showEdit = false; 2567 | $binData = -2; 2568 | } 2569 | $data[$id] = str_replace(' ', ' ', (substr($rowData, 0, 4) === ' ' ? substr($rowData, 4) 2570 | : $rowData)); 2571 | if ($raw === ')') { 2572 | $data[$id] = substr($data[$id], 4); 2573 | } 2574 | if (strpos($data[$id], 'MoAdmin_Reference') === false) { 2575 | $data[$id] = get::htmlentities($data[$id]); 2576 | } 2577 | } 2578 | } 2579 | echo $html->li('
      ' 2581 | . '[' . $html->link("javascript: mo.removeObject('" . $idForUrl . "', '" . $idType 2582 | . "'); void(0);", 'X', array('title' => 'Delete')) . '] ' 2583 | . ($showEdit ? '[' . $html->link($baseUrl . '?db=' . $dbUrl . '&collection=' . urlencode($collection) 2584 | . '&action=editObject&_id=' . $idForUrl . '&idtype=' . $idType, 'E', array('title' => 'Edit')) . '] ' 2585 | : ' [N/A] ') 2586 | . $idString . '
      ' . number_format(++$rowCount) . '
      '
      2587 |            . wordwrap(implode("\n", $data), 136, "\n", true) . '
      '); 2588 | } 2589 | echo '
    '; 2590 | if (!isset($idString)) { 2591 | echo '
    No records in this collection
    '; 2592 | } 2593 | echo '
    '; 2594 | } else if (isset($mo->mongo['editObject'])) { 2595 | echo $form->open(array('action' => $baseUrl . '?db=' . $dbUrl . '&collection=' . urlencode($collection))); 2596 | if (isset($_GET['_id']) && $_GET['_id'] && ($_GET['idtype'] == 'object' || $_GET['idtype'] == 'array')) { 2597 | $_GET['_id'] = unserialize($_GET['_id']); 2598 | if (is_array($_GET['_id'])) { 2599 | $_GET['_id'] = json_encode($_GET['_id']); 2600 | } 2601 | } 2602 | echo $html->h1(isset($_GET['_id']) && $_GET['_id'] ? get::htmlentities($_GET['_id']) : '[New Object]'); 2603 | echo $html->div($form->submit(array('value' => 'Save Changes', 'class' => 'ui-state-hover'))); 2604 | $textarea = array('name' => 'object', 'label' => ''); 2605 | $textarea['value'] = ($mo->mongo['editObject'] !== '' ? var_export($mo->mongo['editObject'], true) 2606 | : 'array (' . PHP_EOL . PHP_EOL . ')'); 2607 | //MongoID as _id 2608 | $textarea['value'] = preg_replace('/\'_id\' => \s*MongoId::__set_state\(array\(\s*\)\)/', '\'_id\' => new MongoId("' 2609 | . (isset($_GET['_id']) ? $_GET['_id'] : '') . '")', $textarea['value']); 2610 | //MongoID in all other occurrences, original ID is not maintained 2611 | $textarea['value'] = preg_replace('/MongoId::__set_state\(array\(\s*\)\)/', 'new MongoId()', $textarea['value']); 2612 | //MongoDate 2613 | $textarea['value'] = preg_replace('/MongoDate::__set_state\(array\(\s*\'sec\' => (\d+),\s*\'usec\' => \d+,\s*\)\)/m', 2614 | 'new MongoDate($1)', $textarea['value']); 2615 | echo $html->div($form->textarea($textarea) 2616 | . $form->hidden(array('name' => 'action', 'value' => 'editObject'))); 2617 | echo $html->div($form->hidden(array('name' => 'db', 'value' => get::htmlentities($db))) 2618 | . $form->submit(array('value' => 'Save Changes', 'class' => 'ui-state-hover'))); 2619 | echo $form->close(); 2620 | echo $html->jsInline('$("textarea[name=object]").css({"min-width": "750px", "max-width": "1250px", ' 2621 | . '"min-height": "450px", "max-height": "2000px", "width": "auto", "height": "auto"}).resizable(); 2622 | ' . $dbcollnavJs); 2623 | } else if (isset($mo->mongo['getStats'])) { 2624 | echo $html->drillDownList($mo->mongo['getStats']); 2625 | } 2626 | echo ''; //end of bodycontent 2627 | 2628 | echo $html->footer(); 2629 | --------------------------------------------------------------------------------