modx.class.php 115 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850
  1. <?php
  2. /*
  3. * This file is part of MODX Revolution.
  4. *
  5. * Copyright (c) MODX, LLC. All Rights Reserved.
  6. *
  7. * For complete copyright and license information, see the COPYRIGHT and LICENSE
  8. * files found in the top-level directory of this distribution.
  9. */
  10. /**
  11. * This is the main file to include in your scripts to use MODX.
  12. *
  13. * For detailed information on using this class, see {@tutorial modx/modx.pkg}.
  14. *
  15. * @package modx
  16. */
  17. /* fix for PHP float bug: http://bugs.php.net/bug.php?id=53632 (php 4 <= 4.4.9 and php 5 <= 5.3.4) */
  18. if (strstr(str_replace('.','',serialize(array_merge($_GET, $_POST, $_COOKIE))), '22250738585072011')) {
  19. header('Status: 422 Unprocessable Entity');
  20. die();
  21. }
  22. if (!defined('MODX_CORE_PATH')) {
  23. define('MODX_CORE_PATH', dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR);
  24. }
  25. require_once (MODX_CORE_PATH . 'xpdo/xpdo.class.php');
  26. /**
  27. * This is the MODX gateway class.
  28. *
  29. * It can be used to interact with the MODX framework and serves as a front
  30. * controller for handling requests to the virtual resources managed by the MODX
  31. * Content Management Framework.
  32. *
  33. * @package modx
  34. */
  35. class modX extends xPDO {
  36. /**
  37. * The parameter for when a session state is not able to be accessed
  38. * @const SESSION_STATE_UNAVAILABLE
  39. */
  40. const SESSION_STATE_UNAVAILABLE = -1;
  41. /**
  42. * The parameter for when a session has not yet been instantiated
  43. * @const SESSION_STATE_UNINITIALIZED
  44. */
  45. const SESSION_STATE_UNINITIALIZED = 0;
  46. /**
  47. * The parameter for when a session has been fully initialized
  48. * @const SESSION_STATE_INITIALIZED
  49. */
  50. const SESSION_STATE_INITIALIZED = 1;
  51. /**
  52. * The parameter marking when a session is being controlled by an external provider
  53. * @const SESSION_STATE_EXTERNAL
  54. */
  55. const SESSION_STATE_EXTERNAL = 2;
  56. /**
  57. * @var modContext The Context represents a unique section of the site which
  58. * this modX instance is controlling.
  59. */
  60. public $context= null;
  61. /**
  62. * @var array An array of secondary contexts loaded on demand.
  63. */
  64. public $contexts= array();
  65. /**
  66. * @var modRequest|modConnectorRequest|modManagerRequest Represents a web request and provides helper methods for
  67. * dealing with request parameters and other attributes of a request.
  68. */
  69. public $request= null;
  70. /**
  71. * @var modResponse|modConnectorResponse|modManagerResponse Represents a web response, providing helper methods for
  72. * managing response header attributes and the body containing the content of
  73. * the response.
  74. */
  75. public $response= null;
  76. /**
  77. * @var modParser The modParser registered for this modX instance,
  78. * responsible for content tag parsing, and loaded only on demand.
  79. */
  80. public $parser= null;
  81. /**
  82. * @var array An array of supplemental service classes for this modX instance.
  83. */
  84. public $services= array ();
  85. /**
  86. * @var array A listing of site Resources and Context-specific meta data.
  87. */
  88. public $resourceListing= null;
  89. /**
  90. * @var array A hierarchy map of Resources.
  91. */
  92. public $resourceMap= null;
  93. /**
  94. * @var array A lookup listing of Resource alias values and associated
  95. * Resource Ids
  96. */
  97. public $aliasMap= null;
  98. /**
  99. * @var modSystemEvent The current event being handled by modX.
  100. */
  101. public $event= null;
  102. /**
  103. * @var array A map of elements registered to specific events.
  104. */
  105. public $eventMap= null;
  106. /**
  107. * @var array A map of actions registered to the manager interface.
  108. */
  109. public $actionMap= null;
  110. /**
  111. * @var array A map of already processed Elements.
  112. */
  113. public $elementCache= array ();
  114. /**
  115. * @var array An array of key=> value pairs that can be used by any Resource
  116. * or Element.
  117. */
  118. public $placeholders= array ();
  119. /**
  120. * @var modResource An instance of the current modResource controlling the
  121. * request.
  122. */
  123. public $resource= null;
  124. /**
  125. * @var string The preferred Culture key for the current request.
  126. */
  127. public $cultureKey= '';
  128. /**
  129. * @var modLexicon Represents a localized dictionary of common words and phrases.
  130. */
  131. public $lexicon= null;
  132. /**
  133. * @var modUser The current user object, if one is authenticated for the
  134. * current request and context.
  135. */
  136. public $user= null;
  137. /**
  138. * @var array Represents the modContentType instances that can be delivered
  139. * by this modX deployment.
  140. */
  141. public $contentTypes= null;
  142. /**
  143. * @var mixed The resource id or alias being requested.
  144. */
  145. public $resourceIdentifier= null;
  146. /**
  147. * @var string The method to use to locate the Resource, 'id' or 'alias'.
  148. */
  149. public $resourceMethod= null;
  150. /**
  151. * @var boolean Indicates if the resource was generated during this request.
  152. */
  153. public $resourceGenerated= false;
  154. /**
  155. * @var array Version information for this MODX deployment.
  156. */
  157. public $version= null;
  158. /**
  159. * @var string Unique site id for each MODX installation.
  160. */
  161. public $site_id;
  162. /**
  163. * @var string Unique uuid for each MODX installation.
  164. */
  165. public $uuid;
  166. /**
  167. * @var boolean Indicates if modX has been successfully initialized for a
  168. * modContext.
  169. */
  170. protected $_initialized= false;
  171. /**
  172. * @var array An array of javascript content to be inserted into the HEAD
  173. * of an HTML resource.
  174. */
  175. public $sjscripts= array ();
  176. /**
  177. * @var array An array of javascript content to be inserted into the BODY
  178. * of an HTML resource.
  179. */
  180. public $jscripts= array ();
  181. /**
  182. * @var array An array of already loaded javascript/css code
  183. */
  184. public $loadedjscripts= array ();
  185. /**
  186. * @var string Stores the virtual path for a request to MODX if the
  187. * friendly_alias_paths option is enabled.
  188. */
  189. public $virtualDir;
  190. /**
  191. * @var modErrorHandler|object An error_handler for the modX instance.
  192. */
  193. public $errorHandler= null;
  194. /**
  195. * @var modError An error response class for the request
  196. */
  197. public $error = null;
  198. /**
  199. * @var modManagerController A controller object that represents a page in the manager
  200. */
  201. public $controller = null;
  202. /**
  203. * @var modRegistry $registry
  204. */
  205. public $registry;
  206. /**
  207. * @var modMail $mail
  208. */
  209. public $mail;
  210. /**
  211. * @var modRestClient $rest
  212. */
  213. public $rest;
  214. /**
  215. * @var array $processors An array of loaded processors and their class name
  216. */
  217. public $processors = array();
  218. /**
  219. * @var array An array of regex patterns regulary cleansed from content.
  220. */
  221. public $sanitizePatterns = array(
  222. 'scripts' => '@<script[^>]*?>.*?</script>@si',
  223. 'entities' => '@&#(\d+);@',
  224. 'tags1' => '@\[\[(.*?)\]\]@si',
  225. 'tags2' => '@(\[\[|\]\])@si',
  226. );
  227. /**
  228. * @var integer An integer representing the session state of modX.
  229. */
  230. protected $_sessionState= modX::SESSION_STATE_UNINITIALIZED;
  231. /**
  232. * @var array A config array that stores the bootstrap settings.
  233. */
  234. protected $_config= null;
  235. /**
  236. * @var array A config array that stores the system-wide settings.
  237. */
  238. public $_systemConfig= array();
  239. /**
  240. * @var array A config array that stores the user settings.
  241. */
  242. public $_userConfig= array();
  243. /**
  244. * @var int The current log sequence
  245. */
  246. protected $_logSequence= 0;
  247. /**
  248. * @var array An array of plugins that have been cached for execution
  249. */
  250. public $pluginCache= array();
  251. /**
  252. * @var array The elemnt source cache used for caching and preparing Element data
  253. */
  254. public $sourceCache= array(
  255. 'modChunk' => array()
  256. ,'modSnippet' => array()
  257. ,'modTemplateVar' => array()
  258. );
  259. /** @var modCacheManager $cacheManager */
  260. public $cacheManager;
  261. /**
  262. * @deprecated
  263. * @var modSystemEvent $Event
  264. */
  265. public $Event= null;
  266. /**
  267. * @deprecated
  268. * @var string $documentOutput
  269. */
  270. public $documentOutput= null;
  271. /**
  272. * Keeps an in-memory representation of what deprecated functions have been logged
  273. * for this request, to avoid spamming the log too often. See the `deprecated` method.
  274. *
  275. * @var array
  276. */
  277. private $loggedDeprecatedFunctions = array();
  278. /**
  279. * Harden the environment against common security flaws.
  280. *
  281. * @static
  282. */
  283. public static function protect() {
  284. if (@ ini_get('register_globals') && isset ($_REQUEST)) {
  285. foreach ($_REQUEST as $key => $value) {
  286. $GLOBALS[$key] = null;
  287. unset ($GLOBALS[$key]);
  288. }
  289. }
  290. $targets= array ('PHP_SELF', 'HTTP_USER_AGENT', 'HTTP_REFERER', 'QUERY_STRING');
  291. foreach ($targets as $target) {
  292. $_SERVER[$target] = isset ($_SERVER[$target]) ? htmlspecialchars($_SERVER[$target], ENT_QUOTES) : null;
  293. }
  294. }
  295. /**
  296. * Sanitize values of an array using regular expression patterns.
  297. *
  298. * @static
  299. * @param array $target The target array to sanitize.
  300. * @param array|string $patterns A regular expression pattern, or array of
  301. * regular expression patterns to apply to all values of the target.
  302. * @param integer $depth The maximum recursive depth to sanitize if the
  303. * target contains values that are arrays.
  304. * @param integer $nesting The maximum nesting level in which to dive
  305. * @return array The sanitized array.
  306. */
  307. public static function sanitize(array &$target, array $patterns= array(), $depth= 99, $nesting= 10) {
  308. foreach ($target as $key => &$value) {
  309. if (is_array($value) && $depth > 0) {
  310. modX :: sanitize($value, $patterns, $depth-1);
  311. } elseif (is_string($value)) {
  312. if (!empty($patterns)) {
  313. $iteration = 1;
  314. $nesting = ((integer) $nesting ? (integer) $nesting : 10);
  315. while ($iteration <= $nesting) {
  316. $matched = false;
  317. foreach ($patterns as $pattern) {
  318. $patternIterator = 1;
  319. $patternMatches = preg_match($pattern, $value);
  320. if ($patternMatches > 0) {
  321. $matched = true;
  322. while ($patternMatches > 0 && $patternIterator <= $nesting) {
  323. $value= preg_replace($pattern, '', $value);
  324. $patternMatches = preg_match($pattern, $value);
  325. }
  326. }
  327. }
  328. if (!$matched) {
  329. break;
  330. }
  331. $iteration++;
  332. }
  333. }
  334. if (get_magic_quotes_gpc()) {
  335. $target[$key]= stripslashes($value);
  336. } else {
  337. $target[$key]= $value;
  338. }
  339. }
  340. }
  341. return $target;
  342. }
  343. /**
  344. * @param array|string $data The target data to sanitize.
  345. * @param array $replaceable
  346. * @return array|string The sanitized data
  347. */
  348. public static function replaceReserved($data, array $replaceable = array ('[' => '&#91;', ']' => '&#93;', '`' => '&#96;'))
  349. {
  350. if (\is_array($data)) {
  351. $result = array();
  352. foreach ($data as $key => &$value) {
  353. $key = self::replaceReserved($key, $replaceable);
  354. $result[$key] = self::replaceReserved($value, $replaceable);
  355. }
  356. } elseif (\is_scalar($data)) {
  357. $result = \str_replace(\array_keys($replaceable), \array_values($replaceable), $data);
  358. } else {
  359. $result = '';
  360. }
  361. return $result;
  362. }
  363. /**
  364. * Sanitizes a string
  365. *
  366. * @param string $str The string to sanitize
  367. * @param array $chars An array of chars to remove
  368. * @param string $allowedTags A list of tags to allow.
  369. * @return string The sanitized string.
  370. */
  371. public function sanitizeString($str,$chars = array('/',"'",'"','(',')',';','>','<'),$allowedTags = '') {
  372. $str = str_replace($chars,'',strip_tags($str,$allowedTags));
  373. return preg_replace("/[^A-Za-z0-9_\-\.\/\\p{L}[\p{L} _.-]/u",'',$str);
  374. }
  375. /**
  376. * Turn an associative or numeric array into a valid query string.
  377. *
  378. * @static
  379. * @param array $parameters An associative or numeric-indexed array of parameters.
  380. * @param string $numPrefix A string prefix added to the numeric-indexed array keys.
  381. * Ignored if associative array is used.
  382. * @param string $argSeparator The string used to separate arguments in the resulting query string.
  383. * @return string A valid query string representing the parameters.
  384. */
  385. public static function toQueryString(array $parameters = array(), $numPrefix = '', $argSeparator = '&') {
  386. return http_build_query($parameters, $numPrefix, $argSeparator);
  387. }
  388. /**
  389. * Create, retrieve, or update specific modX instances.
  390. *
  391. * @static
  392. * @param string|int|null $id An optional identifier for the instance. If not set
  393. * a uniqid will be generated and used as the key for the instance.
  394. * @param array|null $config An optional array of config data for the instance.
  395. * @param bool $forceNew If true a new instance will be created even if an instance
  396. * with the provided $id already exists in modX::$instances.
  397. * @return modX An instance of modX.
  398. * @throws xPDOException
  399. */
  400. public static function getInstance($id = null, $config = null, $forceNew = false) {
  401. $class = __CLASS__;
  402. if (is_null($id)) {
  403. if (!is_null($config) || $forceNew || empty(self::$instances)) {
  404. $id = uniqid($class);
  405. } else {
  406. $instances =& self::$instances;
  407. $id = key($instances);
  408. }
  409. }
  410. if ($forceNew || !array_key_exists($id, self::$instances) || !(self::$instances[$id] instanceof $class)) {
  411. self::$instances[$id] = new $class('', $config);
  412. } elseif (self::$instances[$id] instanceof $class && is_array($config)) {
  413. self::$instances[$id]->config = array_merge(self::$instances[$id]->config, $config);
  414. }
  415. if (!(self::$instances[$id] instanceof $class)) {
  416. throw new xPDOException("Error getting {$class} instance, id = {$id}");
  417. }
  418. return self::$instances[$id];
  419. }
  420. /**
  421. * Construct a new modX instance.
  422. *
  423. * @param string $configPath An absolute filesystem path to look for the config file.
  424. * @param array $options xPDO options that can be passed to the instance.
  425. * @param array $driverOptions PDO driver options that can be passed to the instance.
  426. * @return modX A new modX instance.
  427. */
  428. public function __construct($configPath= '', $options = null, $driverOptions = null) {
  429. try {
  430. $options = $this->loadConfig($configPath, $options, $driverOptions);
  431. parent :: __construct(
  432. null,
  433. null,
  434. null,
  435. $options,
  436. null
  437. );
  438. $this->setLogLevel($this->getOption('log_level', null, xPDO::LOG_LEVEL_ERROR));
  439. $this->setLogTarget($this->getOption('log_target', null, 'FILE'));
  440. $debug = $this->getOption('debug');
  441. if (!is_null($debug) && $debug !== '') {
  442. $this->setDebug($debug);
  443. }
  444. $this->setPackage('modx', MODX_CORE_PATH . 'model/');
  445. $this->loadClass('modAccess');
  446. $this->loadClass('modAccessibleObject');
  447. $this->loadClass('modAccessibleSimpleObject');
  448. $this->loadClass('modResource');
  449. $this->loadClass('modElement');
  450. $this->loadClass('modScript');
  451. $this->loadClass('modPrincipal');
  452. $this->loadClass('modUser');
  453. $this->loadClass('sources.modMediaSource');
  454. } catch (xPDOException $xe) {
  455. $this->sendError('unavailable', array('error_message' => $xe->getMessage()));
  456. } catch (Exception $e) {
  457. $this->sendError('unavailable', array('error_message' => $e->getMessage()));
  458. }
  459. }
  460. /**
  461. * Load the modX configuration when creating an instance of modX.
  462. *
  463. * @param string $configPath An absolute path location to search for the modX config file.
  464. * @param array $data Data provided to initialize the instance with, overriding config file entries.
  465. * @param null $driverOptions Driver options for the primary connection.
  466. * @return array The merged config data ready for use by the modX::__construct() method.
  467. */
  468. protected function loadConfig($configPath = '', $data = array(), $driverOptions = null) {
  469. if (!is_array($data)) $data = array();
  470. modX :: protect();
  471. if (!defined('MODX_CONFIG_KEY')) {
  472. define('MODX_CONFIG_KEY', 'config');
  473. }
  474. if (empty ($configPath)) {
  475. $configPath= MODX_CORE_PATH . 'config/';
  476. }
  477. global $database_dsn, $database_user, $database_password, $config_options, $driver_options, $table_prefix, $site_id, $uuid;
  478. if (file_exists($configPath . MODX_CONFIG_KEY . '.inc.php') && include ($configPath . MODX_CONFIG_KEY . '.inc.php')) {
  479. $cachePath= MODX_CORE_PATH . 'cache/';
  480. if (MODX_CONFIG_KEY !== 'config') $cachePath .= MODX_CONFIG_KEY . '/';
  481. if (!is_array($config_options)) $config_options = array();
  482. if (!is_array($driver_options)) $driver_options = array();
  483. $data = array_merge(
  484. array (
  485. xPDO::OPT_CACHE_KEY => 'default',
  486. xPDO::OPT_CACHE_HANDLER => 'xPDOFileCache',
  487. xPDO::OPT_CACHE_PATH => $cachePath,
  488. xPDO::OPT_TABLE_PREFIX => $table_prefix,
  489. xPDO::OPT_HYDRATE_FIELDS => true,
  490. xPDO::OPT_HYDRATE_RELATED_OBJECTS => true,
  491. xPDO::OPT_HYDRATE_ADHOC_FIELDS => true,
  492. xPDO::OPT_VALIDATOR_CLASS => 'validation.modValidator',
  493. xPDO::OPT_VALIDATE_ON_SAVE => true,
  494. 'cache_system_settings' => true,
  495. 'cache_system_settings_key' => 'system_settings'
  496. ),
  497. $config_options,
  498. $data
  499. );
  500. $primaryConnection = array(
  501. 'dsn' => $database_dsn,
  502. 'username' => $database_user,
  503. 'password' => $database_password,
  504. 'options' => array(
  505. xPDO::OPT_CONN_MUTABLE => isset($data[xPDO::OPT_CONN_MUTABLE]) ? (boolean) $data[xPDO::OPT_CONN_MUTABLE] : true,
  506. ),
  507. 'driverOptions' => $driver_options
  508. );
  509. if (!array_key_exists(xPDO::OPT_CONNECTIONS, $data) || !is_array($data[xPDO::OPT_CONNECTIONS])) {
  510. $data[xPDO::OPT_CONNECTIONS] = array();
  511. }
  512. array_unshift($data[xPDO::OPT_CONNECTIONS], $primaryConnection);
  513. if (!empty($site_id)) $this->site_id = $site_id;
  514. if (!empty($uuid)) $this->uuid = $uuid;
  515. } else {
  516. throw new xPDOException("Could not load MODX config file.");
  517. }
  518. return $data;
  519. }
  520. /**
  521. * Initializes the modX engine.
  522. *
  523. * This includes preparing the session, pre-loading some common
  524. * classes and objects, the current site and context settings, extension
  525. * packages used to override session handling, error handling, or other
  526. * initialization classes
  527. *
  528. * @param string $contextKey Indicates the context to initialize.
  529. * @param array|null $options An array of options for the initialization.
  530. * @return bool True if initialized successfully, or already initialized.
  531. */
  532. public function initialize($contextKey= 'web', $options = null) {
  533. if (!$this->_initialized) {
  534. if (!$this->startTime) {
  535. $this->startTime= microtime(true);
  536. }
  537. $this->getCacheManager();
  538. $this->getConfig();
  539. $this->_initContext($contextKey, false, $options);
  540. $this->_loadExtensionPackages($options);
  541. $this->_initSession($options);
  542. $this->_initErrorHandler($options);
  543. $this->_initCulture($options);
  544. $this->getService('registry', 'registry.modRegistry');
  545. $this->invokeEvent(
  546. 'OnMODXInit',
  547. array(
  548. 'contextKey' => $contextKey,
  549. 'options' => $options
  550. )
  551. );
  552. if (is_array ($this->config)) {
  553. $this->setPlaceholders($this->config, '+');
  554. }
  555. $this->_initialized= true;
  556. }
  557. return $this->_initialized;
  558. }
  559. /**
  560. * Loads any extension packages.
  561. *
  562. * @param array|null An optional array of options that can contain additional
  563. * extension packages which will be merged with those specified via config.
  564. */
  565. protected function _loadExtensionPackages($options = null) {
  566. $cache = $this->call('modExtensionPackage','loadCache',array(&$this));
  567. if (!empty($cache)) {
  568. foreach ($cache as $package) {
  569. $package['table_prefix'] = isset($package['table_prefix']) ? $package['table_prefix'] : null;
  570. $this->addPackage($package['namespace'],$package['path'],$package['table_prefix']);
  571. if (!empty($package['service_name']) && !empty($package['service_class'])) {
  572. $this->getService($package['service_name'],$package['service_class'],$package['path']);
  573. }
  574. }
  575. }
  576. $this->_loadExtensionPackagesDeprecated($options);
  577. }
  578. /**
  579. * Load system-setting based extension packages. This is not recommended; use modExtensionPackage from 2.3 onward.
  580. * The System Setting will be automatically removed in 2.4/3.0 and no longer functional.
  581. *
  582. * @deprecated To be removed in 2.4/3.0
  583. * @param null $options
  584. */
  585. protected function _loadExtensionPackagesDeprecated($options = null) {
  586. $extPackages = $this->getOption('extension_packages');
  587. $extPackages = $this->fromJSON($extPackages);
  588. if (!is_array($extPackages)) $extPackages = array();
  589. if (is_array($options) && array_key_exists('extension_packages', $options)) {
  590. $optPackages = $this->fromJSON($options['extension_packages']);
  591. if (is_array($optPackages)) {
  592. $extPackages = array_merge($extPackages, $optPackages);
  593. }
  594. }
  595. if (!empty($extPackages)) {
  596. foreach ($extPackages as $extPackage) {
  597. if (!is_array($extPackage)) continue;
  598. foreach ($extPackage as $packageName => $package) {
  599. if (!empty($package) && !empty($package['path'])) {
  600. $package['tablePrefix'] = isset($package['tablePrefix']) ? $package['tablePrefix'] : null;
  601. $package['path'] = str_replace(array(
  602. '[[++core_path]]',
  603. '[[++base_path]]',
  604. '[[++assets_path]]',
  605. '[[++manager_path]]',
  606. ),array(
  607. $this->config['core_path'],
  608. $this->config['base_path'],
  609. $this->config['assets_path'],
  610. $this->config['manager_path'],
  611. ),$package['path']);
  612. $this->addPackage($packageName,$package['path'],$package['tablePrefix']);
  613. if (!empty($package['serviceName']) && !empty($package['serviceClass'])) {
  614. $packagePath = str_replace('//','/',$package['path'].$packageName.'/');
  615. $this->getService($package['serviceName'],$package['serviceClass'],$packagePath);
  616. }
  617. }
  618. }
  619. }
  620. }
  621. }
  622. /**
  623. * Sets the debugging features of the modX instance.
  624. *
  625. * @param boolean|int $debug Boolean or bitwise integer describing the
  626. * debug state and/or PHP error reporting level.
  627. * @param boolean $stopOnNotice Indicates if processing should stop when
  628. * encountering PHP errors of type E_NOTICE.
  629. * @return boolean|int The previous value.
  630. *
  631. * @info PHP errors are handle by modErrorHandler with at most LOG_LEVEL_INFO
  632. * When called by modX $debug is a string (ie $this->getOption('debug'))
  633. *
  634. * (bool)true , (string)true , (string)-1 -> LOG_LEVEL_DEBUG (MODX), E_ALL | E_STRICT (PHP)
  635. * (bool)false, (string)false, (string) 0 -> LOG_LEVEL_ERROR (MODX), 0 (PHP)
  636. * (int)nnn -> LOG_LEVEL_INFO (MODX), nnn (PHP)
  637. * (string)E_XXX -> LOG_LEVEL_INFO (MODX), E_XXX (PHP)
  638. */
  639. public function setDebug($debug= true) {
  640. $oldValue= $this->getDebug();
  641. if (($debug === true) || ('true' === $debug) || ('-1' === $debug)) {
  642. error_reporting(-1);
  643. parent :: setDebug(true);
  644. }
  645. else {
  646. if (($debug === false) || ('false' === $debug) || ('0' === $debug)) {
  647. error_reporting(0);
  648. parent :: setDebug(false);
  649. }
  650. else {
  651. $debug = (is_int($debug) ? $debug : defined($debug) ? intval(constant($debug)) : 0);
  652. if ($debug) {
  653. error_reporting($debug);
  654. parent :: setLogLevel(xPDO::LOG_LEVEL_INFO);
  655. }
  656. }
  657. }
  658. return $oldValue;
  659. }
  660. /**
  661. * Get an extended xPDOCacheManager instance responsible for MODX caching.
  662. *
  663. * @param string $class The class name of the cache manager to load
  664. * @param array $options An array of options to send to the cache manager instance
  665. * @return modCacheManager A modCacheManager instance registered for this modX instance.
  666. */
  667. public function getCacheManager($class= 'cache.xPDOCacheManager', $options = array('path' => XPDO_CORE_PATH, 'ignorePkg' => true)) {
  668. if ($this->cacheManager === null) {
  669. if ($this->loadClass($class, $options['path'], $options['ignorePkg'], true)) {
  670. $cacheManagerClass= $this->getOption('modCacheManager.class', null, 'modCacheManager');
  671. if ($className= $this->loadClass($cacheManagerClass, '', false, true)) {
  672. if ($this->cacheManager= new $className ($this)) {
  673. $this->_cacheEnabled= true;
  674. }
  675. }
  676. }
  677. }
  678. return $this->cacheManager;
  679. }
  680. /**
  681. * Gets the MODX parser.
  682. *
  683. * Returns an instance of modParser responsible for parsing tags in element
  684. * content, performing actions, returning content and/or sending other responses
  685. * in the process.
  686. *
  687. * @return modParser The modParser for this modX instance.
  688. */
  689. public function getParser() {
  690. return $this->getService('parser', $this->getOption('parser_class', null, 'modParser'), $this->getOption('parser_class_path', null, ''));
  691. }
  692. /**
  693. * Gets all of the parent resource ids for a given resource.
  694. *
  695. * @param integer $id The resource id for the starting node.
  696. * @param integer $height How many levels max to search for parents (default 10).
  697. * @param array $options An array of filtering options, such as 'context' to specify the context to grab from
  698. * @return array An array of all the parent resource ids for the specified resource.
  699. */
  700. public function getParentIds($id= null, $height= 10,array $options = array()) {
  701. $parentId= 0;
  702. $parents= array ();
  703. if ($id && $height > 0) {
  704. $context = '';
  705. if (!empty($options['context'])) {
  706. $this->getContext($options['context']);
  707. $context = $options['context'];
  708. }
  709. $resourceMap = !empty($context) && !empty($this->contexts[$context]->resourceMap) ? $this->contexts[$context]->resourceMap : $this->resourceMap;
  710. foreach ($resourceMap as $parentId => $mapNode) {
  711. if (array_search($id, $mapNode) !== false) {
  712. $parents[]= $parentId;
  713. break;
  714. }
  715. }
  716. if ($parentId && !empty($parents)) {
  717. $height--;
  718. $parents= array_merge($parents, $this->getParentIds($parentId,$height,$options));
  719. }
  720. }
  721. return $parents;
  722. }
  723. /**
  724. * Gets all of the child resource ids for a given resource.
  725. *
  726. * @see getTree for hierarchical node results
  727. * @param integer $id The resource id for the starting node.
  728. * @param integer $depth How many levels max to search for children (default 10).
  729. * @param array $options An array of filtering options, such as 'context' to specify the context to grab from
  730. * @return array An array of all the child resource ids for the specified resource.
  731. */
  732. public function getChildIds($id= null, $depth= 10,array $options = array()) {
  733. $children= array ();
  734. if ($id !== null && intval($depth) >= 1) {
  735. $id= is_int($id) ? $id : intval($id);
  736. $context = '';
  737. if (!empty($options['context'])) {
  738. $this->getContext($options['context']);
  739. $context = $options['context'];
  740. }
  741. $resourceMap = !empty($context) && !empty($this->contexts[$context]->resourceMap) ? $this->contexts[$context]->resourceMap : $this->resourceMap;
  742. if (isset ($resourceMap["{$id}"])) {
  743. if ($children= $resourceMap["{$id}"]) {
  744. foreach ($children as $child) {
  745. if ($c = $this->getChildIds($child, $depth - 1, $options)) {
  746. $children = array_merge($children, $c);
  747. }
  748. }
  749. }
  750. }
  751. }
  752. return $children;
  753. }
  754. /**
  755. * Get a site tree from a single or multiple modResource instances.
  756. *
  757. * @see getChildIds for linear results
  758. * @param int|array $id A single or multiple modResource ids to build the
  759. * tree from.
  760. * @param int $depth The maximum depth to build the tree (default 10).
  761. * @param array $options An array of filtering options, such as 'context' to specify the context to grab from
  762. * @return array An array containing the tree structure.
  763. */
  764. public function getTree($id= null, $depth= 10, array $options = array()) {
  765. $tree= array ();
  766. if (!empty($options['context'])) {
  767. $this->getContext($options['context']);
  768. }
  769. if ($id !== null) {
  770. if (is_array ($id)) {
  771. foreach ($id as $k => $v) {
  772. $tree[$v] = $this->getTree($v, $depth - 1, $options);
  773. }
  774. } elseif ($branch= $this->getChildIds($id, 1, $options)) {
  775. foreach ($branch as $key => $child) {
  776. if ($depth > 0 && $leaf = $this->getTree($child, $depth - 1, $options)) {
  777. $tree[$child]= $leaf;
  778. } else {
  779. $tree[$child]= $child;
  780. }
  781. }
  782. }
  783. }
  784. return $tree;
  785. }
  786. /**
  787. * Sets a placeholder value.
  788. *
  789. * @param string $key The unique string key which identifies the
  790. * placeholder.
  791. * @param mixed $value The value to set the placeholder to.
  792. */
  793. public function setPlaceholder($key, $value) {
  794. if (is_string($key)) {
  795. $this->placeholders["{$key}"]= $value;
  796. }
  797. }
  798. /**
  799. * Sets a collection of placeholders stored in an array or as object vars.
  800. *
  801. * An optional namespace parameter can be prepended to each placeholder key in the collection,
  802. * to isolate the collection of placeholders.
  803. *
  804. * Note that unlike toPlaceholders(), this function does not add separators between the
  805. * namespace and the placeholder key. Use toPlaceholders() when working with multi-dimensional
  806. * arrays or objects with variables other than scalars so each level gets delimited by a
  807. * separator.
  808. *
  809. * @param array|object $placeholders An array of values or object to set as placeholders.
  810. * @param string $namespace A namespace prefix to prepend to each placeholder key.
  811. */
  812. public function setPlaceholders($placeholders, $namespace= '') {
  813. $this->toPlaceholders($placeholders, $namespace, '');
  814. }
  815. /**
  816. * Sets placeholders from values stored in arrays and objects.
  817. *
  818. * Each recursive level adds to the prefix, building an access path using an optional separator.
  819. *
  820. * @param array|object $subject An array or object to process.
  821. * @param string $prefix An optional prefix to be prepended to the placeholder keys. Recursive
  822. * calls prepend the parent keys.
  823. * @param string $separator A separator to place in between the prefixes and keys. Default is a
  824. * dot or period: '.'.
  825. * @param boolean $restore Set to true if you want overwritten placeholder values returned.
  826. * @return array A multi-dimensional array containing up to two elements: 'keys' which always
  827. * contains an array of placeholder keys that were set, and optionally, if the restore parameter
  828. * is true, 'restore' containing an array of placeholder values that were overwritten by the method.
  829. */
  830. public function toPlaceholders($subject, $prefix= '', $separator= '.', $restore= false) {
  831. $keys = array();
  832. $restored = array();
  833. if (is_object($subject)) {
  834. if ($subject instanceof xPDOObject) {
  835. $subject= $subject->toArray();
  836. } else {
  837. $subject= get_object_vars($subject);
  838. }
  839. }
  840. if (is_array($subject)) {
  841. foreach ($subject as $key => $value) {
  842. $rv = $this->toPlaceholder($key, $value, $prefix, $separator, $restore);
  843. if (isset($rv['keys'])) {
  844. foreach ($rv['keys'] as $rvKey) $keys[] = $rvKey;
  845. }
  846. if ($restore === true && isset($rv['restore'])) {
  847. $restored = array_merge($restored, $rv['restore']);
  848. }
  849. }
  850. }
  851. $return = array('keys' => $keys);
  852. if ($restore === true) $return['restore'] = $restored;
  853. return $return;
  854. }
  855. /**
  856. * Recursively validates and sets placeholders appropriate to the value type passed.
  857. *
  858. * @param string $key The key identifying the value.
  859. * @param mixed $value The value to set.
  860. * @param string $prefix A string prefix to prepend to the key. Recursive calls prepend the
  861. * parent keys as well.
  862. * @param string $separator A separator placed in between the prefix and the key. Default is a
  863. * dot or period: '.'.
  864. * @param boolean $restore Set to true if you want overwritten placeholder values returned.
  865. * @return array A multi-dimensional array containing up to two elements: 'keys' which always
  866. * contains an array of placeholder keys that were set, and optionally, if the restore parameter
  867. * is true, 'restore' containing an array of placeholder values that were overwritten by the method.
  868. */
  869. public function toPlaceholder($key, $value, $prefix= '', $separator= '.', $restore= false) {
  870. $return = array('keys' => array());
  871. if ($restore === true) $return['restore'] = array();
  872. if (!empty($prefix) && !empty($separator)) {
  873. $prefix .= $separator;
  874. }
  875. if (is_array($value) || is_object($value)) {
  876. $return = $this->toPlaceholders($value, "{$prefix}{$key}", $separator, $restore);
  877. } elseif (is_scalar($value)) {
  878. $return['keys'][] = "{$prefix}{$key}";
  879. if ($restore === true && array_key_exists("{$prefix}{$key}", $this->placeholders)) {
  880. $return['restore']["{$prefix}{$key}"] = $this->getPlaceholder("{$prefix}{$key}");
  881. }
  882. $this->setPlaceholder("{$prefix}{$key}", $value);
  883. }
  884. return $return;
  885. }
  886. /**
  887. * Get a placeholder value by key.
  888. *
  889. * @param string $key The key of the placeholder to a return a value from.
  890. * @return mixed The value of the requested placeholder, or an empty string if not located.
  891. */
  892. public function getPlaceholder($key) {
  893. $placeholder= null;
  894. if (is_string($key) && array_key_exists($key, $this->placeholders)) {
  895. $placeholder= & $this->placeholders["{$key}"];
  896. }
  897. return $placeholder;
  898. }
  899. /**
  900. * Unset a placeholder value by key.
  901. *
  902. * @param string $key The key of the placeholder to unset.
  903. */
  904. public function unsetPlaceholder($key) {
  905. if (is_string($key) && array_key_exists($key, $this->placeholders)) {
  906. unset($this->placeholders[$key]);
  907. }
  908. }
  909. /**
  910. * Unset multiple placeholders, either by prefix or an array of keys.
  911. *
  912. * @param string|array $keys A string prefix or an array of keys indicating
  913. * the placeholders to unset.
  914. */
  915. public function unsetPlaceholders($keys) {
  916. if (is_array($keys)) {
  917. foreach ($keys as $key) {
  918. if (is_string($key)) $this->unsetPlaceholder($key);
  919. if (is_array($key)) $this->unsetPlaceholders($key);
  920. }
  921. } elseif (is_string($keys)) {
  922. $placeholderKeys = array_keys($this->placeholders);
  923. foreach ($placeholderKeys as $key) {
  924. if (strpos($key, $keys) === 0) $this->unsetPlaceholder($key);
  925. }
  926. }
  927. }
  928. /**
  929. * Returns the full table name (with dynamic prefix) based on database settings.
  930. * Legacy - Useful when dealing with migrations or prefixed database tables without an xPDO model (which xPDO.getTableName requires.)
  931. *
  932. * @param string $table Name of MODX table, less table prefix.
  933. * @return string Full table name containing database and table prefix.
  934. */
  935. public function getFullTableName( $table = '' ) {
  936. return $this->getOption('dbname') .".". $this->getOption( xPDO::OPT_TABLE_PREFIX ) . $table;
  937. }
  938. /**
  939. * Generates a URL representing a specified resource.
  940. *
  941. * @param integer $id The id of a resource.
  942. * @param string $context Specifies a context to limit URL generation to.
  943. * @param string $args A query string to append to the generated URL.
  944. * @param mixed $scheme The scheme indicates in what format the URL is generated.<br>
  945. * <pre>
  946. * -1 : (default value) URL is relative to site_url
  947. * 0 : see http
  948. * 1 : see https
  949. * full : URL is absolute, prepended with site_url from config
  950. * abs : URL is absolute, prepended with base_url from config
  951. * http : URL is absolute, forced to http scheme
  952. * https : URL is absolute, forced to https scheme
  953. * </pre>
  954. * @param array $options An array of options for generating the Resource URL.
  955. * @return string The URL for the resource.
  956. */
  957. public function makeUrl($id, $context= '', $args= '', $scheme= -1, array $options= array()) {
  958. $url= '';
  959. if ($validid = intval($id)) {
  960. $id = $validid;
  961. if ($context == '' || $this->context->get('key') == $context) {
  962. $url= $this->context->makeUrl($id, $args, $scheme, $options);
  963. }
  964. if (empty($url) && ($context !== $this->context->get('key'))) {
  965. $ctx= null;
  966. if ($context == '') {
  967. /** @var PDOStatement $stmt */
  968. if ($stmt = $this->prepare("SELECT context_key FROM " . $this->getTableName('modResource') . " WHERE id = :id")) {
  969. $stmt->bindValue(':id', $id);
  970. if ($contextKey = $this->getValue($stmt)) {
  971. $ctx = $this->getContext($contextKey);
  972. }
  973. }
  974. } else {
  975. $ctx = $this->getContext($context);
  976. }
  977. if ($ctx) {
  978. $url= $ctx->makeUrl($id, $args, 'full', $options);
  979. }
  980. }
  981. if (!empty($url) && $this->getOption('xhtml_urls', $options, false)) {
  982. $url= preg_replace("/&(?!amp;)/","&amp;", $url);
  983. }
  984. } else {
  985. $this->log(modX::LOG_LEVEL_ERROR, '`' . $id . '` is not a valid integer and may not be passed to makeUrl()');
  986. }
  987. return $url;
  988. }
  989. /**
  990. * Filter a string for use as a URL path segment.
  991. *
  992. * @param string $string The string to filter into a valid path segment.
  993. * @param array $options Optional filter setting overrides.
  994. *
  995. * @return string|null A valid path segment string or null if an error occurs.
  996. */
  997. public function filterPathSegment($string, array $options = array()) {
  998. return $this->call('modResource', 'filterPathSegment', array(&$this, $string, $options));
  999. }
  1000. public function findResource($uri, $context = '') {
  1001. $resourceId = false;
  1002. if (empty($context) && isset($this->context)) $context = $this->context->get('key');
  1003. if (!empty($context) && (!empty($uri) || $uri === '0')) {
  1004. $useAliasMap = (boolean) $this->getOption('cache_alias_map', null, false);
  1005. if ($useAliasMap) {
  1006. if (isset($this->context) && $this->context->get('key') === $context && is_array($this->aliasMap) && array_key_exists($uri, $this->aliasMap)) {
  1007. $resourceId = (integer) $this->aliasMap[$uri];
  1008. } elseif ($ctx = $this->getContext($context)) {
  1009. $useAliasMap = $ctx->getOption('cache_alias_map', false) && is_array($ctx->aliasMap) && array_key_exists($uri, $ctx->aliasMap);
  1010. if ($useAliasMap && array_key_exists($uri, $ctx->aliasMap)) {
  1011. $resourceId = (integer) $ctx->aliasMap[$uri];
  1012. }
  1013. }
  1014. }
  1015. if (!$resourceId && !$useAliasMap) {
  1016. $query = $this->newQuery('modResource', array('context_key' => $context, 'uri' => $uri, 'deleted' => false));
  1017. $query->select($this->getSelectColumns('modResource', '', '', array('id')));
  1018. $stmt = $query->prepare();
  1019. if ($stmt) {
  1020. $value = $this->getValue($stmt);
  1021. if ($value) {
  1022. $resourceId = $value;
  1023. }
  1024. }
  1025. }
  1026. }
  1027. return $resourceId;
  1028. }
  1029. /**
  1030. * Send the user to a type-specific core error page and halt PHP execution.
  1031. *
  1032. * @param string $type The type of error to present.
  1033. * @param array $options An array of options to provide for the error file.
  1034. */
  1035. public function sendError($type = '', $options = array()) {
  1036. if (!is_string($type) || empty($type)) $type = $this->getOption('error_type', $options, 'unavailable');
  1037. while (ob_get_level() && @ob_end_clean()) {}
  1038. if (!XPDO_CLI_MODE) {
  1039. $errorPageTitle = $this->getOption('error_pagetitle', $options, 'Error 503: Service temporarily unavailable');
  1040. $errorMessage = $this->getOption('error_message', $options, '<p>Site temporarily unavailable.</p>');
  1041. $errorHeader = $this->getOption('error_header', $options, $_SERVER['SERVER_PROTOCOL'] . ' 503 Service Unavailable');
  1042. if (file_exists(MODX_CORE_PATH . "error/{$type}.include.php")) {
  1043. @include(MODX_CORE_PATH . "error/{$type}.include.php");
  1044. }
  1045. header($errorHeader);
  1046. echo "<html><head><title>{$errorPageTitle}</title></head><body>{$errorMessage}</body></html>";
  1047. @session_write_close();
  1048. } else {
  1049. echo ucfirst($type) . "\n";
  1050. echo $this->getOption('error_message', $options, 'Service temporarily unavailable') . "\n";
  1051. }
  1052. exit();
  1053. }
  1054. /**
  1055. * Sends a redirect to the specified URL using the specified options.
  1056. *
  1057. * Valid 'type' option values include:
  1058. * REDIRECT_REFRESH Uses the header refresh method
  1059. * REDIRECT_META Sends a a META HTTP-EQUIV="Refresh" tag to the output
  1060. * REDIRECT_HEADER Uses the header location method
  1061. *
  1062. * REDIRECT_HEADER is the default.
  1063. *
  1064. * @param string $url The URL to redirect the client browser to.
  1065. * @param array|boolean $options An array of options for the redirect OR
  1066. * indicates if redirect attempts should be counted and limited to 3 (latter is deprecated
  1067. * usage; use count_attempts in options array).
  1068. * @param string $type The type of redirection to attempt (deprecated, use type in
  1069. * options array).
  1070. * @param string $responseCode The type of HTTP response code HEADER to send for the
  1071. * redirect (deprecated, use responseCode in options array)
  1072. */
  1073. public function sendRedirect($url, $options= false, $type= '', $responseCode = '') {
  1074. if (!$this->getResponse()) {
  1075. $this->log(modX::LOG_LEVEL_FATAL, "Could not load response class.");
  1076. }
  1077. if (!is_array($options)) {
  1078. $options = array('count_attempts' => (boolean) $options);
  1079. }
  1080. if ($type) {
  1081. $this->deprecated('2.0.5', 'Use type in options array instead.', 'sendRedirect method parameter $type');
  1082. $options['type'] = $type;
  1083. $type = '';
  1084. }
  1085. if ($responseCode) {
  1086. $this->deprecated('2.0.5', 'Use responseCode in options array instead.', 'sendRedirect method parameter $responseCode');
  1087. $options['responseCode'] = $responseCode;
  1088. $responseCode = '';
  1089. }
  1090. $this->response->sendRedirect($url, $options, $type, $responseCode);
  1091. }
  1092. /**
  1093. * Forwards the request to another resource without changing the URL.
  1094. *
  1095. * @param integer $id The resource identifier.
  1096. * @param string $options An array of options for the process.
  1097. * @param boolean $sendErrorPage Whether we should skip the sendErrorPage if the resource does not exist.
  1098. */
  1099. public function sendForward($id, $options = null, $sendErrorPage = true) {
  1100. if (!$this->getRequest()) {
  1101. $this->log(modX::LOG_LEVEL_FATAL, "Could not load request class.");
  1102. }
  1103. $idInt = intval($id);
  1104. if (is_string($options) && !empty($options)) {
  1105. $options = array('response_code' => $options);
  1106. } elseif (!is_array($options)) {
  1107. $options = array();
  1108. }
  1109. $this->elementCache = array();
  1110. if ($idInt > 0) {
  1111. $merge = array_key_exists('merge', $options) && !empty($options['merge']);
  1112. $currentResource = array();
  1113. if ($merge) {
  1114. $excludes = array_merge(
  1115. explode(',', $this->getOption('forward_merge_excludes', $options, 'type,published,class_key')),
  1116. array(
  1117. 'content'
  1118. ,'pub_date'
  1119. ,'unpub_date'
  1120. ,'richtext'
  1121. ,'_content'
  1122. ,'_processed'
  1123. )
  1124. );
  1125. if (!empty($this->resource->_fields)) {
  1126. foreach ($this->resource->_fields as $fkey => $fval) {
  1127. if (!in_array($fkey, $excludes)) {
  1128. if (is_scalar($fval) && $fval !== '') {
  1129. $currentResource[$fkey] = $fval;
  1130. } elseif (is_array($fval) && count($fval) === 5 && $fval[1] !== '') {
  1131. $currentResource[$fkey] = $fval;
  1132. }
  1133. }
  1134. }
  1135. }
  1136. }
  1137. $this->resource= $this->request->getResource('id', $idInt, array('forward' => true));
  1138. if ($this->resource) {
  1139. if ($merge && !empty($currentResource)) {
  1140. $this->resource->_fields = array_merge($this->resource->_fields, $currentResource);
  1141. $this->elementCache = array();
  1142. unset($currentResource);
  1143. }
  1144. $this->resourceIdentifier= $this->resource->get('id');
  1145. $this->resourceMethod= 'id';
  1146. if (isset($options['response_code']) && !empty($options['response_code'])) {
  1147. header($options['response_code']);
  1148. }
  1149. $this->request->prepareResponse();
  1150. exit();
  1151. } elseif ($sendErrorPage) {
  1152. $this->sendErrorPage();
  1153. }
  1154. $options= array_merge(
  1155. array(
  1156. 'error_type' => '404'
  1157. ,'error_header' => $this->getOption('error_page_header', $options,$_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found')
  1158. ,'error_pagetitle' => $this->getOption('error_page_pagetitle', $options,'Error 404: Page not found')
  1159. ,'error_message' => $this->getOption('error_page_message', $options,'<h1>Page not found</h1><p>The page you requested was not found.</p>')
  1160. ),
  1161. $options
  1162. );
  1163. }
  1164. $this->sendError($id, $options);
  1165. }
  1166. /**
  1167. * Send the user to a MODX virtual error page.
  1168. *
  1169. * @uses invokeEvent() The OnPageNotFound event is invoked before the error page is forwarded
  1170. * to.
  1171. * @param array $options An array of options to provide for the OnPageNotFound event and error
  1172. * page.
  1173. */
  1174. public function sendErrorPage($options = null) {
  1175. if (!is_array($options)) $options = array();
  1176. $options= array_merge(
  1177. array(
  1178. 'response_code' => $this->getOption('error_page_header', $options, $_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found')
  1179. ,'error_type' => '404'
  1180. ,'error_header' => $this->getOption('error_page_header', $options, $_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found')
  1181. ,'error_pagetitle' => $this->getOption('error_page_pagetitle', $options, 'Error 404: Page not found')
  1182. ,'error_message' => $this->getOption('error_page_message', $options, '<h1>Page not found</h1><p>The page you requested was not found.</p>')
  1183. ),
  1184. $options
  1185. );
  1186. $this->invokeEvent('OnPageNotFound', $options);
  1187. $this->sendForward($this->getOption('error_page', $options, $this->getOption('site_start')), $options, false);
  1188. }
  1189. /**
  1190. * Send the user to the MODX unauthorized page.
  1191. *
  1192. * @uses invokeEvent() The OnPageUnauthorized event is invoked before the unauthorized page is
  1193. * forwarded to.
  1194. * @param array $options An array of options to provide for the OnPageUnauthorized
  1195. * event and unauthorized page.
  1196. */
  1197. public function sendUnauthorizedPage($options = null) {
  1198. if (!is_array($options)) $options = array();
  1199. $options= array_merge(
  1200. array(
  1201. 'response_code' => $this->getOption('unauthorized_page_header' ,$options ,$_SERVER['SERVER_PROTOCOL'] . ' 401 Unauthorized')
  1202. ,'error_type' => '401'
  1203. ,'error_header' => $this->getOption('unauthorized_page_header', $options,$_SERVER['SERVER_PROTOCOL'] . ' 401 Unauthorized')
  1204. ,'error_pagetitle' => $this->getOption('unauthorized_page_pagetitle',$options, 'Error 401: Unauthorized')
  1205. ,'error_message' => $this->getOption('unauthorized_page_message', $options,'<h1>Unauthorized</h1><p>You are not authorized to view the requested content.</p>')
  1206. ),
  1207. $options
  1208. );
  1209. $this->invokeEvent('OnPageUnauthorized', $options);
  1210. $this->sendForward($this->getOption('unauthorized_page', $options, $this->getOption('site_start')), $options);
  1211. }
  1212. /**
  1213. * Get the current authenticated User and assign it to the modX instance.
  1214. *
  1215. * @param string $contextKey An optional context to get the user from.
  1216. * @param boolean $forceLoadSettings If set to true, will load settings
  1217. * regardless of whether the user has an authenticated context or not.
  1218. * @return modUser The user object authenticated for the request.
  1219. */
  1220. public function getUser($contextKey= '',$forceLoadSettings = false) {
  1221. if ($contextKey == '') {
  1222. if ($this->context !== null) {
  1223. $contextKey= $this->context->get('key');
  1224. }
  1225. }
  1226. if ($this->user === null || !is_object($this->user)) {
  1227. $this->user= $this->getAuthenticatedUser($contextKey);
  1228. if ($contextKey !== 'mgr' && !$this->user) {
  1229. $this->user= $this->getAuthenticatedUser('mgr');
  1230. }
  1231. }
  1232. if ($this->user !== null && is_object($this->user)) {
  1233. if ($this->user->hasSessionContext($contextKey) || $forceLoadSettings) {
  1234. if (!$forceLoadSettings && isset ($_SESSION["modx.{$contextKey}.user.config"])) {
  1235. $this->_userConfig= $_SESSION["modx.{$contextKey}.user.config"];
  1236. } else {
  1237. $this->_userConfig= array();
  1238. $settings= $this->user->getSettings();
  1239. if (is_array($settings) && !empty ($settings)) {
  1240. foreach ($settings as $k => $v) {
  1241. $matches= array();
  1242. if (preg_match_all('~\{(.*?)\}~', $v, $matches, PREG_SET_ORDER)) {
  1243. foreach ($matches as $match) {
  1244. if (isset($this->_userConfig["{$match[1]}"])) {
  1245. $matchValue= $this->_userConfig["{$match[1]}"];
  1246. } elseif (isset($this->config["{$match[1]}"])) {
  1247. $matchValue= $this->config["{$match[1]}"];
  1248. } else {
  1249. $matchValue= '';
  1250. }
  1251. $v= str_replace($match[0], $matchValue, $v);
  1252. }
  1253. }
  1254. $this->_userConfig[$k]= $v;
  1255. }
  1256. }
  1257. $_SESSION["modx.{$contextKey}.user.config"]= $this->_userConfig;
  1258. }
  1259. if (is_array($this->_userConfig) && !empty($this->_userConfig)) {
  1260. $this->config= array_merge($this->config, $this->_userConfig);
  1261. }
  1262. }
  1263. } else {
  1264. $this->user = $this->newObject('modUser');
  1265. $this->user->fromArray(array(
  1266. 'id' => 0,
  1267. 'username' => $this->getOption('default_username','','(anonymous)',true)
  1268. ), '', true);
  1269. }
  1270. ksort($this->config);
  1271. $this->toPlaceholders($this->user->get(array('id','username')),'modx.user');
  1272. return $this->user;
  1273. }
  1274. /**
  1275. * Gets the user authenticated in the specified context.
  1276. *
  1277. * @param string $contextKey Optional context key; uses current context by default.
  1278. * @return modUser|null The user object that is authenticated in the specified context,
  1279. * or null if no user is authenticated.
  1280. */
  1281. public function getAuthenticatedUser($contextKey= '') {
  1282. $user= null;
  1283. if ($contextKey == '') {
  1284. if ($this->context !== null) {
  1285. $contextKey= $this->context->get('key');
  1286. }
  1287. }
  1288. if ($contextKey && isset ($_SESSION['modx.user.contextTokens'][$contextKey])) {
  1289. $user= $this->getObject('modUser', intval($_SESSION['modx.user.contextTokens'][$contextKey]), true);
  1290. if ($user) {
  1291. $user->getSessionContexts();
  1292. }
  1293. }
  1294. return $user;
  1295. }
  1296. /**
  1297. * Checks to see if the user has a session in the specified context.
  1298. *
  1299. * @param string $sessionContext The context to test for a session key in.
  1300. * @return boolean True if the user is valid in the context specified.
  1301. */
  1302. public function checkSession($sessionContext= 'web') {
  1303. $hasSession = false;
  1304. if ($this->user !== null) {
  1305. $hasSession = $this->user->hasSessionContext($sessionContext);
  1306. }
  1307. return $hasSession;
  1308. }
  1309. /**
  1310. * Gets the modX core version data.
  1311. *
  1312. * @return array The version data loaded from the config version file.
  1313. */
  1314. public function getVersionData() {
  1315. if ($this->version === null) {
  1316. $this->version= @ include_once MODX_CORE_PATH . "docs/version.inc.php";
  1317. }
  1318. return $this->version;
  1319. }
  1320. /**
  1321. * Reload the config settings.
  1322. *
  1323. * @return array An associative array of configuration key/values
  1324. */
  1325. public function reloadConfig() {
  1326. $this->getCacheManager();
  1327. $this->cacheManager->refresh();
  1328. if (!$this->_loadConfig()) {
  1329. $this->log(modX::LOG_LEVEL_ERROR, 'Could not reload core MODX configuration!');
  1330. }
  1331. return $this->config;
  1332. }
  1333. /**
  1334. * Get the configuration for the site.
  1335. *
  1336. * @return array An associate array of configuration key/values
  1337. */
  1338. public function getConfig() {
  1339. if (!$this->_initialized || !is_array($this->config) || empty ($this->config)) {
  1340. if (!isset ($this->config['base_url']))
  1341. $this->config['base_url']= MODX_BASE_URL;
  1342. if (!isset ($this->config['base_path']))
  1343. $this->config['base_path']= MODX_BASE_PATH;
  1344. if (!isset ($this->config['core_path']))
  1345. $this->config['core_path']= MODX_CORE_PATH;
  1346. if (!isset ($this->config['url_scheme']))
  1347. $this->config['url_scheme']= MODX_URL_SCHEME;
  1348. if (!isset ($this->config['http_host']))
  1349. $this->config['http_host']= MODX_HTTP_HOST;
  1350. if (!isset ($this->config['site_url']))
  1351. $this->config['site_url']= MODX_SITE_URL;
  1352. if (!isset ($this->config['manager_path']))
  1353. $this->config['manager_path']= MODX_MANAGER_PATH;
  1354. if (!isset ($this->config['manager_url']))
  1355. $this->config['manager_url']= MODX_MANAGER_URL;
  1356. if (!isset ($this->config['assets_path']))
  1357. $this->config['assets_path']= MODX_ASSETS_PATH;
  1358. if (!isset ($this->config['assets_url']))
  1359. $this->config['assets_url']= MODX_ASSETS_URL;
  1360. if (!isset ($this->config['connectors_path']))
  1361. $this->config['connectors_path']= MODX_CONNECTORS_PATH;
  1362. if (!isset ($this->config['connectors_url']))
  1363. $this->config['connectors_url']= MODX_CONNECTORS_URL;
  1364. if (!isset ($this->config['connector_url']))
  1365. $this->config['connector_url']= MODX_CONNECTORS_URL . 'index.php';
  1366. if (!isset ($this->config['processors_path']))
  1367. $this->config['processors_path']= MODX_PROCESSORS_PATH;
  1368. if (!isset ($this->config['request_param_id']))
  1369. $this->config['request_param_id']= 'id';
  1370. if (!isset ($this->config['request_param_alias']))
  1371. $this->config['request_param_alias']= 'q';
  1372. if (!isset ($this->config['https_port']))
  1373. $this->config['https_port']= isset($GLOBALS['https_port']) ? $GLOBALS['https_port'] : 443;
  1374. if (!isset ($this->config['error_handler_class']))
  1375. $this->config['error_handler_class']= 'error.modErrorHandler';
  1376. if (!isset ($this->config['server_port']))
  1377. $this->config['server_port']= isset($_SERVER['SERVER_PORT']) ? $_SERVER['SERVER_PORT'] : '';
  1378. $this->_config= $this->config;
  1379. if (!$this->_loadConfig()) {
  1380. $this->log(modX::LOG_LEVEL_FATAL, "Could not load core MODX configuration!");
  1381. return null;
  1382. }
  1383. }
  1384. return $this->config;
  1385. }
  1386. /**
  1387. * Initialize, cleanse, and process a request made to a modX site.
  1388. *
  1389. * @return mixed The result of the request handler.
  1390. */
  1391. public function handleRequest() {
  1392. if ($this->getRequest()) {
  1393. return $this->request->handleRequest();
  1394. }
  1395. return '';
  1396. }
  1397. /**
  1398. * Attempt to load the request handler class, if not already loaded.
  1399. *
  1400. * @access public
  1401. * @param string $class The class name of the response class to load. Defaults to
  1402. * modRequest; is ignored if the Setting "modRequest.class" is set.
  1403. * @param string $path The absolute path by which to load the response class from.
  1404. * Defaults to the current MODX model path.
  1405. * @return boolean Returns true if a valid request handler object was
  1406. * loaded on this or any previous call to the function, false otherwise.
  1407. */
  1408. public function getRequest($class= 'modRequest', $path= '') {
  1409. if ($this->request === null || !($this->request instanceof modRequest)) {
  1410. $requestClass = $this->getOption('modRequest.class',$this->config,$class);
  1411. if ($requestClass !== $class) {
  1412. $this->loadClass('modRequest', '', false, true);
  1413. }
  1414. if ($className= $this->loadClass($requestClass, $path, !empty($path), true))
  1415. $this->request= new $className ($this);
  1416. }
  1417. return is_object($this->request) && $this->request instanceof modRequest;
  1418. }
  1419. /**
  1420. * Attempt to load the response handler class, if not already loaded.
  1421. *
  1422. * @access public
  1423. * @param string $class The class name of the response class to load. Defaults to
  1424. * modResponse; is ignored if the Setting "modResponse.class" is set.
  1425. * @param string $path The absolute path by which to load the response class from.
  1426. * Defaults to the current MODX model path.
  1427. * @return boolean Returns true if a valid response handler object was
  1428. * loaded on this or any previous call to the function, false otherwise.
  1429. */
  1430. public function getResponse($class= 'modResponse', $path= '') {
  1431. $responseClass= $this->getOption('modResponse.class',$this->config,$class);
  1432. $className= $this->loadClass($responseClass, $path, !empty($path), true);
  1433. if ($this->response === null || !($this->response instanceof $className)) {
  1434. if ($className) $this->response= new $className ($this);
  1435. }
  1436. return $this->response instanceof $className;
  1437. }
  1438. /**
  1439. * Register CSS to be injected inside the HEAD tag of a resource.
  1440. *
  1441. * @param string $src The CSS to be injected before the closing HEAD tag in
  1442. * an HTML response.
  1443. * @param string $media all, aural, braille, embossed, handheld, print, projection, screen, tty, tv
  1444. * @return void
  1445. */
  1446. public function regClientCSS($src, $media = null) {
  1447. if (isset ($this->loadedjscripts[$src]) && $this->loadedjscripts[$src]) {
  1448. return;
  1449. }
  1450. $this->loadedjscripts[$src]= true;
  1451. if (strpos(strtolower($src), "<style") !== false || strpos(strtolower($src), "<link") !== false) {
  1452. $this->sjscripts[count($this->sjscripts)]= $src;
  1453. } else {
  1454. if (!empty($media)) {
  1455. $media = ' media="' . $media .'"';
  1456. }
  1457. $this->sjscripts[count($this->sjscripts)]= '<link rel="stylesheet" href="' . $src . '" type="text/css"' . $media . ' />';
  1458. }
  1459. }
  1460. /**
  1461. * Register JavaScript to be injected inside the HEAD tag of a resource.
  1462. *
  1463. * @param string $src The JavaScript to be injected before the closing HEAD
  1464. * tag of an HTML response.
  1465. * @param boolean $plaintext Optional param to treat the $src as plaintext
  1466. * rather than assuming it is JavaScript.
  1467. * @return void
  1468. */
  1469. public function regClientStartupScript($src, $plaintext= false) {
  1470. if (!empty ($src) && !array_key_exists($src, $this->loadedjscripts)) {
  1471. if (isset ($this->loadedjscripts[$src]))
  1472. return;
  1473. $this->loadedjscripts[$src]= true;
  1474. if ($plaintext == true) {
  1475. $this->sjscripts[count($this->sjscripts)]= $src;
  1476. } elseif (strpos(strtolower($src), "<script") !== false) {
  1477. $this->sjscripts[count($this->sjscripts)]= $src;
  1478. } else {
  1479. $this->sjscripts[count($this->sjscripts)]= '<script type="text/javascript" src="' . $src . '"></script>';
  1480. }
  1481. }
  1482. }
  1483. /**
  1484. * Register JavaScript to be injected before the closing BODY tag.
  1485. *
  1486. * @param string $src The JavaScript to be injected before the closing BODY
  1487. * tag in an HTML response.
  1488. * @param boolean $plaintext Optional param to treat the $src as plaintext
  1489. * rather than assuming it is JavaScript.
  1490. * @return void
  1491. */
  1492. public function regClientScript($src, $plaintext= false) {
  1493. if (isset ($this->loadedjscripts[$src]))
  1494. return;
  1495. $this->loadedjscripts[$src]= true;
  1496. if ($plaintext == true) {
  1497. $this->jscripts[count($this->jscripts)]= $src;
  1498. } elseif (strpos(strtolower($src), "<script") !== false) {
  1499. $this->jscripts[count($this->jscripts)]= $src;
  1500. } else {
  1501. $this->jscripts[count($this->jscripts)]= '<script type="text/javascript" src="' . $src . '"></script>';
  1502. }
  1503. }
  1504. /**
  1505. * Register HTML to be injected before the closing HEAD tag.
  1506. *
  1507. * @param string $html The HTML to be injected.
  1508. */
  1509. public function regClientStartupHTMLBlock($html) {
  1510. return $this->regClientStartupScript($html, true);
  1511. }
  1512. /**
  1513. * Register HTML to be injected before the closing BODY tag.
  1514. *
  1515. * @param string $html The HTML to be injected.
  1516. */
  1517. public function regClientHTMLBlock($html) {
  1518. return $this->regClientScript($html, true);
  1519. }
  1520. /**
  1521. * Returns all registered JavaScripts.
  1522. *
  1523. * @access public
  1524. * @return string The parsed HTML of the client scripts.
  1525. */
  1526. public function getRegisteredClientScripts() {
  1527. $string= '';
  1528. if (is_array($this->jscripts)) {
  1529. $string= implode("\n",$this->jscripts);
  1530. }
  1531. return $string;
  1532. }
  1533. /**
  1534. * Returns all registered startup CSS, JavaScript, or HTML blocks.
  1535. *
  1536. * @access public
  1537. * @return string The parsed HTML of the startup scripts.
  1538. */
  1539. public function getRegisteredClientStartupScripts() {
  1540. $string= '';
  1541. if (is_array ($this->sjscripts)) {
  1542. $string= implode("\n", $this->sjscripts);
  1543. }
  1544. return $string;
  1545. }
  1546. /**
  1547. * Invokes a specified Event with an optional array of parameters.
  1548. *
  1549. * @todo refactor this completely, yuck!!
  1550. *
  1551. * @access public
  1552. * @param string $eventName Name of an event to invoke.
  1553. * @param array $params Optional params provided to the elements registered with an event.
  1554. * @return bool|array
  1555. */
  1556. public function invokeEvent($eventName, array $params= array ()) {
  1557. if (!$eventName)
  1558. return false;
  1559. if ($this->eventMap === null && $this->context instanceof modContext)
  1560. $this->_initEventMap($this->context->get('key'));
  1561. if (!isset ($this->eventMap[$eventName])) {
  1562. //$this->log(modX::LOG_LEVEL_DEBUG,'System event '.$eventName.' was executed but does not exist.');
  1563. return false;
  1564. }
  1565. $results= array ();
  1566. if (count($this->eventMap[$eventName])) {
  1567. $this->event= new modSystemEvent();
  1568. foreach ($this->eventMap[$eventName] as $pluginId => $pluginPropset) {
  1569. $plugin= null;
  1570. if (!version_compare(PHP_VERSION, '5.4', '>=')) {
  1571. $this->Event = & $this->event;
  1572. } else {
  1573. $this->Event = clone $this->event;
  1574. }
  1575. $this->event->resetEventObject();
  1576. $this->event->name= $eventName;
  1577. if (isset ($this->pluginCache[$pluginId])) {
  1578. $plugin= $this->newObject('modPlugin');
  1579. $plugin->fromArray($this->pluginCache[$pluginId], '', true, true);
  1580. $plugin->_processed = false;
  1581. if ($plugin->get('disabled')) {
  1582. $plugin= null;
  1583. }
  1584. } else {
  1585. $plugin= $this->getObject('modPlugin', array ('id' => intval($pluginId), 'disabled' => '0'), true);
  1586. }
  1587. if ($plugin && !$plugin->get('disabled')) {
  1588. $this->event->plugin =& $plugin;
  1589. $this->event->activated= true;
  1590. $this->event->activePlugin= $plugin->get('name');
  1591. $this->event->propertySet= (($pspos = strpos($pluginPropset, ':')) >= 1) ? substr($pluginPropset, $pspos + 1) : '';
  1592. /* merge in plugin properties */
  1593. $eventParams = array_merge($plugin->getProperties(),$params);
  1594. $msg= $plugin->process($eventParams);
  1595. $results[]= $this->event->_output;
  1596. if ($msg && is_string($msg)) {
  1597. $this->log(modX::LOG_LEVEL_ERROR, '[' . $this->event->name . ']' . $msg);
  1598. } elseif ($msg === false) {
  1599. $this->log(modX::LOG_LEVEL_ERROR, '[' . $this->event->name . '] Plugin ' . $plugin->name . ' failed!');
  1600. }
  1601. $this->event->plugin = null;
  1602. $this->event->activePlugin= '';
  1603. $this->event->propertySet= '';
  1604. if (!$this->event->isPropagatable()) {
  1605. break;
  1606. }
  1607. }
  1608. }
  1609. }
  1610. return $results;
  1611. }
  1612. /**
  1613. * Loads and runs a specific processor.
  1614. *
  1615. * @param string $action The processor to run, eg: context/update
  1616. * @param array $scriptProperties Optional. An array of parameters to pass to the processor.
  1617. * @param array $options Optional. An array of options for running the processor, such as:
  1618. *
  1619. * - processors_path - If specified, will override the default MODX processors path.
  1620. * - location - A prefix to load processor files from, will prepend to the action parameter
  1621. * (Note: location will be deprecated in future Revolution versions.)
  1622. *
  1623. * @return mixed The result of the processor.
  1624. */
  1625. public function runProcessor($action = '',$scriptProperties = array(),$options = array()) {
  1626. if (!$this->loadClass('modProcessor','',false,true)) {
  1627. $this->log(modX::LOG_LEVEL_ERROR,'Could not load modProcessor class.');
  1628. return false;
  1629. }
  1630. $result = null;
  1631. /* backwards compat for $options['action']
  1632. * @deprecated Removing in 2.2
  1633. */
  1634. if (empty($action)) {
  1635. if (!empty($options['action'])) {
  1636. $action = $options['action'];
  1637. } else {
  1638. return $result;
  1639. }
  1640. }
  1641. /* calculate processor file path from options and action */
  1642. $isClass = true;
  1643. $processorsPath = isset($options['processors_path']) && !empty($options['processors_path']) ? $options['processors_path'] : $this->config['processors_path'];
  1644. if (isset($options['location']) && !empty($options['location'])) $processorsPath .= ltrim($options['location'],'/') . '/';
  1645. // Prevent path traversal through the action
  1646. $action = preg_replace('/[\.]{2,}/', '', htmlspecialchars($action));
  1647. // Find the processor file, preferring class based processors over old-style processors
  1648. $processorFile = $processorsPath.ltrim($action . '.class.php','/');
  1649. if (!file_exists($processorFile)) {
  1650. $processorFile = $processorsPath.ltrim($action . '.php','/');
  1651. $isClass = false;
  1652. }
  1653. // Prepare a response
  1654. $response = '';
  1655. if (file_exists($processorFile)) {
  1656. if (!isset($this->lexicon)) $this->getService('lexicon', 'modLexicon');
  1657. if (!isset($this->error)) $this->getService('error', 'error.modError');
  1658. if ($isClass) {
  1659. /* ensure processor file is only included once if run multiple times in a request */
  1660. if (!array_key_exists($processorFile,$this->processors)) {
  1661. $className = include_once $processorFile;
  1662. /* handle already included core classes */
  1663. if ($className == 1) {
  1664. $s = explode('/',$action);
  1665. $o = array();
  1666. foreach ($s as $k) {
  1667. $o[] = ucfirst(str_replace(array('.','_','-'),'',$k));
  1668. }
  1669. $className = 'mod'.implode('',$o).'Processor';
  1670. }
  1671. $this->processors[$processorFile] = $className;
  1672. } else {
  1673. $className = $this->processors[$processorFile];
  1674. }
  1675. if (!empty($className)) {
  1676. $processor = call_user_func_array(array($className,'getInstance'),array(&$this,$className,$scriptProperties));
  1677. }
  1678. }
  1679. if (empty($processor)) {
  1680. $processor = new modDeprecatedProcessor($this, $scriptProperties);
  1681. }
  1682. $processor->setPath($processorFile);
  1683. $response = $processor->run();
  1684. } else {
  1685. $this->log(modX::LOG_LEVEL_ERROR, "Processor {$processorFile} does not exist; " . print_r($options, true));
  1686. }
  1687. return $response;
  1688. }
  1689. /**
  1690. * Returns the current user ID, for the current or specified context.
  1691. *
  1692. * @param string $context The key of a valid modContext so you can retrieve
  1693. * the current user ID from a different context than the current.
  1694. * @return integer The ID of the current user.
  1695. */
  1696. public function getLoginUserID($context= '') {
  1697. $userId = 0;
  1698. if (empty($context) && $this->context instanceof modContext && $this->user instanceof modUser) {
  1699. if ($this->user->hasSessionContext($this->context->get('key'))) {
  1700. $userId = $this->user->get('id');
  1701. }
  1702. } else {
  1703. $user = $this->getAuthenticatedUser($context);
  1704. if ($user instanceof modUser) {
  1705. $userId = $user->get('id');
  1706. }
  1707. }
  1708. return $userId;
  1709. }
  1710. /**
  1711. * Returns the current user name, for the current or specified context.
  1712. *
  1713. * @param string $context The key of a valid modContext so you can retrieve
  1714. * the username from a different context than the current.
  1715. * @return string The username of the current user.
  1716. */
  1717. public function getLoginUserName($context= '') {
  1718. $userName = '';
  1719. if (empty($context) && $this->context instanceof modContext && $this->user instanceof modUser) {
  1720. if ($this->user->hasSessionContext($this->context->get('key'))) {
  1721. $userName = $this->user->get('username');
  1722. }
  1723. } else {
  1724. $user = $this->getAuthenticatedUser($context);
  1725. if ($user instanceof modUser) {
  1726. $userName = $user->get('username');
  1727. }
  1728. }
  1729. return $userName;
  1730. }
  1731. /**
  1732. * Returns whether modX instance has been initialized or not.
  1733. *
  1734. * @access public
  1735. * @return boolean
  1736. */
  1737. public function isInitialized() {
  1738. return $this->_initialized;
  1739. }
  1740. /**
  1741. * Legacy fatal error message.
  1742. *
  1743. * @deprecated
  1744. * @param string $msg
  1745. * @param string $query
  1746. * @param bool $is_error
  1747. * @param string $nr
  1748. * @param string $file
  1749. * @param string $source
  1750. * @param string $text
  1751. * @param string $line
  1752. */
  1753. public function messageQuit($msg='unspecified error', $query='', $is_error=true, $nr='', $file='', $source='', $text='', $line='') {
  1754. $this->deprecated('2.2.0', 'Use modX::log with modX::LOG_LEVEL_FATAL instead.');
  1755. $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");
  1756. }
  1757. /**
  1758. * Process and return the output from a PHP snippet by name.
  1759. *
  1760. * @param string $snippetName The name of the snippet.
  1761. * @param array $params An associative array of properties to pass to the
  1762. * snippet.
  1763. * @return string The processed output of the snippet.
  1764. */
  1765. public function runSnippet($snippetName, array $params= array ()) {
  1766. $output= '';
  1767. if ($this->getParser()) {
  1768. $snippet= $this->parser->getElement('modSnippet', $snippetName);
  1769. if ($snippet instanceof modSnippet) {
  1770. $snippet->setCacheable(false);
  1771. $output= $snippet->process($params);
  1772. }
  1773. }
  1774. return $output;
  1775. }
  1776. /**
  1777. * Process and return the output from a Chunk by name.
  1778. *
  1779. * @param string $chunkName The name of the chunk.
  1780. * @param array $properties An associative array of properties to process
  1781. * the Chunk with, treated as placeholders within the scope of the Element.
  1782. * @return string The processed output of the Chunk.
  1783. */
  1784. public function getChunk($chunkName, array $properties= array ()) {
  1785. $output= '';
  1786. if ($this->getParser()) {
  1787. $chunk= $this->parser->getElement('modChunk', $chunkName);
  1788. if ($chunk instanceof modChunk) {
  1789. $chunk->setCacheable(false);
  1790. $output= $chunk->process($properties);
  1791. }
  1792. }
  1793. return $output;
  1794. }
  1795. /**
  1796. * Parse a chunk using an associative array of replacement variables.
  1797. *
  1798. * @param string $chunkName The name of the chunk.
  1799. * @param array $chunkArr An array of properties to replace in the chunk.
  1800. * @param string $prefix The placeholder prefix, defaults to [[+.
  1801. * @param string $suffix The placeholder suffix, defaults to ]].
  1802. * @return string The processed chunk with the placeholders replaced.
  1803. */
  1804. public function parseChunk($chunkName, $chunkArr, $prefix='[[+', $suffix=']]') {
  1805. $chunk= $this->getChunk($chunkName);
  1806. if (!empty($chunk) || $chunk === '0') {
  1807. if(is_array($chunkArr)) {
  1808. foreach ($chunkArr as $key => $value) {
  1809. $chunk= str_replace($prefix.$key.$suffix, $value, $chunk);
  1810. }
  1811. }
  1812. }
  1813. return $chunk;
  1814. }
  1815. /**
  1816. * Strip unwanted HTML and PHP tags and supplied patterns from content.
  1817. *
  1818. * @see modX::$sanitizePatterns
  1819. * @param string $html The string to strip
  1820. * @param string $allowed An array of allowed HTML tags
  1821. * @param array $patterns An array of patterns to sanitize with; otherwise will use modX::$sanitizePatterns
  1822. * @param int $depth The depth in which the parser will strip given the patterns specified
  1823. * @return boolean True if anything was stripped
  1824. */
  1825. public function stripTags($html, $allowed= '', $patterns= array(), $depth= 10) {
  1826. $stripped= strip_tags($html, $allowed);
  1827. if (is_array($patterns)) {
  1828. if (empty($patterns)) {
  1829. $patterns = $this->sanitizePatterns;
  1830. }
  1831. foreach ($patterns as $pattern) {
  1832. $depth = ((integer) $depth ? (integer) $depth : 10);
  1833. $iteration = 1;
  1834. while ($iteration <= $depth && preg_match($pattern, $stripped)) {
  1835. $stripped= preg_replace($pattern, '', $stripped);
  1836. $iteration++;
  1837. }
  1838. }
  1839. }
  1840. return $stripped;
  1841. }
  1842. /**
  1843. * Returns true if user has the specified policy permission.
  1844. *
  1845. * @param string $pm Permission key to check.
  1846. * @return boolean
  1847. */
  1848. public function hasPermission($pm) {
  1849. $state = $this->context->checkPolicy($pm);
  1850. return $state;
  1851. }
  1852. /**
  1853. * Logs a manager action.
  1854. *
  1855. * @param string $action The action to pull from the lexicon module.
  1856. * @param string $class_key The class key that the action is being performed on.
  1857. * @param mixed $item The primary key id or array of keys to grab the object with.
  1858. * @param int|null $userId
  1859. * @return modManagerLog The newly created modManagerLog object.
  1860. */
  1861. public function logManagerAction($action, $class_key, $item, $userId = null) {
  1862. if($userId === null) {
  1863. if ($this->user instanceof modUser) {
  1864. $userId = $this->user->get('id');
  1865. }
  1866. }
  1867. $ml = $this->newObject('modManagerLog');
  1868. $ml->set('user', (integer) $userId);
  1869. $ml->set('occurred', strftime('%Y-%m-%d %H:%M:%S'));
  1870. $ml->set('action', empty($action) ? 'unknown' : $action);
  1871. $ml->set('classKey', empty($class_key) ? '' : $class_key);
  1872. $ml->set('item', empty($item) ? 'unknown' : $item);
  1873. if (!$ml->save()) {
  1874. $this->log(modX::LOG_LEVEL_ERROR, $this->lexicon('manager_log_err_save'));
  1875. return null;
  1876. }
  1877. return $ml;
  1878. }
  1879. /**
  1880. * Remove an event from the eventMap so it will not be invoked.
  1881. *
  1882. * @param string $event
  1883. * @param integer $pluginId Plugin identifier to remove from the eventMap for the specified event.
  1884. * @return boolean false if the event parameter is not specified or is not
  1885. * present in the eventMap.
  1886. */
  1887. public function removeEventListener($event, $pluginId = 0) {
  1888. $removed = false;
  1889. if (!empty($event) && isset($this->eventMap[$event])) {
  1890. if (intval($pluginId)) {
  1891. unset ($this->eventMap[$event][$pluginId]);
  1892. } else {
  1893. unset ($this->eventMap[$event]);
  1894. }
  1895. $removed = true;
  1896. }
  1897. return $removed;
  1898. }
  1899. /**
  1900. * Remove all registered events for the current request.
  1901. */
  1902. public function removeAllEventListener() {
  1903. unset ($this->eventMap);
  1904. $this->eventMap= array ();
  1905. }
  1906. /**
  1907. * Add a plugin to the eventMap within the current execution cycle.
  1908. *
  1909. * @param string $event Name of the event.
  1910. * @param integer $pluginId Plugin identifier to add to the event.
  1911. * @param string $propertySetName The name of property set bound to the plugin
  1912. * @return boolean true if the event is successfully added, otherwise false.
  1913. */
  1914. public function addEventListener($event, $pluginId, $propertySetName = '') {
  1915. $added = false;
  1916. $pluginId = intval($pluginId);
  1917. if ($event && $pluginId) {
  1918. if (!isset($this->eventMap[$event]) || empty ($this->eventMap[$event])) {
  1919. $this->eventMap[$event]= array();
  1920. }
  1921. $this->eventMap[$event][$pluginId]= $pluginId . (!empty($propertySetName) ? ':' . $propertySetName : '');
  1922. $added = true;
  1923. }
  1924. return $added;
  1925. }
  1926. /**
  1927. * Switches the primary Context for the modX instance.
  1928. *
  1929. * Be aware that switching contexts does not allow custom session handling
  1930. * classes to be loaded. The gateway defines the session handling that is
  1931. * applied to a single request. To create a context with a custom session
  1932. * handler you must create a unique context gateway that initializes that
  1933. * context directly.
  1934. *
  1935. * @param string $contextKey The key of the context to switch to.
  1936. * @param boolean $reload Set to true to force the context data to be regenerated
  1937. * before being switched to.
  1938. * @return boolean True if the switch was successful, otherwise false.
  1939. */
  1940. public function switchContext($contextKey, $reload = false) {
  1941. $switched= false;
  1942. if ($this->context->key != $contextKey) {
  1943. $switched= $this->_initContext($contextKey, $reload);
  1944. if ($switched) {
  1945. if (is_array($this->config)) {
  1946. $this->setPlaceholders($this->config, '+');
  1947. }
  1948. }
  1949. }
  1950. return $switched;
  1951. }
  1952. /**
  1953. * Retrieve a context by name without initializing it.
  1954. *
  1955. * Within a request, contexts retrieved using this function will cache the
  1956. * context data into the modX::$contexts array to avoid loading the same
  1957. * context multiple times.
  1958. *
  1959. * @access public
  1960. * @param string $contextKey The context to retrieve.
  1961. * @return modContext A modContext object retrieved from cache or
  1962. * database.
  1963. */
  1964. public function getContext($contextKey) {
  1965. if (!isset($this->contexts[$contextKey])) {
  1966. $this->contexts[$contextKey]= $this->getObject('modContext', array('key' => $contextKey));
  1967. if ($this->contexts[$contextKey]) {
  1968. $this->contexts[$contextKey]->prepare();
  1969. }
  1970. }
  1971. return $this->contexts[$contextKey];
  1972. }
  1973. /**
  1974. * Gets a map of events and registered plugins for the specified context.
  1975. *
  1976. * Service #s:
  1977. * 1 - Parser Service Events
  1978. * 2 - Manager Access Events
  1979. * 3 - Web Access Service Events
  1980. * 4 - Cache Service Events
  1981. * 5 - Template Service Events
  1982. * 6 - User Defined Events
  1983. *
  1984. * @param string $contextKey Context identifier.
  1985. * @return array A map of events and registered plugins for each.
  1986. */
  1987. public function getEventMap($contextKey) {
  1988. $eventElementMap= array ();
  1989. if ($contextKey) {
  1990. switch ($contextKey) {
  1991. case 'mgr':
  1992. /* dont load Web Access Service Events */
  1993. $service= "Event.service IN (1,2,4,5,6) AND";
  1994. break;
  1995. default:
  1996. /* dont load Manager Access Events */
  1997. $service= "Event.service IN (1,3,4,5,6) AND";
  1998. }
  1999. $pluginEventTbl= $this->getTableName('modPluginEvent');
  2000. $eventTbl= $this->getTableName('modEvent');
  2001. $pluginTbl= $this->getTableName('modPlugin');
  2002. $propsetTbl= $this->getTableName('modPropertySet');
  2003. $sql= "
  2004. SELECT
  2005. Event.name AS event,
  2006. PluginEvent.pluginid,
  2007. PropertySet.name AS propertyset
  2008. FROM {$pluginEventTbl} PluginEvent
  2009. INNER JOIN {$pluginTbl} Plugin ON Plugin.id = PluginEvent.pluginid AND Plugin.disabled = 0
  2010. INNER JOIN {$eventTbl} Event ON {$service} Event.name = PluginEvent.event
  2011. LEFT JOIN {$propsetTbl} PropertySet ON PluginEvent.propertyset = PropertySet.id
  2012. ORDER BY Event.name, PluginEvent.priority ASC
  2013. ";
  2014. $stmt= $this->prepare($sql);
  2015. if ($stmt && $stmt->execute()) {
  2016. while ($ee = $stmt->fetch(PDO::FETCH_ASSOC)) {
  2017. $eventElementMap[$ee['event']][(string) $ee['pluginid']]= $ee['pluginid'] . (!empty($ee['propertyset']) ? ':' . $ee['propertyset'] : '');
  2018. }
  2019. }
  2020. }
  2021. return $eventElementMap;
  2022. }
  2023. /**
  2024. * Checks for locking on a page.
  2025. *
  2026. * @param integer $id Id of the user checking for a lock.
  2027. * @param string $action The action identifying what is locked.
  2028. * @param string $type Message indicating the kind of lock being checked.
  2029. * @return string|boolean If locked, will return a locked message
  2030. */
  2031. public function checkForLocks($id,$action,$type) {
  2032. $msg= false;
  2033. $id= intval($id);
  2034. if (!$id) $id= $this->getLoginUserID();
  2035. if ($au = $this->getObject('modActiveUser',array('action' => $action, 'internalKey:!=' => $id))) {
  2036. $msg = $this->lexicon('lock_msg',array(
  2037. 'name' => $au->get('username'),
  2038. 'object' => $type,
  2039. ));
  2040. }
  2041. return $msg;
  2042. }
  2043. /**
  2044. * Grabs a processed lexicon string.
  2045. *
  2046. * @access public
  2047. * @param string $key
  2048. * @param array $params
  2049. * @param string $language
  2050. * @return null|string The translated string, or null if none is set
  2051. */
  2052. public function lexicon($key,$params = array(),$language = '') {
  2053. $language = !empty($language) ? $language : $this->getOption('cultureKey',null,'en');
  2054. if ($this->lexicon) {
  2055. return $this->lexicon->process($key,$params,$language);
  2056. } else {
  2057. $this->log(modX::LOG_LEVEL_ERROR,'Culture not initialized; cannot use lexicon.');
  2058. }
  2059. return null;
  2060. }
  2061. /**
  2062. * Returns the state of the SESSION being used by modX.
  2063. *
  2064. * The possible values for session state are:
  2065. *
  2066. * modX::SESSION_STATE_UNINITIALIZED
  2067. * modX::SESSION_STATE_UNAVAILABLE
  2068. * modX::SESSION_STATE_EXTERNAL
  2069. * modX::SESSION_STATE_INITIALIZED
  2070. *
  2071. * @return integer Returns an integer representing the session state.
  2072. */
  2073. public function getSessionState() {
  2074. if ($this->_sessionState !== modX::SESSION_STATE_INITIALIZED) {
  2075. if (XPDO_CLI_MODE || headers_sent()) {
  2076. $this->_sessionState = modX::SESSION_STATE_UNAVAILABLE;
  2077. }
  2078. elseif (isset($_SESSION)) {
  2079. $this->_sessionState = modX::SESSION_STATE_EXTERNAL;
  2080. }
  2081. }
  2082. return $this->_sessionState;
  2083. }
  2084. /**
  2085. * Executed before parser processing of an element.
  2086. */
  2087. public function beforeProcessing() {}
  2088. /**
  2089. * Executed before the response is rendered.
  2090. */
  2091. public function beforeRender() {}
  2092. /**
  2093. * Executed before the handleRequest function.
  2094. */
  2095. public function beforeRequest() {
  2096. unset($this->placeholders['+username'],$this->placeholders['+password'],$this->placeholders['+dbname'],$this->placeholders['+host']);
  2097. }
  2098. /**
  2099. * Determines the current site_status.
  2100. *
  2101. * @return boolean True if the site is online or the user has a valid
  2102. * user session in the 'mgr' context; false otherwise.
  2103. */
  2104. public function checkSiteStatus() {
  2105. $status = false;
  2106. if ($this->config['site_status'] == '1' || ($this->getSessionState() === modX::SESSION_STATE_INITIALIZED && $this->hasPermission('view_offline'))) {
  2107. $status = true;
  2108. }
  2109. return $status;
  2110. }
  2111. /**
  2112. * Add an extension package to MODX
  2113. *
  2114. * @param string $name
  2115. * @param string $path
  2116. * @param array $options
  2117. * @return boolean
  2118. */
  2119. public function addExtensionPackage($name,$path,array $options = array()) {
  2120. $extPackages = $this->getOption('extension_packages');
  2121. $extPackages = !empty($extPackages) ? $extPackages : array();
  2122. $extPackages = is_array($extPackages) ? $extPackages : $this->fromJSON($extPackages);
  2123. $extPackages[$name] = $options;
  2124. $extPackages['path'] = $path;
  2125. /** @var modSystemSetting $setting */
  2126. $setting = $this->getObject('modSystemSetting',array(
  2127. 'key' => 'extension_packages',
  2128. ));
  2129. if (empty($setting)) {
  2130. $setting = $this->newObject('modSystemSetting');
  2131. $setting->set('key','extension_packages');
  2132. $setting->set('namespace','core');
  2133. $setting->set('xtype','textfield');
  2134. $setting->set('area','system');
  2135. }
  2136. $value = $setting->get('value');
  2137. $value = is_array($value) ? $value : $this->fromJSON($value);
  2138. if (empty($value)) {
  2139. $value = array();
  2140. $value[$name] = $options;
  2141. $value[$name]['path'] = $path;
  2142. $value = '['.$this->toJSON($value).']';
  2143. } else {
  2144. $found = false;
  2145. foreach ($value as $k => $v) {
  2146. foreach ($v as $kk => $vv) {
  2147. if ($kk == $name) {
  2148. $found = true;
  2149. }
  2150. }
  2151. }
  2152. if (!$found) {
  2153. $extPack[$name] = $options;
  2154. $extPack[$name]['path'] = $path;
  2155. $value[] = $extPack;
  2156. }
  2157. $value = $this->toJSON($value);
  2158. }
  2159. $value = str_replace('\\','',$value);
  2160. $setting->set('value',$value);
  2161. return $setting->save();
  2162. }
  2163. /**
  2164. * Remove an extension package from MODX
  2165. *
  2166. * @param string $name
  2167. * @return boolean
  2168. */
  2169. public function removeExtensionPackage($name) {
  2170. /** @var modSystemSetting $setting */
  2171. $setting = $this->getObject('modSystemSetting',array(
  2172. 'key' => 'extension_packages',
  2173. ));
  2174. if (!$setting) {
  2175. return false;
  2176. }
  2177. $value = $setting->get('value');
  2178. $value = is_array($value) ? $value : $this->fromJSON($value);
  2179. $found = false;
  2180. foreach ($value as $idx => $extPack) {
  2181. foreach ($extPack as $key => $opt) {
  2182. if ($key == $name) {
  2183. unset($value[$idx]);
  2184. $found = true;
  2185. }
  2186. }
  2187. }
  2188. $removed = false;
  2189. if ($found) {
  2190. $value = $this->toJSON($value);
  2191. $value = str_replace('\\','',$value);
  2192. $setting->set('value',$value);
  2193. $removed = $setting->save();
  2194. }
  2195. return $removed;
  2196. }
  2197. /**
  2198. * Reload data for a specified Context, without switching to it.
  2199. *
  2200. * Note that the Context will be loaded even if it is not already.
  2201. *
  2202. * @param string $key The key of the Context to (re)load.
  2203. * @return boolean True if the Context was (re)loaded successfully; false otherwise.
  2204. */
  2205. public function reloadContext($key = null) {
  2206. $reloaded = false;
  2207. if ($this->context instanceof modContext) {
  2208. if (empty($key)) {
  2209. $key = $this->context->get('key');
  2210. }
  2211. if ($key === $this->context->get('key')) {
  2212. $reloaded = $this->_initContext($key, true);
  2213. if ($reloaded && is_array($this->config)) {
  2214. $this->setPlaceholders($this->config, '+');
  2215. }
  2216. } else {
  2217. if (!array_key_exists($key, $this->contexts) || !($this->contexts[$key] instanceof modContext)) {
  2218. $this->contexts[$key] = $this->newObject('modContext');
  2219. $this->contexts[$key]->_fields['key']= $key;
  2220. }
  2221. $reloaded = $this->contexts[$key]->prepare(true);
  2222. }
  2223. } elseif (!empty($key) && (!array_key_exists($key, $this->contexts) || !($this->contexts[$key] instanceof modContext))) {
  2224. $this->contexts[$key] = $this->newObject('modContext');
  2225. $this->contexts[$key]->_fields['key']= $key;
  2226. $reloaded = $this->contexts[$key]->prepare(true);
  2227. }
  2228. return $reloaded;
  2229. }
  2230. /**
  2231. * Start a PHP Session if one is not already available.
  2232. *
  2233. * @return bool Returns true if a session is successfully or already started, false otherwise.
  2234. */
  2235. public function startSession()
  2236. {
  2237. if ($this->_sessionState === modX::SESSION_STATE_UNINITIALIZED) {
  2238. if (!session_start()) {
  2239. $this->_sessionState = isset($_SESSION)
  2240. ? modX::SESSION_STATE_EXTERNAL
  2241. : modX::SESSION_STATE_UNAVAILABLE;
  2242. } elseif (isset($_SESSION)) {
  2243. $this->_sessionState = modX::SESSION_STATE_INITIALIZED;
  2244. }
  2245. }
  2246. return isset($_SESSION);
  2247. }
  2248. /**
  2249. * Marks the calling function as deprecated, sending a message into the error log.
  2250. *
  2251. * This automatically determines where the deprecated method was called from, and
  2252. * includes that in the log message.
  2253. *
  2254. * @param string $since The version the function was marked as deprecated
  2255. * @param string $recommendation A description or recommendation on what to replace a method with
  2256. * @param string $deprecatedDef Can be used to override the definition (i.e. function name) for the log; useful if not a specific method but an entire entity is deprecated.
  2257. */
  2258. public function deprecated($since, $recommendation = '', $deprecatedDef = '')
  2259. {
  2260. if (!$this->getOption('log_deprecated', null, true)) {
  2261. return;
  2262. }
  2263. // We use the trace to identify both the method that is deprecated, and the caller
  2264. $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
  2265. $deprecatedMethod = isset($trace[1]) ? $trace[1] : array();
  2266. $caller = isset($trace[2]) ? $trace[2] : array();
  2267. // Format the deprecated function definition with the class, if it has one
  2268. if ($deprecatedDef === '') {
  2269. $deprecatedDef = isset($deprecatedMethod['class'])
  2270. ? $deprecatedMethod['class'] . '::' . $deprecatedMethod['function']
  2271. : $deprecatedMethod['function'];
  2272. }
  2273. $callerDef = isset($caller['class']) ? $caller['class'] . '::' . $caller['function'] : '';
  2274. // The message that gets logged
  2275. $msg = $deprecatedDef . ' is deprecated since version ' . $since . '. ' . $recommendation;
  2276. // Only log deprecated functions once - even when called many times in a single request.
  2277. if (in_array($msg.$callerDef, $this->loggedDeprecatedFunctions, true)) {
  2278. return;
  2279. }
  2280. $this->loggedDeprecatedFunctions[] = $msg.$callerDef;
  2281. // Send to the standard log, providing also the file and line the deprecated method was called from
  2282. $this->log(self::LOG_LEVEL_ERROR, $msg, '', $callerDef, $deprecatedMethod['file'], $deprecatedMethod['line']);
  2283. }
  2284. /**
  2285. * Loads a specified Context.
  2286. *
  2287. * Merges any context settings with the modX::$config, and performs any
  2288. * other context specific initialization tasks.
  2289. *
  2290. * @access protected
  2291. * @param string $contextKey A context identifier.
  2292. * @param boolean $regenerate If true, force regeneration of the context even if already initialized.
  2293. * @param array $options Array of options to override context settings.
  2294. * @return boolean True if the context was properly initialized.
  2295. */
  2296. protected function _initContext($contextKey, $regenerate = false, $options = null) {
  2297. $initialized= false;
  2298. $oldContext = is_object($this->context) ? $this->context->get('key') : '';
  2299. if (isset($this->contexts[$contextKey]) && $this->contexts[$contextKey] instanceof modContext) {
  2300. $this->context= & $this->contexts[$contextKey];
  2301. } else {
  2302. $this->context= $this->newObject('modContext');
  2303. $this->context->_fields['key']= $contextKey;
  2304. if (!$this->context->validate()) {
  2305. $this->log(modX::LOG_LEVEL_ERROR, 'No valid context specified: ' . $contextKey);
  2306. $this->context = null;
  2307. }
  2308. }
  2309. if ($this->context) {
  2310. if (!$this->context->prepare((boolean) $regenerate, is_array($options) ? $options : array())) {
  2311. $this->context= null;
  2312. $this->log(modX::LOG_LEVEL_ERROR, 'Could not prepare context: ' . $contextKey);
  2313. } else {
  2314. //This fixes error with multiple contexts
  2315. $this->contexts[$contextKey]=$this->context;
  2316. if ($this->context->checkPolicy('load')) {
  2317. $this->aliasMap= & $this->context->aliasMap;
  2318. $this->resourceMap= & $this->context->resourceMap;
  2319. $this->eventMap= & $this->context->eventMap;
  2320. $this->pluginCache= & $this->context->pluginCache;
  2321. $this->config= array_merge($this->_systemConfig, $this->context->config);
  2322. $iniTZ = ini_get('date.timezone');
  2323. $cfgTZ = $this->getOption('date_timezone', $options, '');
  2324. if (!empty($cfgTZ)) {
  2325. if (empty($iniTZ) || $iniTZ !== $cfgTZ) {
  2326. date_default_timezone_set($cfgTZ);
  2327. }
  2328. } elseif (empty($iniTZ)) {
  2329. date_default_timezone_set('UTC');
  2330. }
  2331. if ($this->_initialized) {
  2332. $this->user = null;
  2333. $this->getUser();
  2334. }
  2335. $initialized = true;
  2336. } elseif (isset($this->contexts[$oldContext])) {
  2337. $this->context =& $this->contexts[$oldContext];
  2338. } else {
  2339. $this->log(modX::LOG_LEVEL_ERROR, 'Could not load context: ' . $contextKey);
  2340. }
  2341. }
  2342. }
  2343. if ($initialized) {
  2344. $this->setLogLevel($this->getOption('log_level', $options, xPDO::LOG_LEVEL_ERROR));
  2345. $logTarget = $this->getOption('log_target', $options, 'FILE', true);
  2346. if ($logTarget === 'FILE') {
  2347. $options = array();
  2348. $filename = $this->getOption('error_log_filename', $options, '');
  2349. if (!empty($filename)) $options['filename'] = $filename;
  2350. $filepath = $this->getOption('error_log_filepath', $options, '');
  2351. if (!empty($filepath)) $options['filepath'] = rtrim($filepath, '/') . '/';
  2352. $this->setLogTarget(array(
  2353. 'target' => 'FILE',
  2354. 'options' => $options
  2355. ));
  2356. } else {
  2357. $this->setLogTarget($logTarget);
  2358. }
  2359. $debug = $this->getOption('debug');
  2360. if (!is_null($debug) && $debug !== '') {
  2361. $this->setDebug($debug);
  2362. }
  2363. }
  2364. return $initialized;
  2365. }
  2366. /**
  2367. * Initializes the culture settings.
  2368. *
  2369. * @param array|null $options Options for the culture initialization process.
  2370. */
  2371. protected function _initCulture($options = null) {
  2372. $cultureKey = $this->getOption('cultureKey', $options, 'en');
  2373. if (!empty($_SESSION['cultureKey'])) $cultureKey = $_SESSION['cultureKey'];
  2374. if (!empty($_REQUEST['cultureKey'])) $cultureKey = $_REQUEST['cultureKey'];
  2375. $this->cultureKey = $cultureKey;
  2376. $this->setOption('cultureKey', $cultureKey);
  2377. if ($this->getOption('setlocale', $options, true)) {
  2378. $locale = setlocale(LC_ALL, null);
  2379. $result = setlocale(LC_ALL, $this->getOption('locale', null, $locale));
  2380. if ($result === false) {
  2381. $this->log(modX::LOG_LEVEL_ERROR, 'Could not set the locale. Please check if the locale ' . $this->getOption('locale', null, $locale) . ' exists on your system');
  2382. }
  2383. }
  2384. $this->getService('lexicon', $this->getOption('lexicon_class', $options, 'modLexicon'), '', is_array($options) ? $options : array());
  2385. $this->invokeEvent('OnInitCulture');
  2386. }
  2387. /**
  2388. * Loads the error handler for this instance.
  2389. *
  2390. * @param array|null $options An array of options for the errorHandler.
  2391. */
  2392. protected function _initErrorHandler($options = null) {
  2393. if ($this->errorHandler == null || !is_object($this->errorHandler)) {
  2394. if ($ehClass = $this->getOption('error_handler_class', $options, 'modErrorHandler', true)) {
  2395. $ehPath = $this->getOption('error_handler_path', $options, '', true);
  2396. if ($ehClass = $this->loadClass($ehClass, $ehPath, false, true)) {
  2397. if ($this->errorHandler = new $ehClass($this)) {
  2398. $result = set_error_handler(array ($this->errorHandler, 'handleError'), $this->getOption('error_handler_types', $options, error_reporting(), true));
  2399. if ($result === false) {
  2400. $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));
  2401. }
  2402. }
  2403. }
  2404. }
  2405. }
  2406. }
  2407. /**
  2408. * Populates the map of events and registered plugins for each.
  2409. *
  2410. * @param string $contextKey Context identifier.
  2411. */
  2412. protected function _initEventMap($contextKey) {
  2413. if ($this->eventMap === null) {
  2414. $this->eventMap= $this->getEventMap($contextKey);
  2415. }
  2416. }
  2417. /**
  2418. * Loads the session handler and starts the session.
  2419. *
  2420. * @param array|null $options Options to override Settings explicitly.
  2421. */
  2422. protected function _initSession($options = null) {
  2423. $contextKey= $this->context instanceof modContext ? $this->context->get('key') : null;
  2424. if ($this->getOption('session_enabled', $options, true) || isset($_GET['preview'])) {
  2425. if (!in_array($this->getSessionState(), array(modX::SESSION_STATE_INITIALIZED, modX::SESSION_STATE_EXTERNAL, modX::SESSION_STATE_UNAVAILABLE), true)) {
  2426. $sh = false;
  2427. if ($sessionHandlerClass = $this->getOption('session_handler_class', $options)) {
  2428. if ($shClass = $this->loadClass($sessionHandlerClass, '', false, true)) {
  2429. if ($sh = new $shClass($this)) {
  2430. session_set_save_handler(
  2431. array (& $sh, 'open'),
  2432. array (& $sh, 'close'),
  2433. array (& $sh, 'read'),
  2434. array (& $sh, 'write'),
  2435. array (& $sh, 'destroy'),
  2436. array (& $sh, 'gc')
  2437. );
  2438. }
  2439. }
  2440. }
  2441. if (
  2442. (is_string($sessionHandlerClass) && !$sh instanceof $sessionHandlerClass) ||
  2443. !is_string($sessionHandlerClass)
  2444. ) {
  2445. $sessionSavePath = $this->getOption('session_save_path', $options);
  2446. if ($sessionSavePath && is_writable($sessionSavePath)) {
  2447. session_save_path($sessionSavePath);
  2448. }
  2449. }
  2450. $cookieDomain= $this->getOption('session_cookie_domain', $options, '');
  2451. $cookiePath= $this->getOption('session_cookie_path', $options, MODX_BASE_URL);
  2452. if (empty($cookiePath)) $cookiePath = $this->getOption('base_url', $options, MODX_BASE_URL);
  2453. $cookieSecure= (boolean) $this->getOption('session_cookie_secure', $options, false);
  2454. $cookieHttpOnly= (boolean) $this->getOption('session_cookie_httponly', $options, true);
  2455. $cookieLifetime= (integer) $this->getOption('session_cookie_lifetime', $options, 0);
  2456. $gcMaxlifetime = (integer) $this->getOption('session_gc_maxlifetime', $options, $cookieLifetime);
  2457. if ($gcMaxlifetime > 0) {
  2458. ini_set('session.gc_maxlifetime', $gcMaxlifetime);
  2459. }
  2460. $site_sessionname = $this->getOption('session_name', $options, '');
  2461. if (!empty($site_sessionname)) session_name($site_sessionname);
  2462. session_set_cookie_params($cookieLifetime, $cookiePath, $cookieDomain, $cookieSecure, $cookieHttpOnly);
  2463. if ($this->getOption('anonymous_sessions', $options, true) || isset($_COOKIE[session_name()])) {
  2464. if (!$this->startSession()) {
  2465. $this->log(modX::LOG_LEVEL_ERROR, 'Unable to initialize a session', '', __METHOD__, __FILE__, __LINE__);
  2466. $this->getUser($contextKey);
  2467. return;
  2468. }
  2469. $this->getUser($contextKey);
  2470. $cookieExpiration = 0;
  2471. if (isset ($_SESSION['modx.' . $contextKey . '.session.cookie.lifetime'])) {
  2472. $sessionCookieLifetime = (integer)$_SESSION['modx.' . $contextKey . '.session.cookie.lifetime'];
  2473. if ($sessionCookieLifetime !== $cookieLifetime) {
  2474. if ($sessionCookieLifetime) {
  2475. $cookieExpiration = time() + $sessionCookieLifetime;
  2476. }
  2477. setcookie(session_name(), session_id(), $cookieExpiration, $cookiePath, $cookieDomain,
  2478. $cookieSecure, $cookieHttpOnly);
  2479. }
  2480. }
  2481. } else {
  2482. $this->getUser($contextKey);
  2483. }
  2484. } else {
  2485. $this->getUser($contextKey);
  2486. }
  2487. } else {
  2488. $this->getUser($contextKey);
  2489. }
  2490. }
  2491. /**
  2492. * Loads the modX system configuration settings.
  2493. *
  2494. * @access protected
  2495. * @return boolean True if successful.
  2496. */
  2497. protected function _loadConfig() {
  2498. $this->config = $this->_config;
  2499. $this->getCacheManager();
  2500. $config = $this->cacheManager->get('config', array(
  2501. xPDO::OPT_CACHE_KEY => $this->getOption('cache_system_settings_key', null, 'system_settings'),
  2502. xPDO::OPT_CACHE_HANDLER => $this->getOption('cache_system_settings_handler', null, $this->getOption(xPDO::OPT_CACHE_HANDLER)),
  2503. xPDO::OPT_CACHE_FORMAT => (integer) $this->getOption('cache_system_settings_format', null, $this->getOption(xPDO::OPT_CACHE_FORMAT, null, xPDOCacheManager::CACHE_PHP))
  2504. ));
  2505. if (empty($config)) {
  2506. $config = $this->cacheManager->generateConfig();
  2507. }
  2508. if (empty($config)) {
  2509. $config = array();
  2510. if (!$settings = $this->getCollection('modSystemSetting')) {
  2511. return false;
  2512. }
  2513. foreach ($settings as $setting) {
  2514. $config[$setting->get('key')]= $setting->get('value');
  2515. }
  2516. }
  2517. $this->config = array_merge($this->config, $config);
  2518. $this->_systemConfig = $this->config;
  2519. return true;
  2520. }
  2521. /**
  2522. * Provides modX the ability to use modRegister instances as log targets.
  2523. *
  2524. * {@inheritdoc}
  2525. */
  2526. protected function _log($level, $msg, $target= '', $def= '', $file= '', $line= '') {
  2527. if (empty($target)) {
  2528. $target = $this->logTarget;
  2529. }
  2530. $targetOptions = array();
  2531. $targetObj = $target;
  2532. if (is_array($target)) {
  2533. if (isset($target['options'])) $targetOptions = $target['options'];
  2534. $targetObj = isset($target['target']) ? $target['target'] : 'ECHO';
  2535. }
  2536. if (is_object($targetObj) && $targetObj instanceof modRegister) {
  2537. if ($level === modX::LOG_LEVEL_FATAL) {
  2538. if (empty ($file)) $file= (isset ($_SERVER['PHP_SELF'])) ? $_SERVER['PHP_SELF'] : (isset ($_SERVER['SCRIPT_FILENAME']) ? $_SERVER['SCRIPT_FILENAME'] : '');
  2539. $this->_logInRegister($targetObj, $level, $msg, $def, $file, $line);
  2540. $this->sendError('fatal');
  2541. }
  2542. if ($this->_debug === true || $level <= $this->logLevel) {
  2543. if (empty ($file)) $file= (isset ($_SERVER['PHP_SELF'])) ? $_SERVER['PHP_SELF'] : (isset ($_SERVER['SCRIPT_FILENAME']) ? $_SERVER['SCRIPT_FILENAME'] : '');
  2544. $this->_logInRegister($targetObj, $level, $msg, $def, $file, $line);
  2545. }
  2546. } else {
  2547. if ($level === modX::LOG_LEVEL_FATAL) {
  2548. while (ob_get_level() && @ob_end_clean()) {}
  2549. if ($targetObj == 'FILE' && $cacheManager= $this->getCacheManager()) {
  2550. $filename = isset($targetOptions['filename']) ? $targetOptions['filename'] : 'error.log';
  2551. $filepath = isset($targetOptions['filepath']) ? $targetOptions['filepath'] : $this->getCachePath() . xPDOCacheManager::LOG_DIR;
  2552. $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');
  2553. }
  2554. $this->sendError('fatal');
  2555. }
  2556. parent :: _log($level, $msg, $target, $def, $file, $line);
  2557. }
  2558. }
  2559. /**
  2560. * Provides custom logging functionality for modRegister targets.
  2561. *
  2562. * @access protected
  2563. * @param modRegister $register The modRegister instance to send to
  2564. * @param int $level The level of error or message that occurred
  2565. * @param string $msg The message to send to the register
  2566. * @param string $def The type of error that occurred
  2567. * @param string $file The filename of the file that the message occurs for
  2568. * @param string $line The line number of the file that the message occurs for
  2569. */
  2570. protected function _logInRegister($register, $level, $msg, $def, $file, $line) {
  2571. $timestamp = strftime('%Y-%m-%d %H:%M:%S');
  2572. $messageKey = (string) time();
  2573. $messageKey .= '-' . sprintf("%06d", $this->_logSequence);
  2574. $message = array(
  2575. 'timestamp' => $timestamp,
  2576. 'level' => $this->_getLogLevel($level),
  2577. 'msg' => $msg,
  2578. 'def' => $def,
  2579. 'file' => $file,
  2580. 'line' => $line
  2581. );
  2582. $options = array();
  2583. if ($level === xPDO::LOG_LEVEL_FATAL) {
  2584. $options['kill'] = true;
  2585. }
  2586. $register->send('', array($messageKey => $message), $options);
  2587. $this->_logSequence++;
  2588. }
  2589. /**
  2590. * Executed after the response is sent and execution is completed.
  2591. *
  2592. * @access protected
  2593. */
  2594. public function _postProcess() {
  2595. if ($this->resourceGenerated && $this->getOption('cache_resource', null, true)) {
  2596. if (is_object($this->resource) && $this->resource instanceof modResource && $this->resource->get('id') && $this->resource->get('cacheable')) {
  2597. $this->resource->_contextKey = $this->context->get('key');
  2598. $this->invokeEvent('OnBeforeSaveWebPageCache');
  2599. $this->cacheManager->generateResource($this->resource);
  2600. }
  2601. }
  2602. $this->invokeEvent('OnWebPageComplete');
  2603. }
  2604. }
  2605. /**
  2606. * Represents a modEvent when invoking events.
  2607. * @package modx
  2608. */
  2609. class modSystemEvent {
  2610. /**
  2611. * @var string For new creations of objects in model events
  2612. */
  2613. const MODE_NEW = 'new';
  2614. /**
  2615. * @var string For updating objects in model events
  2616. */
  2617. const MODE_UPD = 'upd';
  2618. /**
  2619. * The name of the Event
  2620. * @var string $name
  2621. */
  2622. public $name = '';
  2623. /**
  2624. * The name of the active plugin being invoked
  2625. * @var string $activePlugin
  2626. * @deprecated
  2627. */
  2628. public $activePlugin = '';
  2629. /**
  2630. * A reference/instance of the currently processed modPlugin object
  2631. *
  2632. * @var modPlugin|null
  2633. */
  2634. public $plugin = null;
  2635. /**
  2636. * @var string The name of the active property set for the invoked Event
  2637. * @deprecated
  2638. */
  2639. public $propertySet = '';
  2640. /**
  2641. * Whether or not to allow further execution of Plugins for this event
  2642. * @var boolean $_propagate
  2643. */
  2644. protected $_propagate = true;
  2645. /**
  2646. * The current output for the event
  2647. * @var string $_output
  2648. */
  2649. public $_output;
  2650. /**
  2651. * Whether or not this event has been activated
  2652. * @var boolean
  2653. */
  2654. public $activated;
  2655. /**
  2656. * Any returned values for this event
  2657. * @var mixed $returnedValues
  2658. */
  2659. public $returnedValues;
  2660. /**
  2661. * Any params passed to this event
  2662. * @var array $params
  2663. */
  2664. public $params;
  2665. /**
  2666. * Display a message to the user during the event.
  2667. *
  2668. * @todo Remove this; the centralized modRegistry will handle configurable
  2669. * logging of any kind of message or data to any repository or output
  2670. * context. Use {@link modX::_log()} in the meantime.
  2671. * @param string $msg The message to display.
  2672. */
  2673. public function alert($msg) {}
  2674. /**
  2675. * Render output from the event.
  2676. * @param string $output The output to render.
  2677. */
  2678. public function output($output) {
  2679. if ($this->_output === '') {
  2680. $this->_output = $output;
  2681. } else {
  2682. $this->_output .= $output;
  2683. }
  2684. }
  2685. /**
  2686. * Stop further execution of plugins for this event.
  2687. */
  2688. public function stopPropagation() {
  2689. $this->_propagate = false;
  2690. }
  2691. /**
  2692. * Returns whether the event will propagate or not.
  2693. *
  2694. * @access public
  2695. * @return boolean
  2696. */
  2697. public function isPropagatable() {
  2698. return $this->_propagate;
  2699. }
  2700. /**
  2701. * Reset the event instance for reuse.
  2702. */
  2703. public function resetEventObject(){
  2704. $this->returnedValues = null;
  2705. $this->name = '';
  2706. $this->_output = '';
  2707. $this->_propagate = true;
  2708. $this->activated = false;
  2709. }
  2710. }