modcontext.class.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  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. * Represents a virtual site context within a modX repository.
  12. *
  13. * @property string $key The key of the context
  14. * @property string $description The description of the context
  15. *
  16. * @package modx
  17. */
  18. class modContext extends modAccessibleObject {
  19. /**
  20. * An array of configuration options for this context
  21. * @var array $config
  22. */
  23. public $config= null;
  24. /**
  25. * The alias map for this context
  26. * @var array $aliasMap
  27. */
  28. public $aliasMap= null;
  29. /**
  30. * The resource map for all resources in this context
  31. * @var array $resourceMap
  32. */
  33. public $resourceMap= null;
  34. /**
  35. * A map of WebLink Resources with their target URLs
  36. * @var array $webLinkMap
  37. */
  38. public $webLinkMap= null;
  39. /**
  40. * The event map for all events being executed in this context
  41. * @var array $eventMap
  42. */
  43. public $eventMap= null;
  44. /**
  45. * The plugin cache array for all plugins being fired in this context
  46. * @var array $pluginCache
  47. */
  48. public $pluginCache= null;
  49. /**
  50. * The key for the cache for this context
  51. * @var string $_cacheKey
  52. */
  53. protected $_cacheKey= '[contextKey]/context';
  54. /**
  55. * Prepare and execute a PDOStatement to retrieve data needed for $aliasMap and $resourceMap.
  56. *
  57. * @static
  58. * @param modContext &$context A reference to a specific modContext instance.
  59. * @return PDOStatement|bool A PDOStatement, prepared and executed, with the map data, or false
  60. * if the statement could not be prepared or executed.
  61. */
  62. public static function getResourceCacheMapStmt(&$context) {
  63. return false;
  64. }
  65. /**
  66. * Prepare and execute a PDOStatement to retrieve data needed for $webLinkMap.
  67. *
  68. * @static
  69. * @param modContext &$context A reference to a specific modContext instance.
  70. * @return PDOStatement|bool A PDOStatement, prepared and executed, with the map data, or false
  71. * if the statement could not be prepared or executed.
  72. */
  73. public static function getWebLinkCacheMapStmt(&$context) {
  74. return false;
  75. }
  76. /**
  77. * Prepare a context for use.
  78. *
  79. * @uses modCacheManager::generateContext() This method is responsible for
  80. * preparing the context for use.
  81. * {@internal You can override this behavior here, but you will only need to
  82. * override the modCacheManager::generateContext() method in most cases}}
  83. * @access public
  84. * @param boolean $regenerate If true, the existing cache file will be ignored
  85. * and regenerated.
  86. * @return boolean Indicates if the context was successfully prepared.
  87. */
  88. public function prepare($regenerate= false, array $options = array()) {
  89. $prepared= false;
  90. if ($this->config === null || $regenerate) {
  91. if ($this->xpdo->getCacheManager()) {
  92. $context = array();
  93. if ($regenerate || !($context = $this->xpdo->cacheManager->get($this->getCacheKey(), array(
  94. xPDO::OPT_CACHE_KEY => $this->xpdo->getOption('cache_context_settings_key', null, 'context_settings'),
  95. xPDO::OPT_CACHE_HANDLER => $this->xpdo->getOption('cache_context_settings_handler', null, $this->xpdo->getOption(xPDO::OPT_CACHE_HANDLER, null, 'cache.xPDOFileCache')),
  96. xPDO::OPT_CACHE_FORMAT => (integer) $this->xpdo->getOption('cache_context_settings_format', null, $this->xpdo->getOption(xPDO::OPT_CACHE_FORMAT, null, xPDOCacheManager::CACHE_PHP)),
  97. )))) {
  98. $context = $this->xpdo->cacheManager->generateContext($this->get('key'), $options);
  99. }
  100. if (!empty($context)) {
  101. foreach ($context as $var => $val) {
  102. if ($var === 'policies') {
  103. $this->setPolicies($val);
  104. continue;
  105. }
  106. $this->$var = $val;
  107. }
  108. $prepared= true;
  109. }
  110. }
  111. } else {
  112. $prepared= true;
  113. }
  114. return $prepared;
  115. }
  116. /**
  117. * Returns a context-specific setting value.
  118. *
  119. * @param string $key The option key to check.
  120. * @param string $default A default value to use if not found.
  121. * @param array $options An array of additional options to merge over top of
  122. * the context settings.
  123. * @return mixed The option value or the provided default.
  124. */
  125. public function getOption($key, $default = null, $options = null) {
  126. if (is_array($options)) {
  127. $options = array_merge($this->config, $options);
  128. } else {
  129. $options =& $this->config;
  130. }
  131. return $this->xpdo->getOption($key, $options, $default);
  132. }
  133. /**
  134. * Returns the file name representing this context in the cache.
  135. *
  136. * @access public
  137. * @return string The cache filename.
  138. */
  139. public function getCacheKey() {
  140. if ($this->get('key')) {
  141. $this->_cacheKey= str_replace('[contextKey]', $this->get('key'), $this->_cacheKey);
  142. } else {
  143. $this->_cacheKey= str_replace('[contextKey]', uniqid('ctx_'), $this->_cacheKey);
  144. }
  145. return $this->_cacheKey;
  146. }
  147. /**
  148. * Loads the access control policies applicable to this element.
  149. *
  150. * {@inheritdoc}
  151. */
  152. public function findPolicy($context = '') {
  153. $policy = array();
  154. $enabled = true;
  155. $context = !empty($context) ? $context : $this->xpdo->context->get('key');
  156. if (!is_object($this->xpdo->context) || $context === $this->xpdo->context->get('key')) {
  157. $enabled = (boolean) $this->xpdo->getOption('access_context_enabled', null, true);
  158. } elseif ($this->xpdo->getContext($context)) {
  159. $enabled = (boolean) $this->xpdo->contexts[$context]->getOption('access_context_enabled', true);
  160. }
  161. if ($enabled) {
  162. if (empty($this->_policies) || !isset($this->_policies[$context])) {
  163. $c = $this->xpdo->newQuery('modAccessContext');
  164. $c->leftJoin('modAccessPolicy','Policy');
  165. $c->select(array(
  166. 'modAccessContext.id',
  167. 'modAccessContext.target',
  168. 'modAccessContext.principal',
  169. 'modAccessContext.authority',
  170. 'modAccessContext.policy',
  171. 'Policy.data',
  172. ));
  173. $c->where(array(
  174. 'modAccessContext.principal_class' => 'modUserGroup',
  175. 'modAccessContext.target' => $this->get('key'),
  176. ));
  177. $c->sortby('modAccessContext.target,modAccessContext.principal,modAccessContext.authority,modAccessContext.policy');
  178. $acls = $this->xpdo->getCollection('modAccessContext',$c);
  179. foreach ($acls as $acl) {
  180. $policy['modAccessContext'][$acl->get('target')][] = array(
  181. 'principal' => $acl->get('principal'),
  182. 'authority' => $acl->get('authority'),
  183. 'policy' => $acl->get('data') ? $this->xpdo->fromJSON($acl->get('data'), true) : array(),
  184. );
  185. }
  186. $this->_policies[$context] = $policy;
  187. } else {
  188. $policy = $this->_policies[$context];
  189. }
  190. }
  191. return $policy;
  192. }
  193. /**
  194. * Generates a URL representing a specified resource in this context.
  195. *
  196. * Note that if this method is called from a context other than the one
  197. * initialized for the modX instance, and the scheme is not specified, an
  198. * empty string, or abs, the method will force the scheme to full.
  199. *
  200. * @access public
  201. * @param integer $id The id of a resource.
  202. * @param string $args A query string to append to the generated URL.
  203. * @param mixed $scheme The scheme indicates in what format the URL is generated.<br>
  204. * <pre>
  205. * -1 : (default value) URL is relative to site_url
  206. * 0 : see http
  207. * 1 : see https
  208. * full : URL is absolute, prepended with site_url from config
  209. * abs : URL is absolute, prepended with base_url from config
  210. * http : URL is absolute, forced to http scheme
  211. * https : URL is absolute, forced to https scheme
  212. * </pre>
  213. * @param array $options An array of options for generating the Resource URL.
  214. * @return string The URL for the resource.
  215. */
  216. public function makeUrl($id, $args = '', $scheme = -1, array $options = array()) {
  217. $url = '';
  218. $found = false;
  219. if ($id= intval($id)) {
  220. if ($this->config === null) {
  221. $this->prepare();
  222. }
  223. if (is_object($this->xpdo->context) && $this->get('key') !== $this->xpdo->context->get('key')) {
  224. $config = array_merge($this->xpdo->_systemConfig, $this->config, $this->xpdo->_userConfig, $options);
  225. if ($scheme === -1 || $scheme === '-1' || $scheme === '' || strpos($scheme, 'abs') !== false) {
  226. $scheme= 'full';
  227. }
  228. } else {
  229. $config = array_merge($this->xpdo->config, $options);
  230. }
  231. if ($config['friendly_urls'] == 1) {
  232. if ((integer) $id === (integer) $config['site_start']) {
  233. $alias= ($scheme === '' || $scheme === -1 || $scheme === '-1') ? $config['base_url'] : '';
  234. $found= true;
  235. } else {
  236. $alias= $this->getResourceURI($id);
  237. if (!$alias) {
  238. $alias= '';
  239. $this->xpdo->log(xPDO::LOG_LEVEL_WARN, '`' . $id . '` was requested but no alias was located.');
  240. } else {
  241. $found= true;
  242. }
  243. }
  244. } elseif (array_keys(array((string) $id), $this->resourceMap, true) !== false) {
  245. $found= true;
  246. }
  247. if ($found) {
  248. $target = null;
  249. if (isset($config['use_weblink_target']) && !empty($config['use_weblink_target'])) {
  250. if (array_key_exists($id, $this->webLinkMap)) {
  251. $target = $this->webLinkMap[$id];
  252. if (!empty($target)) {
  253. $alias = $target;
  254. }
  255. }
  256. }
  257. $targetHasQS = (empty($config['friendly_urls']) || strpos($alias, '?') !== false);
  258. if (is_array($args)) {
  259. $args = modX::toQueryString($args);
  260. }
  261. if ($args != '') {
  262. if (!$targetHasQS) {
  263. /* add ? to $args if missing */
  264. $c= substr($args, 0, 1);
  265. if ($c == '&') {
  266. $args= '?' . substr($args, 1);
  267. } elseif ($c != '?') {
  268. $args= '?' . $args;
  269. }
  270. } elseif ($args != '') {
  271. /* add & to $args if missing */
  272. $c= substr($args, 0, 1);
  273. if ($c == '?')
  274. $args= '&' . substr($args, 1);
  275. elseif ($c != '&') $args= '&' . $args;
  276. }
  277. }
  278. if ($config['friendly_urls'] == 1 || $target !== null) {
  279. $url= $alias . $args;
  280. } else {
  281. $url= $config['request_controller'] . '?' . $config['request_param_id'] . '=' . $id . $args;
  282. }
  283. $host= '';
  284. if ($target === null && $scheme !== -1 && $scheme !== '') {
  285. if ($scheme === 1 || $scheme === 0) {
  286. $https_port= $this->getOption('https_port',$config,443);
  287. $isSecure= ($_SERVER['SERVER_PORT'] == $https_port || (isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS'])=='on')) ? 1 : 0;
  288. if ($scheme != $isSecure) {
  289. $scheme = $isSecure ? 'http' : 'https';
  290. }
  291. }
  292. switch ($scheme) {
  293. case 'full':
  294. $host= $config['site_url'];
  295. break;
  296. case 'abs':
  297. case 'absolute':
  298. $host= $config['base_url'];
  299. break;
  300. case 'https':
  301. case 'http':
  302. $host= $scheme . '://' . $config['http_host'] . $config['base_url'];
  303. break;
  304. }
  305. $url= $host . $url;
  306. }
  307. } else {
  308. $this->xpdo->log(
  309. xPDO::LOG_LEVEL_INFO,
  310. "Resource with id {$id} was not found in context {$this->key}",
  311. '',
  312. __METHOD__,
  313. $this->xpdo->resource ? "resource {$this->xpdo->resource->id}" : __FILE__,
  314. $this->xpdo->resource ? '' : __LINE__
  315. );
  316. }
  317. }
  318. if ($this->xpdo->getDebug() === true) {
  319. $this->xpdo->log(xPDO::LOG_LEVEL_DEBUG, "modContext[" . $this->get('key') . "]->makeUrl({$id}) = {$url}");
  320. }
  321. return $url;
  322. }
  323. /**
  324. * Overrides xPDOObject::remove to fire modX-specific events
  325. *
  326. * {@inheritDoc}
  327. */
  328. public function remove(array $ancestors = array()) {
  329. if ($this->xpdo instanceof modX) {
  330. $this->xpdo->invokeEvent('OnContextBeforeRemove',array(
  331. 'context' => &$this,
  332. 'ancestors' => $ancestors,
  333. ));
  334. }
  335. $removed = parent :: remove($ancestors);
  336. if ($removed && $this->xpdo instanceof modX) {
  337. $this->xpdo->invokeEvent('OnContextRemove',array(
  338. 'context' => &$this,
  339. 'ancestors' => $ancestors,
  340. ));
  341. }
  342. return $removed;
  343. }
  344. /**
  345. * Overrides xPDOObject::save to fire modX-specific events.
  346. *
  347. * {@inheritDoc}
  348. */
  349. public function save($cacheFlag= null) {
  350. $isNew = $this->isNew();
  351. if ($this->xpdo instanceof modX) {
  352. $this->xpdo->invokeEvent('OnContextBeforeSave',array(
  353. 'context' => &$this,
  354. 'mode' => $isNew ? modSystemEvent::MODE_NEW : modSystemEvent::MODE_UPD,
  355. 'cacheFlag' => $cacheFlag,
  356. ));
  357. }
  358. $saved = parent :: save($cacheFlag);
  359. if ($saved && $this->xpdo instanceof modX) {
  360. $this->xpdo->invokeEvent('OnContextSave',array(
  361. 'context' => &$this,
  362. 'mode' => $isNew ? modSystemEvent::MODE_NEW : modSystemEvent::MODE_UPD,
  363. 'cacheFlag' => $cacheFlag,
  364. ));
  365. }
  366. return $saved;
  367. }
  368. /**
  369. * Get and execute a PDOStatement representing data for the aliasMap and resourceMap.
  370. *
  371. * @return PDOStatement|null
  372. */
  373. public function getResourceCacheMap() {
  374. return $this->xpdo->call('modContext', 'getResourceCacheMapStmt', array(&$this));
  375. }
  376. /**
  377. * Get and execute a PDOStatement representing data for the webLinkMap.
  378. *
  379. * @return PDOStatement|null
  380. */
  381. public function getWebLinkCacheMap() {
  382. return $this->xpdo->call('modContext', 'getWebLinkCacheMapStmt', array(&$this));
  383. }
  384. /**
  385. * Get a Resource URI in this Context by id.
  386. *
  387. * @param string|integer $id The integer id of the Resource.
  388. * @return string|bool The URI of the Resource, or false if not found in this Context.
  389. */
  390. public function getResourceURI($id) {
  391. if ($this->getOption('cache_alias_map') && isset($this->aliasMap)) {
  392. $uri = array_search($id, $this->aliasMap);
  393. } else {
  394. $query = $this->xpdo->newQuery('modResource', array(
  395. 'id' => $id,
  396. 'deleted' => false,
  397. 'context_key' => $this->get('key')
  398. ));
  399. $query->select($this->xpdo->getSelectColumns('modResource', '', '', array('uri')));
  400. $uri = $this->xpdo->getValue($query->prepare());
  401. }
  402. return $uri;
  403. }
  404. }