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

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

http://github.com/modxcms/revolution
PHP | 805 lines | 626 code | 38 blank | 141 comment | 142 complexity | 1a9c69f44de29c8624615c9a2dfa0dbc MD5 | raw file
Possible License(s): GPL-2.0, Apache-2.0, BSD-3-Clause, LGPL-2.1
  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. * The default xPDOCacheManager instance for MODX.
  12. *
  13. * Through this class, MODX provides several types of default, file-based
  14. * caching to reduce load and dependencies on the database, including:
  15. * <ul>
  16. * <li>partial modResource caching, which stores the object properties,
  17. * along with individual modElement cache items</li>
  18. * <li>full caching of modContext and modSystemSetting data</li>
  19. * <li>object-level caching</li>
  20. * <li>db query-level caching</li>
  21. * <li>optional JSON object caching for increased Ajax performance
  22. * possibilities</li>
  23. * </ul>
  24. *
  25. * @package modx
  26. */
  27. class modCacheManager extends xPDOCacheManager {
  28. /**
  29. * @var modX A reference to the modX instance
  30. */
  31. public $modx= null;
  32. /**
  33. * Constructor for modCacheManager that overrides xPDOCacheManager constructor to assign modX reference
  34. * @param xPDO $xpdo A reference to the xPDO/modX instance
  35. * @param array $options An array of configuration options
  36. */
  37. function __construct(& $xpdo, array $options = array()) {
  38. parent :: __construct($xpdo, $options);
  39. $this->modx =& $this->xpdo;
  40. }
  41. /**
  42. * Generates a cache entry for a MODX site Context.
  43. *
  44. * Context cache entries can override site configuration settings and are responsible for
  45. * loading the various listings and maps in the modX class, including resourceMap, aliasMap,
  46. * and eventMap. It can also be used to setup or transform any other modX properties.
  47. *
  48. * @todo Further refactor the generation of aliasMap and resourceMap so it uses less memory/file size.
  49. *
  50. * @param string $key The modContext key to be cached.
  51. * @param array $options Options for context settings generation.
  52. * @return array An array containing all the context variable values.
  53. */
  54. public function generateContext($key, array $options = array()) {
  55. $results = array();
  56. if (!$this->getOption('transient_context', $options, false)) {
  57. /** @var modContext $obj */
  58. $obj= $this->modx->getObject('modContext', $key, true);
  59. if (is_object($obj) && $obj instanceof modContext && $obj->get('key')) {
  60. $cacheKey = $obj->getCacheKey();
  61. $contextKey = is_object($this->modx->context) ? $this->modx->context->get('key') : $key;
  62. $contextConfig= array_merge($this->modx->_systemConfig, $options);
  63. /* generate the ContextSettings */
  64. $results['config']= array();
  65. if ($settings= $obj->getMany('ContextSettings')) {
  66. /** @var modContextSetting $setting */
  67. foreach ($settings as $setting) {
  68. $k= $setting->get('key');
  69. $v= $setting->get('value');
  70. $matches = array();
  71. if (preg_match_all('~\{(.*?)\}~', $v, $matches, PREG_SET_ORDER)) {
  72. foreach ($matches as $match) {
  73. if (array_key_exists("{$match[1]}", $contextConfig)) {
  74. $matchValue= $contextConfig["{$match[1]}"];
  75. $v= str_replace($match[0], $matchValue, $v);
  76. }
  77. }
  78. }
  79. $results['config'][$k]= $v;
  80. $contextConfig[$k]= $v;
  81. }
  82. }
  83. $results['config'] = array_merge($results['config'], $options);
  84. /* generate the aliasMap and resourceMap */
  85. $collResources = $obj->getResourceCacheMap();
  86. $friendlyUrls = $this->getOption('friendly_urls', $contextConfig, false);
  87. $cacheAliasMap = $this->getOption('cache_alias_map', $options, false);
  88. if ($friendlyUrls && $cacheAliasMap) {
  89. $results['aliasMap']= array ();
  90. }
  91. if ($collResources) {
  92. /** @var Object $r */
  93. while ($r = $collResources->fetch(PDO::FETCH_OBJ)) {
  94. if (!isset($results['resourceMap'][(integer) $r->parent])) {
  95. $results['resourceMap'][(integer) $r->parent] = array();
  96. }
  97. $results['resourceMap'][(integer) $r->parent][] = (integer) $r->id;
  98. if ($friendlyUrls && $cacheAliasMap) {
  99. if (array_key_exists($r->uri, $results['aliasMap'])) {
  100. $this->modx->log(xPDO::LOG_LEVEL_ERROR, "Resource URI {$r->uri} already exists for resource id = {$results['aliasMap'][$r->uri]}; skipping duplicate resource URI for resource id = {$r->id}");
  101. continue;
  102. }
  103. $results['aliasMap'][$r->uri]= (integer) $r->id;
  104. }
  105. }
  106. }
  107. /* generate the webLinkMap */
  108. $collWebLinks = $obj->getWebLinkCacheMap();
  109. $results['webLinkMap']= array();
  110. if ($collWebLinks) {
  111. while ($wl = $collWebLinks->fetch(PDO::FETCH_OBJ)) {
  112. $results['webLinkMap'][(integer) $wl->id] = $wl->content;
  113. }
  114. }
  115. /* generate the eventMap and pluginCache */
  116. $results['eventMap'] = array();
  117. $results['pluginCache'] = array();
  118. $eventMap= $this->modx->getEventMap($obj->get('key'));
  119. if (is_array ($eventMap) && !empty($eventMap)) {
  120. $results['eventMap'] = $eventMap;
  121. $pluginIds= array();
  122. $plugins= array();
  123. $this->modx->loadClass('modScript');
  124. foreach ($eventMap as $pluginKeys) {
  125. foreach ($pluginKeys as $pluginKey) {
  126. if (isset ($pluginIds[$pluginKey])) {
  127. continue;
  128. }
  129. $pluginIds[$pluginKey]= $pluginKey;
  130. }
  131. }
  132. if (!empty($pluginIds)) {
  133. $pluginQuery = $this->modx->newQuery('modPlugin', array('id:IN' => array_keys($pluginIds)), true);
  134. $pluginQuery->select($this->modx->getSelectColumns('modPlugin', 'modPlugin'));
  135. if ($pluginQuery->prepare() && $pluginQuery->stmt->execute()) {
  136. $plugins= $pluginQuery->stmt->fetchAll(PDO::FETCH_ASSOC);
  137. }
  138. }
  139. if (!empty($plugins)) {
  140. foreach ($plugins as $plugin) {
  141. $results['pluginCache'][(string) $plugin['id']]= $plugin;
  142. }
  143. }
  144. }
  145. /* cache the Context ACL policies */
  146. $results['policies'] = $obj->findPolicy($contextKey);
  147. } else {
  148. $results = false;
  149. }
  150. } else {
  151. $results = $this->getOption("{$key}_results", $options, array());
  152. $cacheKey = "{$key}/context";
  153. $options['cache_context_settings'] = array_key_exists('cache_context_settings', $results) ? (boolean) $results : false;
  154. }
  155. if ($this->getOption('cache_context_settings', $options, true) && is_array($results) && !empty($results)) {
  156. $options[xPDO::OPT_CACHE_KEY] = $this->getOption('cache_context_settings_key', $options, 'context_settings');
  157. $options[xPDO::OPT_CACHE_HANDLER] = $this->getOption('cache_context_settings_handler', $options, $this->getOption(xPDO::OPT_CACHE_HANDLER, $options));
  158. $options[xPDO::OPT_CACHE_FORMAT] = (integer) $this->getOption('cache_context_settings_format', $options, $this->getOption(xPDO::OPT_CACHE_FORMAT, $options, xPDOCacheManager::CACHE_PHP));
  159. $options[xPDO::OPT_CACHE_ATTEMPTS] = (integer) $this->getOption('cache_context_settings_attempts', $options, $this->getOption(xPDO::OPT_CACHE_ATTEMPTS, $options, 10));
  160. $options[xPDO::OPT_CACHE_ATTEMPT_DELAY] = (integer) $this->getOption('cache_context_settings_attempt_delay', $options, $this->getOption(xPDO::OPT_CACHE_ATTEMPT_DELAY, $options, 1000));
  161. $lifetime = (integer) $this->getOption('cache_context_settings_expires', $options, $this->getOption(xPDO::OPT_CACHE_EXPIRES, $options, 0));
  162. if (!$this->set($cacheKey, $results, $lifetime, $options)) {
  163. $this->modx->log(modX::LOG_LEVEL_ERROR, 'Could not cache context settings for ' . $key . '.');
  164. }
  165. }
  166. return $results;
  167. }
  168. public function getElementMediaSourceCache(modElement $element,$contextKey, array $options = array()) {
  169. $options[xPDO::OPT_CACHE_KEY] = $this->getOption('cache_media_sources_key', $options, 'media_sources');
  170. $options[xPDO::OPT_CACHE_HANDLER] = $this->getOption('cache_media_sources_handler', $options, $this->getOption(xPDO::OPT_CACHE_HANDLER, $options));
  171. $options[xPDO::OPT_CACHE_FORMAT] = (integer) $this->getOption('cache_media_sources_format', $options, $this->getOption(xPDO::OPT_CACHE_FORMAT, $options, xPDOCacheManager::CACHE_PHP));
  172. $options[xPDO::OPT_CACHE_ATTEMPTS] = (integer) $this->getOption('cache_media_sources_attempts', $options, $this->getOption(xPDO::OPT_CACHE_ATTEMPTS, $options, 10));
  173. $options[xPDO::OPT_CACHE_ATTEMPT_DELAY] = (integer) $this->getOption('cache_media_sources_attempt_delay', $options, $this->getOption(xPDO::OPT_CACHE_ATTEMPT_DELAY, $options, 1000));
  174. $cacheKey = $contextKey.'/source';
  175. $sourceCache = $this->get($cacheKey, $options);
  176. if (empty($sourceCache)) {
  177. $c = $this->modx->newQuery('sources.modMediaSourceElement');
  178. $c->innerJoin('sources.modMediaSource','Source');
  179. $c->where(array(
  180. 'modMediaSourceElement.context_key' => $contextKey,
  181. ));
  182. $c->select($this->modx->getSelectColumns('sources.modMediaSourceElement','modMediaSourceElement'));
  183. $c->select(array(
  184. 'Source.name',
  185. 'Source.description',
  186. 'Source.properties',
  187. 'source_class_key' => 'Source.class_key',
  188. ));
  189. $c->sortby($this->modx->getSelectColumns('sources.modMediaSourceElement','modMediaSourceElement','',array('object')),'ASC');
  190. $sourceElements = $this->modx->getCollection('sources.modMediaSourceElement',$c);
  191. $coreSourceClasses = $this->modx->getOption('core_media_sources',null,'modFileMediaSource,modS3MediaSource');
  192. $coreSourceClasses = explode(',',$coreSourceClasses);
  193. $sourceCache = array();
  194. /** @var modMediaSourceElement $sourceElement */
  195. foreach ($sourceElements as $sourceElement) {
  196. $classKey = $sourceElement->get('source_class_key');
  197. $classKey = in_array($classKey,$coreSourceClasses) ? 'sources.'.$classKey : $classKey;
  198. /** @var modMediaSource $source */
  199. $source = $this->modx->newObject($classKey);
  200. $source->fromArray($sourceElement->toArray(),'',true,true);
  201. $sourceArray = $source->toArray();
  202. $sourceArray = array_merge($source->getPropertyList(),$sourceArray);
  203. $sourceArray['class_key'] = $source->_class;
  204. $sourceArray['object'] = $source->get('object');
  205. $sourceCache[$sourceArray['object']] = $sourceArray;
  206. }
  207. $lifetime = (integer) $this->getOption('cache_media_sources_expires', $options, $this->getOption(xPDO::OPT_CACHE_EXPIRES, $options, 0));
  208. if (!$this->set($cacheKey, $sourceCache, $lifetime, $options)) {
  209. $this->modx->log(modX::LOG_LEVEL_ERROR, 'Could not cache source data for ' . $element->get('id') . '.');
  210. }
  211. }
  212. $data = !empty($sourceCache[$element->get('id')]) ? $sourceCache[$element->get('id')] : array();
  213. return $data;
  214. }
  215. /**
  216. * Generates the system settings cache for a MODX site.
  217. *
  218. * @param array $options Options for system settings generation.
  219. * @return array The generated system settings array.
  220. */
  221. public function generateConfig(array $options = array()) {
  222. $config = array();
  223. if ($collection= $this->modx->getCollection('modSystemSetting')) {
  224. foreach ($collection as $setting) {
  225. $k= $setting->get('key');
  226. $v= $setting->get('value');
  227. $matches= array();
  228. if (preg_match_all('~\{(.*?)\}~', $v, $matches, PREG_SET_ORDER)) {
  229. foreach ($matches as $match) {
  230. if (isset ($this->modx->config["{$match[1]}"])) {
  231. $matchValue= $this->modx->config["{$match[1]}"];
  232. $v= str_replace($match[0], $matchValue, $v);
  233. }
  234. }
  235. }
  236. $config[$k]= $v;
  237. }
  238. }
  239. if (!empty($config) && $this->getOption('cache_system_settings', $options, true)) {
  240. $options[xPDO::OPT_CACHE_KEY] = $this->getOption('cache_system_settings_key', $options, 'system_settings');
  241. $options[xPDO::OPT_CACHE_HANDLER] = $this->getOption('cache_system_settings_handler', $options, $this->getOption(xPDO::OPT_CACHE_HANDLER, $options));
  242. $options[xPDO::OPT_CACHE_FORMAT] = (integer) $this->getOption('cache_system_settings_format', $options, $this->getOption(xPDO::OPT_CACHE_FORMAT, $options, xPDOCacheManager::CACHE_PHP));
  243. $options[xPDO::OPT_CACHE_ATTEMPTS] = (integer) $this->getOption('cache_system_settings_attempts', $options, $this->getOption(xPDO::OPT_CACHE_ATTEMPTS, $options, 10));
  244. $options[xPDO::OPT_CACHE_ATTEMPT_DELAY] = (integer) $this->getOption('cache_system_settings_attempt_delay', $options, $this->getOption(xPDO::OPT_CACHE_ATTEMPT_DELAY, $options, 1000));
  245. $lifetime = (integer) $this->getOption('cache_system_settings_expires', $options, $this->getOption(xPDO::OPT_CACHE_EXPIRES, $options, 0));
  246. if (!$this->set('config', $config, $lifetime, $options)) {
  247. $this->modx->log(modX::LOG_LEVEL_ERROR, 'Could not cache system settings.');
  248. }
  249. }
  250. return $config;
  251. }
  252. /**
  253. * Generates a cache entry for a Resource or Resource-derived object.
  254. *
  255. * Resource classes can define their own cacheKey.
  256. *
  257. * @param modResource $obj The Resource instance to be cached.
  258. * @param array $options Options for resource generation.
  259. * @return array The generated resource representation.
  260. */
  261. public function generateResource(modResource & $obj, array $options = array()) {
  262. $results= array();
  263. if ($this->getOption('cache_resource', $options, true)) {
  264. if (is_object($obj) && $obj instanceof modResource && $obj->getProcessed() && $obj->get('cacheable') && $obj->get('id')) {
  265. $results['resourceClass']= $obj->_class;
  266. $results['resource']['_processed']= $obj->getProcessed();
  267. $results['resource']= $obj->toArray('', true);
  268. $results['resource']['_content']= $obj->_content;
  269. $results['resource']['_isForward']= $obj->_isForward;
  270. if ($contentType = $obj->getOne('ContentType')) {
  271. $results['contentType']= $contentType->toArray('', true);
  272. }
  273. /* TODO: remove legacy docGroups and cache ABAC policies instead */
  274. if ($docGroups= $obj->getMany('ResourceGroupResources')) {
  275. $groups= array();
  276. foreach ($docGroups as $docGroupPk => $docGroup) {
  277. $groups[(string) $docGroupPk] = $docGroup->toArray('', true);
  278. }
  279. $results['resourceGroups']= $groups;
  280. }
  281. $context = $obj->_contextKey ? $obj->_contextKey : 'web';
  282. $policies = $obj->findPolicy($context);
  283. if (is_array($policies)) {
  284. $results['policyCache']= $policies;
  285. }
  286. if (!empty($this->modx->elementCache)) {
  287. $results['elementCache']= $this->modx->elementCache;
  288. }
  289. if (!empty($this->modx->sourceCache)) {
  290. $results['sourceCache']= $this->modx->sourceCache;
  291. }
  292. if (!empty($obj->_sjscripts)) {
  293. $results['resource']['_sjscripts']= $obj->_sjscripts;
  294. }
  295. if (!empty($obj->_jscripts)) {
  296. $results['resource']['_jscripts']= $obj->_jscripts;
  297. }
  298. if (!empty($obj->_loadedjscripts)) {
  299. $results['resource']['_loadedjscripts']= $obj->_loadedjscripts;
  300. }
  301. }
  302. if (!empty($results)) {
  303. $options[xPDO::OPT_CACHE_KEY] = $this->getOption('cache_resource_key', $options, 'resource');
  304. $options[xPDO::OPT_CACHE_HANDLER] = $this->getOption('cache_resource_handler', $options, $this->getOption(xPDO::OPT_CACHE_HANDLER, $options));
  305. $options[xPDO::OPT_CACHE_FORMAT] = (integer) $this->getOption('cache_resource_format', $options, $this->getOption(xPDO::OPT_CACHE_FORMAT, $options, xPDOCacheManager::CACHE_PHP));
  306. $options[xPDO::OPT_CACHE_ATTEMPTS] = (integer) $this->getOption('cache_resource_attempts', $options, $this->getOption(xPDO::OPT_CACHE_ATTEMPTS, $options, 1));
  307. $options[xPDO::OPT_CACHE_ATTEMPT_DELAY] = (integer) $this->getOption('cache_resource_attempt_delay', $options, $this->getOption(xPDO::OPT_CACHE_ATTEMPT_DELAY, $options, 1000));
  308. $lifetime = (integer) $this->getOption('cache_resource_expires', $options, $this->getOption(xPDO::OPT_CACHE_EXPIRES, $options, 0));
  309. if (!$this->set($obj->getCacheKey(), $results, $lifetime, $options)) {
  310. $this->modx->log(modX::LOG_LEVEL_ERROR, "Could not cache resource " . $obj->get('id'));
  311. }
  312. } else {
  313. $this->modx->log(modX::LOG_LEVEL_ERROR, "Could not retrieve data to cache for resource " . $obj->get('id'));
  314. }
  315. }
  316. return $results;
  317. }
  318. /**
  319. * Generates a lexicon topic cache file from a collection of entries
  320. *
  321. * @access public
  322. * @param string $cacheKey The key to use when caching the lexicon topic.
  323. * @param array $entries An array of key => value pairs of lexicon entries.
  324. * @param array $options An optional array of caching options.
  325. * @return array An array representing the lexicon topic cache.
  326. */
  327. public function generateLexiconTopic($cacheKey, $entries = array(), $options = array()) {
  328. if (!empty($entries) && $this->getOption('cache_lexicon_topics', $options, true)) {
  329. $options[xPDO::OPT_CACHE_KEY] = $this->getOption('cache_lexicon_topics_key', $options, 'lexicon_topics');
  330. $options[xPDO::OPT_CACHE_HANDLER] = $this->getOption('cache_lexicon_topics_handler', $options, $this->getOption(xPDO::OPT_CACHE_HANDLER, $options));
  331. $options[xPDO::OPT_CACHE_FORMAT] = (integer) $this->getOption('cache_lexicon_topics_format', $options, $this->getOption(xPDO::OPT_CACHE_FORMAT, $options, xPDOCacheManager::CACHE_PHP));
  332. $options[xPDO::OPT_CACHE_ATTEMPTS] = (integer) $this->getOption('cache_lexicon_topics_attempts', $options, $this->getOption(xPDO::OPT_CACHE_ATTEMPTS, $options, 1));
  333. $options[xPDO::OPT_CACHE_ATTEMPT_DELAY] = (integer) $this->getOption('cache_lexicon_topics_attempt_delay', $options, $this->getOption(xPDO::OPT_CACHE_ATTEMPT_DELAY, $options, 1000));
  334. $lifetime = (integer) $this->getOption('cache_lexicon_topics_expires', $options, $this->getOption(xPDO::OPT_CACHE_EXPIRES, $options, 0));
  335. if (!$this->set($cacheKey, $entries, $lifetime, $options)) {
  336. $this->modx->log(modX::LOG_LEVEL_ERROR, "Error caching lexicon topic " . $cacheKey);
  337. }
  338. }
  339. return $entries;
  340. }
  341. /**
  342. * Generates a cache file for the manager actions.
  343. *
  344. * @access public
  345. * @param string $cacheKey The key to use when caching the action map.
  346. * @param array $options An array of options
  347. * @return array An array representing the action map.
  348. */
  349. public function generateActionMap($cacheKey, array $options = array()) {
  350. $results= array();
  351. $c = $this->modx->newQuery('modAction');
  352. $c->select(array(
  353. $this->modx->getSelectColumns('modAction', 'modAction'),
  354. $this->modx->getSelectColumns('modNamespace', 'Namespace', 'namespace_', array('name','path','assets_path'))
  355. ));
  356. $c->innerJoin('modNamespace','Namespace');
  357. $c->sortby('namespace','ASC');
  358. $c->sortby('controller','ASC');
  359. if ($c->prepare() && $c->stmt->execute()) {
  360. $actions = $c->stmt->fetchAll(PDO::FETCH_ASSOC);
  361. foreach ($actions as $action) {
  362. if (empty($action['namespace_path']) || $action['namespace_name'] == 'core') {
  363. $action['namespace_path'] = $this->modx->getOption('manager_path');
  364. }
  365. if ($action['namespace_name'] != 'core') {
  366. $nsPath = $action['namespace_path'];
  367. if (!empty($nsPath)) {
  368. $nsPath = $this->modx->call('modNamespace','translatePath',array(&$this->modx,$nsPath));
  369. $action['namespace_path'] = $nsPath;
  370. }
  371. }
  372. $results[$action['id']] = $action;
  373. }
  374. }
  375. if (!empty($results) && $this->getOption('cache_action_map', $options, true)) {
  376. $options[xPDO::OPT_CACHE_KEY] = $this->getOption('cache_action_map_key', $options, 'action_map');
  377. $options[xPDO::OPT_CACHE_HANDLER] = $this->getOption('cache_action_map_handler', $options, $this->getOption(xPDO::OPT_CACHE_HANDLER, $options));
  378. $options[xPDO::OPT_CACHE_FORMAT] = (integer) $this->getOption('cache_action_map_format', $options, $this->getOption(xPDO::OPT_CACHE_FORMAT, $options, xPDOCacheManager::CACHE_PHP));
  379. $options[xPDO::OPT_CACHE_ATTEMPTS] = (integer) $this->getOption('cache_action_map_attempts', $options, $this->getOption(xPDO::OPT_CACHE_ATTEMPTS, $options, 1));
  380. $options[xPDO::OPT_CACHE_ATTEMPT_DELAY] = (integer) $this->getOption('cache_action_map_attempt_delay', $options, $this->getOption(xPDO::OPT_CACHE_ATTEMPT_DELAY, $options, 1000));
  381. $lifetime = (integer) $this->getOption('cache_action_map_expires', $options, $this->getOption(xPDO::OPT_CACHE_EXPIRES, $options, 0));
  382. if (!$this->set($cacheKey, $results, $lifetime, $options)) {
  383. $this->modx->log(modX::LOG_LEVEL_ERROR, "Error caching action map {$cacheKey}");
  384. }
  385. }
  386. return $results;
  387. }
  388. public function generateNamespacesCache($cacheKey, array $options = array()) {
  389. $results = array();
  390. $c = $this->modx->newQuery('modNamespace');
  391. $c->select($this->modx->getSelectColumns('modNamespace', 'modNamespace'));
  392. $c->sortby('name','ASC');
  393. if ($c->prepare() && $c->stmt->execute()) {
  394. $namespaces = $c->stmt->fetchAll(PDO::FETCH_ASSOC);
  395. foreach ($namespaces as $namespace) {
  396. if ($namespace['name'] == 'core') {
  397. $namespace['path'] = $this->modx->getOption('manager_path',null,MODX_MANAGER_PATH);
  398. $namespace['assets_path'] = $this->modx->getOption('manager_path',null,MODX_MANAGER_PATH).'assets/';
  399. } else {
  400. $namespace['path'] = $this->modx->call('modNamespace','translatePath',array(&$this->modx,$namespace['path']));
  401. $namespace['assets_path'] = $this->modx->call('modNamespace','translatePath',array(&$this->modx,$namespace['assets_path']));
  402. }
  403. $results[$namespace['name']] = $namespace;
  404. }
  405. }
  406. if (!empty($results) && $this->getOption('cache_namespaces', $options, true)) {
  407. $options[xPDO::OPT_CACHE_KEY] = $this->getOption('cache_namespaces_key', $options,'namespaces');
  408. $options[xPDO::OPT_CACHE_HANDLER] = $this->getOption('cache_namespaces_handler', $options, $this->getOption(xPDO::OPT_CACHE_HANDLER, $options));
  409. $options[xPDO::OPT_CACHE_FORMAT] = (integer) $this->getOption('cache_namespaces_format', $options, $this->getOption(xPDO::OPT_CACHE_FORMAT, $options, xPDOCacheManager::CACHE_PHP));
  410. $options[xPDO::OPT_CACHE_ATTEMPTS] = (integer) $this->getOption('cache_namespaces_attempts', $options, $this->getOption(xPDO::OPT_CACHE_ATTEMPTS, $options, 1));
  411. $options[xPDO::OPT_CACHE_ATTEMPT_DELAY] = (integer) $this->getOption('cache_namespaces_attempt_delay', $options, $this->getOption(xPDO::OPT_CACHE_ATTEMPT_DELAY, $options, 1000));
  412. $lifetime = (integer) $this->getOption('cache_namespaces_expires', $options, $this->getOption(xPDO::OPT_CACHE_EXPIRES, $options, 0));
  413. if (!$this->set($cacheKey, $results, $lifetime, $options)) {
  414. $this->modx->log(modX::LOG_LEVEL_ERROR, "Error caching namespaces {$cacheKey}");
  415. }
  416. }
  417. return $results;
  418. }
  419. public function generateExtensionPackagesCache($cacheKey,array $options = array()) {
  420. $results = array();
  421. $c = $this->modx->newQuery('modExtensionPackage');
  422. $c->innerJoin('modNamespace','Namespace');
  423. $c->select($this->modx->getSelectColumns('modExtensionPackage', 'modExtensionPackage'));
  424. $c->select(array(
  425. 'namespace_path' => 'Namespace.path',
  426. ));
  427. $c->sortby('namespace','ASC');
  428. if ($c->prepare() && $c->stmt->execute()) {
  429. $extensionPackages = $c->stmt->fetchAll(PDO::FETCH_ASSOC);
  430. foreach ($extensionPackages as $extensionPackage) {
  431. $extensionPackage['path'] = str_replace(array(
  432. '[[++core_path]]',
  433. '[[++base_path]]',
  434. '[[++assets_path]]',
  435. '[[++manager_path]]',
  436. ),array(
  437. $this->modx->getOption('core_path',null,MODX_CORE_PATH),
  438. $this->modx->getOption('base_path',null,MODX_BASE_PATH),
  439. $this->modx->getOption('assets_path',null,MODX_ASSETS_PATH),
  440. $this->modx->getOption('manager_path',null,MODX_MANAGER_PATH),
  441. ),$extensionPackage['path']);
  442. if (empty($extensionPackage['path'])) {
  443. $extensionPackage['path'] = $this->modx->call('modNamespace','translatePath',array(&$this->modx,$extensionPackage['namespace_path']));
  444. }
  445. if (empty($extensionPackage['name'])) {
  446. $extensionPackage['name'] = $extensionPackage['namespace'];
  447. }
  448. $extensionPackage['path'] = rtrim($extensionPackage['path'],'/').'/model/';
  449. $results[] = $extensionPackage;
  450. }
  451. }
  452. if (!empty($results) && $this->getOption('cache_extension_packages', $options, true)) {
  453. $options[xPDO::OPT_CACHE_KEY] = $this->getOption('cache_extension_packages_key', $options,'namespaces');
  454. $options[xPDO::OPT_CACHE_HANDLER] = $this->getOption('cache_extension_packages_handler', $options, $this->getOption(xPDO::OPT_CACHE_HANDLER, $options));
  455. $options[xPDO::OPT_CACHE_FORMAT] = (integer) $this->getOption('cache_extension_packages_format', $options, $this->getOption(xPDO::OPT_CACHE_FORMAT, $options, xPDOCacheManager::CACHE_PHP));
  456. $options[xPDO::OPT_CACHE_ATTEMPTS] = (integer) $this->getOption('cache_extension_packages_attempts', $options, $this->getOption(xPDO::OPT_CACHE_ATTEMPTS, $options, 1));
  457. $options[xPDO::OPT_CACHE_ATTEMPT_DELAY] = (integer) $this->getOption('cache_extension_packages_attempt_delay', $options, $this->getOption(xPDO::OPT_CACHE_ATTEMPT_DELAY, $options, 1000));
  458. $lifetime = (integer) $this->getOption('cache_extension_packages_expires', $options, $this->getOption(xPDO::OPT_CACHE_EXPIRES, $options, 0));
  459. if (!$this->set($cacheKey, $results, $lifetime, $options)) {
  460. $this->modx->log(modX::LOG_LEVEL_ERROR, "Error caching extension packages {$cacheKey}");
  461. }
  462. }
  463. return $results;
  464. }
  465. /**
  466. * Generates a file representing an executable modScript function.
  467. *
  468. * @param modScript $objElement A {@link modScript} instance to generate the
  469. * script file for.
  470. * @param string $objContent Optional script content to override the
  471. * persistent instance.
  472. * @param array $options An array of additional options for the operation.
  473. * @return boolean|string The actual generated source content representing the modScript or
  474. * false if the source content could not be generated.
  475. */
  476. public function generateScript(modScript &$objElement, $objContent= null, array $options= array()) {
  477. $results= false;
  478. if (is_object($objElement) && $objElement instanceof modScript) {
  479. $results= $objElement->getContent(is_string($objContent) ? array('content' => $objContent) : array());
  480. $results = rtrim($results, "\n") . "\nreturn;\n";
  481. if ($this->getOption('returnFunction', $options, false)) {
  482. return $results;
  483. }
  484. if ($this->getOption('cache_scripts', $options, true)) {
  485. $options[xPDO::OPT_CACHE_KEY] = $this->getOption('cache_scripts_key', $options, 'scripts');
  486. $options[xPDO::OPT_CACHE_HANDLER] = $this->getOption('cache_scripts_handler', $options, $this->getOption(xPDO::OPT_CACHE_HANDLER, $options));
  487. $options[xPDO::OPT_CACHE_FORMAT] = (integer) $this->getOption('cache_scripts_format', $options, $this->getOption(xPDO::OPT_CACHE_FORMAT, $options, xPDOCacheManager::CACHE_PHP));
  488. $options[xPDO::OPT_CACHE_ATTEMPTS] = (integer) $this->getOption('cache_scripts_attempts', $options, $this->getOption(xPDO::OPT_CACHE_ATTEMPTS, $options, 1));
  489. $options[xPDO::OPT_CACHE_ATTEMPT_DELAY] = (integer) $this->getOption('cache_scripts_attempt_delay', $options, $this->getOption(xPDO::OPT_CACHE_ATTEMPT_DELAY, $options, 1000));
  490. $lifetime = (integer) $this->getOption('cache_scripts_expires', $options, $this->getOption(xPDO::OPT_CACHE_EXPIRES, $options, 0));
  491. if (empty($results) || !$this->set($objElement->getScriptCacheKey(), $results, $lifetime, $options)) {
  492. $this->modx->log(modX::LOG_LEVEL_ERROR, "Error caching script " . $objElement->getScriptCacheKey());
  493. }
  494. }
  495. }
  496. return $results;
  497. }
  498. /**
  499. * Implements MODX cache refresh process, converting cache partitions to cache providers.
  500. *
  501. * @param array $providers
  502. * @param array $results
  503. * @return boolean
  504. */
  505. public function refresh(array $providers = array(), array &$results = array()) {
  506. if (empty($providers)) {
  507. $contexts = array();
  508. $query = $this->xpdo->newQuery('modContext');
  509. $query->select($this->xpdo->escape('key'));
  510. if ($query->prepare() && $query->stmt->execute()) {
  511. $contexts = $query->stmt->fetchAll(PDO::FETCH_COLUMN);
  512. }
  513. $providers = array(
  514. 'auto_publish' => array('contexts' => array_diff($contexts, array('mgr'))),
  515. 'system_settings' => array(),
  516. 'context_settings' => array('contexts' => $contexts),
  517. 'db' => array(),
  518. 'media_sources' => array(),
  519. 'lexicon_topics' => array(),
  520. 'scripts' => array(),
  521. 'default' => array(),
  522. 'resource' => array('contexts' => array_diff($contexts, array('mgr'))),
  523. 'menu' => array(),
  524. 'action_map' => array()
  525. );
  526. }
  527. $cleared = array();
  528. foreach ($providers as $partition => $partOptions) {
  529. $partKey = $this->xpdo->getOption("cache_{$partition}_key", $partOptions, $partition);
  530. if (array_search($partKey, $cleared) !== false) {
  531. $results[$partition] = false;
  532. continue;
  533. }
  534. $partHandler = $this->xpdo->getOption("cache_{$partition}_handler", $partOptions, $this->xpdo->getOption(xPDO::OPT_CACHE_HANDLER));
  535. if (!is_array($partOptions)) $partOptions = array();
  536. $partOptions = array_merge($partOptions, array(xPDO::OPT_CACHE_KEY => $partKey, xPDO::OPT_CACHE_HANDLER => $partHandler));
  537. switch ($partition) {
  538. case 'auto_publish':
  539. $results['auto_publish'] = $this->autoPublish($partOptions);
  540. break;
  541. case 'system_settings':
  542. $results['system_settings'] = ($this->generateConfig($partOptions) ? true : false);
  543. break;
  544. case 'context_settings':
  545. if (array_key_exists('contexts', $partOptions)) {
  546. $contextResults = array();
  547. foreach ($partOptions['contexts'] as $context) {
  548. $contextResults[$context] = ($this->generateContext($context) ? true : false);
  549. }
  550. $results['context_settings'] = $contextResults;
  551. } else {
  552. $results['context_settings'] = false;
  553. }
  554. break;
  555. case 'resource':
  556. $clearPartial = $this->getOption('cache_resource_clear_partial', null, false);
  557. $cacheHandler = $this->getOption('cache_handler', null, 'xPDOFileCache');
  558. if (!$clearPartial || $cacheHandler !== 'xPDOFileCache') {
  559. $results[$partition] = $this->clean($partOptions);
  560. } else {
  561. /* Only clear resource cache for the provided contexts. */
  562. foreach ($partOptions['contexts'] as $ctx) {
  563. $this->modx->cacheManager->delete(
  564. $ctx,
  565. array(
  566. xPDO::OPT_CACHE_KEY => $this->modx->getOption('cache_resource_key', null, 'resource'),
  567. xPDO::OPT_CACHE_HANDLER => $this->modx->getOption('cache_resource_handler', null, $this->modx->getOption(xPDO::OPT_CACHE_HANDLER)),
  568. xPDO::OPT_CACHE_FORMAT => (int) $this->modx->getOption('cache_resource_format', null, $this->modx->getOption(xPDO::OPT_CACHE_FORMAT, null, xPDOCacheManager::CACHE_PHP))
  569. )
  570. );
  571. }
  572. }
  573. break;
  574. case 'scripts':
  575. /* clean the configurable source cache and remove the include files */
  576. $results[$partition] = $this->clean($partOptions);
  577. $this->deleteTree($this->getCachePath() . 'includes/');
  578. break;
  579. case 'db':
  580. if (!$this->getOption('cache_db', $partOptions, false)) {
  581. break;
  582. }
  583. $results[$partition] = $this->clean($partOptions);
  584. break;
  585. default:
  586. $results[$partition] = $this->clean($partOptions);
  587. break;
  588. }
  589. $cleared[] = $partKey;
  590. }
  591. /* invoke OnCacheUpdate event */
  592. $this->modx->invokeEvent('OnCacheUpdate', array(
  593. 'results' => $results,
  594. 'paths' => $providers,
  595. 'options' => array_values($providers),
  596. ));
  597. return (array_search(false, $results, true) === false);
  598. }
  599. /**
  600. * Check for and process Resources with pub_date or unpub_date set to now or in past.
  601. *
  602. * @todo Implement Context-isolated auto-publishing.
  603. * @param array $options An array of options for the process.
  604. * @return array An array containing published and unpublished Resource counts.
  605. */
  606. public function autoPublish(array $options = array()) {
  607. $publishingResults= array();
  608. $tblResource= $this->modx->getTableName('modResource');
  609. $timeNow= time();
  610. /* generate list of resources that are going to be published */
  611. $stmt = $this->modx->prepare("SELECT id, context_key, pub_date, unpub_date FROM {$tblResource} WHERE pub_date IS NOT NULL AND pub_date < {$timeNow} AND pub_date > 0");
  612. if ($stmt->execute()) {
  613. $publishingResults['published_resources'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
  614. }
  615. /* generate list of resources that are going to be unpublished */
  616. $stmt = $this->modx->prepare("SELECT id, context_key, pub_date, unpub_date FROM {$tblResource} WHERE unpub_date IS NOT NULL AND unpub_date < {$timeNow} AND unpub_date > 0");
  617. if ($stmt->execute()) {
  618. $publishingResults['unpublished_resources'] = $stmt->fetchAll(PDO::FETCH_ASSOC);
  619. }
  620. /* publish and unpublish resources using pub_date and unpub_date checks */
  621. $publishingResults['published']= $this->modx->exec("UPDATE {$tblResource} SET published=1, publishedon=pub_date, pub_date=0 WHERE pub_date IS NOT NULL AND pub_date < {$timeNow} AND pub_date > 0");
  622. $publishingResults['unpublished']= $this->modx->exec("UPDATE {$tblResource} SET published=0, publishedon=0, pub_date=0, unpub_date=0 WHERE unpub_date IS NOT NULL AND unpub_date < {$timeNow} AND unpub_date > 0");
  623. /* update publish time file */
  624. $timesArr= array ();
  625. $minpub= 0;
  626. $minunpub= 0;
  627. $sql= "SELECT MIN(pub_date) FROM {$tblResource} WHERE published = 0 AND pub_date > ?";
  628. $stmt= $this->modx->prepare($sql);
  629. if ($stmt) {
  630. $stmt->bindValue(1, 0);
  631. if ($stmt->execute()) {
  632. foreach ($stmt->fetchAll(PDO::FETCH_NUM) as $value) {
  633. $minpub= $value[0];
  634. unset($value);
  635. break;
  636. }
  637. } else {
  638. $publishingResults['errors'][]= $this->modx->lexicon('cache_publish_event_error',array('info' => $stmt->errorInfo()));
  639. }
  640. }
  641. else {
  642. $publishingResults['errors'][]= $this->modx->lexicon('cache_publish_event_error',array('info' => $sql));
  643. }
  644. if ($minpub) $timesArr[]= $minpub;
  645. $sql= "SELECT MIN(unpub_date) FROM {$tblResource} WHERE published = 1 AND unpub_date > ?";
  646. $stmt= $this->modx->prepare($sql);
  647. if ($stmt) {
  648. $stmt->bindValue(1, 0);
  649. if ($stmt->execute()) {
  650. foreach ($stmt->fetchAll(PDO::FETCH_NUM) as $value) {
  651. $minunpub= $value[0];
  652. unset($value);
  653. break;
  654. }
  655. } else {
  656. $publishingResults['errors'][]= $this->modx->lexicon('cache_unpublish_event_error',array('info' => $stmt->errorInfo()));
  657. }
  658. } else {
  659. $publishingResults['errors'][]= $this->modx->lexicon('cache_unpublish_event_error',array('info' => $sql));
  660. }
  661. if ($minunpub) $timesArr[]= $minunpub;
  662. if (count($timesArr) > 0) {
  663. $nextevent= min($timesArr);
  664. } else {
  665. $nextevent= 0;
  666. }
  667. /* cache the time of the next auto_publish event */
  668. $options[xPDO::OPT_CACHE_KEY] = $this->getOption('cache_auto_publish_key', $options, 'auto_publish');
  669. $options[xPDO::OPT_CACHE_HANDLER] = $this->getOption('cache_auto_publish_handler', $options, $this->getOption(xPDO::OPT_CACHE_HANDLER, $options));
  670. $options[xPDO::OPT_CACHE_ATTEMPTS] = (integer) $this->getOption('cache_auto_publish_attempts', $options, $this->getOption(xPDO::OPT_CACHE_ATTEMPTS, $options, 1));
  671. $options[xPDO::OPT_CACHE_ATTEMPT_DELAY] = (integer) $this->getOption('cache_auto_publish_attempt_delay', $options, $this->getOption(xPDO::OPT_CACHE_ATTEMPT_DELAY, $options, 1000));
  672. if (!$this->set('auto_publish', $nextevent, 0, $options)) {
  673. $this->modx->log(modX::LOG_LEVEL_ERROR, "Error caching time of next auto publishing event");
  674. $publishingResults['errors'][]= $this->modx->lexicon('cache_sitepublishing_file_error');
  675. } else {
  676. if ($publishingResults['published'] !== 0 || $publishingResults['unpublished'] !== 0) {
  677. $this->modx->invokeEvent('OnResourceAutoPublish', array(
  678. 'results' => $publishingResults
  679. ));
  680. }
  681. }
  682. return $publishingResults;
  683. }
  684. /**
  685. * Clear part or all of the MODX cache.
  686. *
  687. * @deprecated Use refresh()
  688. * @param array $paths An optional array of paths, relative to the cache
  689. * path, to be deleted.
  690. * @param array $options An optional associative array of cache clearing options: <ul>
  691. * <li><strong>objects</strong>: an array of objects or paths to flush from the db object cache</li>
  692. * <li><strong>extensions</strong>: an array of file extensions to match when deleting the cache directories</li>
  693. * </ul>
  694. * @return array
  695. */
  696. public function clearCache(array $paths= array(), array $options= array()) {
  697. $this->modx->deprecated('2.1.0', 'Use modCacheManager::refresh() instead.');
  698. $results= array();
  699. $delObjs= array();
  700. if ($clearObjects = $this->getOption('objects', $options)) {
  701. $objectOptions = array_merge($options, array('cache_prefix' => $this->getOption('cache_db_prefix', $options, xPDOCacheManager::CACHE_DIR)));
  702. /* clear object cache by key, or * = flush entire object cache */
  703. if (is_array($clearObjects)) {
  704. foreach ($clearObjects as $key) {
  705. if ($this->delete($key, $objectOptions))
  706. $delObjs[]= $key;
  707. }
  708. }
  709. elseif (is_string($clearObjects) && $clearObjects == '*') {
  710. $delObjs= $this->clean($objectOptions);
  711. }
  712. }
  713. $results['deleted_objects']= $delObjs;
  714. $extensions= $this->getOption('extensions', $options, array('.cache.php'));
  715. if (empty($paths)) {
  716. $paths= array('');
  717. }
  718. $delFiles= array();
  719. foreach ($paths as $pathIdx => $path) {
  720. $deleted= false;
  721. $abspath= $this->modx->getOption(xPDO::OPT_CACHE_PATH) . $path;
  722. if (file_exists($abspath)) {
  723. if (is_dir($abspath)) {
  724. $deleted= $this->deleteTree($abspath, array('deleteTop' => false, 'skipDirs' => false, 'extensions' => $extensions));
  725. } else {
  726. if (unlink($abspath)) {
  727. $deleted= array($path);
  728. }
  729. }
  730. if (is_array($deleted))
  731. $delFiles= array_merge($delFiles, $deleted);
  732. }
  733. if ($path == '') break;
  734. }
  735. $results['deleted_files']= $delFiles;
  736. $results['deleted_files_count']= count($delFiles);
  737. if (isset($options['publishing']) && $options['publishing']) {
  738. $results['publishing']= $this->autoPublish($options);
  739. }
  740. /* invoke OnCacheUpdate event */
  741. $this->modx->invokeEvent('OnCacheUpdate', array(
  742. 'results' => $results,
  743. 'paths' => $paths,
  744. 'options' => $options,
  745. ));
  746. return $results;
  747. }
  748. /**
  749. * Flush permissions for users
  750. *
  751. * @return bool True if successful
  752. */
  753. public function flushPermissions() {
  754. $ctxQuery = $this->modx->newQuery('modContext');
  755. $ctxQuery->select($this->modx->getSelectColumns('modContext', '', '', array('key')));
  756. if ($ctxQuery->prepare() && $ctxQuery->stmt->execute()) {
  757. $contexts = $ctxQuery->stmt->fetchAll(PDO::FETCH_COLUMN);
  758. if ($contexts) {
  759. $serialized = serialize($contexts);
  760. if ($this->modx->exec("UPDATE {$this->modx->getTableName('modUser')} SET {$this->modx->escape('session_stale')} = {$this->modx->quote($serialized)}") !== false) {
  761. return true;
  762. }
  763. }
  764. }
  765. return false;
  766. }
  767. }