You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2628 lines
110 KiB

  1. <?php error_reporting(E_ALL | E_STRICT);
  2. /**
  3. * phpMoAdmin - built on a stripped-down version of the high-performance Vork Enterprise Framework
  4. *
  5. * www.phpMoAdmin.com
  6. * www.Vork.us
  7. * www.MongoDB.org
  8. *
  9. * @version 1.1.5
  10. * @author Eric David Benari, Chief Architect, phpMoAdmin
  11. * @license GPL v3 - https://www.gnu.org/licenses/gpl-3.0.en.html
  12. */
  13. /**
  14. * To enable password protection, uncomment below and then change the username => password
  15. * You can add as many users as needed, eg.: array('scott' => 'tiger', 'samantha' => 'goldfish', 'gene' => 'alpaca')
  16. */
  17. //$accessControl = array('scott' => 'tiger');
  18. /**
  19. * Uncomment to restrict databases-access to just the databases added to the array below
  20. * uncommenting will also remove the ability to create a new database
  21. */
  22. //moadminModel::$databaseWhitelist = array('admin');
  23. /**
  24. * Sets the design theme - themes options are: swanky-purse, trontastic, simple-gray and classic
  25. */
  26. define('THEME', 'trontastic');
  27. /**
  28. * To connect to a remote or authenticated Mongo instance, define the connection string in the MONGO_CONNECTION constant
  29. * mongodb://[username:password@]host1[:port1][,host2[:port2:],...]
  30. * If you do not know what this means then it is not relevant to your application and you can safely leave it as-is
  31. */
  32. define('MONGO_CONNECTION', '');
  33. /**
  34. * Set to true when connecting to a Mongo replicaSet
  35. * If you do not know what this means then it is not relevant to your application and you can safely leave it as-is
  36. */
  37. define('REPLICA_SET', false);
  38. /**
  39. * Default limit for number of objects to display per page - set to 0 for no limit
  40. */
  41. define('OBJECT_LIMIT', 100);
  42. /**
  43. * Contributing-developers of the phpMoAdmin project should set this to true, everyone else can leave this as false
  44. */
  45. define('DEBUG_MODE', false);
  46. /**
  47. * Vork core-functionality tools
  48. */
  49. class get {
  50. /**
  51. * Opens up public access to config constants and variables and the cache object
  52. * @var object
  53. */
  54. public static $config;
  55. /**
  56. * Index of objects loaded, used to maintain uniqueness of singletons
  57. * @var array
  58. */
  59. public static $loadedObjects = array();
  60. /**
  61. * Is PHP Version 5.2.3 or better when htmlentities() added its fourth argument
  62. * @var Boolean
  63. */
  64. public static $isPhp523orNewer = true;
  65. /**
  66. * Gets the current URL
  67. *
  68. * @param array Optional, keys:
  69. * get - Boolean Default: false - include GET URL if it exists
  70. * abs - Boolean Default: false - true=absolute URL (aka. FQDN), false=just the path for relative links
  71. * ssl - Boolean Default: null - true=https, false=http, unset/null=auto-selects the current protocol
  72. * a true or false value implies abs=true
  73. * @return string
  74. */
  75. public static function url(array $args = array()) {
  76. $ssl = null;
  77. $get = false;
  78. $abs = false;
  79. extract($args);
  80. if (!isset($_SERVER['HTTP_HOST']) && PHP_SAPI == 'cli') {
  81. $_SERVER['HTTP_HOST'] = trim(`hostname`);
  82. $argv = $_SERVER['argv'];
  83. array_shift($argv);
  84. $_SERVER['REDIRECT_URL'] = '/' . implode('/', $argv);
  85. $get = false; // command-line has no GET
  86. }
  87. $url = (isset($_SERVER['REDIRECT_URL']) ? $_SERVER['REDIRECT_URL'] : $_SERVER['SCRIPT_NAME']);
  88. if (substr($url, -1) == '/') { //strip trailing slash for URL consistency
  89. $url = substr($url, 0, -1);
  90. }
  91. if (is_null($ssl) && $abs == true) {
  92. $ssl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on');
  93. }
  94. if ($abs || !is_null($ssl)) {
  95. $url = (!$ssl ? 'http://' : 'https://') . $_SERVER['HTTP_HOST'] . $url;
  96. }
  97. if ($get && isset($_SERVER['QUERY_STRING']) && $_SERVER['QUERY_STRING']) {
  98. $url .= '?' . $_SERVER['QUERY_STRING'];
  99. }
  100. return ($url ? $url : '/');
  101. }
  102. /**
  103. * Overloads the php function htmlentities and changes the default charset to UTF-8 and the default value for the
  104. * fourth parameter $doubleEncode to false. Also adds ability to pass a null value to get the default $quoteStyle
  105. * and $charset (removes need to repeatedly define ENT_COMPAT, 'UTF-8', just to access the $doubleEncode argument)
  106. *
  107. * If you are using a PHP version prior to 5.2.3 the $doubleEncode parameter is not available and won't do anything
  108. *
  109. * @param string $string
  110. * @param int $quoteStyle Uses ENT_COMPAT if null or omitted
  111. * @param string $charset Uses UTF-8 if null or omitted
  112. * @param boolean $doubleEncode This is ignored in old versions of PHP before 5.2.3
  113. * @return string
  114. */
  115. public static function htmlentities($string, $quoteStyle = ENT_COMPAT, $charset = 'UTF-8', $doubleEncode = false) {
  116. $quoteStyle = (!is_null($quoteStyle) ? $quoteStyle : ENT_COMPAT);
  117. $charset = (!is_null($charset) ? $charset : 'UTF-8');
  118. return (self::$isPhp523orNewer ? htmlentities($string, $quoteStyle, $charset, $doubleEncode)
  119. : htmlentities($string, $quoteStyle, $charset));
  120. }
  121. /**
  122. * Initialize the character maps needed for the xhtmlentities() method and verifies the argument values
  123. * passed to it are valid.
  124. *
  125. * @param int $quoteStyle
  126. * @param string $charset Only valid options are UTF-8 and ISO-8859-1 (Latin-1)
  127. * @param boolean $doubleEncode
  128. */
  129. protected static function initXhtmlentities($quoteStyle, $charset, $doubleEncode) {
  130. $chars = get_html_translation_table(HTML_ENTITIES, $quoteStyle);
  131. if (isset($chars)) {
  132. unset($chars['<'], $chars['>']);
  133. $charMaps[$quoteStyle]['ISO-8859-1'][true] = $chars;
  134. $charMaps[$quoteStyle]['ISO-8859-1'][false] = array_combine(array_values($chars), $chars);
  135. $charMaps[$quoteStyle]['UTF-8'][true] = array_combine(array_map('utf8_encode', array_keys($chars)), $chars);
  136. $charMaps[$quoteStyle]['UTF-8'][false] = array_merge($charMaps[$quoteStyle]['ISO-8859-1'][false],
  137. $charMaps[$quoteStyle]['UTF-8'][true]);
  138. self::$loadedObjects['xhtmlEntities'] = $charMaps;
  139. }
  140. if (!isset($charMaps[$quoteStyle][$charset][$doubleEncode])) {
  141. if (!isset($chars)) {
  142. $invalidArgument = 'quoteStyle = ' . $quoteStyle;
  143. } else if (!isset($charMaps[$quoteStyle][$charset])) {
  144. $invalidArgument = 'charset = ' . $charset;
  145. } else {
  146. $invalidArgument = 'doubleEncode = ' . (string) $doubleEncode;
  147. }
  148. trigger_error('Undefined argument sent to xhtmlentities() method: ' . $invalidArgument, E_USER_NOTICE);
  149. }
  150. }
  151. /**
  152. * Converts special characters in a string to XHTML-valid ASCII encoding the same as htmlentities except
  153. * this method allows the use of HTML tags within your string. Signature is the same as htmlentities except
  154. * that the only character sets available (third argument) are UTF-8 (default) and ISO-8859-1 (Latin-1).
  155. *
  156. * @param string $string
  157. * @param int $quoteStyle Constants available are ENT_NOQUOTES (default), ENT_QUOTES, ENT_COMPAT
  158. * @param string $charset Only valid options are UTF-8 (default) and ISO-8859-1 (Latin-1)
  159. * @param boolean $doubleEncode Default is false
  160. * @return string
  161. */
  162. public static function xhtmlentities($string, $quoteStyle = ENT_NOQUOTES, $charset = 'UTF-8',
  163. $doubleEncode = false) {
  164. $quoteStyles = array(ENT_NOQUOTES, ENT_QUOTES, ENT_COMPAT);
  165. $quoteStyle = (!in_array($quoteStyle, $quoteStyles) ? current($quoteStyles) : $quoteStyle);
  166. $charset = ($charset != 'ISO-8859-1' ? 'UTF-8' : $charset);
  167. $doubleEncode = (Boolean) $doubleEncode;
  168. if (!isset(self::$loadedObjects['xhtmlEntities'][$quoteStyle][$charset][$doubleEncode])) {
  169. self::initXhtmlentities($quoteStyle, $charset, $doubleEncode);
  170. }
  171. return strtr($string, self::$loadedObjects['xhtmlEntities'][$quoteStyle][$charset][$doubleEncode]);
  172. }
  173. /**
  174. * Loads an object as a singleton
  175. *
  176. * @param string $objectType
  177. * @param string $objectName
  178. * @return object
  179. */
  180. protected static function _loadObject($objectType, $objectName) {
  181. if (isset(self::$loadedObjects[$objectType][$objectName])) {
  182. return self::$loadedObjects[$objectType][$objectName];
  183. }
  184. $objectClassName = $objectName . ucfirst($objectType);
  185. if (class_exists($objectClassName)) {
  186. $objectObject = new $objectClassName;
  187. self::$loadedObjects[$objectType][$objectName] = $objectObject;
  188. return $objectObject;
  189. } else {
  190. $errorMsg = 'Class for ' . $objectType . ' ' . $objectName . ' could not be found';
  191. }
  192. trigger_error($errorMsg, E_USER_WARNING);
  193. }
  194. /**
  195. * Returns a helper object
  196. *
  197. * @param string $model
  198. * @return object
  199. */
  200. public static function helper($helper) {
  201. if (is_array($helper)) {
  202. array_walk($helper, array('self', __METHOD__));
  203. return;
  204. }
  205. if (!isset(self::$config['helpers']) || !in_array($helper, self::$config['helpers'])) {
  206. self::$config['helpers'][] = $helper;
  207. }
  208. return self::_loadObject('helper', $helper);
  209. }
  210. }
  211. /**
  212. * Public interface to load elements and cause redirects
  213. */
  214. class load {
  215. /**
  216. * Sends a redirects header and disables view rendering
  217. * This redirects via a browser command, this is not the same as changing controllers which is handled within MVC
  218. *
  219. * @param string $url Optional, if undefined this will refresh the page (mostly useful for dumping post values)
  220. */
  221. public static function redirect($url = null) {
  222. header('Location: ' . ($url ? $url : get::url(array('get' => true))));
  223. }
  224. }
  225. /**
  226. * Thrown when the mongod server is not accessible
  227. */
  228. class cannotConnectToMongoServer extends Exception {
  229. public function __toString() {
  230. return '<h1>Cannot connect to the MongoDB database.</h1> ' . PHP_EOL . 'If Mongo is installed then be sure that'
  231. . ' an instance of the "mongod" server, not "mongo" shell, is running. <br />' . PHP_EOL
  232. . 'Instructions and database download: <a href="http://vork.us/go/fhk4">http://vork.us/go/fhk4</a>';
  233. }
  234. }
  235. /**
  236. * Thrown when the mongo extension for PHP is not installed
  237. */
  238. class mongoExtensionNotInstalled extends Exception {
  239. public function __toString() {
  240. return '<h1>PHP cannot access MongoDB, you need to install the Mongo extension for PHP.</h1> '
  241. . PHP_EOL . 'Instructions and driver download: '
  242. . '<a href="http://vork.us/go/tv27">http://vork.us/go/tv27</a>';
  243. }
  244. }
  245. /**
  246. * phpMoAdmin data model
  247. */
  248. class moadminModel {
  249. /**
  250. * mongo connection - if a MongoDB object already exists (from a previous script) then only DB operations use this
  251. * @var Mongo
  252. */
  253. protected $_db;
  254. /**
  255. * Name of last selected DB
  256. * @var string Defaults to admin as that is available in all Mongo instances
  257. */
  258. public static $dbName = 'admin';
  259. /**
  260. * MongoDB
  261. * @var MongoDB
  262. */
  263. public $mongo;
  264. /**
  265. * Returns a new Mongo connection
  266. * @return Mongo
  267. */
  268. protected function _mongo() {
  269. $connection = (!MONGO_CONNECTION ? 'mongodb://localhost:27017' : MONGO_CONNECTION);
  270. $Mongo = (class_exists('MongoClient') === true ? 'MongoClient' : 'Mongo');
  271. return (!REPLICA_SET ? new $Mongo($connection) : new $Mongo($connection, array('replicaSet' => true)));
  272. }
  273. /**
  274. * Connects to a Mongo database if the name of one is supplied as an argument
  275. * @param string $db
  276. */
  277. public function __construct($db = null) {
  278. if (self::$databaseWhitelist && !in_array($db, self::$databaseWhitelist)) {
  279. $db = self::$dbName = $_GET['db'] = current(self::$databaseWhitelist);
  280. }
  281. if ($db) {
  282. if (!extension_loaded('mongo')) {
  283. throw new mongoExtensionNotInstalled();
  284. }
  285. try {
  286. $this->_db = $this->_mongo();
  287. $this->mongo = $this->_db->selectDB($db);
  288. } catch (MongoConnectionException $e) {
  289. throw new cannotConnectToMongoServer();
  290. }
  291. }
  292. }
  293. /**
  294. * Executes a native JS MongoDB command
  295. * This method is not currently used for anything
  296. * @param string $cmd
  297. * @return mixed
  298. */
  299. protected function _exec($cmd) {
  300. $exec = $this->mongo->execute($cmd);
  301. return $exec['retval'];
  302. }
  303. /**
  304. * Change the DB connection
  305. * @param string $db
  306. */
  307. public function setDb($db) {
  308. if (self::$databaseWhitelist && !in_array($db, self::$databaseWhitelist)) {
  309. $db = current(self::$databaseWhitelist);
  310. }
  311. if (!isset($this->_db)) {
  312. $this->_db = $this->_mongo();
  313. }
  314. $this->mongo = $this->_db->selectDB($db);
  315. self::$dbName = $db;
  316. }
  317. /**
  318. * Total size of all the databases
  319. * @var int
  320. */
  321. public $totalDbSize = 0;
  322. /**
  323. * Adds ability to restrict databases-access to those on the whitelist
  324. * @var array
  325. */
  326. public static $databaseWhitelist = array();
  327. /**
  328. * Gets list of databases
  329. * @return array
  330. */
  331. public function listDbs() {
  332. $return = array();
  333. $restrictDbs = (bool) self::$databaseWhitelist;
  334. $dbs = $this->_db->selectDB('admin')->command(array('listDatabases' => 1));
  335. $this->totalDbSize = $dbs['totalSize'];
  336. foreach ($dbs['databases'] as $db) {
  337. if (!$restrictDbs || in_array($db['name'], self::$databaseWhitelist)) {
  338. $return[$db['name']] = $db['name'] . ' ('
  339. . (!$db['empty'] ? round($db['sizeOnDisk'] / 1000000) . 'mb' : 'empty') . ')';
  340. }
  341. }
  342. ksort($return);
  343. $dbCount = 0;
  344. foreach ($return as $key => $val) {
  345. $return[$key] = ++$dbCount . '. ' . $val;
  346. }
  347. return $return;
  348. }
  349. /**
  350. * Generate system info and stats
  351. * @return array
  352. */
  353. public function getStats() {
  354. $admin = $this->_db->selectDB('admin');
  355. $return = $admin->command(array('buildinfo' => 1));
  356. try {
  357. $return = array_merge($return, $admin->command(array('serverStatus' => 1)));
  358. } catch (MongoCursorException $e) {}
  359. $profile = $admin->command(array('profile' => -1));
  360. $return['profilingLevel'] = $profile['was'];
  361. $return['mongoDbTotalSize'] = round($this->totalDbSize / 1000000) . 'mb';
  362. $prevError = $admin->command(array('getpreverror' => 1));
  363. if (!$prevError['n']) {
  364. $return['previousDbErrors'] = 'None';
  365. } else {
  366. $return['previousDbErrors']['error'] = $prevError['err'];
  367. $return['previousDbErrors']['numberOfOperationsAgo'] = $prevError['nPrev'];
  368. }
  369. if (isset($return['globalLock']['totalTime'])) {
  370. $return['globalLock']['totalTime'] .= ' &#0181;Sec';
  371. }
  372. if (isset($return['uptime'])) {
  373. $return['uptime'] = round($return['uptime'] / 60) . ':' . str_pad($return['uptime'] % 60, 2, '0', STR_PAD_LEFT)
  374. . ' minutes';
  375. }
  376. $unshift['mongo'] = $return['version'] . ' (' . $return['bits'] . '-bit)';
  377. $unshift['mongoPhpDriver'] = Mongo::VERSION;
  378. $unshift['phpMoAdmin'] = '1.1.4';
  379. $unshift['php'] = PHP_VERSION . ' (' . (PHP_INT_MAX > 2200000000 ? 64 : 32) . '-bit)';
  380. $unshift['gitVersion'] = $return['gitVersion'];
  381. unset($return['ok'], $return['version'], $return['gitVersion'], $return['bits']);
  382. $return = array_merge(array('version' => $unshift), $return);
  383. $iniIndex = array(-1 => 'Unlimited', 'Off', 'On');
  384. $phpIni = array('allow_persistent', 'auto_reconnect', 'chunk_size', 'cmd', 'default_host', 'default_port',
  385. 'max_connections', 'max_persistent');
  386. foreach ($phpIni as $ini) {
  387. $key = 'php_' . $ini;
  388. $return[$key] = ini_get('mongo.' . $ini);
  389. if (isset($iniIndex[$return[$key]])) {
  390. $return[$key] = $iniIndex[$return[$key]];
  391. }
  392. }
  393. return $return;
  394. }
  395. /**
  396. * Repairs a database
  397. * @return array Success status
  398. */
  399. public function repairDb() {
  400. return $this->mongo->repair();
  401. }
  402. /**
  403. * Drops a database
  404. */
  405. public function dropDb() {
  406. $this->mongo->drop();
  407. return;
  408. if (!isset($this->_db)) {
  409. $this->_db = $this->_mongo();
  410. }
  411. $this->_db->dropDB($this->mongo);
  412. }
  413. /**
  414. * Gets a list of database collections
  415. * @return array
  416. */
  417. public function listCollections() {
  418. $collections = array();
  419. $MongoCollectionObjects = $this->mongo->listCollections();
  420. foreach ($MongoCollectionObjects as $collection) {
  421. $collection = substr(strstr((string) $collection, '.'), 1);
  422. $collections[$collection] = $this->mongo->selectCollection($collection)->count();
  423. }
  424. ksort($collections);
  425. return $collections;
  426. }
  427. /**
  428. * Drops a collection
  429. * @param string $collection
  430. */
  431. public function dropCollection($collection) {
  432. $this->mongo->selectCollection($collection)->drop();
  433. }
  434. /**
  435. * Creates a collection
  436. * @param string $collection
  437. */
  438. public function createCollection($collection) {
  439. if ($collection) {
  440. $this->mongo->createCollection($collection);
  441. }
  442. }
  443. /**
  444. * Renames a collection
  445. *
  446. * @param string $from
  447. * @param string $to
  448. */
  449. public function renameCollection($from, $to) {
  450. $result = $this->_db->selectDB('admin')->command(array(
  451. 'renameCollection' => self::$dbName . '.' . $from,
  452. 'to' => self::$dbName . '.' . $to,
  453. ));
  454. }
  455. /**
  456. * Gets a list of the indexes on a collection
  457. *
  458. * @param string $collection
  459. * @return array
  460. */
  461. public function listIndexes($collection) {
  462. return $this->mongo->selectCollection($collection)->getIndexInfo();
  463. }
  464. /**
  465. * Ensures an index
  466. *
  467. * @param string $collection
  468. * @param array $indexes
  469. * @param array $unique
  470. */
  471. public function ensureIndex($collection, array $indexes, array $unique) {
  472. $unique = ($unique ? true : false); //signature requires a bool in both Mongo v. 1.0.1 and 1.2.0
  473. $this->mongo->selectCollection($collection)->ensureIndex($indexes, $unique);
  474. }
  475. /**
  476. * Removes an index
  477. *
  478. * @param string $collection
  479. * @param array $index Must match the array signature of the index
  480. */
  481. public function deleteIndex($collection, array $index) {
  482. $this->mongo->selectCollection($collection)->deleteIndex($index);
  483. }
  484. /**
  485. * Sort array - currently only used for collections
  486. * @var array
  487. */
  488. public $sort = array('_id' => 1);
  489. /**
  490. * Number of rows in the entire resultset (before limit-clause is applied)
  491. * @var int
  492. */
  493. public $count;
  494. /**
  495. * Array keys in the first and last object in a collection merged together (used to build sort-by options)
  496. * @var array
  497. */
  498. public $colKeys = array();
  499. /**
  500. * Get the records in a collection
  501. *
  502. * @param string $collection
  503. * @return array
  504. */
  505. public function listRows($collection) {
  506. foreach ($this->sort as $key => $val) { //cast vals to int
  507. $sort[$key] = (int) $val;
  508. }
  509. $col = $this->mongo->selectCollection($collection);
  510. $find = array();
  511. if (isset($_GET['find']) && $_GET['find']) {
  512. $_GET['find'] = trim($_GET['find']);
  513. if (strpos($_GET['find'], 'array') === 0) {
  514. eval('$find = ' . $_GET['find'] . ';');
  515. } else if (is_string($_GET['find'])) {
  516. if ($findArr = json_decode($_GET['find'], true)) {
  517. $find = $findArr;
  518. }
  519. }
  520. }
  521. if (isset($_GET['search']) && $_GET['search']) {
  522. switch (substr(trim($_GET['search']), 0, 1)) { //first character
  523. case '/': //regex
  524. $find[$_GET['searchField']] = new mongoRegex($_GET['search']);
  525. break;
  526. case '{': //JSON
  527. if ($search = json_decode($_GET['search'], true)) {
  528. $find[$_GET['searchField']] = $search;
  529. }
  530. break;
  531. case '(':
  532. $types = array('bool', 'boolean', 'int', 'integer', 'float', 'double', 'string', 'array', 'object',
  533. 'null', 'mongoid');
  534. $closeParentheses = strpos($_GET['search'], ')');
  535. if ($closeParentheses) {
  536. $cast = strtolower(substr($_GET['search'], 1, ($closeParentheses - 1)));
  537. if (in_array($cast, $types)) {
  538. $search = trim(substr($_GET['search'], ($closeParentheses + 1)));
  539. if ($cast == 'mongoid') {
  540. $search = new MongoID($search);
  541. } else {
  542. settype($search, $cast);
  543. }
  544. $find[$_GET['searchField']] = $search;
  545. break;
  546. }
  547. } //else no-break
  548. default: //text-search
  549. if (strpos($_GET['search'], '*') === false) {
  550. if (!is_numeric($_GET['search'])) {
  551. $find[$_GET['searchField']] = $_GET['search'];
  552. } else { //$_GET is always a string-type
  553. $in = array((string) $_GET['search'], (int) $_GET['search'], (float) $_GET['search']);
  554. $find[$_GET['searchField']] = array('$in' => $in);
  555. }
  556. } else { //text with wildcards
  557. $regex = '/' . str_replace('\*', '.*', preg_quote($_GET['search'])) . '/i';
  558. $find[$_GET['searchField']] = new mongoRegex($regex);
  559. }
  560. break;
  561. }
  562. }
  563. $cols = (!isset($_GET['cols']) ? array() : array_fill_keys($_GET['cols'], true));
  564. $cur = $col->find($find, $cols)->sort($sort);
  565. $this->count = $cur->count();
  566. //get keys of first object
  567. if ($_SESSION['limit'] && $this->count > $_SESSION['limit'] //more results than per-page limit
  568. && (!isset($_GET['export']) || $_GET['export'] != 'nolimit')) {
  569. if ($this->count > 1) {
  570. $this->colKeys = phpMoAdmin::getArrayKeys($col->findOne());
  571. }
  572. $cur->limit($_SESSION['limit']);
  573. if (isset($_GET['skip'])) {
  574. if ($this->count <= $_GET['skip']) {
  575. $_GET['skip'] = ($this->count - $_SESSION['limit']);
  576. }
  577. $cur->skip($_GET['skip']);
  578. }
  579. } else if ($this->count) { // results exist but are fewer than per-page limit
  580. $this->colKeys = phpMoAdmin::getArrayKeys($cur->getNext());
  581. } else if ($find && $col->count()) { //query is not returning anything, get cols from first obj in collection
  582. $this->colKeys = phpMoAdmin::getArrayKeys($col->findOne());
  583. }
  584. //get keys of last or much-later object
  585. if ($this->count > 1) {
  586. $curLast = $col->find()->sort($sort);
  587. if ($this->count > 2) {
  588. $curLast->skip(min($this->count, 100) - 1);
  589. }
  590. $this->colKeys = array_merge($this->colKeys, phpMoAdmin::getArrayKeys($curLast->getNext()));
  591. ksort($this->colKeys);
  592. }
  593. return $cur;
  594. }
  595. /**
  596. * Returns a serialized element back to its native PHP form
  597. *
  598. * @param string $_id
  599. * @param string $idtype
  600. * @return mixed
  601. */
  602. protected function _unserialize($_id, $idtype) {
  603. if ($idtype == 'object' || $idtype == 'array') {
  604. $errLevel = error_reporting();
  605. error_reporting(0); //unserializing an object that is not serialized throws a warning
  606. $_idObj = unserialize($_id);
  607. error_reporting($errLevel);
  608. if ($_idObj !== false) {
  609. $_id = $_idObj;
  610. }
  611. } else if (gettype($_id) != $idtype) {
  612. settype($_id, $idtype);
  613. }
  614. return $_id;
  615. }
  616. /**
  617. * Removes an object from a collection
  618. *
  619. * @param string $collection
  620. * @param string $_id
  621. * @param string $idtype
  622. */
  623. public function removeObject($collection, $_id, $idtype) {
  624. $this->mongo->selectCollection($collection)->remove(array('_id' => $this->_unserialize($_id, $idtype)));
  625. }
  626. /**
  627. * Retieves an object for editing
  628. *
  629. * @param string $collection
  630. * @param string $_id
  631. * @param string $idtype
  632. * @return array
  633. */
  634. public function editObject($collection, $_id, $idtype) {
  635. return $this->mongo->selectCollection($collection)->findOne(array('_id' => $this->_unserialize($_id, $idtype)));
  636. }
  637. /**
  638. * Saves an object
  639. *
  640. * @param string $collection
  641. * @param string $obj
  642. * @return array
  643. */
  644. public function saveObject($collection, $obj) {
  645. eval('$obj=' . $obj . ';'); //cast from string to array
  646. return $this->mongo->selectCollection($collection)->save($obj);
  647. }
  648. /**
  649. * Imports data into the current collection
  650. *
  651. * @param string $collection
  652. * @param array $data
  653. * @param string $importMethod Valid options are batchInsert, save, insert, update
  654. */
  655. public function import($collection, array $data, $importMethod) {
  656. $coll = $this->mongo->selectCollection($collection);
  657. switch ($importMethod) {
  658. case 'batchInsert':
  659. foreach ($data as &$obj) {
  660. $obj = unserialize($obj);
  661. }
  662. $coll->$importMethod($data);
  663. break;
  664. case 'update':
  665. foreach ($data as $obj) {
  666. $obj = unserialize($obj);
  667. if (is_object($obj) && property_exists($obj, '_id')) {
  668. $_id = $obj->_id;
  669. } else if (is_array($obj) && isset($obj['_id'])) {
  670. $_id = $obj['_id'];
  671. } else {
  672. continue;
  673. }
  674. $coll->$importMethod(array('_id' => $_id), $obj);
  675. }
  676. break;
  677. default: //insert & save
  678. foreach ($data as $obj) {
  679. $coll->$importMethod(unserialize($obj));
  680. }
  681. break;
  682. }
  683. }
  684. }
  685. /**
  686. * phpMoAdmin application control
  687. */
  688. class moadminComponent {
  689. /**
  690. * $this->mongo is used to pass properties from component to view without relying on a controller to return them
  691. * @var array
  692. */
  693. public $mongo = array();
  694. /**
  695. * Model object
  696. * @var moadminModel
  697. */
  698. public static $model;
  699. /**
  700. * Removes the POST/GET params
  701. */
  702. protected function _dumpFormVals() {
  703. load::redirect(get::url() . '?action=listRows&db=' . urlencode($_GET['db'])
  704. . '&collection=' . urlencode($_GET['collection']));
  705. }
  706. /**
  707. * Routes requests and sets return data
  708. */
  709. public function __construct() {
  710. if (class_exists('mvc')) {
  711. mvc::$view = '#moadmin';
  712. }
  713. $this->mongo['dbs'] = self::$model->listDbs();
  714. if (isset($_GET['db'])) {
  715. if (strpos($_GET['db'], '.') !== false) {
  716. $_GET['db'] = $_GET['newdb'];
  717. }
  718. self::$model->setDb($_GET['db']);
  719. }
  720. if (isset($_POST['limit'])) {
  721. $_SESSION['limit'] = (int) $_POST['limit'];
  722. } else if (!isset($_SESSION['limit'])) {
  723. $_SESSION['limit'] = OBJECT_LIMIT;
  724. }
  725. if (isset($_FILES['import']) && is_uploaded_file($_FILES['import']['tmp_name']) && isset($_GET['collection'])) {
  726. $data = json_decode(file_get_contents($_FILES['import']['tmp_name']));
  727. self::$model->import($_GET['collection'], $data, $_POST['importmethod']);
  728. }
  729. $action = (isset($_GET['action']) ? $_GET['action'] : 'listCollections');
  730. if (isset($_POST['object'])) {
  731. if (self::$model->saveObject($_GET['collection'], $_POST['object'])) {
  732. return $this->_dumpFormVals();
  733. } else {
  734. $action = 'editObject';
  735. $_POST['errors']['object'] = 'Error: object could not be saved - check your array syntax.';
  736. }
  737. } else if ($action == 'createCollection') {
  738. self::$model->$action($_GET['collection']);
  739. } else if ($action == 'renameCollection'
  740. && isset($_POST['collectionto']) && $_POST['collectionto'] != $_POST['collectionfrom']) {
  741. self::$model->$action($_POST['collectionfrom'], $_POST['collectionto']);
  742. $_GET['collection'] = $_POST['collectionto'];
  743. $action = 'listRows';
  744. }
  745. if (isset($_GET['sort'])) {
  746. self::$model->sort = array($_GET['sort'] => $_GET['sortdir']);
  747. }
  748. $this->mongo['listCollections'] = self::$model->listCollections();
  749. if ($action == 'editObject') {
  750. $this->mongo[$action] = (isset($_GET['_id'])
  751. ? self::$model->$action($_GET['collection'], $_GET['_id'], $_GET['idtype']) : '');
  752. return;
  753. } else if ($action == 'removeObject') {
  754. self::$model->$action($_GET['collection'], $_GET['_id'], $_GET['idtype']);
  755. return $this->_dumpFormVals();
  756. } else if ($action == 'ensureIndex') {
  757. foreach ($_GET['index'] as $key => $field) {
  758. $indexes[$field] = (isset($_GET['isdescending'][$key]) && $_GET['isdescending'][$key] ? -1 : 1);
  759. }
  760. self::$model->$action($_GET['collection'], $indexes, ($_GET['unique'] == 'Unique' ? array('unique' => true)
  761. : array()));
  762. $action = 'listCollections';
  763. } else if ($action == 'deleteIndex') {
  764. self::$model->$action($_GET['collection'], unserialize($_GET['index']));
  765. return $this->_dumpFormVals();
  766. } else if ($action == 'getStats') {
  767. $this->mongo[$action] = self::$model->$action();
  768. unset($this->mongo['listCollections']);
  769. } else if ($action == 'repairDb' || $action == 'getStats') {
  770. $this->mongo[$action] = self::$model->$action();
  771. $action = 'listCollections';
  772. } else if ($action == 'dropDb') {
  773. self::$model->$action();
  774. load::redirect(get::url());
  775. return;
  776. }
  777. if (isset($_GET['collection']) && $action != 'listCollections' && method_exists(self::$model, $action)) {
  778. $this->mongo[$action] = self::$model->$action($_GET['collection']);
  779. $this->mongo['count'] = self::$model->count;
  780. $this->mongo['colKeys'] = self::$model->colKeys;
  781. }
  782. if ($action == 'listRows') {
  783. $this->mongo['listIndexes'] = self::$model->listIndexes($_GET['collection']);
  784. } else if ($action == 'dropCollection') {
  785. return load::redirect(get::url() . '?db=' . urlencode($_GET['db']));
  786. }
  787. }
  788. }
  789. /**
  790. * HTML helper tools
  791. */
  792. class htmlHelper {
  793. /**
  794. * Internal storage of the link-prefix and hypertext protocol values
  795. * @var string
  796. */
  797. protected $_linkPrefix, $_protocol;
  798. /**
  799. * Internal list of included CSS & JS files used by $this->_tagBuilder() to assure that files are not included twice
  800. * @var array
  801. */
  802. protected $_includedFiles = array();
  803. /**
  804. * Flag array to avoid defining singleton JavaScript & CSS snippets more than once
  805. * @var array
  806. */
  807. protected $_jsSingleton = array(), $_cssSingleton = array();
  808. /**
  809. * Sets the protocol (http/https) - this is modified from the original Vork version for phpMoAdmin usage
  810. */
  811. public function __construct() {
  812. $this->_protocol = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' ? 'https://' : 'http://');
  813. }
  814. /**
  815. * Creates simple HTML wrappers, accessed via $this->__call()
  816. *
  817. * JS and CSS files are never included more than once even if requested twice. If DEBUG mode is enabled than the
  818. * second request will be added to the debug log as a duplicate. The jsSingleton and cssSingleton methods operate
  819. * the same as the js & css methods except that they will silently skip duplicate requests instead of logging them.
  820. *
  821. * jsInlineSingleton and cssInlineSingleton makes sure a JavaScript or CSS snippet will only be output once, even
  822. * if echoed out multiple times and this method will attempt to place the JS code into the head section, if <head>
  823. * has already been echoed out then it will return the JS code inline the same as jsInline. Eg.:
  824. * $helloJs = "function helloWorld() {alert('Hello World');}";
  825. * echo $html->jsInlineSingleton($helloJs);
  826. *
  827. * Adding an optional extra argument to jsInlineSingleton/cssInlineSingleton will return the inline code bare (plus
  828. * a trailing linebreak) if it cannot place it into the head section, this is used for joint JS/CSS statements:
  829. * echo $html->jsInline($html->jsInlineSingleton($helloJs, true) . 'helloWorld();');
  830. *
  831. * @param string $tagType
  832. * @param array $args
  833. * @return string
  834. */
  835. protected function _tagBuilder($tagType, $args = array()) {
  836. $arg = current($args);
  837. if (empty($arg) || $arg === '') {
  838. $errorMsg = 'Missing argument for ' . __CLASS__ . '::' . $tagType . '()';
  839. trigger_error($errorMsg, E_USER_WARNING);
  840. }
  841. if (is_array($arg)) {
  842. foreach ($arg as $thisArg) {
  843. $return[] = $this->_tagBuilder($tagType, array($thisArg));
  844. }
  845. $return = implode(PHP_EOL, $return);
  846. } else {
  847. switch ($tagType) {
  848. case 'js':
  849. case 'jsSingleton':
  850. case 'css': //Optional extra argument to define CSS media type
  851. case 'cssSingleton':
  852. case 'jqueryTheme':
  853. if ($tagType == 'jqueryTheme') {
  854. $arg = $this->_protocol . 'ajax.googleapis.com/ajax/libs/jqueryui/1/themes/'
  855. . str_replace(' ', '-', strtolower($arg)) . '/jquery-ui.css';
  856. $tagType = 'css';
  857. }
  858. if (!isset($this->_includedFiles[$tagType][$arg])) {
  859. if ($tagType == 'css' || $tagType == 'cssSingleton') {
  860. $return = '<link rel="stylesheet" type="text/css" href="' . $arg . '"'
  861. . ' media="' . (isset($args[1]) ? $args[1] : 'all') . '" />';
  862. } else {
  863. $return = '<script type="text/javascript" src="' . $arg . '"></script>';
  864. }
  865. $this->_includedFiles[$tagType][$arg] = true;
  866. } else {
  867. $return = null;
  868. if (DEBUG_MODE && ($tagType == 'js' || $tagType == 'css')) {
  869. debug::log($arg . $tagType . ' file has already been included', 'warn');
  870. }
  871. }
  872. break;
  873. case 'cssInline': //Optional extra argument to define CSS media type
  874. $return = '<style type="text/css" media="' . (isset($args[1]) ? $args[1] : 'all') . '">'
  875. . PHP_EOL . '/*<![CDATA[*/'
  876. . PHP_EOL . '<!--'
  877. . PHP_EOL . $arg
  878. . PHP_EOL . '//-->'
  879. . PHP_EOL . '/*]]>*/'
  880. . PHP_EOL . '</style>';
  881. break;
  882. case 'jsInline':
  883. $return = '<script type="text/javascript">'
  884. . PHP_EOL . '//<![CDATA['
  885. . PHP_EOL . '<!--'
  886. . PHP_EOL . $arg
  887. . PHP_EOL . '//-->'
  888. . PHP_EOL . '//]]>'
  889. . PHP_EOL . '</script>';
  890. break;
  891. case 'jsInlineSingleton': //Optional extra argument to supress adding of inline JS/CSS wrapper
  892. case 'cssInlineSingleton':
  893. $tagTypeBase = substr($tagType, 0, -15);
  894. $return = null;
  895. $md5 = md5($arg);
  896. if (!isset($this->{'_' . $tagTypeBase . 'Singleton'}[$md5])) {
  897. $this->{'_' . $tagTypeBase . 'Singleton'}[$md5] = true;
  898. if (!$this->_bodyOpen) {
  899. $this->vorkHead[$tagTypeBase . 'Inline'][] = $arg;
  900. } else {
  901. $return = (!isset($args[1]) || !$args[1] ? $this->{$tagTypeBase . 'Inline'}($arg)
  902. : $arg . PHP_EOL);
  903. }
  904. }
  905. break;
  906. case 'div':
  907. case 'li':
  908. case 'p':
  909. case 'h1':
  910. case 'h2':
  911. case 'h3':
  912. case 'h4':
  913. $return = '<' . $tagType . '>' . $arg . '</' . $tagType . '>';
  914. break;
  915. default:
  916. $errorMsg = 'TagType ' . $tagType . ' not valid in ' . __CLASS__ . '::' . __METHOD__;
  917. throw new Exception($errorMsg);
  918. break;
  919. }
  920. }
  921. return $return;
  922. }
  923. /**
  924. * Creates virtual wrapper methods via $this->_tagBuilder() for the simple wrapper functions including:
  925. * $html->css, js, cssInline, jsInline, div, li, p and h1-h4
  926. *
  927. * @param string $method
  928. * @param array $arg
  929. * @return string
  930. */
  931. public function __call($method, $args) {
  932. $validTags = array('css', 'js', 'cssSingleton', 'jsSingleton', 'jqueryTheme',
  933. 'cssInline', 'jsInline', 'jsInlineSingleton', 'cssInlineSingleton',
  934. 'div', 'li', 'p', 'h1', 'h2', 'h3', 'h4');
  935. if (in_array($method, $validTags)) {
  936. return $this->_tagBuilder($method, $args);
  937. } else {
  938. $errorMsg = 'Call to undefined method ' . __CLASS__ . '::' . $method . '()';
  939. trigger_error($errorMsg, E_USER_ERROR);
  940. }
  941. }
  942. /**
  943. * Flag to make sure that header() can only be opened one-at-a-time and footer() can only be used after header()
  944. * @var boolean
  945. */
  946. private $_bodyOpen = false;
  947. /**
  948. * Sets the default doctype to XHTML 1.1
  949. * @var string
  950. */
  951. protected $_docType = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">';
  952. /**
  953. * Allows modification of the docType
  954. *
  955. * Can either set to an actual doctype definition or to one of the presets (case-insensitive):
  956. * XHTML Mobile 1.2
  957. * XHTML Mobile 1.1
  958. * XHTML Mobile 1.0
  959. * Mobile 1.2 (alias for XHTML Mobile 1.2)
  960. * Mobile 1.1 (alias for XHTML Mobile 1.1)
  961. * Mobile 1.0 (alias for XHTML Mobile 1.0)
  962. * Mobile (alias for the most-strict Mobile DTD, currently 1.2)
  963. * XHTML 1.1 (this is the default DTD, there is no need to apply this method for an XHTML 1.1 doctype)
  964. * XHTML (Alias for XHTML 1.1)
  965. * XHTML 1.0 Strict
  966. * XHTML 1.0 Transitional
  967. * XHTML 1.0 Frameset
  968. * XHTML 1.0 (Alias for XHTML 1.0 Strict)
  969. * HTML 5
  970. * HTML 4.01
  971. * HTML (Alias for HTML 4.01)
  972. *
  973. * @param string $docType
  974. */
  975. public function setDocType($docType) {
  976. $docType = str_replace(' ', '', strtolower($docType));
  977. if ($docType == 'xhtml1.1' || $docType == 'xhtml') {
  978. return; //XHTML 1.1 is the default
  979. } else if ($docType == 'xhtml1.0') {
  980. $docType = 'strict';
  981. }
  982. $docType = str_replace(array('xhtml mobile', 'xhtml1.0'), array('mobile', ''), $docType);
  983. $docTypes = array(
  984. 'mobile1.2' => '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.2//EN" '
  985. . '"http://www.openmobilealliance.org/tech/DTD/xhtml-mobile12.dtd">',
  986. 'mobile1.1' => '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.1//EN '
  987. . '"http://www.openmobilealliance.org/tech/DTD/xhtml-mobile11.dtd">',
  988. 'mobile1.0' => '<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.0//EN" '
  989. . '"http://www.wapforum.org/DTD/xhtml-mobile10.dtd">',
  990. 'strict' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" '
  991. . '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">',
  992. 'transitional' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" '
  993. . '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">',
  994. 'frameset' => '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN" '
  995. . '"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">',
  996. 'html4.01' => '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" '
  997. . '"http://www.w3.org/TR/html4/strict.dtd">',
  998. 'html5' => '<!DOCTYPE html>'
  999. );
  1000. $docTypes['mobile'] = $docTypes['mobile1.2'];
  1001. $docTypes['html'] = $docTypes['html4.01'];
  1002. $this->_docType = (isset($docTypes[$docType]) ? $docTypes[$docType] : $docType);
  1003. }
  1004. /**
  1005. * Array used internally by Vork to cache JavaScript and CSS snippets and place them in the head section
  1006. * Changing the contents of this property may cause Vork components to be rendered incorrectly.
  1007. * @var array
  1008. */
  1009. public $vorkHead = array();
  1010. /**
  1011. * Returns an HTML header and opens the body container
  1012. * This method will trigger an error if executed more than once without first calling
  1013. * the footer() method on the prior usage
  1014. * This is meant to be utilized within layouts, not views (but will work in either)
  1015. *
  1016. * @param array $args
  1017. * @return string
  1018. */
  1019. public function header(array $args) {
  1020. if (!$this->_bodyOpen) {
  1021. $this->_bodyOpen = true;
  1022. extract($args);
  1023. $return = $this->_docType
  1024. . PHP_EOL . '<html xmlns="http://www.w3.org/1999/xhtml">'
  1025. . PHP_EOL . '<head>'
  1026. . PHP_EOL . '<title>' . $title . '</title>';
  1027. if (!isset($metaheader['Content-Type'])) {
  1028. $metaheader['Content-Type'] = 'text/html; charset=utf-8';
  1029. }
  1030. foreach ($metaheader as $name => $content) {
  1031. $return .= PHP_EOL . '<meta http-equiv="' . $name . '" content="' . $content . '" />';
  1032. }
  1033. $meta['generator'] = 'Vork 2.00';
  1034. foreach ($meta as $name => $content) {
  1035. $return .= PHP_EOL . '<meta name="' . $name . '" content="' . $content . '" />';
  1036. }
  1037. if (isset($favicon)) {
  1038. $return .= PHP_EOL . '<link rel="shortcut icon" href="' . $favicon . '" type="image/x-icon" />';
  1039. }
  1040. if (isset($animatedFavicon)) {
  1041. $return .= PHP_EOL . '<link rel="icon" href="' . $animatedFavicon . '" type="image/gif" />';
  1042. }
  1043. $containers = array('css', 'cssInline', 'js', 'jsInline', 'jqueryTheme');
  1044. foreach ($containers as $container) {
  1045. if (isset($$container)) {
  1046. $return .= PHP_EOL . $this->$container($$container);
  1047. }
  1048. }
  1049. if ($this->vorkHead) { //used internally by Vork tools
  1050. foreach ($this->vorkHead as $container => $objArray) { //works only for inline code, not external files
  1051. $return .= PHP_EOL . $this->$container(implode(PHP_EOL, $objArray));
  1052. }
  1053. }
  1054. if (isset($head)) {
  1055. $return .= PHP_EOL . (is_array($head) ? implode(PHP_EOL, $head) : $head);
  1056. }
  1057. $return .= PHP_EOL . '</head>' . PHP_EOL . '<body>';
  1058. return $return;
  1059. } else {
  1060. $errorMsg = 'Invalid usage of ' . __METHOD__ . '() - the header has already been returned';
  1061. trigger_error($errorMsg, E_USER_NOTICE);
  1062. }
  1063. }
  1064. /**
  1065. * Returns an HTML footer and optional Google Analytics
  1066. * This method will trigger an error if executed without first calling the header() method
  1067. * This is meant to be utilized within layouts, not views (but will work in either)
  1068. *
  1069. * @param array $args
  1070. * @return string
  1071. */
  1072. public function footer(array $args = array()) {
  1073. if ($this->_bodyOpen) {
  1074. $this->_bodyOpen = false;
  1075. return '</body></html>';
  1076. } else {
  1077. $errorMsg = 'Invalid usage of ' . __METHOD__ . '() - header() has not been called';
  1078. trigger_error($errorMsg, E_USER_NOTICE);
  1079. }
  1080. }
  1081. /**
  1082. * Establishes a basic set of JavaScript tools, just echo $html->jsTools() before any JavaScript code that
  1083. * will use the tools.
  1084. *
  1085. * This method will only operate from the first occurrence in your code, subsequent calls will not output anything
  1086. * but you should add it anyway as it will make sure that your code continues to work if you later remove a
  1087. * previous call to jsTools.
  1088. *
  1089. * Tools provided:
  1090. *
  1091. * dom() method is a direct replacement for document.getElementById() that works in all JS-capable
  1092. * browsers Y2k and newer.
  1093. *
  1094. * vork object - defines a global vork storage space; use by appending your own properties, eg.: vork.widgetCount
  1095. *
  1096. * @param Boolean $noJsWrapper set to True if calling from within a $html->jsInline() wrapper
  1097. * @return string
  1098. */
  1099. public function jsTools($noJsWrapper = false) {
  1100. return $this->jsInlineSingleton("var vork = function() {}
  1101. var dom = function(id) {
  1102. if (typeof document.getElementById != 'undefined') {
  1103. dom = function(id) {return document.getElementById(id);}
  1104. } else if (typeof document.all != 'undefined') {
  1105. dom = function(id) {return document.all[id];}
  1106. } else {
  1107. return false;
  1108. }
  1109. return dom(id);
  1110. }", $noJsWrapper);
  1111. }
  1112. /**
  1113. * Load a JavaScript library via Google's AJAX API
  1114. * http://code.google.com/apis/ajaxlibs/documentation/
  1115. *
  1116. * Version is optional and can be exact (1.8.2) or just version-major (1 or 1.8)
  1117. *
  1118. * Usage:
  1119. * echo $html->jsLoad('jquery');
  1120. * echo $html->jsLoad(array('yui', 'mootools'));
  1121. * echo $html->jsLoad(array('yui' => 2.7, 'jquery', 'dojo' => '1.3.1', 'scriptaculous'));
  1122. *
  1123. * //You can also use the Google API format JSON-decoded in which case version is required & name must be lowercase
  1124. * $jsLibs = array(array('name' => 'mootools', 'version' => 1.2, 'base_domain' => 'ditu.google.cn'), array(...));
  1125. * echo $html->jsLoad($jsLibs);
  1126. *
  1127. * @param mixed $library Can be a string, array(str1, str2...) or , array(name1 => version1, name2 => version2...)
  1128. * or JSON-decoded Google API syntax array(array('name' => 'yui', 'version' => 2), array(...))
  1129. * @param mixed $version Optional, int or str, this is only used if $library is a string
  1130. * @param array $options Optional, passed to Google "optionalSettings" argument, only used if $library == str
  1131. * @return str
  1132. */
  1133. public function jsLoad($library, $version = null, array $options = array()) {
  1134. $versionDefaults = array('swfobject' => 2, 'yui' => 2, 'ext-core' => 3, 'mootools' => 1.2);
  1135. if (!is_array($library)) { //jsLoad('yui')
  1136. $library = strtolower($library);
  1137. if (!$version) {
  1138. $version = (!isset($versionDefaults[$library]) ? 1 : $versionDefaults[$library]);
  1139. }
  1140. $library = array('name' => $library, 'version' => $version);
  1141. $library = array(!$options ? $library : array_merge($library, $options));
  1142. } else {
  1143. foreach ($library as $key => $val) {
  1144. if (!is_array($val)) {
  1145. if (is_int($key)) { //jsLoad(array('yui', 'prototype'))
  1146. $val = strtolower($val);
  1147. $version = (!isset($versionDefaults[$val]) ? 1 : $versionDefaults[$val]);
  1148. $library[$key] = array('name' => $val, 'version' => $version);
  1149. } else if (!is_array($val)) { // //jsLoad(array('yui' => '2.8.0r4', 'prototype' => 1.6))
  1150. $library[$key] = array('name' => strtolower($key), 'version' => $val);
  1151. }
  1152. }
  1153. }
  1154. }
  1155. $url = $this->_protocol . 'www.google.com/jsapi';
  1156. if (!isset($this->_includedFiles['js'][$url])) { //autoload library
  1157. $this->_includedFiles['js'][$url] = true;
  1158. $url .= '?autoload=' . urlencode(json_encode(array('modules' => array_values($library))));
  1159. $return = $this->js($url);
  1160. } else { //load inline
  1161. foreach ($library as $lib) {
  1162. $js = 'google.load("' . $lib['name'] . '", "' . $lib['version'] . '"';
  1163. if (count($lib) > 2) {
  1164. unset($lib['name'], $lib['version']);
  1165. $js .= ', ' . json_encode($lib);
  1166. }
  1167. $jsLoads[] = $js . ');';
  1168. }
  1169. $return = $this->jsInline(implode(PHP_EOL, $jsLoads));
  1170. }
  1171. return $return;
  1172. }
  1173. /**
  1174. * Takes an array of key-value pairs and formats them in the syntax of HTML-container properties
  1175. *
  1176. * @param array $properties
  1177. * @return string
  1178. */
  1179. public static function formatProperties(array $properties) {
  1180. $return = array();
  1181. foreach ($properties as $name => $value) {
  1182. $return[] = $name . '="' . get::htmlentities($value) . '"';
  1183. }
  1184. return implode(' ', $return);
  1185. }
  1186. /**
  1187. * Creates an anchor or link container
  1188. *
  1189. * @param array $args
  1190. * @return string
  1191. */
  1192. public function anchor(array $args) {
  1193. if (!isset($args['text']) && isset($args['href'])) {
  1194. $args['text'] = $args['href'];
  1195. }
  1196. if (!isset($args['title']) && isset($args['text'])) {
  1197. $args['title'] = str_replace(array("\n", "\r"), ' ', strip_tags($args['text']));
  1198. }
  1199. $return = '';
  1200. if (isset($args['ajaxload'])) {
  1201. $return = $this->jsSingleton('/js/ajax.js');
  1202. $onclick = "return ajax.load('" . $args['ajaxload'] . "', this.href);";
  1203. $args['onclick'] = (!isset($args['onclick']) ? $onclick : $args['onclick'] . '; ' . $onclick);
  1204. unset($args['ajaxload']);
  1205. }
  1206. $text = (isset($args['text']) ? $args['text'] : null);
  1207. unset($args['text']);
  1208. return $return . '<a ' . self::formatProperties($args) . '>' . $text . '</a>';
  1209. }
  1210. /**
  1211. * Shortcut to access the anchor method
  1212. *
  1213. * @param str $href
  1214. * @param str $text
  1215. * @param array $args
  1216. * @return str
  1217. */
  1218. public function link($href, $text = null, array $args = array()) {
  1219. if (strpos($href, 'http') !== 0) {
  1220. $href = $this->_linkPrefix . $href;
  1221. }
  1222. $args['href'] = $href;
  1223. if ($text !== null) {
  1224. $args['text'] = $text;
  1225. }
  1226. return $this->anchor($args);
  1227. }
  1228. /**
  1229. * Wrapper display computer-code samples
  1230. *
  1231. * @param str $str
  1232. * @return str
  1233. */
  1234. public function code($str) {
  1235. return '<code>' . str_replace(' ', '&nbsp;&nbsp;', nl2br(get::htmlentities($str))) . '</code>';
  1236. }
  1237. /**
  1238. * Will return true if the number passed in is even, false if odd.
  1239. *
  1240. * @param int $number
  1241. * @return boolean
  1242. */
  1243. public function isEven($number) {
  1244. return (Boolean) ($number % 2 == 0);
  1245. }
  1246. /**
  1247. * Internal incrementing integar for the alternator() method
  1248. * @var int
  1249. */
  1250. private $alternator = 1;
  1251. /**
  1252. * Returns an alternating Boolean, useful to generate alternating background colors
  1253. * Eg.:
  1254. * $colors = array(true => 'gray', false => 'white');
  1255. * echo '<div style="background: ' . $colors[$html->alternator()] . ';">...</div>'; //gray background
  1256. * echo '<div style="background: ' . $colors[$html->alternator()] . ';">...</div>'; //white background
  1257. * echo '<div style="background: ' . $colors[$html->alternator()] . ';">...</div>'; //gray background
  1258. *
  1259. * @return Boolean
  1260. */
  1261. public function alternator() {
  1262. return $this->isEven(++$this->alternator);
  1263. }
  1264. /**
  1265. * Creates a list from an array with automatic nesting
  1266. *
  1267. * @param array $list
  1268. * @param string $kvDelimiter Optional, sets delimiter to appear between keys and values
  1269. * @param string $listType Optional, must be a valid HTML list type, either "ul" (default) or "ol"
  1270. * @return string
  1271. */
  1272. public function drillDownList(array $list, $kvDelimiter = ': ', $listType = 'ul') {
  1273. foreach ($list as $key => $val) {
  1274. $val = (is_array($val) ? $this->drillDownList($val, $kvDelimiter, $listType) : $val);
  1275. $str = trim($key && !is_int($key) ? $key . $kvDelimiter . $val : $val);
  1276. if ($str) {
  1277. $return[] = $this->li($str);
  1278. }
  1279. }
  1280. if (isset($return)) {
  1281. return ($listType ? '<' . $listType . '>' : '')
  1282. . implode(PHP_EOL, $return)
  1283. . ($listType ? '</' . $listType . '>' : '');
  1284. }
  1285. }
  1286. /**
  1287. * Returns a list of notifications if there are any - similar to the Flash feature of Ruby on Rails
  1288. *
  1289. * @param mixed $messages String or an array of strings
  1290. * @param string $class
  1291. * @return string Returns null if there are no notifications to return
  1292. */
  1293. public function getNotifications($messages, $class = 'errormessage') {
  1294. if (isset($messages) && $messages) {
  1295. return '<div class="' . $class . '">'
  1296. . (is_array($messages) ? implode('<br />', $messages) : $messages) . '</div>';
  1297. }
  1298. }
  1299. }
  1300. /**
  1301. * Vork form-helper
  1302. */
  1303. class formHelper {
  1304. /**
  1305. * Internal flag to keep track if a form tag has been opened and not yet closed
  1306. * @var boolean
  1307. */
  1308. private $_formOpen = false;
  1309. /**
  1310. * Internal form element counter
  1311. * @var int
  1312. */
  1313. private $_inputCounter = array();
  1314. /**
  1315. * Converts dynamically-assigned array indecies to use an explicitely defined index
  1316. *
  1317. * @param string $name
  1318. * @return string
  1319. */
  1320. protected function _indexDynamicArray($name) {
  1321. $dynamicArrayStart = strpos($name, '[]');
  1322. if ($dynamicArrayStart) {
  1323. $prefix = substr($name, 0, $dynamicArrayStart);
  1324. if (!isset($this->_inputCounter[$prefix])) {
  1325. $this->_inputCounter[$prefix] = -1;
  1326. }
  1327. $name = $prefix . '[' . ++$this->_inputCounter[$prefix] . substr($name, ($dynamicArrayStart + 1));
  1328. }
  1329. return $name;
  1330. }
  1331. /**
  1332. * Form types that do not change value with user input
  1333. * @var array
  1334. */
  1335. protected $_staticTypes = array('hidden', 'submit', 'button', 'image');
  1336. /**
  1337. * Sets the standard properties available to all input elements in addition to user-defined properties
  1338. * Standard properties are: name, value, class, style, id
  1339. *
  1340. * @param array $args
  1341. * @param array $propertyNames Optional, an array of user-defined properties
  1342. * @return array
  1343. */
  1344. protected function _getProperties(array $args, array $propertyNames = array()) {
  1345. $method = (isset($this->_formOpen['method']) && $this->_formOpen['method'] == 'get' ? $_GET : $_POST);
  1346. if (isset($args['name']) && (!isset($args['type']) || !in_array($args['type'], $this->_staticTypes))) {
  1347. $arrayStart = strpos($args['name'], '[');
  1348. if (!$arrayStart) {
  1349. if (isset($method[$args['name']])) {
  1350. $args['value'] = $method[$args['name']];
  1351. }
  1352. } else {
  1353. $name = $this->_indexDynamicArray($args['name']);
  1354. if (preg_match_all('/\[(.*)\]/', $name, $arrayIndex)) {
  1355. array_shift($arrayIndex); //dump the 0 index element containing full match string
  1356. }
  1357. $name = substr($name, 0, $arrayStart);
  1358. if (isset($method[$name])) {
  1359. $args['value'] = $method[$name];
  1360. if (!isset($args['type']) || $args['type'] != 'checkbox') {
  1361. foreach ($arrayIndex as $idx) {
  1362. if (isset($args['value'][current($idx)])) {
  1363. $args['value'] = $args['value'][current($idx)];
  1364. } else {
  1365. unset($args['value']);
  1366. break;
  1367. }
  1368. }
  1369. }
  1370. }
  1371. }
  1372. }
  1373. $return = array();
  1374. $validProperties = array_merge($propertyNames, array('name', 'value', 'class', 'style', 'id'));
  1375. foreach ($validProperties as $propertyName) {
  1376. if (isset($args[$propertyName])) {
  1377. $return[$propertyName] = $args[$propertyName];
  1378. }
  1379. }
  1380. return $return;
  1381. }
  1382. /**
  1383. * Begins a form
  1384. * Includes a safety mechanism to prevent re-opening an already-open form
  1385. *
  1386. * @param array $args
  1387. * @return string
  1388. */
  1389. public function open(array $args = array()) {
  1390. if (!$this->_formOpen) {
  1391. if (!isset($args['method'])) {
  1392. $args['method'] = 'post';
  1393. }
  1394. $this->_formOpen = array('id' => (isset($args['id']) ? $args['id'] : true),
  1395. 'method' => $args['method']);
  1396. if (!isset($args['action'])) {
  1397. $args['action'] = $_SERVER['REQUEST_URI'];
  1398. }
  1399. if (isset($args['upload']) && $args['upload'] && !isset($args['enctype'])) {
  1400. $args['enctype'] = 'multipart/form-data';
  1401. }
  1402. if (isset($args['legend'])) {
  1403. $legend = $args['legend'];
  1404. unset($args['legend']);
  1405. if (!isset($args['title'])) {
  1406. $args['title'] = $legend;
  1407. }
  1408. } else if (isset($args['title'])) {
  1409. $legend = $args['title'];
  1410. }
  1411. if (isset($args['alert'])) {
  1412. if ($args['alert']) {
  1413. $alert = (is_array($args['alert']) ? implode('<br />', $args['alert']) : $args['alert']);
  1414. }
  1415. unset($args['alert']);
  1416. }
  1417. $return = '<form ' . htmlHelper::formatProperties($args) . '>' . PHP_EOL . '<fieldset>' . PHP_EOL;
  1418. if (isset($legend)) {
  1419. $return .= '<legend>' . $legend . '</legend>' . PHP_EOL;
  1420. }
  1421. if (isset($alert)) {
  1422. $return .= $this->getErrorMessageContainer((isset($args['id']) ? $args['id'] : 'form'), $alert);
  1423. }
  1424. return $return;
  1425. } else if (DEBUG_MODE) {
  1426. $errorMsg = 'Invalid usage of ' . __METHOD__ . '() - a form is already open';
  1427. trigger_error($errorMsg, E_USER_NOTICE);
  1428. }
  1429. }
  1430. /**
  1431. * Closes a form if one is open
  1432. *
  1433. * @return string
  1434. */
  1435. public function close() {
  1436. if ($this->_formOpen) {
  1437. $this->_formOpen = false;
  1438. return '</fieldset></form>';
  1439. } else if (DEBUG_MODE) {
  1440. $errorMsg = 'Invalid usage of ' . __METHOD__ . '() - there is no open form to close';
  1441. trigger_error($errorMsg, E_USER_NOTICE);
  1442. }
  1443. }
  1444. /**
  1445. * Adds label tags to a form element
  1446. *
  1447. * @param array $args
  1448. * @param str $formElement
  1449. * @return str
  1450. */
  1451. protected function _getLabel(array $args, $formElement) {
  1452. if (!isset($args['label']) && isset($args['name'])
  1453. && (!isset($args['type']) || !in_array($args['type'], $this->_staticTypes))) {
  1454. $args['label'] = ucfirst($args['name']);
  1455. }
  1456. if (isset($args['label'])) {
  1457. $label = get::xhtmlentities($args['label']);
  1458. if (isset($_POST['errors']) && isset($args['name']) && isset($_POST['errors'][$args['name']])) {
  1459. $label .= ' ' . $this->getErrorMessageContainer($args['name'], $_POST['errors'][$args['name']]);
  1460. }
  1461. $labelFirst = (!isset($args['labelFirst']) || $args['labelFirst']);
  1462. if (isset($args['id'])) {
  1463. $label = '<label for="' . $args['id'] . '" id="' . $args['id'] . 'label">'
  1464. . $label . '</label>';
  1465. }
  1466. if (isset($args['addBreak']) && $args['addBreak']) {
  1467. $label = ($labelFirst ? $label . '<br />' : '<br />' . $label);
  1468. }
  1469. $formElement = ($labelFirst ? $label . $formElement : $formElement . $label);
  1470. if (!isset($args['id'])) {
  1471. $formElement = '<label>' . $formElement . '</label>';
  1472. }
  1473. }
  1474. return $formElement;
  1475. }
  1476. /**
  1477. * Returns a standardized container to wrap an error message
  1478. *
  1479. * @param string $id
  1480. * @param string $errorMessage Optional, you may want to leave this blank and populate dynamically via JavaScript
  1481. * @return string
  1482. */
  1483. public function getErrorMessageContainer($id, $errorMessage = '') {
  1484. return '<span class="errormessage" id="' . $id . 'errorwrapper">'
  1485. . get::htmlentities($errorMessage) . '</span>';
  1486. }
  1487. /**
  1488. * Used for text, textarea, hidden, password, file, button, image and submit
  1489. *
  1490. * Valid args are any properties valid within an HTML input as well as label
  1491. *
  1492. * @param array $args
  1493. * @return string
  1494. */
  1495. public function input(array $args) {
  1496. $args['type'] = (isset($args['type']) ? $args['type'] : 'text');
  1497. switch ($args['type']) {
  1498. case 'select':
  1499. return $this->select($args);
  1500. break;
  1501. case 'checkbox':
  1502. return $this->checkboxes($args);
  1503. break;
  1504. case 'radio':
  1505. return $this->radios($args);
  1506. break;
  1507. }
  1508. if ($args['type'] == 'textarea' && isset($args['maxlength'])) {
  1509. if (!isset($args['id']) && isset($args['name'])) {
  1510. $args['id'] = $args['name'];
  1511. }
  1512. if (isset($args['id'])) {
  1513. $maxlength = $args['maxlength'];
  1514. }
  1515. unset($args['maxlength']);
  1516. }
  1517. if ($args['type'] == 'submit' && !isset($args['class'])) {
  1518. $args['class'] = $args['type'];
  1519. }
  1520. $takeFocus = (isset($args['focus']) && $args['focus'] && $args['type'] != 'hidden');
  1521. if ($takeFocus && !isset($args['id'])) {
  1522. if (isset($args['name'])) {
  1523. $args['id'] = $args['name'];
  1524. } else {
  1525. $takeFocus = false;
  1526. if (DEBUG_MODE) {
  1527. $errorMsg = 'Either name or id is required to use the focus option on a form input';
  1528. trigger_error($errorMsg, E_USER_NOTICE);
  1529. }
  1530. }
  1531. }
  1532. $properties = $this->_getProperties($args, array('type', 'maxlength'));
  1533. if ($args['type'] == 'image') {
  1534. $properties['src'] = $args['src'];
  1535. $properties['alt'] = (isset($args['alt']) ? $args['alt'] : '');
  1536. $optionalProperties = array('height', 'width');
  1537. foreach ($optionalProperties as $optionalProperty) {
  1538. if (isset($args[$optionalProperty])) {
  1539. $properties[$optionalProperty] = $args[$optionalProperty];
  1540. }
  1541. }
  1542. }
  1543. if ($args['type'] != 'textarea') {
  1544. $return[] = '<input ' . htmlHelper::formatProperties($properties) . ' />';
  1545. } else {
  1546. unset($properties['type']);
  1547. if (isset($properties['value'])) {
  1548. $value = $properties['value'];
  1549. unset($properties['value']);
  1550. }
  1551. if (isset($args['preview']) && $args['preview'] && !isset($properties['id'])) {
  1552. $properties['id'] = 'textarea_' . rand(100, 999);
  1553. }
  1554. $properties['rows'] = (isset($args['rows']) ? $args['rows'] : 13);
  1555. $properties['cols'] = (isset($args['cols']) ? $args['cols'] : 55);
  1556. $return[] = '<textarea ' . htmlHelper::formatProperties($properties);
  1557. if (isset($maxlength)) {
  1558. $return[] = ' onkeyup="document.getElementById(\''
  1559. . $properties['id'] . 'errorwrapper\').innerHTML = (this.value.length > '
  1560. . $maxlength . ' ? \'Form content exceeds maximum length of '
  1561. . $maxlength . ' characters\' : \'Length: \' + this.value.length + \' (maximum: '
  1562. . $maxlength . ' characters)\')"';
  1563. }
  1564. $return[] = '>';
  1565. if (isset($value)) {
  1566. $return[] = get::htmlentities($value, null, null, true); //double-encode allowed
  1567. }
  1568. $return[] = '</textarea>';
  1569. if (isset($maxlength) && (!isset($args['validatedInput']) || !$args['validatedInput'])) {
  1570. $return[] = $this->getErrorMessageContainer($properties['id']);
  1571. }
  1572. }
  1573. if (!isset($args['addBreak'])) {
  1574. $args['addBreak'] = true;
  1575. }
  1576. if ($takeFocus) {
  1577. $html = get::helper('html');
  1578. $return[] = $html->jsInline($html->jsTools(true) . 'dom("' . $args['id'] . '").focus();');
  1579. }
  1580. if (isset($args['preview']) && $args['preview']) {
  1581. $js = 'document.writeln(\'<div class="htmlpreviewlabel">'
  1582. . '<label for="livepreview_' . $properties['id'] . '">Preview:</label></div>'
  1583. . '<div id="livepreview_' . $properties['id'] . '" class="htmlpreview"></div>\');' . PHP_EOL
  1584. . 'if (dom("livepreview_' . $properties['id'] . '")) {' . PHP_EOL
  1585. . ' var updateLivePreview_' . $properties['id'] . ' = '
  1586. . 'function() {dom("livepreview_' . $properties['id'] . '").innerHTML = '
  1587. . 'dom("' . $properties['id'] . '").value};' . PHP_EOL
  1588. . ' dom("' . $properties['id'] . '").onkeyup = updateLivePreview_' . $properties['id'] . ';'
  1589. . ' updateLivePreview_' . $properties['id'] . '();' . PHP_EOL
  1590. . '}';
  1591. if (!isset($html)) {
  1592. $html = get::helper('html');
  1593. }
  1594. $return[] = $html->jsInline($html->jsTools(true) . $js);
  1595. }
  1596. return $this->_getLabel($args, implode($return));
  1597. }
  1598. /**
  1599. * Returns a form select element
  1600. *
  1601. * $args = array(
  1602. * 'name' => '',
  1603. * 'multiple' => true,
  1604. * 'leadingOptions' => array(),
  1605. * 'optgroups' => array('group 1' => array('label' => 'g1o1', 'value' => 'grp 1 opt 1'),
  1606. * 'group 2' => array(),),
  1607. * 'options' => array('value1' => 'text1', 'value2' => 'text2', 'value3' => 'text3'),
  1608. * 'value' => array('value2', 'value3') //if (multiple==false) 'value' => (str) 'value3'
  1609. * );
  1610. *
  1611. * @param array $args
  1612. * @return str
  1613. */
  1614. public function select(array $args) {
  1615. if (!isset($args['id'])) {
  1616. $args['id'] = $args['name'];
  1617. }
  1618. if (isset($args['multiple']) && $args['multiple']) {
  1619. $args['multiple'] = 'multiple';
  1620. if (substr($args['name'], -2) != '[]') {
  1621. $args['name'] .= '[]';
  1622. }
  1623. }
  1624. $properties = $this->_getProperties($args, array('multiple'));
  1625. $values = (isset($properties['value']) ? $properties['value'] : null);
  1626. unset($properties['value']);
  1627. if (!is_array($values)) {
  1628. $values = ($values != '' ? array($values) : array());
  1629. }
  1630. $return = '<select ' . htmlHelper::formatProperties($properties) . '>';
  1631. if (isset($args['prependBlank']) && $args['prependBlank']) {
  1632. $return .= '<option value=""></option>';
  1633. }
  1634. if (isset($args['leadingOptions'])) {
  1635. $useValues = (key($args['leadingOptions']) !== 0
  1636. || (isset($args['useValue']) && $args['useValue']));
  1637. foreach ($args['leadingOptions'] as $value => $text) {
  1638. if (!$useValues) {
  1639. $value = $text;
  1640. }
  1641. $return .= '<option value="' . get::htmlentities($value) . '"';
  1642. if (in_array((string) $value, $values)) {
  1643. $return .= ' selected="selected"';
  1644. }
  1645. $return .= '>' . get::htmlentities($text) . '</option>';
  1646. }
  1647. }
  1648. if (isset($args['optgroups'])) {
  1649. foreach ($args['optgroups'] as $groupLabel => $optgroup) {
  1650. $return .= '<optgroup label="' . get::htmlentities($groupLabel) . '">';
  1651. foreach ($optgroup as $value => $label) {
  1652. $return .= '<option value="' . get::htmlentities($value) . '"';
  1653. if (isset($label)) {
  1654. $return .= ' label="' . get::htmlentities($label) . '"';
  1655. }
  1656. if (in_array((string) $value, $values)) {
  1657. $return .= ' selected="selected"';
  1658. }
  1659. $return .= '>' . get::htmlentities($label) . '</option>';
  1660. }
  1661. $return .= '</optgroup>';
  1662. }
  1663. }
  1664. if (isset($args['options'])) {
  1665. $useValues = (key($args['options']) !== 0 || (isset($args['useValue']) && $args['useValue']));
  1666. foreach ($args['options'] as $value => $text) {
  1667. if (!$useValues) {
  1668. $value = $text;
  1669. }
  1670. $return .= '<option value="' . get::htmlentities($value) . '"';
  1671. if (in_array((string) $value, $values)) {
  1672. $return .= ' selected="selected"';
  1673. }
  1674. $return .= '>' . get::htmlentities($text) . '</option>';
  1675. }
  1676. }
  1677. $return .= '</select>';
  1678. if (!isset($args['addBreak'])) {
  1679. $args['addBreak'] = true;
  1680. }
  1681. $return = $this->_getLabel($args, $return);
  1682. if (isset($args['error'])) {
  1683. $return .= $this->getErrorMessageContainer($args['id'], '<br />' . $args['error']);
  1684. }
  1685. return $return;
  1686. }
  1687. /**
  1688. * Cache containing individual radio or checkbox elements in an array
  1689. * @var array
  1690. */
  1691. public $radios = array(), $checkboxes = array();
  1692. /**
  1693. * Returns a set of radio form elements
  1694. *
  1695. * array(
  1696. * 'name' => '',
  1697. * 'value' => '',
  1698. * 'id' => '',
  1699. * 'legend' => '',
  1700. * 'options' => array('value1' => 'text1', 'value2' => 'text2', 'value3' => 'text3'),
  1701. * 'options' => array('text1', 'text2', 'text3'), //also acceptable (cannot do half this, half above syntax)
  1702. * )
  1703. *
  1704. * @param array $args
  1705. * @return str
  1706. */
  1707. public function radios(array $args) {
  1708. $id = (isset($args['id']) ? $args['id'] : $args['name']);
  1709. $properties = $this->_getProperties($args);
  1710. if (isset($properties['value'])) {
  1711. $checked = $properties['value'];
  1712. unset($properties['value']);
  1713. }
  1714. $properties['type'] = (isset($args['type']) ? $args['type'] : 'radio');
  1715. $useValues = (key($args['options']) !== 0 || (isset($args['useValue']) && $args['useValue']));
  1716. foreach ($args['options'] as $value => $text) {
  1717. if (!$useValues) {
  1718. $value = $text;
  1719. }
  1720. $properties['id'] = $id . '_' . preg_replace('/\W/', '', $value);
  1721. $properties['value'] = $value;
  1722. if (isset($checked) &&
  1723. ((($properties['type'] == 'radio' || !is_array($checked)) && $value == $checked)
  1724. || ($properties['type'] == 'checkbox' && is_array($checked) && in_array((string) $value, $checked)))) {
  1725. $properties['checked'] = 'checked';
  1726. $rowClass = (!isset($properties['class']) ? 'checked' : $properties['class'] . ' checked');
  1727. }
  1728. $labelFirst = (isset($args['labelFirst']) ? $args['labelFirst'] : false);
  1729. $labelArgs = array('label' => $text, 'id' => $properties['id'], 'labelFirst' => $labelFirst);
  1730. $input = '<input ' . htmlHelper::formatProperties($properties) . ' />';
  1731. $row = $this->_getLabel($labelArgs, $input);
  1732. if (isset($rowClass)) {
  1733. $row = '<span class="' . $rowClass . '">' . $row . '</span>';
  1734. }
  1735. $radios[] = $row;
  1736. unset($properties['checked'], $rowClass);
  1737. }
  1738. $this->{$properties['type'] == 'radio' ? 'radios' : 'checkboxes'} = $radios;
  1739. $break = (!isset($args['optionBreak']) ? '<br />' : $args['optionBreak']);
  1740. $addFieldset = (isset($args['addFieldset']) ? $args['addFieldset']
  1741. : ((isset($args['label']) && $args['label']) || count($args['options']) > 1));
  1742. if ($addFieldset) {
  1743. $return = '<fieldset id="' . $id . '">';
  1744. if (isset($args['label'])) {
  1745. $return .= '<legend>' . get::htmlentities($args['label']) . '</legend>';
  1746. }
  1747. $return .= implode($break, $radios) . '</fieldset>';
  1748. } else {
  1749. $return = implode($break, $radios);
  1750. }
  1751. if (isset($_POST['errors']) && isset($_POST['errors'][$id])) {
  1752. $return = $this->getErrorMessageContainer($id, $_POST['errors'][$id]) . $return;
  1753. }
  1754. return $return;
  1755. }
  1756. /**
  1757. * Returns a set of checkbox form elements
  1758. *
  1759. * This method essentially extends the radios method and uses an identical signature except
  1760. * that $args['value'] can also accept an array of values to be checked.
  1761. *
  1762. * @param array $args
  1763. * @return str
  1764. */
  1765. public function checkboxes(array $args) {
  1766. $args['type'] = 'checkbox';
  1767. if (isset($args['value']) && !is_array($args['value'])) {
  1768. $args['value'] = array($args['value']);
  1769. }
  1770. $nameParts = explode('[', $args['name']);
  1771. if (!isset($args['id'])) {
  1772. $args['id'] = $nameParts[0];
  1773. }
  1774. if (!isset($nameParts[1]) && count($args['options']) > 1) {
  1775. $args['name'] .= '[]';
  1776. }
  1777. return $this->radios($args);
  1778. }
  1779. /**
  1780. * Opens up shorthand usage of form elements like $form->file() and $form->submit()
  1781. *
  1782. * @param string $name
  1783. * @param array $args
  1784. * @return mixed
  1785. */
  1786. public function __call($name, array $args) {
  1787. $inputShorthand = array('text', 'textarea', 'password', 'file', 'hidden', 'submit', 'button', 'image');
  1788. if (in_array($name, $inputShorthand)) {
  1789. $args[0]['type'] = $name;
  1790. return $this->input($args[0]);
  1791. }
  1792. trigger_error('Call to undefined method ' . __CLASS__ . '::' . $name . '()', E_USER_ERROR);
  1793. }
  1794. }
  1795. class jsonHelper {
  1796. /**
  1797. * Outputs content in JSON format
  1798. * @param mixed $content Can be a JSON string or an array of any data that will automatically be converted to JSON
  1799. * @param string $filename Default filename within the user-prompt for saving the JSON file
  1800. */
  1801. public function echoJson($content, $filename = null) {
  1802. header('Cache-Control: no-cache, must-revalidate');
  1803. header('Expires: Mon, 01 Jan 2000 01:00:00 GMT');
  1804. header('Content-type: application/json');
  1805. if ($filename) {
  1806. header('Content-Disposition: attachment; filename=' . $filename);
  1807. }
  1808. echo (!is_array($content) && !is_object($content) ? $content : json_encode($content));
  1809. }
  1810. }
  1811. /**
  1812. * phpMoAdmin specific functionality
  1813. */
  1814. class phpMoAdmin {
  1815. /**
  1816. * Sets the depth limit for phpMoAdmin::getArrayKeys (and prevents an endless loop with self-referencing objects)
  1817. */
  1818. const DRILL_DOWN_DEPTH_LIMIT = 8;
  1819. /**
  1820. * Retrieves all the keys & subkeys of an array recursively drilling down
  1821. *
  1822. * @param array $array
  1823. * @param string $path
  1824. * @param int $drillDownDepthCount
  1825. * @return array
  1826. */
  1827. public static function getArrayKeys(array $array, $path = '', $drillDownDepthCount = 0) {
  1828. $return = array();
  1829. if ($drillDownDepthCount) {
  1830. $path .= '.';
  1831. }
  1832. if (++$drillDownDepthCount < self::DRILL_DOWN_DEPTH_LIMIT) {
  1833. foreach ($array as $key => $val) {
  1834. $return[$id] = $id = $path . $key;
  1835. if (is_array($val)) {
  1836. $return = array_merge($return, self::getArrayKeys($val, $id, $drillDownDepthCount));
  1837. }
  1838. }
  1839. }
  1840. return $return;
  1841. }
  1842. /**
  1843. * Strip slashes recursively - used only when magic quotes is enabled (this reverses magic quotes)
  1844. *
  1845. * @param mixed $val
  1846. * @return mixed
  1847. */
  1848. public static function stripslashes($val) {
  1849. return (is_array($val) ? array_map(array('self', 'stripslashes'), $val) : stripslashes($val));
  1850. }
  1851. }
  1852. /**
  1853. * phpMoAdmin bootstrap
  1854. */
  1855. session_start();
  1856. if (get_magic_quotes_gpc()) {
  1857. $_GET = phpMoAdmin::stripslashes($_GET);
  1858. $_POST = phpMoAdmin::stripslashes($_POST);
  1859. }
  1860. if (isset($accessControl) && !isset($_SESSION['user']) && isset($_POST['username'])) {
  1861. $_POST = array_map('trim', $_POST);
  1862. if (isset($accessControl[$_POST['username']]) && $accessControl[$_POST['username']] == $_POST['password']) {
  1863. $_SESSION['user'] = $_POST['username'];
  1864. } else {
  1865. $_POST['errors']['username'] = 'Incorrect username or password';
  1866. }
  1867. }
  1868. $isAuthenticated = (!isset($accessControl) || isset($_SESSION['user']));
  1869. $html = get::helper('html');
  1870. $ver = explode('.', phpversion());
  1871. get::$isPhp523orNewer = ($ver[0] >= 5 && ($ver[1] > 2 || ($ver[1] == 2 && $ver[2] >= 3)));
  1872. $form = new formHelper;
  1873. if ($isAuthenticated) {
  1874. if (!isset($_GET['db'])) {
  1875. $_GET['db'] = moadminModel::$dbName;
  1876. } else if (strpos($_GET['db'], '.') !== false) {
  1877. $_GET['db'] = $_GET['newdb'];
  1878. }
  1879. try {
  1880. moadminComponent::$model = new moadminModel($_GET['db']);
  1881. } catch(Exception $e) {
  1882. echo $e;
  1883. exit(0);
  1884. }
  1885. $mo = new moadminComponent;
  1886. if (isset($_GET['export']) && isset($mo->mongo['listRows'])) {
  1887. $rows = array();
  1888. foreach ($mo->mongo['listRows'] as $row) {
  1889. $rows[] = serialize($row);
  1890. }
  1891. $filename = get::htmlentities($_GET['db']);
  1892. if (isset($_GET['collection'])) {
  1893. $filename .= '~' . get::htmlentities($_GET['collection']);
  1894. }
  1895. $filename .= '.json';
  1896. get::helper('json')->echoJson($rows, $filename);
  1897. exit(0);
  1898. }
  1899. }
  1900. /**
  1901. * phpMoAdmin front-end view-element
  1902. */
  1903. $headerArgs['title'] = (isset($_GET['action']) ? 'phpMoAdmin - ' . get::htmlentities($_GET['action']) : 'phpMoAdmin');
  1904. if (THEME != 'classic') {
  1905. $headerArgs['jqueryTheme'] = (in_array(THEME, array('swanky-purse', 'trontastic', 'simple-gray')) ? THEME : 'classic');
  1906. }
  1907. $headerArgs['cssInline'] = '
  1908. /* reset */
  1909. html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address,
  1910. big, cite, code, del, dfn, em, font, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i,
  1911. center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td {
  1912. margin: 0; padding: 0; border: 0; outline: 0; font-size: 100%; vertical-align: baseline; background: transparent;}
  1913. input, textarea {margin: 0; padding: 0;}
  1914. body {line-height: 1;}
  1915. blockquote, q {quotes: none;}
  1916. blockquote:before, blockquote:after, q:before, q:after {content: ""; content: none;}
  1917. :focus {outline: 0;}
  1918. ins {text-decoration: none;}
  1919. del {text-decoration: line-through;}
  1920. table {border-collapse: collapse; border-spacing: 0;}
  1921. html{color:#000;}
  1922. caption,th{text-align:left;}
  1923. h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}
  1924. abbr,acronym{font-variant:normal;}
  1925. sup{vertical-align:text-top;}
  1926. sub{vertical-align:text-bottom;}
  1927. input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}
  1928. input,textarea,select{*font-size:100%;}
  1929. legend{color:#000;}
  1930. /* \*/ html, body{height:100%;} /* */
  1931. /* initialize */
  1932. html {background: #74736d;}
  1933. body {margin: auto; width: 990px; font-family: "Arial"; font-size: small; background: #000000; color: #ffffff;}
  1934. #bodycontent {padding: 10px; border: 0px solid;}
  1935. textarea {width: 640px; height: 70px;}
  1936. a, .textLink {text-decoration: none; color: #96f226; font-weight: bold;}
  1937. a:hover, .textLink:hover {text-decoration: underline; color: #9fda58;}
  1938. a:hover pre, h1 a:hover {text-decoration: none;}
  1939. h1, h2, h3, h4 {margin-bottom: 3px;}
  1940. h1, h2 {margin-left: -1px;}
  1941. h1 {font-family: "Arial Black"; font-size: 27px; color: #b8ec79;}
  1942. h1.midpageh1 {margin-top: 10px;}
  1943. h2 {font-size: large; font-weight: bold; margin-top: 10px; color: #660000;}
  1944. h3 {font-weight: bold; color: #687d1c;}
  1945. h4 {font-weight: bold; color: #10478b;}
  1946. p {margin-bottom: 10px; line-height: 1.75;}
  1947. li {line-height: 1.5; margin-left: 15px;}
  1948. .errormessage {color: #990000; font-weight: bold; background: #ffffff; border: 1px solid #ff0000; padding: 2px;}
  1949. .rownumber {float: right; padding: 0px 5px 0px 5px; border-left: 1px dotted; border-bottom: 1px dotted; color: #ffffff;
  1950. margin-top: 4px; margin-right: -1px;}
  1951. .ui-widget-header .rownumber {margin-top: 2px; margin-right: 0px;}
  1952. pre {border: 1px solid; margin: 1px; padding-left: 5px;}
  1953. li .ui-widget-content {margin: 1px 1px 3px 1px;}
  1954. #mongo_rows {padding-top: 10px;}
  1955. #moadminlogo {color: #96f226; border: 0px solid; padding-left: 10px; font-size: 4px!important;
  1956. width: 265px; height: 63px; overflow: hidden;}';
  1957. switch (THEME) {
  1958. case 'swanky-purse':
  1959. $headerArgs['cssInline'] .= '
  1960. html {background: #261803;}
  1961. h1, .rownumber {color: #baaa5a;}
  1962. 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;}
  1963. #moadminlogo {color: #baaa5a;}
  1964. li .ui-widget-header {margin: 0px 1px 0px 1px;}
  1965. .ui-widget-header .rownumber {margin-top: 2px; margin-right: -1px;}';
  1966. break;
  1967. case 'classic':
  1968. $headerArgs['cssInline'] .= '
  1969. html, .ui-widget-header, button {background: #ccc78c;}
  1970. .ui-widget-content, input.ui-state-hover {background: #edf2ed;}
  1971. h1, .rownumber {color: #796f54;}
  1972. body {background: #ffffcc; color: #000000;}
  1973. #bodycontent {background: #ffffcc;}
  1974. #moadminlogo, button {color: #bb0022;}
  1975. a, .textLink {color: #990000;}
  1976. a:hover, .textLink:hover {color: #7987ae;}
  1977. li .ui-widget-header {margin: 0px 1px 0px 1px;}
  1978. .rownumber {margin-top: 2px; margin-right: 0px;}
  1979. .ui-dialog {border: 3px outset;}
  1980. .ui-dialog .ui-dialog-titlebar {padding: 3px; margin: 1px; border: 3px ridge;}
  1981. .ui-dialog #confirm {padding: 10px;}
  1982. .ui-dialog .ui-icon-closethick, .ui-dialog button {float: right; margin: 4px;}
  1983. .ui-dialog .ui-icon-closethick {margin-top: -13px;}
  1984. body:first-of-type .ui-dialog .ui-icon-closethick {margin-top: -2px;} /*Chrome/Safari*/
  1985. .ui-resizable {position: relative;}
  1986. .ui-resizable-handle {position: absolute;font-size: 0.1px;z-index: 99999; display: block;}
  1987. .ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle {display: none;}
  1988. .ui-resizable-n {cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0px;}
  1989. .ui-resizable-s {cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0px;}
  1990. .ui-resizable-e {cursor: e-resize; width: 7px; right: -5px; top: 0px; height: 100%;}
  1991. .ui-resizable-w {cursor: w-resize; width: 7px; left: -5px; top: 0px; height: 100%;}
  1992. .ui-resizable-se {cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px;}
  1993. .ui-resizable-sw {cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px;}
  1994. .ui-resizable-nw {cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px;}
  1995. .ui-resizable-ne {cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}';
  1996. break;
  1997. case 'simple-gray':
  1998. $headerArgs['cssInline'] .= '
  1999. html, body {background: #eee; color: #333;}
  2000. body {font-family: sans-serif, Arial;}
  2001. pre {font-size: 13px; padding: 10px; margin: 0; background: #fff; border: none;}
  2002. #moadminlogo {background: none;}
  2003. a, .textLink {font-weight: normal;}
  2004. a, .textLink, #moadminlogo, h1 {color: #0088cc;}
  2005. a:hover, .textLink:hover {color: #005580;}
  2006. li .ui-widget-content, li .ui-widget-header {background: #ffe; line-height: 2em; margin: 0 !important;
  2007. border-bottom: 1px solid #eee;}
  2008. ol a {font-weight: bold;}
  2009. #mongo_rows ol {margin: 1em 0;}
  2010. .ui-dialog, #mongo_rows ol li {border-radius: 4px; background: #f7f7f7; margin-bottom: .5em; padding: .5em; border: 1px solid #ddd;}
  2011. .ui-dialog .ui-dialog-titlebar {padding: 3px; margin: 1px; border: 3px ridge;}
  2012. .ui-dialog #confirm {padding: 10px;}
  2013. .ui-dialog .ui-icon-closethick, .ui-dialog button {margin: 4px;}
  2014. .ui-dialog .ui-icon-closethick {float: right; margin-top: -4px; font-size: large;}
  2015. ';
  2016. break;
  2017. }
  2018. echo $html->header($headerArgs);
  2019. echo $html->jsLoad(array('jquery', 'jqueryui'));
  2020. $baseUrl = $_SERVER['SCRIPT_NAME'];
  2021. $db = (isset($_GET['db']) ? $_GET['db'] : (isset($_POST['db']) ? $_POST['db'] : 'admin')); //admin is in every Mongo DB
  2022. $dbUrl = urlencode($db);
  2023. $phpmoadmin = '<pre id="moadminlogo">
  2024. ,ggg, ,ggg,_,ggg, ,ggg,
  2025. ,dPYb, dP""Y8dP""Y88P""Y8b dP""8I 8I
  2026. IP\'`Yb Yb, `88\' `88\' `88 dP 88 8I
  2027. I8 8I `" 88 88 88 dP 88 8I gg
  2028. I8 8\' 88 88 88 ,8\' 88 8I ""
  2029. gg,gggg, I8 dPgg, gg,gggg, 88 88 88 ,ggggg, d88888888 ,gggg,8I ,ggg,,ggg,,ggg, gg ,ggg,,ggg,
  2030. I8P" "Yb I8dP" "8I I8P" "Yb 88 88 88 dP" "Y8 ,8" 88 dP" "Y8I ,8" "8P" "8P" "8, 88 ,8" "8P" "8,
  2031. I8\' ,8i I8P I8 I8\' ,8i 88 88 88 i8\' ,8 dP ,8P Y8 i8\' ,8I I8 8I 8I 8I 88 I8 8I 8I
  2032. ,I8 _ ,d8\' ,d8 I8,,I8 _ ,d8\' 88 88 Y8,,d8, ,d8 Yb,_,dP `8b,,d8, ,d8b,,dP 8I 8I Yb,_,88,_,dP 8I Yb,
  2033. PI8 YY88888P88P `Y8PI8 YY88 88 88 `Y8P"Y8888P" "Y8P" `Y8P"Y8888P"`Y88P\' 8I 8I `Y88P""Y88P\' 8I `Y8
  2034. I8 I8
  2035. I8 I8
  2036. I8 I8
  2037. I8 I8
  2038. I8 I8
  2039. I8 I8
  2040. </pre>';
  2041. echo '<div id="bodycontent" class="ui-widget-content"><h1 style="float: right;">'
  2042. . $html->link('http://www.phpmoadmin.com', $phpmoadmin, array('title' => 'phpMoAdmin')) . '</h1>';
  2043. if (isset($accessControl) && !isset($_SESSION['user'])) {
  2044. echo $form->open();
  2045. echo $html->div($form->input(array('name' => 'username', 'focus' => true)));
  2046. echo $html->div($form->password(array('name' => 'password')));
  2047. echo $html->div($form->submit(array('value' => 'Login', 'class' => 'ui-state-hover')));
  2048. echo $form->close();
  2049. exit(0);
  2050. }
  2051. echo '<div id="dbcollnav">';
  2052. $formArgs = array('method' => 'get');
  2053. if (isset($mo->mongo['repairDb'])) {
  2054. $formArgs['alert'] = (isset($mo->mongo['repairDb']['ok']) && $mo->mongo['repairDb']['ok']
  2055. ? 'Database has been repaired and compacted' : 'Database could not be repaired');
  2056. }
  2057. echo $form->open($formArgs);
  2058. echo $html->div($form->select(array('name' => 'db', 'options' => $mo->mongo['dbs'], 'label' => '', 'value' => $db,
  2059. 'addBreak' => false))
  2060. . $form->submit(array('value' => 'Change database', 'class' => 'ui-state-hover'))
  2061. . ' <span style="font-size: xx-large;">' . get::htmlentities($db)
  2062. . '</span> [' . $html->link("javascript: mo.repairDatabase('" . get::htmlentities($db)
  2063. . "'); void(0);", 'repair database') . '] [' . $html->link("javascript: mo.dropDatabase('"
  2064. . get::htmlentities($db) . "'); void(0);", 'drop database') . ']');
  2065. echo $form->close();
  2066. $js = 'var mo = {}
  2067. mo.urlEncode = function(str) {
  2068. return escape(str)'
  2069. . '.replace(/\+/g, "%2B").replace(/%20/g, "+").replace(/\*/g, "%2A").replace(/\//g, "%2F").replace(/@/g, "%40");
  2070. }
  2071. mo.repairDatabase = function(db) {
  2072. mo.confirm("Are you sure that you want to repair and compact the " + db + " database?", function() {
  2073. window.location.replace("' . $baseUrl . '?db=' . $dbUrl . '&action=repairDb");
  2074. });
  2075. }
  2076. mo.dropDatabase = function(db) {
  2077. mo.confirm("Are you sure that you want to drop the " + db + " database?", function() {
  2078. mo.confirm("All the collections in the " + db + " database will be lost along with all the data within them!'
  2079. . '\n\nAre you 100% sure that you want to drop this database?'
  2080. . '\n\nLast chance to cancel!", function() {
  2081. window.location.replace("' . $baseUrl . '?db=' . $dbUrl . '&action=dropDb");
  2082. });
  2083. });
  2084. }';
  2085. if (!moadminModel::$databaseWhitelist) {
  2086. $js .= '
  2087. $("select[name=db]").prepend(\'<option value="new.database">Use new ==&gt;</option>\')'
  2088. . '.after(\'<input type="text" name="newdb" name style="display: none;" />\').change(function() {
  2089. ($(this).val() == "new.database" ? $("input[name=newdb]").show() : $("input[name=newdb]").hide());
  2090. });';
  2091. }
  2092. $js .= '
  2093. mo.confirm = function(dialog, func, title) {
  2094. if (typeof title == "undefined") {
  2095. title = "Please confirm:";
  2096. }
  2097. if (!$("#confirm").length) {
  2098. $("#dbcollnav").append(\'<div id="confirm" style="display: none;"></div>\');
  2099. }
  2100. mo.userFunc = func; //overcomes JS scope issues
  2101. $("#confirm").html(dialog).attr("title", title).dialog({modal: true, buttons: {
  2102. "Yes": function() {$(this).dialog("close"); mo.userFunc();},
  2103. Cancel: function() {$(this).dialog("close");}
  2104. }}).dialog("open");
  2105. }
  2106. ';
  2107. echo $html->jsInline($js);
  2108. if (isset($_GET['collection'])) {
  2109. $collection = get::htmlentities($_GET['collection']);
  2110. unset($_GET['collection']);
  2111. }
  2112. if (isset($mo->mongo['listCollections'])) {
  2113. echo '<div id="mongo_collections">';
  2114. echo $form->open(array('method' => 'get'));
  2115. echo $html->div($form->input(array('name' => 'collection', 'label' => '', 'addBreak' => false))
  2116. . $form->hidden(array('name' => 'action', 'value' => 'createCollection'))
  2117. . $form->submit(array('value' => 'Add new collection', 'class' => 'ui-state-hover'))
  2118. . $form->hidden(array('name' => 'db', 'value' => get::htmlentities($db)))
  2119. . ' &nbsp; &nbsp; &nbsp; [' . $html->link($baseUrl . '?action=getStats', 'stats') . ']');
  2120. echo $form->close();
  2121. if (!$mo->mongo['listCollections']) {
  2122. echo $html->div('No collections exist');
  2123. } else {
  2124. echo '<ol>';
  2125. foreach ($mo->mongo['listCollections'] as $col => $rowCount) {
  2126. echo $html->li($html->link($baseUrl . '?db='
  2127. . $dbUrl . '&action=listRows&collection=' . urlencode($col), $col)
  2128. . ' <span title="' . $rowCount . ' objects">(' . number_format($rowCount) . ')</span>');
  2129. }
  2130. echo '</ol>';
  2131. echo $html->jsInline('mo.collectionDrop = function(collection) {
  2132. mo.confirm.collection = collection;
  2133. mo.confirm("Are you sure that you want to drop " + collection + "?",
  2134. function() {
  2135. mo.confirm("All the data in the " + mo.confirm.collection + " collection will be lost;'
  2136. . ' are you 100% sure that you want to drop it?\n\nLast chance to cancel!",
  2137. function() {
  2138. window.location.replace("' . $baseUrl . '?db=' . $dbUrl
  2139. . '&action=dropCollection&collection=" + mo.urlEncode(mo.confirm.collection));
  2140. }
  2141. );
  2142. }
  2143. );
  2144. }
  2145. $(document).ready(function() {
  2146. $("#mongo_collections li").each(function() {
  2147. $(this).prepend("[<a href=\"javascript: mo.collectionDrop(\'" + $(this).find("a").html() + "\'); void(0);\"'
  2148. . ' title=\"drop this collection\">X</a>] ");
  2149. });
  2150. });
  2151. ');
  2152. }
  2153. $url = $baseUrl . '?' . http_build_query($_GET);
  2154. if (isset($collection)) {
  2155. $url .= '&collection=' . urlencode($collection);
  2156. }
  2157. echo $form->open(array('action' => $url, 'style' => 'width: 80px; height: 20px;'))
  2158. . $form->input(array('name' => 'limit', 'value' => $_SESSION['limit'], 'label' => '', 'addBreak' => false,
  2159. 'style' => 'width: 40px;'))
  2160. . $form->submit(array('value' => 'limit', 'class' => 'ui-state-hover'))
  2161. . $form->close();
  2162. echo '</div>';
  2163. }
  2164. echo '</div>'; //end of dbcollnav
  2165. $dbcollnavJs = '$("#dbcollnav").after(\'<a id="dbcollnavlink" href="javascript: $(\\\'#dbcollnav\\\').show();'
  2166. . ' $(\\\'#dbcollnavlink\\\').hide(); void(0);">[Show Database &amp; Collection selection]</a>\').hide();';
  2167. if (isset($mo->mongo['listRows'])) {
  2168. echo $form->open(array('action' => $baseUrl . '?db=' . $dbUrl . '&action=renameCollection',
  2169. 'style' => 'width: 600px; display: none;', 'id' => 'renamecollectionform'))
  2170. . $form->hidden(array('name' => 'collectionfrom', 'value' => $collection))
  2171. . $form->input(array('name' => 'collectionto', 'value' => $collection, 'label' => '', 'addBreak' => false))
  2172. . $form->submit(array('value' => 'Rename Collection', 'class' => 'ui-state-hover'))
  2173. . $form->close();
  2174. $js = "$('#collectionname').hide(); $('#renamecollectionform').show(); void(0);";
  2175. echo '<h1 id="collectionname">' . $html->link('javascript: ' . $js, $collection) . '</h1>';
  2176. if (isset($mo->mongo['listIndexes'])) {
  2177. echo '<ol id="indexes" style="display: none; margin-bottom: 10px;">';
  2178. echo $form->open(array('method' => 'get'));
  2179. echo '<div id="indexInput">'
  2180. . $form->input(array('name' => 'index[]', 'label' => '', 'addBreak' => false))
  2181. . $form->checkboxes(array('name' => 'isdescending[]', 'options' => array('Descending')))
  2182. . '</div>'
  2183. . '<a id="addindexcolumn" style="margin-left: 160px;" href="javascript: '
  2184. . "$('#addindexcolumn').before('<div>' + $('#indexInput').html().replace(/isdescending_Descending/g, "
  2185. . "'isdescending_Descending' + mo.indexCount++) + '</div>'); void(0);"
  2186. . '">[Add another index field]</a>'
  2187. . $form->radios(array('name' => 'unique', 'options' => array('Index', 'Unique'), 'value' => 'Index'))
  2188. . $form->submit(array('value' => 'Add new index', 'class' => 'ui-state-hover'))
  2189. . $form->hidden(array('name' => 'action', 'value' => 'ensureIndex'))
  2190. . $form->hidden(array('name' => 'db', 'value' => get::htmlentities($db)))
  2191. . $form->hidden(array('name' => 'collection', 'value' => $collection))
  2192. . $form->close();
  2193. foreach ($mo->mongo['listIndexes'] as $indexArray) {
  2194. $index = '';
  2195. foreach ($indexArray['key'] as $key => $direction) {
  2196. $index .= (!$index ? $key : ', ' . $key);
  2197. if (!is_object($direction)) {
  2198. $index .= ' [' . ($direction == -1 ? 'desc' : 'asc') . ']';
  2199. }
  2200. }
  2201. if (isset($indexArray['unique']) && $indexArray['unique']) {
  2202. $index .= ' [unique]';
  2203. }
  2204. if (key($indexArray['key']) != '_id' || count($indexArray['key']) !== 1) {
  2205. $index = '[' . $html->link($baseUrl . '?db=' . $dbUrl . '&collection=' . urlencode($collection)
  2206. . '&action=deleteIndex&index='
  2207. . serialize($indexArray['key']), 'X', array('title' => 'Drop Index',
  2208. 'onclick' => "mo.confirm.href=this.href; "
  2209. . "mo.confirm('Are you sure that you want to drop this index?', "
  2210. . "function() {window.location.replace(mo.confirm.href);}); return false;")
  2211. ) . '] '
  2212. . $index;
  2213. }
  2214. echo '<li>' . $index . '</li>';
  2215. }
  2216. echo '</ol>';
  2217. }
  2218. echo '<ul id="export" style="display: none; margin-bottom: 10px;">';
  2219. echo $html->li($html->link(get::url(array('get' => true)) . '&export=nolimit',
  2220. 'Export full results of this query (ignoring limit and skip clauses)'));
  2221. echo $html->li($html->link(get::url(array('get' => true)) . '&export=limited',
  2222. 'Export exactly the results visible on this page'));
  2223. echo '</ul>';
  2224. echo '<div id="import" style="display: none; margin-bottom: 10px;">';
  2225. echo $form->open(array('upload' => true));
  2226. echo $form->file(array('name' => 'import'));
  2227. echo $form->radios(array('name' => 'importmethod', 'value' => 'insert', 'options' => array(
  2228. 'insert' => 'Insert: skips over duplicate records',
  2229. 'save' => 'Save: overwrites duplicate records',
  2230. 'update' => 'Update: overwrites only records that currently exist (skips new objects)',
  2231. 'batchInsert' => 'Batch-Insert: Halt upon reaching first duplicate record (may result in partial dataset)',
  2232. )));
  2233. echo $form->submit(array('value' => 'Import records into this collection'));
  2234. echo $form->close();
  2235. echo '</div>';
  2236. $objCount = $mo->mongo['listRows']->count(true); //count of rows returned
  2237. $paginator = number_format($mo->mongo['count']) . ' objects'; //count of rows in collection
  2238. if ($objCount && $mo->mongo['count'] != $objCount) {
  2239. $skip = (isset($_GET['skip']) ? $_GET['skip'] : 0);
  2240. $get = $_GET;
  2241. unset($get['skip']);
  2242. $url = $baseUrl . '?' . http_build_query($get) . '&collection=' . urlencode($collection) . '&skip=';
  2243. $paginator = number_format($skip + 1) . '-' . number_format(min($skip + $objCount, $mo->mongo['count']))
  2244. . ' of ' . $paginator;
  2245. $remainder = ($mo->mongo['count'] % $_SESSION['limit']);
  2246. $lastPage = ($mo->mongo['count'] - ($remainder ? $remainder : $_SESSION['limit']));
  2247. $isLastPage = ($mo->mongo['count'] <= ($objCount + $skip));
  2248. if ($skip) { //back
  2249. $backPage = (!$isLastPage ? max($skip - $objCount, 0) : ($lastPage - $_SESSION['limit']));
  2250. $backLinks = $html->link($url . 0, '{{', array('title' => 'First')) . ' '
  2251. . $html->link($url . $backPage, '&lt;&lt;&lt;', array('title' => 'Previous'));
  2252. $paginator = addslashes($backLinks) . ' ' . $paginator;
  2253. }
  2254. if (!$isLastPage) { //forward
  2255. $forwardLinks = $html->link($url . ($skip + $objCount), '&gt;&gt;&gt;', array('title' => 'Next')) . ' '
  2256. . $html->link($url . $lastPage, '}}', array('title' => 'Last'));
  2257. $paginator .= ' ' . addslashes($forwardLinks);
  2258. }
  2259. }
  2260. $get = $_GET;
  2261. $get['collection'] = urlencode($collection);
  2262. $queryGet = $searchGet = $sortGet = $get;
  2263. unset($sortGet['sort'], $sortGet['sortdir']);
  2264. unset($searchGet['search'], $searchGet['searchField']);
  2265. unset($queryGet['find']);
  2266. echo $html->jsInline('mo.indexCount = 1;
  2267. $(document).ready(function() {
  2268. $("#mongo_rows").prepend("<div style=\"float: right; line-height: 1.5; margin-top: -45px\">'
  2269. . '[<a href=\"javascript: $(\'#mongo_rows\').find(\'pre\').height(\'100px\').css(\'overflow\', \'auto\');'
  2270. . ' void(0);\" title=\"display compact view of row content\">Compact</a>] '
  2271. . '[<a href=\"javascript: $(\'#mongo_rows\').find(\'pre\').height(\'300px\').css(\'overflow\', \'auto\');'
  2272. . ' void(0);\" title=\"display uniform-view row content\">Uniform</a>] '
  2273. . '[<a href=\"javascript: $(\'#mongo_rows\').find(\'pre\').height(\'auto\').css(\'overflow\', \'hidden\');'
  2274. . ' void(0);\" title=\"display full row content\">Full</a>]'
  2275. . '<div class=\"ui-widget-header\" style=\"padding-left: 5px;\">' . $paginator . '</div></div>");
  2276. });
  2277. mo.removeObject = function(_id, idType) {
  2278. mo.confirm("Are you sure that you want to delete this " + _id + " object?", function() {
  2279. window.location.replace("' . $baseUrl . '?db=' . $dbUrl . '&collection=' . urlencode($collection)
  2280. . '&action=removeObject&_id=" + mo.urlEncode(_id) + "&idtype=" + idType);
  2281. });
  2282. }
  2283. ' . $dbcollnavJs . "
  2284. mo.submitSort = function() {
  2285. document.location = '" . $baseUrl . '?' . http_build_query($sortGet) . "&sort='
  2286. + $('#sort').val() + '&sortdir=' + $('#sortdir').val();
  2287. }
  2288. mo.submitSearch = function() {
  2289. document.location = '" . $baseUrl . '?' . http_build_query($searchGet) . "&search='
  2290. + encodeURIComponent($('#search').val()) + '&searchField=' + $('#searchField').val();
  2291. }
  2292. mo.submitQuery = function() {
  2293. document.location = '" . $baseUrl . '?' . http_build_query($queryGet) . "&find=' + $('#find').val();
  2294. }
  2295. ");
  2296. echo '<div id="mongo_rows">';
  2297. echo $form->open(array('method' => 'get', 'onsubmit' => 'mo.submitSearch(); return false;'));
  2298. echo '[' . $html->link($baseUrl . '?db=' . $dbUrl . '&collection=' . urlencode($collection) . '&action=editObject',
  2299. 'insert new object') . '] ';
  2300. if (isset($index)) {
  2301. $jsShowIndexes = "javascript: $('#indexeslink').hide(); $('#indexes').show(); void(0);";
  2302. echo $html->link($jsShowIndexes, '[show indexes]', array('id' => 'indexeslink')) . ' ';
  2303. }
  2304. $jsShowExport = "javascript: $('#exportlink').hide(); $('#export').show(); void(0);";
  2305. echo $html->link($jsShowExport, '[export]', array('id' => 'exportlink')) . ' ';
  2306. $jsShowImport = "javascript: $('#importlink').hide(); $('#import').show(); void(0);";
  2307. echo $html->link($jsShowImport, '[import]', array('id' => 'importlink')) . ' ';
  2308. $linkSubmitArgs = array('class' => 'ui-state-hover', 'style' => 'padding: 3px 8px 3px 8px;');
  2309. $inlineFormArgs = array('label' => '', 'addBreak' => false);
  2310. if ($mo->mongo['colKeys']) {
  2311. $colKeys = $mo->mongo['colKeys'];
  2312. unset($colKeys['_id']);
  2313. natcasesort($colKeys);
  2314. $sort = array('name' => 'sort', 'id' => 'sort', 'options' => $colKeys, 'label' => '',
  2315. 'leadingOptions' => array('_id' => '_id', '$natural' => '$natural'), 'addBreak' => false);
  2316. $sortdir = array('name' => 'sortdir', 'id' => 'sortdir', 'options' => array(1 => 'asc', -1 => 'desc'));
  2317. $sortdir = array_merge($sortdir, $inlineFormArgs);
  2318. $formInputs = $form->select($sort) . $form->select($sortdir) . ' '
  2319. . $html->link("javascript: mo.submitSort(); void(0);", 'Sort', $linkSubmitArgs);
  2320. if (!isset($_GET['sort']) || !$_GET['sort']) {
  2321. $jsLink = "javascript: $('#sortlink').hide(); $('#sortform').show(); void(0);";
  2322. $formInputs = $html->link($jsLink, '[sort]', array('id' => 'sortlink')) . ' '
  2323. . '<div id="sortform" style="display: none;">' . $formInputs . '</div>';
  2324. } else {
  2325. $formInputs = $html->div($formInputs);
  2326. }
  2327. echo $formInputs;
  2328. $search = array('name' => 'search', 'id' => 'search', 'style' => 'width: 300px;');
  2329. $search = array_merge($search, $inlineFormArgs);
  2330. $searchField = array('name' => 'searchField', 'id' => 'searchField', 'options' => $colKeys,
  2331. 'leadingOptions' => array('_id' => '_id'));
  2332. $searchField = array_merge($searchField, $inlineFormArgs);
  2333. $linkSubmitArgs['title'] = 'Search may be a exact-text, (type-casted) value, (mongoid) 4c6...80c,'
  2334. . ' text with * wildcards, regex or JSON (with Mongo-operators enabled)';
  2335. $formInputs = $form->select($searchField) . $form->input($search) . ' '
  2336. . $html->link("javascript: mo.submitSearch(); void(0);", 'Search', $linkSubmitArgs);
  2337. if (!isset($_GET['search']) || !$_GET['search']) {
  2338. $jsLink = "javascript: $('#searchlink').hide(); $('#searchform').show(); void(0);";
  2339. $formInputs = $html->link($jsLink, '[search]', array('id' => 'searchlink')) . ' '
  2340. . '<div id="searchform" style="display: none;">' . $formInputs . '</div>';
  2341. } else {
  2342. $formInputs = $html->div($formInputs);
  2343. }
  2344. echo $formInputs;
  2345. }
  2346. $linkSubmitArgs['title'] = 'Query may be a JSON object or a PHP array';
  2347. $query = array('name' => 'find', 'id' => 'find', 'style' => 'width: 600px;');
  2348. $query = array_merge($query, $inlineFormArgs);
  2349. $formInputs = $form->textarea($query) . ' '
  2350. . $html->link("javascript: mo.submitQuery(); void(0);", 'Query', $linkSubmitArgs);
  2351. if (!isset($_GET['find']) || !$_GET['find']) {
  2352. $jsLink = "javascript: $('#querylink').hide(); $('#queryform').show(); void(0);";
  2353. $formInputs = $html->link($jsLink, '[query]', array('id' => 'querylink')) . ' '
  2354. . '<div id="queryform" style="display: none;">' . $formInputs . '</div>';
  2355. } else {
  2356. $formInputs = $html->div($formInputs);
  2357. }
  2358. echo $formInputs;
  2359. echo $form->close();
  2360. echo '<ol style="list-style: none; margin-left: -15px;">';
  2361. $rowCount = (!isset($skip) ? 0 : $skip);
  2362. $isChunksTable = (substr($collection, -7) == '.chunks');
  2363. if ($isChunksTable) {
  2364. $chunkUrl = $baseUrl . '?db=' . $dbUrl . '&action=listRows&collection=' . urlencode(substr($collection, 0, -7))
  2365. . '.files#';
  2366. }
  2367. foreach ($mo->mongo['listRows'] as $row) {
  2368. $showEdit = true;
  2369. $id = $idString = $row['_id'];
  2370. if (is_object($idString)) {
  2371. $idString = '(' . get_class($idString) . ') ' . $idString;
  2372. $idForUrl = serialize($id);
  2373. } else if (is_array($idString)) {
  2374. $idString = '(array) ' . json_encode($idString);
  2375. $idForUrl = serialize($id);
  2376. } else {
  2377. $idForUrl = urlencode($id);
  2378. }
  2379. $idType = gettype($row['_id']);
  2380. if ($isChunksTable && isset($row['data']) && is_object($row['data'])
  2381. && get_class($row['data']) == 'MongoBinData') {
  2382. $showEdit = false;
  2383. $row['data'] = $html->link($chunkUrl . $row['files_id'], 'MongoBinData Object',
  2384. array('class' => 'MoAdmin_Reference'));
  2385. }
  2386. $data = explode("\n", substr(print_r($row, true), 8, -2));
  2387. $binData = 0;
  2388. foreach ($data as $id => $rowData) {
  2389. $raw = trim($rowData);
  2390. if ($binData) {
  2391. if (strpos($rowData, '] => ') !== false) {
  2392. ++$binData;
  2393. }
  2394. unset($data[$id]);
  2395. continue;
  2396. }
  2397. if ($raw === '') {
  2398. unset($data[$id]);
  2399. } else if ($raw === '(') { //one-true-brace
  2400. $data[($id - 1)] .= ' (';
  2401. unset($data[$id]);
  2402. } else {
  2403. if (strpos($data[$id], 'MongoBinData Object') !== false) {
  2404. $showEdit = false;
  2405. $binData = -2;
  2406. }
  2407. $data[$id] = str_replace(' ', ' ', (substr($rowData, 0, 4) === ' ' ? substr($rowData, 4)
  2408. : $rowData));
  2409. if ($raw === ')') {
  2410. $data[$id] = substr($data[$id], 4);
  2411. }
  2412. if (strpos($data[$id], 'MoAdmin_Reference') === false) {
  2413. $data[$id] = get::htmlentities($data[$id]);
  2414. }
  2415. }
  2416. }
  2417. echo $html->li('<div style="margin-top: 5px; padding-left: 5px;" class="'
  2418. . ($html->alternator() ? 'ui-widget-header' : 'ui-widget-content') . '" id="' . $row['_id'] . '">'
  2419. . '[' . $html->link("javascript: mo.removeObject('" . $idForUrl . "', '" . $idType
  2420. . "'); void(0);", 'X', array('title' => 'Delete')) . '] '
  2421. . ($showEdit ? '[' . $html->link($baseUrl . '?db=' . $dbUrl . '&collection=' . urlencode($collection)
  2422. . '&action=editObject&_id=' . $idForUrl . '&idtype=' . $idType, 'E', array('title' => 'Edit')) . '] '
  2423. : ' [<span title="Cannot edit objects containing MongoBinData">N/A</span>] ')
  2424. . $idString . '<div class="rownumber">' . number_format(++$rowCount) . '</div></div><pre>'
  2425. . wordwrap(implode("\n", $data), 136, "\n", true) . '</pre>');
  2426. }
  2427. echo '</ol>';
  2428. if (!isset($idString)) {
  2429. echo '<div class="errormessage">No records in this collection</div>';
  2430. }
  2431. echo '</div>';
  2432. } else if (isset($mo->mongo['editObject'])) {
  2433. echo $form->open(array('action' => $baseUrl . '?db=' . $dbUrl . '&collection=' . urlencode($collection)));
  2434. if (isset($_GET['_id']) && $_GET['_id'] && ($_GET['idtype'] == 'object' || $_GET['idtype'] == 'array')) {
  2435. $_GET['_id'] = unserialize($_GET['_id']);
  2436. if (is_array($_GET['_id'])) {
  2437. $_GET['_id'] = json_encode($_GET['_id']);
  2438. }
  2439. }
  2440. echo $html->h1(isset($_GET['_id']) && $_GET['_id'] ? get::htmlentities($_GET['_id']) : '[New Object]');
  2441. echo $html->div($form->submit(array('value' => 'Save Changes', 'class' => 'ui-state-hover')));
  2442. $textarea = array('name' => 'object', 'label' => '');
  2443. $textarea['value'] = ($mo->mongo['editObject'] !== '' ? var_export($mo->mongo['editObject'], true)
  2444. : 'array (' . PHP_EOL . PHP_EOL . ')');
  2445. //MongoID as _id
  2446. $textarea['value'] = preg_replace('/\'_id\' => \s*MongoId::__set_state\(array\(\s*\)\)/', '\'_id\' => new MongoId("'
  2447. . (isset($_GET['_id']) ? $_GET['_id'] : '') . '")', $textarea['value']);
  2448. //MongoID in all other occurrences, original ID is not maintained
  2449. $textarea['value'] = preg_replace('/MongoId::__set_state\(array\(\s*\)\)/', 'new MongoId()', $textarea['value']);
  2450. //MongoDate
  2451. $textarea['value'] = preg_replace('/MongoDate::__set_state\(array\(\s*\'sec\' => (\d+),\s*\'usec\' => \d+,\s*\)\)/m',
  2452. 'new MongoDate($1)', $textarea['value']);
  2453. echo $html->div($form->textarea($textarea)
  2454. . $form->hidden(array('name' => 'action', 'value' => 'editObject')));
  2455. echo $html->div($form->hidden(array('name' => 'db', 'value' => get::htmlentities($db)))
  2456. . $form->submit(array('value' => 'Save Changes', 'class' => 'ui-state-hover')));
  2457. echo $form->close();
  2458. echo $html->jsInline('$("textarea[name=object]").css({"min-width": "750px", "max-width": "1250px", '
  2459. . '"min-height": "450px", "max-height": "2000px", "width": "auto", "height": "auto"}).resizable();
  2460. ' . $dbcollnavJs);
  2461. } else if (isset($mo->mongo['getStats'])) {
  2462. echo $html->drillDownList($mo->mongo['getStats']);
  2463. }
  2464. echo '</div>'; //end of bodycontent
  2465. echo $html->footer();