PageRenderTime 58ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 1ms

/modx/core/model/modx/modx.class.php

https://bitbucket.org/argnist/mohana
PHP | 2187 lines | 1327 code | 101 blank | 759 comment | 321 complexity | 120bc3dd08dc7533b9e740b45bad4036 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, LGPL-2.1, GPL-2.0, GPL-3.0, LGPL-2.0

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /*
  3. * MODX Revolution
  4. *
  5. * Copyright 2006-2011 by MODX, LLC.
  6. * All rights reserved.
  7. *
  8. * This program is free software; you can redistribute it and/or modify it under
  9. * the terms of the GNU General Public License as published by the Free Software
  10. * Foundation; either version 2 of the License, or (at your option) any later
  11. * version.
  12. *
  13. * This program is distributed in the hope that it will be useful, but WITHOUT
  14. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
  15. * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
  16. * details.
  17. *
  18. * You should have received a copy of the GNU General Public License along with
  19. * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
  20. * Place, Suite 330, Boston, MA 02111-1307 USA
  21. *
  22. */
  23. /**
  24. * This is the main file to include in your scripts to use MODX.
  25. *
  26. * For detailed information on using this class, see {@tutorial modx/modx.pkg}.
  27. *
  28. * @package modx
  29. */
  30. /* fix for PHP float bug: http://bugs.php.net/bug.php?id=53632 (php 4 <= 4.4.9 and php 5 <= 5.3.4) */
  31. if (strstr(str_replace('.','',serialize(array_merge($_GET, $_POST, $_COOKIE))), '22250738585072011')) {
  32. header('Status: 422 Unprocessable Entity');
  33. die();
  34. }
  35. if (!defined('MODX_CORE_PATH')) {
  36. define('MODX_CORE_PATH', dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR);
  37. }
  38. if (!defined('MODX_CONFIG_KEY')) {
  39. define('MODX_CONFIG_KEY', 'config');
  40. }
  41. require_once (MODX_CORE_PATH . 'xpdo/xpdo.class.php');
  42. /**
  43. * This is the MODX gateway class.
  44. *
  45. * It can be used to interact with the MODX framework and serves as a front
  46. * controller for handling requests to the virtual resources managed by the MODX
  47. * Content Management Framework.
  48. *
  49. * @package modx
  50. */
  51. class modX extends xPDO {
  52. const LOG_LEVEL_FATAL = 0;
  53. const LOG_LEVEL_ERROR = 1;
  54. const LOG_LEVEL_WARN = 2;
  55. const LOG_LEVEL_INFO = 3;
  56. const LOG_LEVEL_DEBUG = 4;
  57. const SESSION_STATE_UNAVAILABLE = -1;
  58. const SESSION_STATE_UNINITIALIZED = 0;
  59. const SESSION_STATE_INITIALIZED = 1;
  60. const SESSION_STATE_EXTERNAL = 2;
  61. /**
  62. * @var modContext The Context represents a unique section of the site which
  63. * this modX instance is controlling.
  64. */
  65. public $context= null;
  66. /**
  67. * @var array An array of secondary contexts loaded on demand.
  68. */
  69. public $contexts= array();
  70. /**
  71. * @var modRequest Represents a web request and provides helper methods for
  72. * dealing with request parameters and other attributes of a request.
  73. */
  74. public $request= null;
  75. /**
  76. * @var modResponse Represents a web response, providing helper methods for
  77. * managing response header attributes and the body containing the content of
  78. * the response.
  79. */
  80. public $response= null;
  81. /**
  82. * @var modParser The modParser registered for this modX instance,
  83. * responsible for content tag parsing, and loaded only on demand.
  84. */
  85. public $parser= null;
  86. /**
  87. * @var array An array of supplemental service classes for this modX instance.
  88. */
  89. public $services= array ();
  90. /**
  91. * @var array A listing of site Resources and Context-specific meta data.
  92. */
  93. public $resourceListing= null;
  94. /**
  95. * @var array A hierarchy map of Resources.
  96. */
  97. public $resourceMap= null;
  98. /**
  99. * @var array A lookup listing of Resource alias values and associated
  100. * Resource Ids
  101. */
  102. public $aliasMap= null;
  103. /**
  104. * @var modSystemEvent The current event being handled by modX.
  105. */
  106. public $event= null;
  107. /**
  108. * @var array A map of elements registered to specific events.
  109. */
  110. public $eventMap= null;
  111. /**
  112. * @var array A map of actions registered to the manager interface.
  113. */
  114. public $actionMap= null;
  115. /**
  116. * @var array A map of already processed Elements.
  117. */
  118. public $elementCache= array ();
  119. /**
  120. * @var array An array of key=> value pairs that can be used by any Resource
  121. * or Element.
  122. */
  123. public $placeholders= array ();
  124. /**
  125. * @var modResource An instance of the current modResource controlling the
  126. * request.
  127. */
  128. public $resource= null;
  129. /**
  130. * @var string The preferred Culture key for the current request.
  131. */
  132. public $cultureKey= '';
  133. /**
  134. * @var modLexicon Represents a localized dictionary of common words and phrases.
  135. */
  136. public $lexicon= null;
  137. /**
  138. * @var modUser The current user object, if one is authenticated for the
  139. * current request and context.
  140. */
  141. public $user= null;
  142. /**
  143. * @var array Represents the modContentType instances that can be delivered
  144. * by this modX deployment.
  145. */
  146. public $contentTypes= null;
  147. /**
  148. * @var mixed The resource id or alias being requested.
  149. */
  150. public $resourceIdentifier= null;
  151. /**
  152. * @var string The method to use to locate the Resource, 'id' or 'alias'.
  153. */
  154. public $resourceMethod= null;
  155. /**
  156. * @var boolean Indicates if the resource was generated during this request.
  157. */
  158. public $resourceGenerated= false;
  159. /**
  160. * @var array Version information for this MODX deployment.
  161. */
  162. public $version= null;
  163. /**
  164. * @var boolean Indicates if modX has been successfully initialized for a
  165. * modContext.
  166. */
  167. protected $_initialized= false;
  168. /**
  169. * @var array An array of javascript content to be inserted into the HEAD
  170. * of an HTML resource.
  171. */
  172. public $sjscripts= array ();
  173. /**
  174. * @var array An array of javascript content to be inserted into the BODY
  175. * of an HTML resource.
  176. */
  177. public $jscripts= array ();
  178. public $loadedjscripts= array ();
  179. /**
  180. * @var string Stores the virtual path for a request to MODX if the
  181. * friendly_alias_paths option is enabled.
  182. */
  183. public $virtualDir;
  184. /**
  185. * @var object An error_handler for the modX instance.
  186. */
  187. public $errorHandler= null;
  188. /**
  189. * @var array An array of regex patterns regulary cleansed from content.
  190. */
  191. public $sanitizePatterns = array(
  192. 'scripts' => '@<script[^>]*?>.*?</script>@si',
  193. 'entities' => '@&#(\d+);@e',
  194. 'tags' => '@\[\[(.[^\[\[]*?)\]\]@si',
  195. );
  196. /**
  197. * @var integer An integer representing the session state of modX.
  198. */
  199. protected $_sessionState= modX::SESSION_STATE_UNINITIALIZED;
  200. /**
  201. * @var array A config array that stores the bootstrap settings.
  202. */
  203. protected $_config= null;
  204. /**
  205. * @var array A config array that stores the system-wide settings.
  206. */
  207. public $_systemConfig= null;
  208. /**
  209. * @var array A config array that stores the user settings.
  210. */
  211. public $_userConfig= array();
  212. protected $_logSequence= 0;
  213. public $pluginCache= array();
  214. public $sourceCache= array(
  215. 'modChunk' => array()
  216. ,'modSnippet' => array()
  217. ,'modTemplateVar' => array()
  218. );
  219. /**#@+
  220. * @deprecated
  221. */
  222. public $Event= null;
  223. public $documentOutput= null;
  224. public $stopOnNotice= false;
  225. /**#@-*/
  226. /**
  227. * Harden the environment against common security flaws.
  228. *
  229. * @static
  230. */
  231. public static function protect() {
  232. if (isset ($_SERVER['QUERY_STRING']) && strpos(urldecode($_SERVER['QUERY_STRING']), chr(0)) !== false) die();
  233. if (@ ini_get('register_globals') && isset ($_REQUEST)) {
  234. while (list($key, $value)= each($_REQUEST)) {
  235. $GLOBALS[$key] = null;
  236. unset ($GLOBALS[$key]);
  237. }
  238. }
  239. $targets= array ('PHP_SELF', 'HTTP_USER_AGENT', 'HTTP_REFERER', 'QUERY_STRING');
  240. foreach ($targets as $target) {
  241. $_SERVER[$target] = isset ($_SERVER[$target]) ? htmlspecialchars($_SERVER[$target], ENT_QUOTES) : null;
  242. }
  243. }
  244. /**
  245. * Sanitize values of an array using regular expression patterns.
  246. *
  247. * @static
  248. * @param array $target The target array to sanitize.
  249. * @param array|string $patterns A regular expression pattern, or array of
  250. * regular expression patterns to apply to all values of the target.
  251. * @param integer $depth The maximum recursive depth to sanitize if the
  252. * target contains values that are arrays.
  253. * @return array The sanitized array.
  254. */
  255. public static function sanitize(array &$target, array $patterns= array(), $depth= 3, $nesting= 10) {
  256. while (list($key, $value)= each($target)) {
  257. if (is_array($value) && $depth > 0) {
  258. modX :: sanitize($value, $patterns, $depth-1);
  259. } elseif (is_string($value)) {
  260. if (!empty($patterns)) {
  261. foreach ($patterns as $pattern) {
  262. $nesting = ((integer) $nesting ? (integer) $nesting : 10);
  263. $iteration = 1;
  264. while ($iteration <= $nesting && preg_match($pattern, $value)) {
  265. $value= preg_replace($pattern, '', $value);
  266. $iteration++;
  267. }
  268. }
  269. }
  270. if (get_magic_quotes_gpc()) {
  271. $target[$key]= stripslashes($value);
  272. } else {
  273. $target[$key]= $value;
  274. }
  275. }
  276. }
  277. return $target;
  278. }
  279. /**
  280. * Sanitizes a string
  281. *
  282. * @param string $str The string to sanitize
  283. * @param array $chars An array of chars to remove
  284. * @param string $allowedTags A list of tags to allow.
  285. * @return string The sanitized string.
  286. */
  287. public function sanitizeString($str,$chars = array('/',"'",'"','(',')',';','>','<'),$allowedTags = '') {
  288. $str = str_replace($chars,'',strip_tags($str,$allowedTags));
  289. return preg_replace("/[^A-Za-z0-9_\-\.\/]/",'',$str);
  290. }
  291. /**
  292. * Turn an associative array into a valid query string.
  293. *
  294. * @static
  295. * @param array $parameters An associative array of parameters.
  296. * @return string A valid query string representing the parameters.
  297. */
  298. public static function toQueryString(array $parameters = array()) {
  299. $qs = array();
  300. foreach ($parameters as $paramKey => $paramVal) {
  301. $qs[] = urlencode($paramKey) . '=' . urlencode($paramVal);
  302. }
  303. return implode('&', $qs);
  304. }
  305. /**
  306. * Construct a new modX instance.
  307. *
  308. * @param string $configPath An absolute filesystem path to look for the config file.
  309. * @param array $options Options that can be passed to the instance.
  310. * @return modX A new modX instance.
  311. */
  312. public function __construct($configPath= '', array $options = array()) {
  313. global $database_dsn, $database_user, $database_password, $config_options, $table_prefix, $site_id, $uuid;
  314. modX :: protect();
  315. if (empty ($configPath)) {
  316. $configPath= MODX_CORE_PATH . 'config/';
  317. }
  318. if (@ include ($configPath . MODX_CONFIG_KEY . '.inc.php')) {
  319. $cachePath= MODX_CORE_PATH . 'cache/';
  320. if (MODX_CONFIG_KEY !== 'config') $cachePath .= MODX_CONFIG_KEY . '/';
  321. $options = array_merge(
  322. array (
  323. xPDO::OPT_CACHE_KEY => 'default',
  324. xPDO::OPT_CACHE_HANDLER => 'xPDOFileCache',
  325. xPDO::OPT_CACHE_PATH => $cachePath,
  326. xPDO::OPT_TABLE_PREFIX => $table_prefix,
  327. xPDO::OPT_HYDRATE_FIELDS => true,
  328. xPDO::OPT_HYDRATE_RELATED_OBJECTS => true,
  329. xPDO::OPT_HYDRATE_ADHOC_FIELDS => true,
  330. xPDO::OPT_LOADER_CLASSES => array('modAccessibleObject'),
  331. xPDO::OPT_VALIDATOR_CLASS => 'validation.modValidator',
  332. xPDO::OPT_VALIDATE_ON_SAVE => true,
  333. 'cache_system_settings' => true,
  334. 'cache_system_settings_key' => 'system_settings'
  335. ),
  336. $config_options,
  337. $options
  338. );
  339. parent :: __construct(
  340. $database_dsn,
  341. $database_user,
  342. $database_password,
  343. $options,
  344. array (
  345. PDO::ATTR_ERRMODE => PDO::ERRMODE_SILENT,
  346. PDO::ATTR_PERSISTENT => false,
  347. )
  348. );
  349. $this->setPackage('modx', MODX_CORE_PATH . 'model/', $table_prefix);
  350. $this->setLogTarget($this->getOption('log_target', null, 'FILE'));
  351. if (!empty($site_id)) $this->site_id = $site_id;
  352. if (!empty($uuid)) $this->uuid = $uuid;
  353. } else {
  354. $this->sendError($this->getOption('error_type', null, 'unavailable'), $options);
  355. }
  356. }
  357. /**
  358. * Initializes the modX engine.
  359. *
  360. * This includes preparing the session, pre-loading some common
  361. * classes and objects, the current site and context settings, extension
  362. * packages used to override session handling, error handling, or other
  363. * initialization classes
  364. *
  365. * @param string Indicates the context to initialize.
  366. * @return void
  367. */
  368. public function initialize($contextKey= 'web') {
  369. if (!$this->_initialized) {
  370. if (!$this->startTime) {
  371. $this->startTime= $this->getMicroTime();
  372. }
  373. $this->loadClass('modAccess');
  374. $this->loadClass('modAccessibleObject');
  375. $this->loadClass('modAccessibleSimpleObject');
  376. $this->loadClass('modResource');
  377. $this->loadClass('modElement');
  378. $this->loadClass('modScript');
  379. $this->loadClass('modPrincipal');
  380. $this->loadClass('modUser');
  381. $this->getCacheManager();
  382. $this->getConfig();
  383. $this->_initContext($contextKey);
  384. $this->_loadExtensionPackages();
  385. $this->_initSession();
  386. $this->_initErrorHandler();
  387. $this->_initCulture();
  388. $this->getService('registry', 'registry.modRegistry');
  389. if (is_array ($this->config)) {
  390. $this->setPlaceholders($this->config, '+');
  391. }
  392. $this->_initialized= true;
  393. }
  394. }
  395. /**
  396. * Loads any specified extension packages
  397. */
  398. protected function _loadExtensionPackages() {
  399. $extPackages = $this->getOption('extension_packages');
  400. if (empty($extPackages)) return;
  401. $extPackages = $this->fromJSON($extPackages);
  402. if (!empty($extPackages)) {
  403. foreach ($extPackages as $extPackage) {
  404. if (!is_array($extPackage)) continue;
  405. foreach ($extPackage as $packageName => $package) {
  406. if (!empty($package) && !empty($package['path'])) {
  407. $package['tablePrefix'] = !empty($package['tablePrefix']) ? $package['tablePrefix'] : null;
  408. $package['path'] = str_replace(array(
  409. '[[++core_path]]',
  410. '[[++base_path]]',
  411. '[[++assets_path]]',
  412. '[[++manager_path]]',
  413. ),array(
  414. $this->config['core_path'],
  415. $this->config['base_path'],
  416. $this->config['assets_path'],
  417. $this->config['manager_path'],
  418. ),$package['path']);
  419. $this->addPackage($packageName,$package['path'],$package['tablePrefix']);
  420. if (!empty($package['serviceName']) && !empty($package['serviceClass'])) {
  421. $packagePath = str_replace('//','/',$package['path'].$packageName.'/');
  422. $this->getService($package['serviceName'],$package['serviceClass'],$packagePath);
  423. }
  424. }
  425. }
  426. }
  427. }
  428. }
  429. /**
  430. * Sets the debugging features of the modX instance.
  431. *
  432. * @param boolean|int $debug Boolean or bitwise integer describing the
  433. * debug state and/or PHP error reporting level.
  434. * @param boolean $stopOnNotice Indicates if processing should stop when
  435. * encountering PHP errors of type E_NOTICE.
  436. * @return boolean|int The previous value.
  437. */
  438. public function setDebug($debug= true, $stopOnNotice= false) {
  439. $oldValue= $this->getDebug();
  440. if ($debug === true) {
  441. error_reporting(-1);
  442. parent :: setDebug(true);
  443. } elseif ($debug === false) {
  444. error_reporting(0);
  445. parent :: setDebug(false);
  446. } else {
  447. error_reporting(intval($debug));
  448. parent :: setDebug(intval($debug));
  449. }
  450. $this->stopOnNotice= $stopOnNotice;
  451. return $oldValue;
  452. }
  453. /**
  454. * Get an extended xPDOCacheManager instance responsible for MODX caching.
  455. *
  456. * @return object A modCacheManager registered for this modX instance.
  457. */
  458. public function getCacheManager($class= 'cache.xPDOCacheManager', $options = array('path' => XPDO_CORE_PATH, 'ignorePkg' => true)) {
  459. if ($this->cacheManager === null) {
  460. if ($this->loadClass($class, $options['path'], $options['ignorePkg'], true)) {
  461. $cacheManagerClass= $this->getOption('modCacheManager.class', null, 'modCacheManager');
  462. if ($className= $this->loadClass($cacheManagerClass, '', false, true)) {
  463. if ($this->cacheManager= new $className ($this)) {
  464. $this->_cacheEnabled= true;
  465. }
  466. }
  467. }
  468. }
  469. return $this->cacheManager;
  470. }
  471. /**
  472. * Gets the MODX parser.
  473. *
  474. * Returns an instance of modParser responsible for parsing tags in element
  475. * content, performing actions, returning content and/or sending other responses
  476. * in the process.
  477. *
  478. * @return object The modParser for this modX instance.
  479. */
  480. public function getParser() {
  481. return $this->getService('parser', 'modParser');
  482. }
  483. /**
  484. * Gets all of the parent resource ids for a given resource.
  485. *
  486. * @param integer $id The resource id for the starting node.
  487. * @param integer $height How many levels max to search for parents (default 10).
  488. * @param array $options An array of filtering options, such as 'context' to specify the context to grab from
  489. * @return array An array of all the parent resource ids for the specified resource.
  490. */
  491. public function getParentIds($id= null, $height= 10,array $options = array()) {
  492. $parentId= 0;
  493. $parents= array ();
  494. if ($id && $height > 0) {
  495. $context = '';
  496. if (!empty($options['context'])) {
  497. $this->getContext($options['context']);
  498. $context = $options['context'];
  499. }
  500. $resourceMap = !empty($context) && !empty($this->contexts[$context]->resourceMap) ? $this->contexts[$context]->resourceMap : $this->resourceMap;
  501. foreach ($resourceMap as $parentId => $mapNode) {
  502. if (array_search($id, $mapNode) !== false) {
  503. $parents[]= $parentId;
  504. break;
  505. }
  506. }
  507. if ($parentId && !empty($parents)) {
  508. $height--;
  509. $parents= array_merge($parents, $this->getParentIds($parentId,$height,$options));
  510. }
  511. }
  512. return $parents;
  513. }
  514. /**
  515. * Gets all of the child resource ids for a given resource.
  516. *
  517. * @see getTree for hierarchical node results
  518. * @param integer $id The resource id for the starting node.
  519. * @param integer $depth How many levels max to search for children (default 10).
  520. * @param array $options An array of filtering options, such as 'context' to specify the context to grab from
  521. * @return array An array of all the child resource ids for the specified resource.
  522. */
  523. public function getChildIds($id= null, $depth= 10,array $options = array()) {
  524. $children= array ();
  525. if ($id !== null && intval($depth) >= 1) {
  526. $id= is_int($id) ? $id : intval($id);
  527. $context = '';
  528. if (!empty($options['context'])) {
  529. $this->getContext($options['context']);
  530. $context = $options['context'];
  531. }
  532. $resourceMap = !empty($context) && !empty($this->contexts[$context]->resourceMap) ? $this->contexts[$context]->resourceMap : $this->resourceMap;
  533. if (isset ($resourceMap["{$id}"])) {
  534. if ($children= $resourceMap["{$id}"]) {
  535. foreach ($children as $child) {
  536. $processDepth = $depth - 1;
  537. if ($c= $this->getChildIds($child,$processDepth,$options)) {
  538. $children= array_merge($children, $c);
  539. }
  540. }
  541. }
  542. }
  543. }
  544. return $children;
  545. }
  546. /**
  547. * Get a site tree from a single or multiple modResource instances.
  548. *
  549. * @see getChildIds for linear results
  550. * @param int|array $id A single or multiple modResource ids to build the
  551. * tree from.
  552. * @param int $depth The maximum depth to build the tree (default 10).
  553. * @return array An array containing the tree structure.
  554. */
  555. public function getTree($id= null, $depth= 10) {
  556. $tree= array ();
  557. if ($id !== null) {
  558. if (is_array ($id)) {
  559. foreach ($id as $k => $v) {
  560. $tree[$v]= $this->getTree($v, $depth);
  561. }
  562. }
  563. elseif ($branch= $this->getChildIds($id, 1)) {
  564. foreach ($branch as $key => $child) {
  565. if ($depth > 0 && $leaf= $this->getTree($child, $depth--)) {
  566. $tree[$child]= $leaf;
  567. } else {
  568. $tree[$child]= $child;
  569. }
  570. }
  571. }
  572. }
  573. return $tree;
  574. }
  575. /**
  576. * Sets a placeholder value.
  577. *
  578. * @param string $key The unique string key which identifies the
  579. * placeholder.
  580. * @param mixed $value The value to set the placeholder to.
  581. */
  582. public function setPlaceholder($key, $value) {
  583. if (is_string($key)) {
  584. $this->placeholders["{$key}"]= $value;
  585. }
  586. }
  587. /**
  588. * Sets a collection of placeholders stored in an array or as object vars.
  589. *
  590. * An optional namespace parameter can be prepended to each placeholder key in the collection,
  591. * to isolate the collection of placeholders.
  592. *
  593. * Note that unlike toPlaceholders(), this function does not add separators between the
  594. * namespace and the placeholder key. Use toPlaceholders() when working with multi-dimensional
  595. * arrays or objects with variables other than scalars so each level gets delimited by a
  596. * separator.
  597. *
  598. * @param array|object $placeholders An array of values or object to set as placeholders.
  599. * @param string $namespace A namespace prefix to prepend to each placeholder key.
  600. */
  601. public function setPlaceholders($placeholders, $namespace= '') {
  602. $this->toPlaceholders($placeholders, $namespace, '');
  603. }
  604. /**
  605. * Sets placeholders from values stored in arrays and objects.
  606. *
  607. * Each recursive level adds to the prefix, building an access path using an optional separator.
  608. *
  609. * @param array|object $subject An array or object to process.
  610. * @param string $prefix An optional prefix to be prepended to the placeholder keys. Recursive
  611. * calls prepend the parent keys.
  612. * @param string $separator A separator to place in between the prefixes and keys. Default is a
  613. * dot or period: '.'.
  614. * @param boolean $restore Set to true if you want overwritten placeholder values returned.
  615. * @return array A multi-dimensional array containing up to two elements: 'keys' which always
  616. * contains an array of placeholder keys that were set, and optionally, if the restore parameter
  617. * is true, 'restore' containing an array of placeholder values that were overwritten by the method.
  618. */
  619. public function toPlaceholders($subject, $prefix= '', $separator= '.', $restore= false) {
  620. $keys = array();
  621. $restored = array();
  622. if (is_object($subject)) {
  623. if ($subject instanceof xPDOObject) {
  624. $subject= $subject->toArray();
  625. } else {
  626. $subject= get_object_vars($subject);
  627. }
  628. }
  629. if (is_array($subject)) {
  630. foreach ($subject as $key => $value) {
  631. $rv = $this->toPlaceholder($key, $value, $prefix, $separator, $restore);
  632. if (isset($rv['keys'])) {
  633. foreach ($rv['keys'] as $rvKey) $keys[] = $rvKey;
  634. }
  635. if ($restore === true && isset($rv['restore'])) {
  636. $restored = array_merge($restored, $rv['restore']);
  637. }
  638. }
  639. }
  640. $return = array('keys' => $keys);
  641. if ($restore === true) $return['restore'] = $restored;
  642. return $return;
  643. }
  644. /**
  645. * Recursively validates and sets placeholders appropriate to the value type passed.
  646. *
  647. * @param string $key The key identifying the value.
  648. * @param mixed $value The value to set.
  649. * @param string $prefix A string prefix to prepend to the key. Recursive calls prepend the
  650. * parent keys as well.
  651. * @param string $separator A separator placed in between the prefix and the key. Default is a
  652. * dot or period: '.'.
  653. * @param boolean $restore Set to true if you want overwritten placeholder values returned.
  654. * @return array A multi-dimensional array containing up to two elements: 'keys' which always
  655. * contains an array of placeholder keys that were set, and optionally, if the restore parameter
  656. * is true, 'restore' containing an array of placeholder values that were overwritten by the method.
  657. */
  658. public function toPlaceholder($key, $value, $prefix= '', $separator= '.', $restore= false) {
  659. $return = array('keys' => array());
  660. if ($restore === true) $return['restore'] = array();
  661. if (!empty($prefix) && !empty($separator)) {
  662. $prefix .= $separator;
  663. }
  664. if (is_array($value) || is_object($value)) {
  665. $return = $this->toPlaceholders($value, "{$prefix}{$key}", $separator, $restore);
  666. } elseif (is_scalar($value)) {
  667. $return['keys'][] = "{$prefix}{$key}";
  668. if ($restore === true && array_key_exists("{$prefix}{$key}", $this->placeholders)) {
  669. $return['restore']["{$prefix}{$key}"] = $this->getPlaceholder("{$prefix}{$key}");
  670. }
  671. $this->setPlaceholder("{$prefix}{$key}", $value);
  672. }
  673. return $return;
  674. }
  675. /**
  676. * Get a placeholder value by key.
  677. *
  678. * @param string $key The key of the placeholder to a return a value from.
  679. * @return mixed The value of the requested placeholder, or an empty string if not located.
  680. */
  681. public function getPlaceholder($key) {
  682. $placeholder= null;
  683. if (is_string($key) && array_key_exists($key, $this->placeholders)) {
  684. $placeholder= & $this->placeholders["{$key}"];
  685. }
  686. return $placeholder;
  687. }
  688. /**
  689. * Unset a placeholder value by key.
  690. *
  691. * @param string $key The key of the placeholder to unset.
  692. */
  693. public function unsetPlaceholder($key) {
  694. if (is_string($key) && array_key_exists($key, $this->placeholders)) {
  695. unset($this->placeholders[$key]);
  696. }
  697. }
  698. /**
  699. * Unset multiple placeholders, either by prefix or an array of keys.
  700. *
  701. * @param string|array $keys A string prefix or an array of keys indicating
  702. * the placeholders to unset.
  703. */
  704. public function unsetPlaceholders($keys) {
  705. if (is_array($keys)) {
  706. foreach ($keys as $key) {
  707. if (is_string($key)) $this->unsetPlaceholder($key);
  708. if (is_array($key)) $this->unsetPlaceholders($key);
  709. }
  710. } elseif (is_string($keys)) {
  711. $placeholderKeys = array_keys($this->placeholders);
  712. foreach ($placeholderKeys as $key) {
  713. if (strpos($key, $keys) === 0) $this->unsetPlaceholder($key);
  714. }
  715. }
  716. }
  717. /**
  718. * Generates a URL representing a specified resource.
  719. *
  720. * @param integer $id The id of a resource.
  721. * @param string $context Specifies a context to limit URL generation to.
  722. * @param string $args A query string to append to the generated URL.
  723. * @param mixed $scheme The scheme indicates in what format the URL is generated.<br>
  724. * <pre>
  725. * -1 : (default value) URL is relative to site_url
  726. * 0 : see http
  727. * 1 : see https
  728. * full : URL is absolute, prepended with site_url from config
  729. * abs : URL is absolute, prepended with base_url from config
  730. * http : URL is absolute, forced to http scheme
  731. * https : URL is absolute, forced to https scheme
  732. * </pre>
  733. * @return string The URL for the resource.
  734. */
  735. public function makeUrl($id, $context= '', $args= '', $scheme= -1) {
  736. $url= '';
  737. if ($validid = intval($id)) {
  738. $id = $validid;
  739. if ($context == '' || $this->context->get('key') == $context) {
  740. $url= $this->context->makeUrl($id, $args, $scheme);
  741. }
  742. if (empty($url) && ($context !== $this->context->get('key'))) {
  743. $ctx= null;
  744. if ($context == '') {
  745. if ($results = $this->query("SELECT context_key FROM " . $this->getTableName('modResource') . " WHERE id = {$id}")) {
  746. $contexts= $results->fetchAll(PDO::FETCH_COLUMN);
  747. if ($contextKey = reset($contexts)) {
  748. $ctx = $this->getContext($contextKey);
  749. }
  750. }
  751. } else {
  752. $ctx = $this->getContext($context);
  753. }
  754. if ($ctx) {
  755. $url= $ctx->makeUrl($id, $args, 'full');
  756. }
  757. }
  758. if (!empty($url) && $this->getOption('xhtml_urls',null,false)) {
  759. $url= preg_replace("/&(?!amp;)/","&amp;", $url);
  760. }
  761. } else {
  762. $this->log(modX::LOG_LEVEL_ERROR, '`' . $id . '` is not a valid integer and may not be passed to makeUrl()');
  763. }
  764. return $url;
  765. }
  766. /**
  767. * Send the user to a type-specific core error page and halt PHP execution.
  768. *
  769. * @param string $type The type of error to present.
  770. * @param array $options An array of options to provide for the error file.
  771. */
  772. public function sendError($type = '', $options = array()) {
  773. if (!is_string($type) || empty($type)) $type = $this->getOption('error_type', $options, 'unavailable');
  774. while (@ob_end_clean()) {}
  775. if (file_exists(MODX_CORE_PATH . "error/{$type}.include.php")) {
  776. @include(MODX_CORE_PATH . "error/{$type}.include.php");
  777. }
  778. header($this->getOption('error_header', $options, 'HTTP/1.1 503 Service Unavailable'));
  779. $errorPageTitle = $this->getOption('error_pagetitle', $options, 'Error 503: Site temporarily unavailable');
  780. $errorMessage = $this->getOption('error_message', $options, '<h1>' . $this->getOption('site_name', $options, 'Error 503') . '</h1><p>Site temporarily unavailable.</p>');
  781. echo "<html><head><title>{$errorPageTitle}</title></head><body>{$errorMessage}</body></html>";
  782. @session_write_close();
  783. exit();
  784. }
  785. /**
  786. * Sends a redirect to the specified URL using the specified options.
  787. *
  788. * Valid 'type' option values include:
  789. * REDIRECT_REFRESH Uses the header refresh method
  790. * REDIRECT_META Sends a a META HTTP-EQUIV="Refresh" tag to the output
  791. * REDIRECT_HEADER Uses the header location method
  792. *
  793. * REDIRECT_HEADER is the default.
  794. *
  795. * @param string $url The URL to redirect the client browser to.
  796. * @param array|boolean $options An array of options for the redirect OR
  797. * indicates if redirect attempts should be counted and limited to 3 (latter is deprecated
  798. * usage; use count_attempts in options array).
  799. * @param string $type The type of redirection to attempt (deprecated, use type in
  800. * options array).
  801. * @param string $responseCode The type of HTTP response code HEADER to send for the
  802. * redirect (deprecated, use responseCode in options array)
  803. */
  804. public function sendRedirect($url, $options= false, $type= '', $responseCode = '') {
  805. if (!$this->getResponse()) {
  806. $this->log(modX::LOG_LEVEL_FATAL, "Could not load response class.");
  807. }
  808. $this->response->sendRedirect($url, $options, $type, $responseCode);
  809. }
  810. /**
  811. * Forwards the request to another resource without changing the URL.
  812. *
  813. * @param integer $id The resource identifier.
  814. * @param string $options An array of options for the process.
  815. */
  816. public function sendForward($id, $options = null) {
  817. if (!$this->getRequest()) {
  818. $this->log(modX::LOG_LEVEL_FATAL, "Could not load request class.");
  819. }
  820. $idInt = intval($id);
  821. if (is_string($options) && !empty($options)) {
  822. $options = array('response_code' => $options);
  823. } elseif (!is_array($options)) {
  824. $options = array();
  825. }
  826. $this->elementCache = array();
  827. if ($idInt > 0) {
  828. $merge = array_key_exists('merge', $options) && !empty($options['merge']);
  829. $currentResource = array();
  830. if ($merge) {
  831. $excludes = array_merge(
  832. explode(',', $this->getOption('forward_merge_excludes', $options, 'type,published,class_key,context_key')),
  833. array(
  834. 'content'
  835. ,'pub_date'
  836. ,'unpub_date'
  837. ,'richtext'
  838. ,'_content'
  839. ,'_processed'
  840. )
  841. );
  842. reset($this->resource->_fields);
  843. while (list($fkey, $fval) = each($this->resource->_fields)) {
  844. if (!in_array($fkey, $excludes)) {
  845. if (is_scalar($fval) && $fval !== '') {
  846. $currentResource[$fkey] = $fval;
  847. } elseif (is_array($fval) && count($fval) === 5 && $fval[1] !== '') {
  848. $currentResource[$fkey] = $fval;
  849. }
  850. }
  851. }
  852. }
  853. $this->resource= $this->request->getResource('id', $idInt, array('forward' => true));
  854. if ($this->resource) {
  855. if ($merge && !empty($currentResource)) {
  856. $this->resource->_fields = array_merge($this->resource->_fields, $currentResource);
  857. $this->elementCache = array();
  858. unset($currentResource);
  859. }
  860. $this->resourceIdentifier= $this->resource->get('id');
  861. $this->resourceMethod= 'id';
  862. if (isset($options['response_code']) && !empty($options['response_code'])) {
  863. header($options['response_code']);
  864. }
  865. $this->request->prepareResponse();
  866. exit();
  867. }
  868. $options= array_merge(
  869. array(
  870. 'error_type' => '404'
  871. ,'error_header' => $this->getOption('error_page_header', $options,'HTTP/1.1 404 Not Found')
  872. ,'error_pagetitle' => $this->getOption('error_page_pagetitle', $options,'Error 404: Page not found')
  873. ,'error_message' => $this->getOption('error_page_message', $options,'<h1>Page not found</h1><p>The page you requested was not found.</p>')
  874. ),
  875. $options
  876. );
  877. }
  878. $this->sendError($id, $options);
  879. }
  880. /**
  881. * Send the user to a MODX virtual error page.
  882. *
  883. * @uses invokeEvent() The OnPageNotFound event is invoked before the error page is forwarded
  884. * to.
  885. * @param array $options An array of options to provide for the OnPageNotFound event and error
  886. * page.
  887. */
  888. public function sendErrorPage($options = null) {
  889. if (!is_array($options)) $options = array();
  890. $options= array_merge(
  891. array(
  892. 'response_code' => $this->getOption('error_page_header', $options, 'HTTP/1.1 404 Not Found')
  893. ,'error_type' => '404'
  894. ,'error_header' => $this->getOption('error_page_header', $options, 'HTTP/1.1 404 Not Found')
  895. ,'error_pagetitle' => $this->getOption('error_page_pagetitle', $options, 'Error 404: Page not found')
  896. ,'error_message' => $this->getOption('error_page_message', $options, '<h1>Page not found</h1><p>The page you requested was not found.</p>')
  897. ),
  898. $options
  899. );
  900. $this->invokeEvent('OnPageNotFound', $options);
  901. $this->sendForward($this->getOption('error_page', $options, '404'), $options);
  902. }
  903. /**
  904. * Send the user to the MODX unauthorized page.
  905. *
  906. * @uses invokeEvent() The OnPageUnauthorized event is invoked before the unauthorized page is
  907. * forwarded to.
  908. * @param array $options An array of options to provide for the OnPageUnauthorized
  909. * event and unauthorized page.
  910. */
  911. public function sendUnauthorizedPage($options = null) {
  912. if (!is_array($options)) $options = array();
  913. $options= array_merge(
  914. array(
  915. 'response_code' => $this->getOption('unauthorized_page_header' ,$options ,'HTTP/1.1 401 Unauthorized')
  916. ,'error_type' => '401'
  917. ,'error_header' => $this->getOption('unauthorized_page_header', $options,'HTTP/1.1 401 Unauthorized')
  918. ,'error_pagetitle' => $this->getOption('unauthorized_page_pagetitle',$options, 'Error 401: Unauthorized')
  919. ,'error_message' => $this->getOption('unauthorized_page_message', $options,'<h1>Unauthorized</h1><p>You are not authorized to view the requested content.</p>')
  920. ),
  921. $options
  922. );
  923. $this->invokeEvent('OnPageUnauthorized', $options);
  924. $this->sendForward($this->getOption('unauthorized_page', $options, '401'), $options);
  925. }
  926. /**
  927. * Get the current authenticated User and assigns it to the modX instance.
  928. *
  929. * @param string $contextKey An optional context to get the user from.
  930. * @param boolean $forceLoadSettings If set to true, will load settings
  931. * regardless of whether the user has an authenticated context or not.
  932. * @return modUser The user object authenticated for the request.
  933. */
  934. public function getUser($contextKey= '',$forceLoadSettings = false) {
  935. if ($contextKey == '') {
  936. if ($this->context !== null) {
  937. $contextKey= $this->context->get('key');
  938. }
  939. }
  940. if ($this->user === null || !is_object($this->user)) {
  941. $this->user= $this->getAuthenticatedUser($contextKey);
  942. if ($contextKey !== 'mgr' && !$this->user) {
  943. $this->user= $this->getAuthenticatedUser('mgr');
  944. }
  945. }
  946. if ($this->user !== null && is_object($this->user)) {
  947. if ($this->user->hasSessionContext($contextKey) || $forceLoadSettings) {
  948. if (isset ($_SESSION["modx.{$contextKey}.user.config"])) {
  949. $this->_userConfig= $_SESSION["modx.{$contextKey}.user.config"];
  950. } else {
  951. $settings= $this->user->getMany('UserSettings');
  952. if (is_array($settings) && !empty ($settings)) {
  953. foreach ($settings as $setting) {
  954. $v= $setting->get('value');
  955. $matches= array();
  956. if (preg_match_all('~\{(.*?)\}~', $v, $matches, PREG_SET_ORDER)) {
  957. $matchValue= '';
  958. foreach ($matches as $match) {
  959. if (isset ($this->config["{$match[1]}"])) {
  960. $matchValue= $this->config["{$match[1]}"];
  961. } else {
  962. $matchValue= '';
  963. }
  964. $v= str_replace($match[0], $matchValue, $v);
  965. }
  966. }
  967. $this->_userConfig[$setting->get('key')]= $v;
  968. }
  969. }
  970. }
  971. if (is_array ($this->_userConfig) && !empty ($this->_userConfig)) {
  972. $_SESSION["modx.{$contextKey}.user.config"]= $this->_userConfig;
  973. $this->config= array_merge($this->config, $this->_userConfig);
  974. }
  975. }
  976. } else {
  977. $this->user = $this->newObject('modUser', array(
  978. 'id' => 0,
  979. 'username' => '(anonymous)'
  980. )
  981. );
  982. }
  983. ksort($this->config);
  984. $this->toPlaceholders($this->user->get(array('id','username')),'modx.user');
  985. return $this->user;
  986. }
  987. /**
  988. * Gets the user authenticated in the specified context.
  989. *
  990. * @param string $contextKey Optional context key; uses current context by default.
  991. * @return modUser|null The user object that is authenticated in the specified context,
  992. * or null if no user is authenticated.
  993. */
  994. public function getAuthenticatedUser($contextKey= '') {
  995. $user= null;
  996. if ($contextKey == '') {
  997. if ($this->context !== null) {
  998. $contextKey= $this->context->get('key');
  999. }
  1000. }
  1001. if ($contextKey && isset ($_SESSION['modx.user.contextTokens'][$contextKey])) {
  1002. $user= $this->getObject('modUser', intval($_SESSION['modx.user.contextTokens'][$contextKey]), true);
  1003. if ($user) {
  1004. $user->getSessionContexts();
  1005. }
  1006. }
  1007. return $user;
  1008. }
  1009. /**
  1010. * Checks to see if the user has a session in the specified context.
  1011. *
  1012. * @param string $sessionContext The context to test for a session key in.
  1013. * @return boolean True if the user is valid in the context specified.
  1014. */
  1015. public function checkSession($sessionContext= 'web') {
  1016. $hasSession = false;
  1017. if ($this->user !== null) {
  1018. $hasSession = $this->user->hasSessionContext($sessionContext);
  1019. }
  1020. return $hasSession;
  1021. }
  1022. /**
  1023. * Gets the modX core version data.
  1024. *
  1025. * @return array The version data loaded from the config version file.
  1026. */
  1027. public function getVersionData() {
  1028. if ($this->version === null) {
  1029. $this->version= @ include_once MODX_CORE_PATH . "docs/version.inc.php";
  1030. }
  1031. return $this->version;
  1032. }
  1033. /**
  1034. * Reload the config settings.
  1035. *
  1036. * @return array An associative array of configuration key/values
  1037. */
  1038. public function reloadConfig() {
  1039. $this->getCacheManager();
  1040. $this->cacheManager->refresh();
  1041. if (!$this->_loadConfig()) {
  1042. $this->log(modX::LOG_LEVEL_ERROR, 'Could not reload core MODX configuration!');
  1043. }
  1044. return $this->config;
  1045. }
  1046. /**
  1047. * Get the configuration for the site.
  1048. *
  1049. * @return array An associate array of configuration key/values
  1050. */
  1051. public function getConfig() {
  1052. if (!$this->_initialized || !is_array($this->config) || empty ($this->config)) {
  1053. if (!isset ($this->config['base_url']))
  1054. $this->config['base_url']= MODX_BASE_URL;
  1055. if (!isset ($this->config['base_path']))
  1056. $this->config['base_path']= MODX_BASE_PATH;
  1057. if (!isset ($this->config['core_path']))
  1058. $this->config['core_path']= MODX_CORE_PATH;
  1059. if (!isset ($this->config['url_scheme']))
  1060. $this->config['url_scheme']= MODX_URL_SCHEME;
  1061. if (!isset ($this->config['http_host']))
  1062. $this->config['http_host']= MODX_HTTP_HOST;
  1063. if (!isset ($this->config['site_url']))
  1064. $this->config['site_url']= MODX_SITE_URL;
  1065. if (!isset ($this->config['manager_path']))
  1066. $this->config['manager_path']= MODX_MANAGER_PATH;
  1067. if (!isset ($this->config['manager_url']))
  1068. $this->config['manager_url']= MODX_MANAGER_URL;
  1069. if (!isset ($this->config['assets_path']))
  1070. $this->config['assets_path']= MODX_ASSETS_PATH;
  1071. if (!isset ($this->config['assets_url']))
  1072. $this->config['assets_url']= MODX_ASSETS_URL;
  1073. if (!isset ($this->config['connectors_path']))
  1074. $this->config['connectors_path']= MODX_CONNECTORS_PATH;
  1075. if (!isset ($this->config['connectors_url']))
  1076. $this->config['connectors_url']= MODX_CONNECTORS_URL;
  1077. if (!isset ($this->config['processors_path']))
  1078. $this->config['processors_path']= MODX_PROCESSORS_PATH;
  1079. if (!isset ($this->config['request_param_id']))
  1080. $this->config['request_param_id']= 'id';
  1081. if (!isset ($this->config['request_param_alias']))
  1082. $this->config['request_param_alias']= 'q';
  1083. if (!isset ($this->config['https_port']))
  1084. $this->config['https_port']= isset($GLOBALS['https_port']) ? $GLOBALS['https_port'] : 443;
  1085. if (!isset ($this->config['error_handler_class']))
  1086. $this->config['error_handler_class']= 'error.modErrorHandler';
  1087. $this->_config= $this->config;
  1088. if (!$this->_loadConfig()) {
  1089. $this->log(modX::LOG_LEVEL_FATAL, "Could not load core MODX configuration!");
  1090. return null;
  1091. }
  1092. }
  1093. return $this->config;
  1094. }
  1095. /**
  1096. * Initialize, cleanse, and process a request made to a modX site.
  1097. *
  1098. * @return mixed The result of the request handler.
  1099. */
  1100. public function handleRequest() {
  1101. if ($this->getRequest()) {
  1102. return $this->request->handleRequest();
  1103. }
  1104. }
  1105. /**
  1106. * Attempt to load the request handler class, if not already loaded.
  1107. *
  1108. * @access public
  1109. * @param $string The class name of the response class to load. Defaults to
  1110. * modRequest; is ignored if the Setting "modRequest.class" is set.
  1111. * @param $path The absolute path by which to load the response class from.
  1112. * Defaults to the current MODX model path.
  1113. * @return boolean Returns true if a valid request handler object was
  1114. * loaded on this or any previous call to the function, false otherwise.
  1115. */
  1116. public function getRequest($class= 'modRequest', $path= '') {
  1117. if ($this->request === null || !($this->request instanceof modRequest)) {
  1118. $requestClass = $this->getOption('modRequest.class',$this->config,$class);
  1119. if ($className= $this->loadClass($requestClass, $path, !empty($path), true))
  1120. $this->request= new $className ($this);
  1121. }
  1122. return is_object($this->request) && $this->request instanceof modRequest;
  1123. }
  1124. /**
  1125. * Attempt to load the response handler class, if not already loaded.
  1126. *
  1127. * @access public
  1128. * @param $string The class name of the response class to load. Defaults to
  1129. * modResponse; is ignored if the Setting "modResponse.class" is set.
  1130. * @param $path The absolute path by which to load the response class from.
  1131. * Defaults to the current MODX model path.
  1132. * @return boolean Returns true if a valid response handler object was
  1133. * loaded on this or any previous call to the function, false otherwise.
  1134. */
  1135. public function getResponse($class= 'modResponse', $path= '') {
  1136. $responseClass= $this->getOption('modResponse.class',$this->config,$class);
  1137. $className= $this->loadClass($responseClass, $path, !empty($path), true);
  1138. if ($this->response === null || !($this->response instanceof $className)) {
  1139. if ($className) $this->response= new $className ($this);
  1140. }
  1141. return $this->response instanceof $className;
  1142. }
  1143. /**
  1144. * Register CSS to be injected inside the HEAD tag of a resource.
  1145. *
  1146. * @param string $src The CSS to be injected before the closing HEAD tag in
  1147. * an HTML response.
  1148. */
  1149. public function regClientCSS($src) {
  1150. if (isset ($this->loadedjscripts[$src]) && $this->loadedjscripts[$src]) {
  1151. return '';
  1152. }
  1153. $this->loadedjscripts[$src]= true;
  1154. if (strpos(strtolower($src), "<style") !== false || strpos(strtolower($src), "<link") !== false) {
  1155. $this->sjscripts[count($this->sjscripts)]= $src;
  1156. } else {
  1157. $this->sjscripts[count($this->sjscripts)]= '<link rel="stylesheet" href="' . $src . '" type="text/css" />';
  1158. }
  1159. }
  1160. /**
  1161. * Register JavaScript to be injected inside the HEAD tag of a resource.
  1162. *
  1163. * @param string $src The JavaScript to be injected before the closing HEAD
  1164. * tag of an HTML response.
  1165. * @param boolean $plaintext Optional param to treat th…

Large files files are truncated, but you can click here to view the full file