context.module 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. <?php
  2. require('context.core.inc');
  3. define('CONTEXT_GET', 0);
  4. define('CONTEXT_SET', 1);
  5. define('CONTEXT_ISSET', 2);
  6. define('CONTEXT_CLEAR', 3);
  7. define('CONTEXT_CONDITION_MODE_OR', 0);
  8. define('CONTEXT_CONDITION_MODE_AND', 1);
  9. /**
  10. * Master context function. Avoid calling this directly -- use one of the helper functions below.
  11. *
  12. * @param $op
  13. * The operation to perform - handled by the context helper functions. Use them.
  14. * @param $namespace
  15. * A string to be used as the namespace for the context information.
  16. * @param $attribute
  17. * Usually a string to be used as a key to set/retrieve context information. An array can
  18. * also be used when setting context to establish an entire context namespace at once.
  19. * (At some point objects may also be accepted, but currently functionaliy isn't complete.)
  20. * @param $value
  21. * A value to set for the provided key. If omitted the value will be set to true.
  22. *
  23. * @return
  24. * Either the requested value, or false if the operation fails.
  25. */
  26. function context_context($op = CONTEXT_GET, $namespace = NULL, $attribute = NULL, $value = NULL) {
  27. static $context;
  28. $context = !$context ? array() : $context;
  29. switch ($op) {
  30. case CONTEXT_GET:
  31. // return entire context
  32. if (!$namespace) {
  33. return $context;
  34. }
  35. // return entire space if set
  36. elseif (isset($context[(string) $namespace])) {
  37. // return val of key from space
  38. if (is_array($context[(string) $namespace]) && isset($context[(string) $namespace][(string) $attribute])) {
  39. return $context[(string) $namespace][(string) $attribute];
  40. }
  41. elseif (!$attribute) {
  42. return $context[(string) $namespace];
  43. }
  44. }
  45. break;
  46. case CONTEXT_SET:
  47. // bail if invalid space is specified or context is already set
  48. if (is_string($namespace) || is_int($namespace)) {
  49. // initialize namespace if no key is specified
  50. if (!$attribute) {
  51. $context[(string) $namespace] = array();
  52. return TRUE;
  53. }
  54. // set to true if key is a usable identifier. otherwise, allow a key or object to be inserted
  55. if ($value === NULL) {
  56. if (is_string($attribute) || is_int($attribute)) {
  57. $context[(string) $namespace][(string) $attribute] = TRUE;
  58. return TRUE;
  59. }
  60. elseif (is_array($attribute) || is_object($attribute)) {
  61. $context[(string) $namespace] = $attribute;
  62. return TRUE;
  63. }
  64. }
  65. // set value if key is valid
  66. if ((is_string($attribute) || is_int($attribute)) && $value !== NULL) {
  67. $context[$namespace][$attribute] = $value;
  68. return TRUE;
  69. }
  70. }
  71. break;
  72. case CONTEXT_ISSET:
  73. // return entire context
  74. if (!$namespace) return FALSE;
  75. if (!$attribute) {
  76. // return entire space if set
  77. return isset($context[$namespace]);
  78. }
  79. // return val of key from space
  80. return isset($context[$namespace][$attribute]);
  81. case CONTEXT_CLEAR:
  82. $context = array();
  83. return TRUE;
  84. }
  85. return FALSE;
  86. }
  87. /**
  88. * Sets a context by namespace + attribute.
  89. */
  90. function context_set($namespace, $attribute = NULL, $value = NULL) {
  91. return context_context(CONTEXT_SET, $namespace, $attribute, $value);
  92. }
  93. /**
  94. * Retrieves a context by namespace + (optional) attribute.
  95. */
  96. function context_get($namespace = NULL, $attribute = NULL) {
  97. return context_context(CONTEXT_GET, $namespace, $attribute, NULL);
  98. }
  99. /**
  100. * Returns a boolean for whether a context namespace + attribute have been set.
  101. */
  102. function context_isset($namespace = NULL, $attribute = NULL) {
  103. return context_context(CONTEXT_ISSET, $namespace, $attribute, NULL);
  104. }
  105. /**
  106. * Deprecated context_exists() function. Retained for backwards
  107. * compatibility -- please use context_isset() instead.
  108. */
  109. function context_exists($namespace = NULL, $attribute = NULL) {
  110. return context_context(CONTEXT_ISSET, $namespace, $attribute, NULL);
  111. }
  112. /**
  113. * Clears static context array() -- meant only for testing
  114. */
  115. function context_clear() {
  116. return context_context(CONTEXT_CLEAR);
  117. }
  118. /**
  119. * Implemented hooks ==================================================
  120. */
  121. /**
  122. * Implementation of hook_ctools_plugin_type().
  123. */
  124. function context_ctools_plugin_type() {
  125. return array(
  126. 'plugins' => array(
  127. 'cache' => TRUE,
  128. 'use hooks' => TRUE,
  129. 'classes' => array('handler'),
  130. ),
  131. );
  132. }
  133. /**
  134. * Implementation of hook_context_plugins().
  135. *
  136. * This is a ctools plugins hook.
  137. */
  138. function context_context_plugins() {
  139. module_load_include('inc', 'context', 'context.plugins');
  140. return _context_context_plugins();
  141. }
  142. /**
  143. * Implementation of hook_context_registry().
  144. */
  145. function context_context_registry() {
  146. module_load_include('inc', 'context', 'context.plugins');
  147. return _context_context_registry();
  148. }
  149. /**
  150. * Implementation of hook_init().
  151. */
  152. function context_init() {
  153. if ($plugin = context_get_plugin('condition', 'sitewide')) {
  154. $plugin->execute(1);
  155. }
  156. if ($plugin = context_get_plugin('condition', 'path')) {
  157. $plugin->execute();
  158. }
  159. if ($plugin = context_get_plugin('condition', 'query_string')) {
  160. $plugin->execute();
  161. }
  162. if ($plugin = context_get_plugin('condition', 'language')) {
  163. global $language;
  164. $plugin->execute($language->language);
  165. }
  166. if ($plugin = context_get_plugin('condition', 'user')) {
  167. global $user;
  168. $plugin->execute($user);
  169. }
  170. }
  171. /**
  172. * Implementation of hook_preprocess_menu_link().
  173. *
  174. * This allows menus that are not primary/secondary menus to get
  175. * the "active" class assigned to them. This assumes they are using
  176. * theme('menu_link') for the menu rendering to html.
  177. */
  178. function context_preprocess_menu_link(&$variables) {
  179. if ($contexts = context_active_contexts()) {
  180. foreach ($contexts as $context) {
  181. if (isset($context->reactions['menu'])) {
  182. // In context module < v3.2 the url was a string. In version 3.3+ this is
  183. // an array of urls. Implement interims BC layer.
  184. //
  185. // Examples:
  186. // - OLD < v3.2 context reaction structure:
  187. // array('menu' => 'taxonomy/term/1')
  188. //
  189. // - NEW 3.3+ context reaction structure:
  190. // array(
  191. // 'menu' => array(
  192. // 0 => 'navigation:taxonomy/term/1'
  193. // 1 => 'foo-menu:taxonomy/term/1'
  194. // )
  195. // )
  196. $reactions_menu = is_array($context->reactions['menu']) ? array_values($context->reactions['menu']) : array($context->reactions['menu']);
  197. // Get everything after the first ':' character (if found) as the url to
  198. // match against element '#href'.
  199. $urls = array();
  200. foreach ($reactions_menu as $url) {
  201. if (strpos($url, ':') !== FALSE) {
  202. // Get unique menu name 'navigation' from 'navigation:taxonomy/term/1'
  203. $reaction_menu = explode(':', $url);
  204. $path = $reaction_menu[1];
  205. $urls[$path] = $reaction_menu[0];
  206. }
  207. else {
  208. // BC layer for menu contexts that have not re-saved. This is for
  209. // urls like 'taxonomy/term/1'. We need to add a fake menu key
  210. // 'bc-context-menu-layer' or the BC link get's removed by
  211. // array_intersect below.
  212. //
  213. // @TODO: Remove BC layer in 4.x
  214. $urls[$url] = 'context-reaction-menu-bc-layer';
  215. }
  216. }
  217. // Filter urls by the menu name of the current link. The link reaction
  218. // can be configured per menu link in specific menus and the contect
  219. // reaction should not applied to other menus with the same menu link.
  220. $menu_name = $variables['element']['#original_link']['menu_name'];
  221. $menu_paths = array_intersect($urls, array($menu_name, 'context-reaction-menu-bc-layer'));
  222. $reaction_menu_paths = array_keys($menu_paths);
  223. // - If menu href and context reaction menu url match, add the 'active'
  224. // css class to the link of this menu.
  225. // - Do not add class twice on current page.
  226. if (in_array($variables['element']['#href'], $reaction_menu_paths) && $variables['element']['#href'] != $_GET['q']) {
  227. // Initialize classes array if not set.
  228. if (!isset($variables['element']['#localized_options']['attributes']['class'])) {
  229. $variables['element']['#localized_options']['attributes']['class'] = array();
  230. }
  231. // Do not add the 'active' class twice in views tabs.
  232. if (!in_array('active', $variables['element']['#localized_options']['attributes']['class'])) {
  233. $variables['element']['#localized_options']['attributes']['class'][] = 'active';
  234. }
  235. }
  236. }
  237. }
  238. }
  239. }
  240. /**
  241. * Load & crud functions ==============================================
  242. */
  243. /**
  244. * Context loader.
  245. *
  246. * @param $name
  247. * The name for this context object.
  248. *
  249. * @return
  250. * Returns a fully-loaded context definition.
  251. */
  252. function context_load($name = NULL, $reset = FALSE) {
  253. ctools_include('export');
  254. static $contexts;
  255. static $altered;
  256. if (!isset($contexts) || $reset) {
  257. $contexts = $altered = array();
  258. if (!$reset && $contexts = context_cache_get('context')) {
  259. // Nothing here.
  260. }
  261. else {
  262. if ($reset) {
  263. ctools_export_load_object_reset('context');
  264. }
  265. $contexts = ctools_export_load_object('context', 'all');
  266. context_cache_set('context', $contexts);
  267. }
  268. }
  269. if (isset($name)) {
  270. // Allow other modules to alter the value just before it's returned.
  271. if (isset($contexts[$name]) && !isset($altered[$name])) {
  272. $altered[$name] = TRUE;
  273. drupal_alter('context_load', $contexts[$name]);
  274. }
  275. return isset($contexts[$name]) ? $contexts[$name] : FALSE;
  276. }
  277. return $contexts;
  278. }
  279. /**
  280. * Inserts or updates a context object into the database.
  281. * @TODO: should probably return the new cid on success -- make sure
  282. * this doesn't break any checks elsewhere.
  283. *
  284. * @param $context
  285. * The context object to be inserted.
  286. *
  287. * @return
  288. * Returns true on success, false on failure.
  289. */
  290. function context_save($context) {
  291. $existing = context_load($context->name, TRUE);
  292. if ($existing && ($existing->export_type & EXPORT_IN_DATABASE)) {
  293. drupal_write_record('context', $context, 'name');
  294. }
  295. else {
  296. drupal_write_record('context', $context);
  297. }
  298. context_load(NULL, TRUE);
  299. context_invalidate_cache();
  300. return TRUE;
  301. }
  302. /**
  303. * Deletes an existing context.
  304. *
  305. * @param $context
  306. * The context object to be deleted.
  307. *
  308. * @return
  309. * Returns true on success, false on failure.
  310. */
  311. function context_delete($context) {
  312. if (isset($context->name) && ($context->export_type & EXPORT_IN_DATABASE)) {
  313. db_query("DELETE FROM {context} WHERE name = :name", array(':name' => $context->name));
  314. context_invalidate_cache();
  315. return TRUE;
  316. }
  317. return FALSE;
  318. }
  319. /**
  320. * Exports the specified context.
  321. */
  322. function context_export($context, $indent = '') {
  323. $output = ctools_export_object('context', $context, $indent);
  324. $translatables = array();
  325. foreach (array('description', 'tag') as $key) {
  326. if (!empty($context->{$key})) {
  327. $translatables[] = $context->{$key};
  328. }
  329. }
  330. $translatables = array_filter(array_unique($translatables));
  331. if (!empty($translatables)) {
  332. $output .= "\n";
  333. $output .= "{$indent}// Translatables\n";
  334. $output .= "{$indent}// Included for use with string extractors like potx.\n";
  335. sort($translatables);
  336. foreach ($translatables as $string) {
  337. $output .= "{$indent}t(" . ctools_var_export($string) . ");\n";
  338. }
  339. }
  340. return $output;
  341. }
  342. /**
  343. * API FUNCTIONS ======================================================
  344. */
  345. /**
  346. * CTools list callback for bulk export.
  347. */
  348. function context_context_list() {
  349. $contexts = context_load(NULL, TRUE);
  350. $list = array();
  351. foreach ($contexts as $context) {
  352. $list[$context->name] = $context->name;
  353. }
  354. return $list;
  355. }
  356. /**
  357. * Wrapper around cache_get() to make it easier for context to pull different
  358. * datastores from a single cache row.
  359. */
  360. function context_cache_get($key, $reset = FALSE) {
  361. static $cache;
  362. if (!isset($cache) || $reset) {
  363. $cache = cache_get('context', 'cache');
  364. $cache = $cache ? $cache->data : array();
  365. }
  366. return !empty($cache[$key]) ? $cache[$key] : FALSE;
  367. }
  368. /**
  369. * Wrapper around cache_set() to make it easier for context to write different
  370. * datastores to a single cache row.
  371. */
  372. function context_cache_set($key, $value) {
  373. $cache = cache_get('context', 'cache');
  374. $cache = $cache ? $cache->data : array();
  375. $cache[$key] = $value;
  376. cache_set('context', $cache);
  377. }
  378. /**
  379. * Wrapper around context_load() that only returns enabled contexts.
  380. */
  381. function context_enabled_contexts($reset = FALSE) {
  382. $enabled = array();
  383. foreach (context_load(NULL, $reset) as $context) {
  384. if (empty($context->disabled)) {
  385. $enabled[$context->name] = $context;
  386. }
  387. }
  388. return $enabled;
  389. }
  390. /**
  391. * Queue or activate contexts that have met the specified condition.
  392. *
  393. * @param $context
  394. * The context object to queue or activate.
  395. * @param $condition
  396. * String. Name for the condition that has been met.
  397. * @param $reset
  398. * Reset flag for the queue static cache.
  399. */
  400. function context_condition_met($context, $condition, $reset = FALSE) {
  401. static $queue;
  402. if (!isset($queue) || $reset) {
  403. $queue = array();
  404. }
  405. if (!context_isset('context', $context->name)) {
  406. // Context is using AND mode. Queue it.
  407. if (isset($context->condition_mode) && $context->condition_mode == CONTEXT_CONDITION_MODE_AND) {
  408. $queue[$context->name][$condition] = $condition;
  409. // If all conditions have been met. set the context.
  410. if (!array_diff(array_keys($context->conditions), $queue[$context->name])) {
  411. context_set('context', $context->name, $context);
  412. }
  413. }
  414. // Context is using OR mode. Set it.
  415. else {
  416. context_set('context', $context->name, $context);
  417. }
  418. }
  419. }
  420. /**
  421. * Loads any active contexts with associated reactions. This should be run
  422. * at a late stage of the page load to ensure that relevant contexts have been set.
  423. */
  424. function context_active_contexts() {
  425. $contexts = context_get('context');
  426. return !empty($contexts) && is_array($contexts) ? $contexts : array();
  427. }
  428. /**
  429. * Loads an associative array of conditions => context identifiers to allow
  430. * contexts to be set by different conditions.
  431. */
  432. function context_condition_map($reset = FALSE) {
  433. static $condition_map;
  434. if (!isset($condition_map) || $reset) {
  435. if (!$reset && $cache = context_cache_get('condition_map')) {
  436. $condition_map = $cache;
  437. }
  438. else {
  439. $condition_map = array();
  440. foreach (array_keys(context_conditions()) as $condition) {
  441. if ($plugin = context_get_plugin('condition', $condition)) {
  442. foreach (context_enabled_contexts() as $context) {
  443. $values = $plugin->fetch_from_context($context, 'values');
  444. foreach ($values as $value) {
  445. if (!isset($condition_map[$condition][$value])) {
  446. $condition_map[$condition][$value] = array();
  447. }
  448. $condition_map[$condition][$value][] = $context->name;
  449. }
  450. }
  451. }
  452. }
  453. context_cache_set('condition_map', $condition_map);
  454. }
  455. }
  456. return $condition_map;
  457. }
  458. /**
  459. * Invalidates all context caches().
  460. * @TODO: Update to use a CTools API function for clearing plugin caches
  461. * when/if it becomes available.
  462. */
  463. function context_invalidate_cache() {
  464. cache_clear_all('context', 'cache', TRUE);
  465. cache_clear_all('plugins:context', 'cache', TRUE);
  466. }
  467. /**
  468. * Implementation of hook_flush_caches().
  469. */
  470. function context_flush_caches() {
  471. context_invalidate_cache();
  472. }
  473. /**
  474. * Recursive helper function to determine whether an array and its
  475. * children are entirely empty.
  476. */
  477. function context_empty($element) {
  478. $empty = TRUE;
  479. if (is_array($element)) {
  480. foreach ($element as $child) {
  481. $empty = $empty && context_empty($child);
  482. }
  483. }
  484. else {
  485. $empty = $empty && !isset($element);
  486. }
  487. return $empty;
  488. }
  489. /**
  490. * Get a plugin handler.
  491. */
  492. function context_get_plugin($type = 'condition', $key, $reset = FALSE) {
  493. static $cache = array();
  494. if (!isset($cache[$type][$key]) || $reset) {
  495. switch ($type) {
  496. case 'condition':
  497. $registry = context_conditions();
  498. break;
  499. case 'reaction':
  500. $registry = context_reactions();
  501. break;
  502. }
  503. if (isset($registry[$key], $registry[$key]['plugin'])) {
  504. ctools_include('plugins');
  505. $info = $registry[$key];
  506. $plugins = ctools_get_plugins('context', 'plugins');
  507. if (isset($plugins[$info['plugin']]) && $class = ctools_plugin_get_class($plugins[$info['plugin']], 'handler')) {
  508. // Check that class exists until CTools & registry issues are resolved.
  509. if (class_exists($class)) {
  510. $cache[$type][$key] = new $class($key, $info);
  511. }
  512. }
  513. }
  514. }
  515. return isset($cache[$type][$key]) ? $cache[$type][$key] : FALSE;
  516. }
  517. /**
  518. * Get all context conditions.
  519. */
  520. function context_conditions($reset = FALSE) {
  521. return _context_registry('conditions', $reset);
  522. }
  523. /**
  524. * Get all context reactions.
  525. */
  526. function context_reactions($reset = FALSE) {
  527. return _context_registry('reactions', $reset);
  528. }
  529. /**
  530. * Retrieves & caches the context registry.
  531. */
  532. function _context_registry($key = NULL, $reset = FALSE) {
  533. static $registry;
  534. if (!isset($registry) || $reset) {
  535. if (!$reset && $cache = context_cache_get('registry')) {
  536. $registry = $cache;
  537. }
  538. else {
  539. $registry = module_invoke_all('context_registry');
  540. drupal_alter('context_registry', $registry);
  541. context_cache_set('registry', $registry);
  542. }
  543. }
  544. if (isset($key)) {
  545. return isset($registry[$key]) ? $registry[$key] : array();
  546. }
  547. return $registry;
  548. }
  549. /**
  550. * hook_block_view_alter - if the context editor block is on this page,
  551. * ensure that all blocks have some content so that empty blocks are
  552. * not dropped
  553. */
  554. function context_block_view_alter(&$data, $block) {
  555. if (context_isset('context_ui', 'context_ui_editor_present') && empty($data['content'])) {
  556. $data['content']['#markup'] = "<div class='context-block-empty-content'>" . t('This block appears empty when displayed on this page.') . "</div>";
  557. $data['context_block_hidden'] = TRUE;
  558. }
  559. }
  560. /**
  561. * implement hook_page_alter()
  562. *
  563. * used for region context
  564. */
  565. function context_page_alter(&$page) {
  566. if ($plugin = context_get_plugin('reaction', 'region')) {
  567. $plugin->execute($page);
  568. }
  569. }
  570. /**
  571. * hook_block_view_alter - if the context editor block is on this page,
  572. * ensure that all blocks have some content so that empty blocks are
  573. * not dropped
  574. */
  575. function context_preprocess_block(&$vars) {
  576. if (isset($vars['block']->context_block_hidden)) {
  577. $vars['classes_array'][] = 'context-block-hidden';
  578. $vars['classes_array'][] = 'context-block-empty';
  579. }
  580. }