PageRenderTime 66ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/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
  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 the $src as plaintext
  1166. * rather than assuming it is JavaScript.
  1167. */
  1168. public function regClientStartupScript($src, $plaintext= false) {
  1169. if (!empty ($src) && !array_key_exists($src, $this->loadedjscripts)) {
  1170. if (isset ($this->loadedjscripts[$src]))
  1171. return '';
  1172. $this->loadedjscripts[$src]= true;
  1173. if ($plaintext == true) {
  1174. $this->sjscripts[count($this->sjscripts)]= $src;
  1175. } elseif (strpos(strtolower($src), "<script") !== false) {
  1176. $this->sjscripts[count($this->sjscripts)]= $src;
  1177. } else {
  1178. $this->sjscripts[count($this->sjscripts)]= '<script type="text/javascript" src="' . $src . '"></script>';
  1179. }
  1180. }
  1181. }
  1182. /**
  1183. * Register JavaScript to be injected before the closing BODY tag.
  1184. *
  1185. * @param string $src The JavaScript to be injected before the closing BODY
  1186. * tag in an HTML response.
  1187. * @param boolean $plaintext Optional param to treat the $src as plaintext
  1188. * rather than assuming it is JavaScript.
  1189. */
  1190. public function regClientScript($src, $plaintext= false) {
  1191. if (isset ($this->loadedjscripts[$src]))
  1192. return '';
  1193. $this->loadedjscripts[$src]= true;
  1194. if ($plaintext == true) {
  1195. $this->jscripts[count($this->jscripts)]= $src;
  1196. } elseif (strpos(strtolower($src), "<script") !== false) {
  1197. $this->jscripts[count($this->jscripts)]= $src;
  1198. } else {
  1199. $this->jscripts[count($this->jscripts)]= '<script type="text/javascript" src="' . $src . '"></script>';
  1200. }
  1201. }
  1202. /**
  1203. * Register HTML to be injected before the closing HEAD tag.
  1204. *
  1205. * @param string $html The HTML to be injected.
  1206. */
  1207. public function regClientStartupHTMLBlock($html) {
  1208. return $this->regClientStartupScript($html, true);
  1209. }
  1210. /**
  1211. * Register HTML to be injected before the closing BODY tag.
  1212. *
  1213. * @param string $html The HTML to be injected.
  1214. */
  1215. public function regClientHTMLBlock($html) {
  1216. return $this->regClientScript($html, true);
  1217. }
  1218. /**
  1219. * Returns all registered JavaScripts.
  1220. *
  1221. * @access public
  1222. * @return string The parsed HTML of the client scripts.
  1223. */
  1224. public function getRegisteredClientScripts() {
  1225. $string= '';
  1226. if (is_array($this->jscripts)) {
  1227. $string= implode("\n",$this->jscripts);
  1228. }
  1229. return $string;
  1230. }
  1231. /**
  1232. * Returns all registered startup CSS, JavaScript, or HTML blocks.
  1233. *
  1234. * @access public
  1235. * @return string The parsed HTML of the startup scripts.
  1236. */
  1237. public function getRegisteredClientStartupScripts() {
  1238. $string= '';
  1239. if (is_array ($this->sjscripts)) {
  1240. $string= implode("\n", $this->sjscripts);
  1241. }
  1242. return $string;
  1243. }
  1244. /**
  1245. * Invokes a specified Event with an optional array of parameters.
  1246. *
  1247. * @access public
  1248. * @param string $eventName Name of an event to invoke.
  1249. * @param array $params Optional params provided to the elements registered with an event.
  1250. * @todo refactor this completely, yuck!!
  1251. */
  1252. public function invokeEvent($eventName, array $params= array ()) {
  1253. if (!$eventName)
  1254. return false;
  1255. if ($this->eventMap === null)
  1256. $this->_initEventMap($this->context->get('key'));
  1257. if (!isset ($this->eventMap[$eventName])) {
  1258. //$this->log(modX::LOG_LEVEL_DEBUG,'System event '.$eventName.' was executed but does not exist.');
  1259. return false;
  1260. }
  1261. $results= array ();
  1262. if (count($this->eventMap[$eventName])) {
  1263. $this->event= new modSystemEvent();
  1264. foreach ($this->eventMap[$eventName] as $pluginId => $pluginPropset) {
  1265. $plugin= null;
  1266. $this->Event= & $this->event;
  1267. $this->event->resetEventObject();
  1268. $this->event->name= $eventName;
  1269. if (isset ($this->pluginCache[$pluginId])) {
  1270. $plugin= $this->newObject('modPlugin');
  1271. $plugin->fromArray($this->pluginCache[$pluginId], '', true, true);
  1272. $plugin->_processed = false;
  1273. if ($plugin->get('disabled')) {
  1274. $plugin= null;
  1275. }
  1276. } else {
  1277. $plugin= $this->getObject('modPlugin', array ('id' => intval($pluginId), 'disabled' => '0'), true);
  1278. }
  1279. if ($plugin && !$plugin->get('disabled')) {
  1280. $this->event->activated= true;
  1281. $this->event->activePlugin= $plugin->get('name');
  1282. $this->event->propertySet= (($pspos = strpos($pluginPropset, ':')) >= 1) ? substr($pluginPropset, $pspos + 1) : '';
  1283. /* merge in plugin properties */
  1284. $eventParams = array_merge($plugin->getProperties(),$params);
  1285. $msg= $plugin->process($eventParams);
  1286. $results[]= $this->event->_output;
  1287. if ($msg && is_string($msg)) {
  1288. $this->log(modX::LOG_LEVEL_ERROR, '[' . $this->event->name . ']' . $msg);
  1289. } elseif ($msg === false) {
  1290. $this->log(modX::LOG_LEVEL_ERROR, '[' . $this->event->name . '] Plugin failed!');
  1291. }
  1292. $this->event->activePlugin= '';
  1293. $this->event->propertySet= '';
  1294. if (!$this->event->isPropagatable()) {
  1295. break;
  1296. }
  1297. }
  1298. }
  1299. }
  1300. return $results;
  1301. }
  1302. /**
  1303. * Loads and runs a specific processor.
  1304. *
  1305. * @param string $action The processor to run, eg: context/update
  1306. * @param array $scriptProperties Optional. An array of parameters to pass to the processor.
  1307. * @param array $options Optional. An array of options for running the processor, such as:
  1308. *
  1309. * - processors_path - If specified, will override the default MODX processors path.
  1310. * - location - A prefix to load processor files from, will prepend to the action parameter
  1311. * (Note: location will be deprecated in future Revolution versions.)
  1312. *
  1313. * @return mixed The result of the processor.
  1314. */
  1315. public function runProcessor($action = '',$scriptProperties = array(),$options = array()) {
  1316. if (!$this->loadClass('modProcessor','',false,true)) {
  1317. $this->log(modX::LOG_LEVEL_ERROR,'Could not load modProcessor class.');
  1318. return false;
  1319. }
  1320. $result = null;
  1321. /* backwards compat for $options['action']
  1322. * @deprecated Removing in 2.2
  1323. */
  1324. if (empty($action)) {
  1325. if (!empty($options['action'])) {
  1326. $action = $options['action'];
  1327. } else {
  1328. return $result;
  1329. }
  1330. }
  1331. /* calculate processor file path from options and action */
  1332. $processorFile = isset($options['processors_path']) && !empty($options['processors_path']) ? $options['processors_path'] : $this->config['processors_path'];
  1333. if (isset($options['location']) && !empty($options['location'])) $processorFile .= ltrim($options['location'],'/') . '/';
  1334. $processorFile .= ltrim(str_replace('../', '', $action . '.php'),'/');
  1335. if (file_exists($processorFile)) {
  1336. if (!isset($this->lexicon)) $this->getService('lexicon', 'modLexicon');
  1337. if (!isset($this->error)) $this->request->loadErrorHandler();
  1338. $processor = new modProcessor($this);
  1339. $processor->setPath($processorFile);
  1340. $processor->setProperties($scriptProperties);
  1341. $response = $processor->run();
  1342. } else {
  1343. $this->log(modX::LOG_LEVEL_ERROR, "Processor {$processorFile} does not exist; " . print_r($options, true));
  1344. }
  1345. return $response;
  1346. }
  1347. /**
  1348. * Returns the current user ID, for the current or specified context.
  1349. *
  1350. * @param string $context The key of a valid modContext so you can retrieve
  1351. * the current user ID from a different context than the current.
  1352. * @return integer The ID of the current user.
  1353. */
  1354. public function getLoginUserID($context= '') {
  1355. $userId = 0;
  1356. if (empty($context) && $this->context instanceof modContext && $this->user instanceof modUser) {
  1357. if ($this->user->hasSessionContext($this->context->get('key'))) {
  1358. $userId = $this->user->get('id');
  1359. }
  1360. } else {
  1361. $user = $this->getAuthenticatedUser($context);
  1362. if ($user instanceof modUser) {
  1363. $userId = $user->get('id');
  1364. }
  1365. }
  1366. return $userId;
  1367. }
  1368. /**
  1369. * Returns the current user name, for the current or specified context.
  1370. *
  1371. * @param string $context The key of a valid modContext so you can retrieve
  1372. * the username from a different context than the current.
  1373. * @return string The username of the current user.
  1374. */
  1375. public function getLoginUserName($context= '') {
  1376. $userName = '';
  1377. if (empty($context) && $this->context instanceof modContext && $this->user instanceof modUser) {
  1378. if ($this->user->hasSessionContext($this->context->get('key'))) {
  1379. $userName = $this->user->get('username');
  1380. }
  1381. } else {
  1382. $user = $this->getAuthenticatedUser($context);
  1383. if ($user instanceof modUser) {
  1384. $userName = $user->get('username');
  1385. }
  1386. }
  1387. return $userName;
  1388. }
  1389. /**
  1390. * Returns whether modX instance has been initialized or not.
  1391. *
  1392. * @access public
  1393. * @return boolean
  1394. */
  1395. public function isInitialized() {
  1396. return $this->_initialized;
  1397. }
  1398. /**
  1399. * Legacy fatal error message.
  1400. */
  1401. public function messageQuit($msg='unspecified error', $query='', $is_error=true, $nr='', $file='', $source='', $text='', $line='') {
  1402. $this->log(modX::LOG_LEVEL_FATAL, 'msg: ' . $msg . "\n" . 'query: ' . $query . "\n" . 'nr: ' . $nr . "\n" . 'file: ' . $file . "\n" . 'source: ' . $source . "\n" . 'text: ' . $text . "\n" . 'line: ' . $line . "\n");
  1403. }
  1404. /**
  1405. * Process and return the output from a PHP snippet by name.
  1406. *
  1407. * @param string $snippetName The name of the snippet.
  1408. * @param array $params An associative array of properties to pass to the
  1409. * snippet.
  1410. * @return string The processed output of the snippet.
  1411. */
  1412. public function runSnippet($snippetName, array $params= array ()) {
  1413. $output= '';
  1414. if (array_key_exists($snippetName, $this->sourceCache['modSnippet'])) {
  1415. $snippet = $this->newObject('modSnippet');
  1416. $snippet->fromArray($this->sourceCache['modSnippet'][$snippetName]['fields'], '', true, true);
  1417. $snippet->setPolicies($this->sourceCache['modSnippet'][$snippetName]['policies']);
  1418. } else {
  1419. $snippet= $this->getObject('modSnippet', array ('name' => $snippetName), true);
  1420. if (!empty($snippet)) {
  1421. $this->sourceCache['modSnippet'][$snippetName] = array (
  1422. 'fields' => $snippet->toArray(),
  1423. 'policies' => $snippet->getPolicies()
  1424. );
  1425. }
  1426. }
  1427. if (!empty($snippet)) {
  1428. $snippet->setCacheable(false);
  1429. $output= $snippet->process($params);
  1430. }
  1431. return $output;
  1432. }
  1433. /**
  1434. * Process and return the output from a Chunk by name.
  1435. *
  1436. * @param string $chunkName The name of the chunk.
  1437. * @param array $properties An associative array of properties to process
  1438. * the Chunk with, treated as placeholders within the scope of the Element.
  1439. * @return string The processed output of the Chunk.
  1440. */
  1441. public function getChunk($chunkName, array $properties= array ()) {
  1442. $output= '';
  1443. if (array_key_exists($chunkName, $this->sourceCache['modChunk'])) {
  1444. $chunk = $this->newObject('modChunk');
  1445. $chunk->fromArray($this->sourceCache['modChunk'][$chunkName]['fields'], '', true, true);
  1446. $chunk->setPolicies($this->sourceCache['modChunk'][$chunkName]['policies']);
  1447. } else {
  1448. $chunk= $this->getObject('modChunk', array ('name' => $chunkName), true);
  1449. if (!empty($chunk) || $chunk === '0') {
  1450. $this->sourceCache['modChunk'][$chunkName]= array (
  1451. 'fields' => $chunk->toArray(),
  1452. 'policies' => $chunk->getPolicies()
  1453. );
  1454. }
  1455. }
  1456. if (!empty($chunk) || $chunk === '0') {
  1457. $chunk->setCacheable(false);
  1458. $output= $chunk->process($properties);
  1459. }
  1460. return $output;
  1461. }
  1462. /**
  1463. * Parse a chunk using an associative array of replacement variables.
  1464. *
  1465. * @param string $chunkName The name of the chunk.
  1466. * @param array $chunkArr An array of properties to replace in the chunk.
  1467. * @param string $prefix The placeholder prefix, defaults to [[+.
  1468. * @param string $suffix The placeholder suffix, defaults to ]].
  1469. * @return string The processed chunk with the placeholders replaced.
  1470. */
  1471. public function parseChunk($chunkName, $chunkArr, $prefix='[[+', $suffix=']]') {
  1472. $chunk= $this->getChunk($chunkName);
  1473. if (!empty($chunk) || $chunk === '0') {
  1474. if(is_array($chunkArr)) {
  1475. reset($chunkArr);
  1476. while (list($key, $value)= each($chunkArr)) {
  1477. $chunk= str_replace($prefix.$key.$suffix, $value, $chunk);
  1478. }
  1479. }
  1480. }
  1481. return $chunk;
  1482. }
  1483. /**
  1484. * Strip unwanted HTML and PHP tags and supplied patterns from content.
  1485. */
  1486. public function stripTags($html, $allowed= '', $patterns= array(), $depth= 10) {
  1487. $stripped= strip_tags($html, $allowed);
  1488. if (is_array($patterns)) {
  1489. if (empty($patterns)) {
  1490. $patterns = $this->sanitizePatterns;
  1491. }
  1492. foreach ($patterns as $pattern) {
  1493. $depth = ((integer) $depth ? (integer) $depth : 10);
  1494. $iteration = 1;
  1495. while ($iteration <= $depth && preg_match($pattern, $stripped)) {
  1496. $stripped= preg_replace($pattern, '', $stripped);
  1497. $iteration++;
  1498. }
  1499. }
  1500. }
  1501. return $stripped;
  1502. }
  1503. /**
  1504. * Returns true if user has the specified policy permission.
  1505. *
  1506. * @param string $pm Permission key to check.
  1507. * @return boolean
  1508. */
  1509. public function hasPermission($pm) {
  1510. $state = $this->context->checkPolicy($pm);
  1511. return $state;
  1512. }
  1513. /**
  1514. * Logs a manager action.
  1515. * @access public
  1516. * @param string $action The action to pull from the lexicon module.
  1517. * @param string $class_key The class key that the action is being performed
  1518. * on.
  1519. * @param mixed $item The primary key id or array of keys to grab the object
  1520. * with
  1521. * @return modManagerLog The newly created modManagerLog object
  1522. */
  1523. public function logManagerAction($action,$class_key,$item) {
  1524. $ml = $this->newObject('modManagerLog');
  1525. $ml->set('user',$this->user->get('id'));
  1526. $ml->set('occurred',strftime('%Y-%m-%d %H:%M:%S'));
  1527. $ml->set('action',$action);
  1528. $ml->set('classKey',$class_key);
  1529. $ml->set('item',$item);
  1530. if (!$ml->save()) {
  1531. $this->log(modX::LOG_LEVEL_ERROR,$this->lexicon('manager_log_err_save'));
  1532. return null;
  1533. }
  1534. return $ml;
  1535. }
  1536. /**
  1537. * Remove an event from the eventMap so it will not be invoked.
  1538. *
  1539. * @param string $event
  1540. * @return boolean false if the event parameter is not specified or is not
  1541. * present in the eventMap.
  1542. */
  1543. public function removeEventListener($event) {
  1544. $removed = false;
  1545. if (!empty($event) && isset($this->eventMap[$event])) {
  1546. unset ($this->eventMap[$event]);
  1547. $removed = true;
  1548. }
  1549. return $removed;
  1550. }
  1551. /**
  1552. * Remove all registered events for the current request.
  1553. */
  1554. public function removeAllEventListener() {
  1555. unset ($this->eventMap);
  1556. $this->eventMap= array ();
  1557. }
  1558. /**
  1559. * Add a plugin to the eventMap within the current execution cycle.
  1560. *
  1561. * @param string $event Name of the event.
  1562. * @param integer $pluginId Plugin identifier to add to the event.
  1563. * @return boolean true if the event is successfully added, otherwise false.
  1564. */
  1565. public function addEventListener($event, $pluginId) {
  1566. $added = false;
  1567. if ($event && $pluginId) {
  1568. if (!isset($this->eventMap[$event]) || empty ($this->eventMap[$event])) {
  1569. $this->eventMap[$event]= array();
  1570. }
  1571. $this->eventMap[$event][]= $pluginId;
  1572. $added= true;
  1573. }
  1574. return $added;
  1575. }
  1576. /**
  1577. * Switches the primary Context for the modX instance.
  1578. *
  1579. * Be aware that switching contexts does not allow custom session handling
  1580. * classes to be loaded. The gateway defines the session handling that is
  1581. * applied to a single request. To create a context with a custom session
  1582. * handler you must create a unique context gateway that initializes that
  1583. * context directly.
  1584. *
  1585. * @param string $contextKey The key of the context to switch to.
  1586. * @return boolean True if the switch was successful, otherwise false.
  1587. */
  1588. public function switchContext($contextKey) {
  1589. $switched= false;
  1590. if ($this->context->key != $contextKey) {
  1591. $switched= $this->_initContext($contextKey);
  1592. if ($switched) {
  1593. if (is_array($this->config)) {
  1594. $this->setPlaceholders($this->config, '+');
  1595. }
  1596. }
  1597. }
  1598. return $switched;
  1599. }
  1600. /**
  1601. * Retrieve a context by name without initializing it.
  1602. *
  1603. * Within a request, contexts retrieved using this function will cache the
  1604. * context data into the modX::$contexts array to avoid loading the same
  1605. * context multiple times.
  1606. *
  1607. * @access public
  1608. * @param string $contextKey The context to retrieve.
  1609. * @return modContext A modContext object retrieved from cache or
  1610. * database.
  1611. */
  1612. public function getContext($contextKey) {
  1613. if (!isset($this->contexts[$contextKey])) {
  1614. $this->contexts[$contextKey]= $this->getObject('modContext', $contextKey);
  1615. if ($this->contexts[$contextKey]) {
  1616. $this->contexts[$contextKey]->prepare();
  1617. }
  1618. }
  1619. return $this->contexts[$contextKey];
  1620. }
  1621. /**
  1622. * Gets a map of events and registered plugins for the specified context.
  1623. *
  1624. * Service #s:
  1625. * 1 - Parser Service Events
  1626. * 2 - Manager Access Events
  1627. * 3 - Web Access Service Events
  1628. * 4 - Cache Service Events
  1629. * 5 - Template Service Events
  1630. * 6 - User Defined Events
  1631. *
  1632. * @param string $contextKey Context identifier.
  1633. * @return array A map of events and registered plugins for each.
  1634. */
  1635. public function getEventMap($contextKey) {
  1636. $eventElementMap= array ();
  1637. if ($contextKey) {
  1638. switch ($contextKey) {
  1639. case 'mgr':
  1640. /* dont load Web Access Service Events */
  1641. $service= "Event.service IN (1,2,4,5,6) AND";
  1642. break;
  1643. default:
  1644. /* dont load Manager Access Events */
  1645. $service= "Event.service IN (1,3,4,5,6) AND";
  1646. }
  1647. $pluginEventTbl= $this->getTableName('modPluginEvent');
  1648. $eventTbl= $this->getTableName('modEvent');
  1649. $pluginTbl= $this->getTableName('modPlugin');
  1650. $propsetTbl= $this->getTableName('modPropertySet');
  1651. $sql= "
  1652. SELECT
  1653. Event.name AS event,
  1654. PluginEvent.pluginid,
  1655. PropertySet.name AS propertyset
  1656. FROM {$pluginEventTbl} PluginEvent
  1657. INNER JOIN {$pluginTbl} Plugin ON Plugin.id = PluginEvent.pluginid AND Plugin.disabled = 0
  1658. INNER JOIN {$eventTbl} Event ON {$service} Event.name = PluginEvent.event
  1659. LEFT JOIN {$propsetTbl} PropertySet ON PluginEvent.propertyset = PropertySet.id
  1660. ORDER BY Event.name, PluginEvent.priority ASC
  1661. ";
  1662. $stmt= $this->prepare($sql);
  1663. if ($stmt && $stmt->execute()) {
  1664. while ($ee = $stmt->fetch(PDO::FETCH_ASSOC)) {
  1665. $eventElementMap[$ee['event']][(string) $ee['pluginid']]= $ee['pluginid'] . (!empty($ee['propertyset']) ? ':' . $ee['propertyset'] : '');
  1666. }
  1667. }
  1668. }
  1669. return $eventElementMap;
  1670. }
  1671. /**
  1672. * Checks for locking on a page.
  1673. *
  1674. * @param integer $id Id of the user checking for a lock.
  1675. * @param string $action The action identifying what is locked.
  1676. * @param string $type Message indicating the kind of lock being checked.
  1677. */
  1678. public function checkForLocks($id,$action,$type) {
  1679. $msg= false;
  1680. $id= intval($id);
  1681. if (!$id) $id= $this->getLoginUserID();
  1682. if ($au = $this->getObject('modActiveUser',array('action' => $action, 'internalKey:!=' => $id))) {
  1683. $msg = $this->lexicon('lock_msg',array(
  1684. 'name' => $au->get('username'),
  1685. 'object' => $type,
  1686. ));
  1687. }
  1688. return $msg;
  1689. }
  1690. /**
  1691. * Grabs a processed lexicon string.
  1692. *
  1693. * @access public
  1694. * @param string $key
  1695. * @param array $params
  1696. */
  1697. public function lexicon($key,$params = array()) {
  1698. if ($this->lexicon) {
  1699. return $this->lexicon->process($key,$params);
  1700. } else {
  1701. $this->log(modX::LOG_LEVEL_ERROR,'Culture not initialized; cannot use lexicon.');
  1702. }
  1703. }
  1704. /**
  1705. * Returns the state of the SESSION being used by modX.
  1706. *
  1707. * The possible values for session state are:
  1708. *
  1709. * modX::SESSION_STATE_UNINITIALIZED
  1710. * modX::SESSION_STATE_UNAVAILABLE
  1711. * modX::SESSION_STATE_EXTERNAL
  1712. * modX::SESSION_STATE_INITIALIZED
  1713. *
  1714. * @return integer Returns an integer representing the session state.
  1715. */
  1716. public function getSessionState() {
  1717. if ($this->_sessionState == modX::SESSION_STATE_UNINITIALIZED) {
  1718. if (XPDO_CLI_MODE) {
  1719. $this->_sessionState = modX::SESSION_STATE_UNAVAILABLE;
  1720. }
  1721. elseif (isset($_SESSION)) {
  1722. $this->_sessionState = modX::SESSION_STATE_EXTERNAL;
  1723. }
  1724. }
  1725. return $this->_sessionState;
  1726. }
  1727. /**
  1728. * Executed before parser processing of an element.
  1729. */
  1730. public function beforeProcessing() {}
  1731. /**
  1732. * Executed before the response is rendered.
  1733. */
  1734. public function beforeRender() {}
  1735. /**
  1736. * Executed before the handleRequest function.
  1737. */
  1738. public function beforeRequest() {}
  1739. /**
  1740. * Determines the current site_status.
  1741. *
  1742. * @return boolean True if the site is online or the user has a valid
  1743. * user session in the 'mgr' context; false otherwise.
  1744. */
  1745. public function checkSiteStatus() {
  1746. $status = false;
  1747. if ($this->config['site_status'] == '1' || $this->hasPermission('view_offline')) {
  1748. $status = true;
  1749. }
  1750. return $status;
  1751. }
  1752. /**
  1753. * Loads a specified Context.
  1754. *
  1755. * Merges any context settings with the modX::$config, and performs any
  1756. * other context specific initialization tasks.
  1757. *
  1758. * @access protected
  1759. * @param string $contextKey A context identifier.
  1760. */
  1761. protected function _initContext($contextKey) {
  1762. $initialized= false;
  1763. $oldContext = is_object($this->context) ? $this->context->get('key') : '';
  1764. if (isset($this->contexts[$contextKey])) {
  1765. $this->context= & $this->contexts[$contextKey];
  1766. } else {
  1767. $this->context= $this->newObject('modContext');
  1768. $this->context->_fields['key']= $contextKey;
  1769. }
  1770. if ($this->context) {
  1771. if (!$this->context->prepare()) {
  1772. $this->log(modX::LOG_LEVEL_ERROR, 'Could not prepare context: ' . $contextKey);
  1773. } else {
  1774. if ($this->context->checkPolicy('load')) {
  1775. $this->aliasMap= & $this->context->aliasMap;
  1776. $this->resourceMap= & $this->context->resourceMap;
  1777. $this->eventMap= & $this->context->eventMap;
  1778. $this->pluginCache= & $this->context->pluginCache;
  1779. $this->config= array_merge($this->_systemConfig, $this->context->config);
  1780. if ($this->_initialized) {
  1781. $this->getUser();
  1782. }
  1783. $initialized = true;
  1784. } elseif (isset($this->contexts[$oldContext])) {
  1785. $this->context =& $this->contexts[$oldContext];
  1786. } else {
  1787. $this->log(modX::LOG_LEVEL_ERROR, 'Could not load context: ' . $contextKey);
  1788. }
  1789. }
  1790. }
  1791. return $initialized;
  1792. }
  1793. /**
  1794. * Initializes the culture settings.
  1795. *
  1796. * @access protected
  1797. */
  1798. protected function _initCulture() {
  1799. $cultureKey = $this->getOption('cultureKey',null,'en');
  1800. if (!empty($_SESSION['cultureKey'])) $cultureKey = $_SESSION['cultureKey'];
  1801. if (!empty($_REQUEST['cultureKey'])) $cultureKey = $_REQUEST['cultureKey'];
  1802. $this->cultureKey = $cultureKey;
  1803. $this->getService('lexicon','modLexicon');
  1804. $this->invokeEvent('OnInitCulture');
  1805. }
  1806. /**
  1807. * Loads the error handler for this instance.
  1808. * @access protected
  1809. */
  1810. protected function _initErrorHandler() {
  1811. if ($this->errorHandler == null || !is_object($this->errorHandler)) {
  1812. if (isset ($this->config['error_handler_class']) && strlen($this->config['error_handler_class']) > 1) {
  1813. if ($ehClass= $this->loadClass($this->config['error_handler_class'], '', false, true)) {
  1814. if ($this->errorHandler= new $ehClass($this)) {
  1815. $result= set_error_handler(array ($this->errorHandler, 'handleError'));
  1816. if ($result === false) {
  1817. $this->log(modX::LOG_LEVEL_ERROR, 'Could not set error handler. Make sure your class has a function called handleError(). Result: ' . print_r($result, true));
  1818. }
  1819. }
  1820. }
  1821. }
  1822. }
  1823. }
  1824. /**
  1825. * Populates the map of events and registered plugins for each.
  1826. *
  1827. * @access protected
  1828. * @param string $contextKey Context identifier.
  1829. */
  1830. protected function _initEventMap($contextKey) {
  1831. if ($this->eventMap === null) {
  1832. $this->eventMap= $this->getEventMap($contextKey);
  1833. }
  1834. }
  1835. /**
  1836. * Loads the session handler and starts the session.
  1837. * @access protected
  1838. */
  1839. protected function _initSession() {
  1840. $contextKey= $this->context->get('key');
  1841. if ($this->getSessionState() == modX::SESSION_STATE_UNINITIALIZED) {
  1842. $sh= false;
  1843. if ($sessionHandlerClass = $this->getOption('session_handler_class')) {
  1844. if ($shClass= $this->loadClass($sessionHandlerClass, '', false, true)) {
  1845. if ($sh= new $shClass($this)) {
  1846. session_set_save_handler(
  1847. array (& $sh, 'open'),
  1848. array (& $sh, 'close'),
  1849. array (& $sh, 'read'),
  1850. array (& $sh, 'write'),
  1851. array (& $sh, 'destroy'),
  1852. array (& $sh, 'gc')
  1853. );
  1854. }
  1855. }
  1856. }
  1857. if (!$sh) {
  1858. $sessionSavePath = $this->getOption('session_save_path');
  1859. if ($sessionSavePath && is_writable($sessionSavePath)) {
  1860. session_save_path($sessionSavePath);
  1861. }
  1862. }
  1863. $cookieDomain= $this->getOption('session_cookie_domain',null,'');
  1864. $cookiePath= $this->getOption('session_cookie_path',null,MODX_BASE_URL);
  1865. if (empty($cookiePath)) $cookiePath = $this->getOption('base_url',null,MODX_BASE_URL);
  1866. $cookieSecure= (boolean) $this->getOption('session_cookie_secure',null,false);
  1867. $cookieLifetime= (integer) $this->getOption('session_cookie_lifetime',null,0);
  1868. $gcMaxlifetime = (integer) $this->getOption('session_gc_maxlifetime',null,$cookieLifetime);
  1869. if ($gcMaxlifetime > 0) {
  1870. ini_set('session.gc_maxlifetime', $gcMaxlifetime);
  1871. }
  1872. $site_sessionname= $this->getOption('session_name', null,'');
  1873. if (!empty($site_sessionname)) session_name($site_sessionname);
  1874. session_set_cookie_params($cookieLifetime, $cookiePath, $cookieDomain, $cookieSecure);
  1875. session_start();
  1876. $this->_sessionState = modX::SESSION_STATE_INITIALIZED;
  1877. $this->getUser($contextKey);
  1878. $cookieExpiration= 0;
  1879. if (isset ($_SESSION['modx.' . $contextKey . '.session.cookie.lifetime'])) {
  1880. $sessionCookieLifetime= (integer) $_SESSION['modx.' . $contextKey . '.session.cookie.lifetime'];
  1881. if ($sessionCookieLifetime !== $cookieLifetime) {
  1882. if ($sessionCookieLifetime) {
  1883. $cookieExpiration= time() + $sessionCookieLifetime;
  1884. }
  1885. setcookie(session_name(), session_id(), $cookieExpiration, $cookiePath, $cookieDomain, $cookieSecure);
  1886. }
  1887. }
  1888. }
  1889. }
  1890. /**
  1891. * Loads the modX system configuration settings.
  1892. *
  1893. * @access protected
  1894. * @return boolean True if successful.
  1895. */
  1896. protected function _loadConfig() {
  1897. $this->config= $this->_config;
  1898. $this->getCacheManager();
  1899. $config = $this->cacheManager->get('config', array(
  1900. xPDO::OPT_CACHE_KEY => $this->getOption('cache_system_settings_key', null, 'system_settings'),
  1901. xPDO::OPT_CACHE_HANDLER => $this->getOption('cache_system_settings_handler', null, $this->getOption(xPDO::OPT_CACHE_HANDLER)),
  1902. xPDO::OPT_CACHE_FORMAT => (integer) $this->getOption('cache_system_settings_format', null, $this->getOption(xPDO::OPT_CACHE_FORMAT, null, xPDOCacheManager::CACHE_PHP))
  1903. ));
  1904. if (empty($config)) {
  1905. $config = $this->cacheManager->generateConfig();
  1906. }
  1907. if (empty($config)) {
  1908. $config = array();
  1909. if (!$settings= $this->getCollection('modSystemSetting')) {
  1910. return false;
  1911. }
  1912. foreach ($settings as $setting) {
  1913. $config[$setting->get('key')]= $setting->get('value');
  1914. }
  1915. }
  1916. $this->config = array_merge($this->config, $config);
  1917. $this->_systemConfig= $this->config;
  1918. return true;
  1919. }
  1920. /**
  1921. * Provides modX the ability to use modRegister instances as log targets.
  1922. *
  1923. * {@inheritdoc}
  1924. */
  1925. protected function _log($level, $msg, $target= '', $def= '', $file= '', $line= '') {
  1926. if (empty($target)) {
  1927. $target = $this->logTarget;
  1928. }
  1929. $targetOptions = array();
  1930. $targetObj = $target;
  1931. if (is_array($target)) {
  1932. if (isset($target['options'])) $targetOptions = $target['options'];
  1933. $targetObj = isset($target['target']) ? $target['target'] : 'ECHO';
  1934. }
  1935. if (is_object($targetObj) && $targetObj instanceof modRegister) {
  1936. if ($level === modX::LOG_LEVEL_FATAL) {
  1937. if (empty ($file)) $file= (isset ($_SERVER['PHP_SELF'])) ? $_SERVER['PHP_SELF'] : (isset ($_SERVER['SCRIPT_FILENAME']) ? $_SERVER['SCRIPT_FILENAME'] : '');
  1938. $this->_logInRegister($targetObj, $level, $msg, $def, $file, $line);
  1939. $this->sendError('fatal');
  1940. }
  1941. if ($this->_debug === true || $level <= $this->logLevel) {
  1942. if (empty ($file)) $file= (isset ($_SERVER['PHP_SELF'])) ? $_SERVER['PHP_SELF'] : (isset ($_SERVER['SCRIPT_FILENAME']) ? $_SERVER['SCRIPT_FILENAME'] : '');
  1943. $this->_logInRegister($targetObj, $level, $msg, $def, $file, $line);
  1944. }
  1945. } else {
  1946. if ($level === modX::LOG_LEVEL_FATAL) {
  1947. while (@ob_end_clean()) {}
  1948. if ($targetObj == 'FILE' && $cacheManager= $this->getCacheManager()) {
  1949. $filename = isset($targetOptions['filename']) ? $targetOptions['filename'] : 'error.log';
  1950. $filepath = isset($targetOptions['filepath']) ? $targetOptions['filepath'] : $this->getCachePath() . xPDOCacheManager::LOG_DIR;
  1951. $cacheManager->writeFile($filepath . $filename, '[' . strftime('%Y-%m-%d %H:%M:%S') . '] (' . $this->_getLogLevel($level) . $def . $file . $line . ') ' . $msg . "\n" . ($this->getDebug() === true ? '<pre>' . "\n" . print_r(debug_backtrace(), true) . "\n" . '</pre>' : ''), 'a');
  1952. }
  1953. $this->sendError('fatal');
  1954. }
  1955. parent :: _log($level, $msg, $target, $def, $file, $line);
  1956. }
  1957. }
  1958. /**
  1959. * Provides custom logging functionality for modRegister targets.
  1960. *
  1961. * @access protected
  1962. */
  1963. protected function _logInRegister($register, $level, $msg, $def, $file, $line) {
  1964. $timestamp = strftime('%Y-%m-%d %H:%M:%S');
  1965. $messageKey = (string) time();
  1966. $messageKey .= '-' . sprintf("%06d", $this->_logSequence);
  1967. $message = array(
  1968. 'timestamp' => $timestamp,
  1969. 'level' => $this->_getLogLevel($level),
  1970. 'msg' => $msg,
  1971. 'def' => $def,
  1972. 'file' => $file,
  1973. 'line' => $line
  1974. );
  1975. $options = array();
  1976. if ($level === xPDO::LOG_LEVEL_FATAL) {
  1977. $options['kill'] = true;
  1978. }
  1979. $register->send('', array($messageKey => $message), $options);
  1980. $this->_logSequence++;
  1981. }
  1982. /**
  1983. * Executed after the response is sent and execution is completed.
  1984. *
  1985. * @access protected
  1986. */
  1987. public function _postProcess() {
  1988. if ($this->resourceGenerated && $this->getOption('cache_resource', null, true)) {
  1989. if (is_object($this->resource) && $this->resource instanceof modResource && $this->resource->get('id') && $this->resource->get('cacheable')) {
  1990. $this->invokeEvent('OnBeforeSaveWebPageCache');
  1991. $this->cacheManager->generateResource($this->resource);
  1992. }
  1993. }
  1994. $this->invokeEvent('OnWebPageComplete');
  1995. }
  1996. }
  1997. /**
  1998. * Represents a modEvent when invoking events.
  1999. * @package modx
  2000. */
  2001. class modSystemEvent {
  2002. /**
  2003. * @var const For new creations of objects in model events
  2004. */
  2005. const MODE_NEW = 'new';
  2006. /**
  2007. * @var const For updating objects in model events
  2008. */
  2009. const MODE_UPD = 'upd';
  2010. /**@#+
  2011. * @deprecated
  2012. * @var string
  2013. */
  2014. public $name = '';
  2015. public $activePlugin = '';
  2016. public $propertySet = '';
  2017. /**
  2018. * @var boolean
  2019. */
  2020. protected $_propagate = true;
  2021. public $_output;
  2022. /**
  2023. * @var boolean
  2024. */
  2025. public $activated;
  2026. /**
  2027. * @var mixed
  2028. */
  2029. public $returnedValues;
  2030. /**
  2031. * @var array
  2032. */
  2033. public $params;
  2034. /**@#-*/
  2035. /**
  2036. * Display a message to the user during the event.
  2037. *
  2038. * @todo Remove this; the centralized modRegistry will handle configurable
  2039. * logging of any kind of message or data to any repository or output
  2040. * context. Use {@link modX::_log()} in the meantime.
  2041. * @param string $msg The message to display.
  2042. */
  2043. public function alert($msg) {}
  2044. /**
  2045. * Render output from the event.
  2046. * @param string $output The output to render.
  2047. */
  2048. public function output($output) {
  2049. $this->_output .= $output;
  2050. }
  2051. /**
  2052. * Stop further execution of plugins for this event.
  2053. */
  2054. public function stopPropagation() {
  2055. $this->_propagate = false;
  2056. }
  2057. /**
  2058. * Returns whether the event will propagate or not.
  2059. *
  2060. * @access public
  2061. * @return boolean
  2062. */
  2063. public function isPropagatable() {
  2064. return $this->_propagate;
  2065. }
  2066. /**
  2067. * Reset the event instance for reuse.
  2068. */
  2069. public function resetEventObject(){
  2070. $this->returnedValues = null;
  2071. $this->name = '';
  2072. $this->_output = '';
  2073. $this->_propagate = true;
  2074. $this->activated = false;
  2075. }
  2076. }