├── 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 . '' . $tagType . '>';
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 ? '' . $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 '