· 9 years ago · Jan 13, 2017, 09:48 AM
1<?php
2
3/**
4 * @file
5 * CTools primary module file.
6 *
7 * Most of the CTools tools are in their own .inc files. This contains
8 * nothing more than a few convenience functions and some hooks that
9 * must be implemented in the module file.
10 */
11
12define('CTOOLS_API_VERSION', '2.0.9');
13
14/**
15 * The current working ctools version.
16 *
17 * In a release, it should be 7.x-1.x, which should match what drush make will
18 * create. In a dev format, it should be 7.x-1.(x+1)-dev, which will allow
19 * modules depending on new features in ctools to depend on ctools > 7.x-1.x.
20 *
21 * To define a specific version of CTools as a dependency for another module,
22 * simply include a dependency line in that module's info file, e.g.:
23 * ; Requires CTools v7.x-1.4 or newer.
24 * dependencies[] = ctools (>=1.4)
25 */
26define('CTOOLS_MODULE_VERSION', '7.x-1.11');
27
28/**
29 * Test the CTools API version.
30 *
31 * This function can always be used to safely test if CTools has the minimum
32 * API version that your module can use. It can also try to protect you from
33 * running if the CTools API version is too new, but if you do that you need
34 * to be very quick about watching CTools API releases and release new versions
35 * of your software as soon as the new release is made, or people might end up
36 * updating CTools and having your module shut down without any recourse.
37 *
38 * It is recommended that every hook of your module that might use CTools or
39 * might lead to a use of CTools be guarded like this:
40 *
41 * @code
42 * if (!module_invoke('ctools', 'api_version', '1.0')) {
43 * return;
44 * }
45 * @endcode
46 *
47 * Note that some hooks such as _menu() or _theme() must return an array().
48 *
49 * You can use it in your hook_requirements to report this error condition
50 * like this:
51 *
52 * @code
53 * define('MODULENAME_MINIMUM_CTOOLS_API_VERSION', '1.0');
54 * define('MODULENAME_MAXIMUM_CTOOLS_API_VERSION', '1.1');
55 *
56 * function MODULENAME_requirements($phase) {
57 * $requirements = array();
58 * if (!module_invoke('ctools', 'api_version', MODULENAME_MINIMUM_CTOOLS_API_VERSION, MODULENAME_MAXIMUM_CTOOLS_API_VERSION)) {
59 * $requirements['MODULENAME_ctools'] = array(
60 * 'title' => $t('MODULENAME required Chaos Tool Suite (CTools) API Version'),
61 * 'value' => t('Between @a and @b', array('@a' => MODULENAME_MINIMUM_CTOOLS_API_VERSION, '@b' => MODULENAME_MAXIMUM_CTOOLS_API_VERSION)),
62 * 'severity' => REQUIREMENT_ERROR,
63 * );
64 * }
65 * return $requirements;
66 * }
67 * @endcode
68 *
69 * Please note that the version is a string, not an floating point number.
70 * This will matter once CTools reaches version 1.10.
71 *
72 * A CTools API changes history will be kept in API.txt. Not every new
73 * version of CTools will necessarily update the API version.
74 * @param $minimum
75 * The minimum version of CTools necessary for your software to run with it.
76 * @param $maximum
77 * The maximum version of CTools allowed for your software to run with it.
78 */
79function ctools_api_version($minimum, $maximum = NULL) {
80 if (version_compare(CTOOLS_API_VERSION, $minimum, '<')) {
81 return FALSE;
82 }
83
84 if (isset($maximum) && version_compare(CTOOLS_API_VERSION, $maximum, '>')) {
85 return FALSE;
86 }
87
88 return TRUE;
89}
90
91// -----------------------------------------------------------------------
92// General utility functions
93
94/**
95 * Include .inc files as necessary.
96 *
97 * This fuction is helpful for including .inc files for your module. The
98 * general case is including ctools funcitonality like this:
99 *
100 * @code
101 * ctools_include('plugins');
102 * @endcode
103 *
104 * Similar funcitonality can be used for other modules by providing the $module
105 * and $dir arguments like this:
106 *
107 * @code
108 * // include mymodule/includes/import.inc
109 * ctools_include('import', 'mymodule');
110 * // include mymodule/plugins/foobar.inc
111 * ctools_include('foobar', 'mymodule', 'plugins');
112 * @endcode
113 *
114 * @param $file
115 * The base file name to be included.
116 * @param $module
117 * Optional module containing the include.
118 * @param $dir
119 * Optional subdirectory containing the include file.
120 */
121function ctools_include($file, $module = 'ctools', $dir = 'includes') {
122 static $used = array();
123
124 $dir = '/' . ($dir ? $dir . '/' : '');
125
126 if (!isset($used[$module][$dir][$file])) {
127 require_once DRUPAL_ROOT . '/' . drupal_get_path('module', $module) . "$dir$file.inc";
128 $used[$module][$dir][$file] = TRUE;
129 }
130}
131
132/**
133 * Include .inc files in a form context.
134 *
135 * This is a variant of ctools_include that will save information in the
136 * the form_state so that cached forms will properly include things.
137 */
138function ctools_form_include(&$form_state, $file, $module = 'ctools', $dir = 'includes') {
139 if (!isset($form_state['build_info']['args'])) {
140 $form_state['build_info']['args'] = array();
141 }
142
143 $dir = '/' . ($dir ? $dir . '/' : '');
144 form_load_include($form_state, 'inc', $module, $dir . $file);
145}
146
147/**
148 * Add an arbitrary path to the $form_state so it can work with form cache.
149 *
150 * module_load_include uses an unfortunately annoying syntax to work, making it
151 * difficult to translate the more simple $path + $file syntax.
152 */
153function ctools_form_include_file(&$form_state, $filename) {
154 if (!isset($form_state['build_info']['args'])) {
155 $form_state['build_info']['args'] = array();
156 }
157
158 // Now add this to the build info files so that AJAX requests will know to load it.
159 $form_state['build_info']['files']["$filename"] = $filename;
160 require_once DRUPAL_ROOT . '/' . $filename;
161}
162
163/**
164 * Provide the proper path to an image as necessary.
165 *
166 * This helper function is used by ctools but can also be used in other
167 * modules in the same way as explained in the comments of ctools_include.
168 *
169 * @param $image
170 * The base file name (with extension) of the image to be included.
171 * @param $module
172 * Optional module containing the include.
173 * @param $dir
174 * Optional subdirectory containing the include file.
175 */
176function ctools_image_path($image, $module = 'ctools', $dir = 'images') {
177 return drupal_get_path('module', $module) . "/$dir/" . $image;
178}
179
180/**
181 * Include css files as necessary.
182 *
183 * This helper function is used by ctools but can also be used in other
184 * modules in the same way as explained in the comments of ctools_include.
185 *
186 * @param $file
187 * The base file name to be included.
188 * @param $module
189 * Optional module containing the include.
190 * @param $dir
191 * Optional subdirectory containing the include file.
192 */
193function ctools_add_css($file, $module = 'ctools', $dir = 'css') {
194 drupal_add_css(drupal_get_path('module', $module) . "/$dir/$file.css");
195}
196
197/**
198 * Format a css file name for use with $form['#attached']['css'].
199 *
200 * This helper function is used by ctools but can also be used in other
201 * modules in the same way as explained in the comments of ctools_include.
202 *
203 * @code
204 * $form['#attached']['css'] = array(ctools_attach_css('collapsible-div'));
205 * $form['#attached']['css'][ctools_attach_css('collapsible-div')] = array('preprocess' => FALSE);
206 * @endcode
207 *
208 * @param $file
209 * The base file name to be included.
210 * @param $module
211 * Optional module containing the include.
212 * @param $dir
213 * Optional subdirectory containing the include file.
214 */
215function ctools_attach_css($file, $module = 'ctools', $dir = 'css') {
216 return drupal_get_path('module', $module) . "/$dir/$file.css";
217}
218
219/**
220 * Include js files as necessary.
221 *
222 * This helper function is used by ctools but can also be used in other
223 * modules in the same way as explained in the comments of ctools_include.
224 *
225 * @param $file
226 * The base file name to be included.
227 * @param $module
228 * Optional module containing the include.
229 * @param $dir
230 * Optional subdirectory containing the include file.
231 */
232function ctools_add_js($file, $module = 'ctools', $dir = 'js') {
233 drupal_add_js(drupal_get_path('module', $module) . "/$dir/$file.js");
234}
235
236/**
237 * Format a javascript file name for use with $form['#attached']['js'].
238 *
239 * This helper function is used by ctools but can also be used in other
240 * modules in the same way as explained in the comments of ctools_include.
241 *
242 * @code
243 * $form['#attached']['js'] = array(ctools_attach_js('auto-submit'));
244 * @endcode
245 *
246 * @param $file
247 * The base file name to be included.
248 * @param $module
249 * Optional module containing the include.
250 * @param $dir
251 * Optional subdirectory containing the include file.
252 */
253function ctools_attach_js($file, $module = 'ctools', $dir = 'js') {
254 return drupal_get_path('module', $module) . "/$dir/$file.js";
255}
256
257/**
258 * Get a list of roles in the system.
259 *
260 * @return
261 * An array of role names keyed by role ID.
262 *
263 * @deprecated
264 * user_roles() should be used instead.
265 */
266function ctools_get_roles() {
267 return user_roles();
268}
269
270/*
271 * Break x,y,z and x+y+z into an array. Numeric only.
272 *
273 * @param $str
274 * The string to parse.
275 *
276 * @return $object
277 * An object containing
278 * - operator: Either 'and' or 'or'
279 * - value: An array of numeric values.
280 */
281function ctools_break_phrase($str) {
282 $object = new stdClass();
283
284 if (preg_match('/^([0-9]+[+ ])+[0-9]+$/', $str)) {
285 // The '+' character in a query string may be parsed as ' '.
286 $object->operator = 'or';
287 $object->value = preg_split('/[+ ]/', $str);
288 }
289 else if (preg_match('/^([0-9]+,)*[0-9]+$/', $str)) {
290 $object->operator = 'and';
291 $object->value = explode(',', $str);
292 }
293
294 // Keep an 'error' value if invalid strings were given.
295 if (!empty($str) && (empty($object->value) || !is_array($object->value))) {
296 $object->value = array(-1);
297 $object->invalid_input = TRUE;
298 return $object;
299 }
300
301 if (empty($object->value)) {
302 $object->value = array();
303 }
304
305 // Doubly ensure that all values are numeric only.
306 foreach ($object->value as $id => $value) {
307 $object->value[$id] = intval($value);
308 }
309
310 return $object;
311}
312
313/**
314 * Set a token/value pair to be replaced later in the request, specifically in
315 * ctools_page_token_processing().
316 *
317 * @param $token
318 * The token to be replaced later, during page rendering. This should
319 * ideally be a string inside of an HTML comment, so that if there is
320 * no replacement, the token will not render on the page.
321 * @param $type
322 * The type of the token. Can be either 'variable', which will pull data
323 * directly from the page variables
324 * @param $argument
325 * If $type == 'variable' then argument should be the key to fetch from
326 * the $variables. If $type == 'callback' then it should either be the
327 * callback, or an array that will be sent to call_user_func_array().
328 *
329 * @return
330 * A array of token/variable names to be replaced.
331 */
332function ctools_set_page_token($token = NULL, $type = NULL, $argument = NULL) {
333 static $tokens = array();
334
335 if (isset($token)) {
336 $tokens[$token] = array($type, $argument);
337 }
338 return $tokens;
339}
340
341/**
342 * Easily set a token from the page variables.
343 *
344 * This function can be used like this:
345 * $token = ctools_set_variable_token('tabs');
346 *
347 * $token will then be a simple replacement for the 'tabs' about of the
348 * variables available in the page template.
349 */
350function ctools_set_variable_token($token) {
351 $string = '<!-- ctools-page-' . $token . ' -->';
352 ctools_set_page_token($string, 'variable', $token);
353 return $string;
354}
355
356/**
357 * Easily set a token from the page variables.
358 *
359 * This function can be used like this:
360 * $token = ctools_set_variable_token('id', 'mymodule_myfunction');
361 */
362function ctools_set_callback_token($token, $callback) {
363 // If the callback uses arguments they are considered in the token.
364 if (is_array($callback)) {
365 $token .= '-' . md5(serialize($callback));
366 }
367 $string = '<!-- ctools-page-' . $token . ' -->';
368 ctools_set_page_token($string, 'callback', $callback);
369 return $string;
370}
371
372/**
373 * Tell CTools that sidebar blocks should not be rendered.
374 *
375 * It is often desirable to not display sidebars when rendering a page,
376 * particularly when using Panels. This informs CTools to alter out any
377 * sidebar regions during block render.
378 */
379function ctools_set_no_blocks($blocks = FALSE) {
380 $status = &drupal_static(__FUNCTION__, TRUE);
381 $status = $blocks;
382}
383
384/**
385 * Wrapper function to create UUIDs via ctools, falls back on UUID module
386 * if it is enabled. This code is a copy of uuid.inc from the uuid module.
387 * @see http://php.net/uniqid#65879
388 */
389
390function ctools_uuid_generate() {
391 if (!module_exists('uuid')) {
392 ctools_include('uuid');
393
394 $callback = drupal_static(__FUNCTION__);
395
396 if (empty($callback)) {
397 if (function_exists('uuid_create') && !function_exists('uuid_make')) {
398 $callback = '_ctools_uuid_generate_pecl';
399 }
400 elseif (function_exists('com_create_guid')) {
401 $callback = '_ctools_uuid_generate_com';
402 }
403 else {
404 $callback = '_ctools_uuid_generate_php';
405 }
406 }
407 return $callback();
408 }
409 else {
410 return uuid_generate();
411 }
412}
413
414/**
415 * Check that a string appears to be in the format of a UUID.
416 * @see http://drupal.org/project/uuid
417 *
418 * @param $uuid
419 * The string to test.
420 *
421 * @return
422 * TRUE if the string is well formed.
423 */
424function ctools_uuid_is_valid($uuid = '') {
425 if (empty($uuid)) {
426 return FALSE;
427 }
428 if (function_exists('uuid_is_valid') || module_exists('uuid')) {
429 return uuid_is_valid($uuid);
430 }
431 else {
432 ctools_include('uuid');
433 return uuid_is_valid($uuid);
434 }
435}
436
437/**
438 * Add an array of classes to the body.
439 *
440 * @param mixed $classes
441 * A string or an array of class strings to add.
442 * @param string $hook
443 * The theme hook to add the class to. The default is 'html' which will
444 * affect the body tag.
445 */
446function ctools_class_add($classes, $hook = 'html') {
447 if (!is_array($classes)) {
448 $classes = array($classes);
449 }
450
451 $static = &drupal_static('ctools_process_classes', array());
452 if (!isset($static[$hook]['add'])) {
453 $static[$hook]['add'] = array();
454 }
455 foreach ($classes as $class) {
456 $static[$hook]['add'][] = $class;
457 }
458}
459
460/**
461 * Remove an array of classes from the body.
462 *
463 * @param mixed $classes
464 * A string or an array of class strings to remove.
465 * @param string $hook
466 * The theme hook to remove the class from. The default is 'html' which will
467 * affect the body tag.
468 */
469function ctools_class_remove($classes, $hook = 'html') {
470 if (!is_array($classes)) {
471 $classes = array($classes);
472 }
473
474 $static = &drupal_static('ctools_process_classes', array());
475 if (!isset($static[$hook]['remove'])) {
476 $static[$hook]['remove'] = array();
477 }
478 foreach ($classes as $class) {
479 $static[$hook]['remove'][] = $class;
480 }
481}
482
483// -----------------------------------------------------------------------
484// Drupal core hooks
485
486/**
487 * Implement hook_init to keep our global CSS at the ready.
488 */
489function ctools_init() {
490 ctools_add_css('ctools');
491 // If we are sure that CTools' AJAX is in use, change the error handling.
492 if (!empty($_REQUEST['ctools_ajax'])) {
493 ini_set('display_errors', 0);
494 register_shutdown_function('ctools_shutdown_handler');
495 }
496
497 // Clear plugin cache on the module page submit.
498 if ($_GET['q'] == 'admin/modules/list/confirm' && !empty($_POST)) {
499 cache_clear_all('ctools_plugin_files:', 'cache', TRUE);
500 }
501}
502
503/**
504 * Shutdown handler used during ajax operations to help catch fatal errors.
505 */
506function ctools_shutdown_handler() {
507 if (function_exists('error_get_last') AND ($error = error_get_last())) {
508 switch ($error['type']) {
509 case E_ERROR:
510 case E_CORE_ERROR:
511 case E_COMPILE_ERROR:
512 case E_USER_ERROR:
513 // Do this manually because including files here is dangerous.
514 $commands = array(
515 array(
516 'command' => 'alert',
517 'title' => t('Error'),
518 'text' => t('Unable to complete operation. Fatal error in @file on line @line: @message', array(
519 '@file' => $error['file'],
520 '@line' => $error['line'],
521 '@message' => $error['message'],
522 )),
523 ),
524 );
525
526 // Change the status code so that the client will read the AJAX returned.
527 header('HTTP/1.1 200 OK');
528 drupal_json($commands);
529 }
530 }
531}
532
533/**
534 * Implements hook_theme().
535 */
536function ctools_theme() {
537 ctools_include('utility');
538 $items = array();
539 ctools_passthrough('ctools', 'theme', $items);
540 return $items;
541}
542
543/**
544 * Implements hook_menu().
545 */
546function ctools_menu() {
547 ctools_include('utility');
548 $items = array();
549 ctools_passthrough('ctools', 'menu', $items);
550 return $items;
551}
552
553/**
554 * Implements hook_permission().
555 */
556function ctools_permission() {
557 return array(
558 'use ctools import' => array(
559 'title' => t('Use CTools importer'),
560 'description' => t('The import functionality allows users to execute arbitrary PHP code, so extreme caution must be taken.'),
561 'restrict access' => TRUE,
562 ),
563 );
564}
565
566/**
567 * Implementation of hook_cron. Clean up old caches.
568 */
569function ctools_cron() {
570 ctools_include('utility');
571 $items = array();
572 ctools_passthrough('ctools', 'cron', $items);
573}
574
575/**
576 * Implements hook_flush_caches().
577 */
578function ctools_flush_caches() {
579 // Only return the CSS cache bin if it has been activated, to avoid
580 // drupal_flush_all_caches() from trying to truncate a non-existing table.
581 return variable_get('cache_class_cache_ctools_css', FALSE) ? array('cache_ctools_css') : array();
582}
583
584/**
585 * Implements hook_element_info_alter().
586 *
587 */
588function ctools_element_info_alter(&$type) {
589 ctools_include('dependent');
590 ctools_dependent_element_info_alter($type);
591}
592
593/**
594 * Implementation of hook_file_download()
595 *
596 * When using the private file system, we have to let Drupal know it's ok to
597 * download CSS and image files from our temporary directory.
598 */
599function ctools_file_download($filepath) {
600 if (strpos($filepath, 'ctools') === 0) {
601 $mime = file_get_mimetype($filepath);
602 // For safety's sake, we allow only text and images.
603 if (strpos($mime, 'text') === 0 || strpos($mime, 'image') === 0) {
604 return array('Content-type:' . $mime);
605 }
606 }
607}
608
609/**
610 * Implements hook_registry_files_alter().
611 *
612 * Alter the registry of files to automagically include all classes in
613 * class-based plugins.
614 */
615function ctools_registry_files_alter(&$files, $indexed_modules) {
616 ctools_include('registry');
617 return _ctools_registry_files_alter($files, $indexed_modules);
618}
619
620// -----------------------------------------------------------------------
621// FAPI hooks that must be in the .module file.
622
623/**
624 * Alter the comment form to get a little more control over it.
625 */
626function ctools_form_comment_form_alter(&$form, &$form_state) {
627 if (!empty($form_state['ctools comment alter'])) {
628 // Force the form to post back to wherever we are.
629 $form['#action'] = url($_GET['q'], array('fragment' => 'comment-form'));
630 if (empty($form['#submit'])) {
631 $form['#submit'] = array('comment_form_submit');
632 }
633 $form['#submit'][] = 'ctools_node_comment_form_submit';
634 }
635}
636
637function ctools_node_comment_form_submit(&$form, &$form_state) {
638 $form_state['redirect'][0] = $_GET['q'];
639}
640
641// -----------------------------------------------------------------------
642// CTools hook implementations.
643
644/**
645 * Implementation of hook_ctools_plugin_directory() to let the system know
646 * where all our own plugins are.
647 */
648function ctools_ctools_plugin_directory($owner, $plugin_type) {
649 if ($owner == 'ctools') {
650 return 'plugins/' . $plugin_type;
651 }
652}
653
654/**
655 * Implements hook_ctools_plugin_type().
656 */
657function ctools_ctools_plugin_type() {
658 ctools_include('utility');
659 $items = array();
660 // Add all the plugins that have their own declaration space elsewhere.
661 ctools_passthrough('ctools', 'plugin-type', $items);
662
663 return $items;
664}
665
666// -----------------------------------------------------------------------
667// Drupal theme preprocess hooks that must be in the .module file.
668
669/**
670 * A theme preprocess function to automatically allow panels-based node
671 * templates based upon input when the panel was configured.
672 */
673function ctools_preprocess_node(&$vars) {
674 // The 'ctools_template_identifier' attribute of the node is added when the pane is
675 // rendered.
676 if (!empty($vars['node']->ctools_template_identifier)) {
677 $vars['ctools_template_identifier'] = check_plain($vars['node']->ctools_template_identifier);
678 $vars['theme_hook_suggestions'][] = 'node__panel__' . check_plain($vars['node']->ctools_template_identifier);
679 }
680}
681
682
683/**
684 * Implements hook_page_alter().
685 *
686 * Last ditch attempt to remove sidebar regions if the "no blocks"
687 * functionality has been activated.
688 *
689 * @see ctools_block_list_alter().
690 */
691function ctools_page_alter(&$page) {
692 $check = drupal_static('ctools_set_no_blocks', TRUE);
693 if (!$check) {
694 foreach ($page as $region_id => $region) {
695 // @todo -- possibly we can set configuration for this so that users can
696 // specify which blocks will not get rendered.
697 if (strpos($region_id, 'sidebar') !== FALSE) {
698 unset($page[$region_id]);
699 }
700 }
701 }
702 $page['#post_render'][] = 'ctools_page_token_processing';
703}
704
705/**
706 * A theme post_render callback to allow content type plugins to use page
707 * template variables which are not yet available when the content type is
708 * rendered.
709 */
710function ctools_page_token_processing($children, $elements) {
711 $tokens = ctools_set_page_token();
712 if (!empty($tokens)) {
713 foreach ($tokens as $token => $key) {
714 list($type, $argument) = $key;
715 switch ($type) {
716 case 'variable':
717 $tokens[$token] = isset($elements[$argument]) ? $elements[$argument] : '';
718 break;
719 case 'callback':
720 if (is_string($argument) && function_exists($argument)) {
721 $tokens[$token] = $argument($elements);
722 }
723 if (is_array($argument) && function_exists($argument[0])) {
724 $function = array_shift($argument);
725 $argument = array_merge(array(&$elements), $argument);
726 $tokens[$token] = call_user_func_array($function, $argument);
727 }
728 break;
729 }
730 }
731 $children = strtr($children, $tokens);
732 }
733 return $children;
734}
735
736/**
737 * Implements hook_process().
738 *
739 * Add and remove CSS classes from the variables array. We use process so that
740 * we alter anything added in the preprocess hooks.
741 */
742function ctools_process(&$variables, $hook) {
743 if (!isset($variables['classes'])) {
744 return;
745 }
746
747 $classes = drupal_static('ctools_process_classes', array());
748
749 // Process the classses to add.
750 if (!empty($classes[$hook]['add'])) {
751 $add_classes = array_map('drupal_clean_css_identifier', $classes[$hook]['add']);
752 $variables['classes_array'] = array_unique(array_merge($variables['classes_array'], $add_classes));
753 }
754
755 // Process the classes to remove.
756 if (!empty($classes[$hook]['remove'])) {
757 $remove_classes = array_map('drupal_clean_css_identifier', $classes[$hook]['remove']);
758 $variables['classes_array'] = array_diff($variables['classes_array'], $remove_classes);
759 }
760
761 // Update the classes within the attributes array to match the classes array
762 if (isset($variables['attributes_array']['class'])) {
763 $variables['attributes_array']['class'] = array_unique(array_merge($variables['classes_array'], $variables['attributes_array']['class']));
764 $variables['attributes'] = $variables['attributes_array'] ? drupal_attributes($variables['attributes_array']) : '';
765 }
766
767 // Since this runs after template_process(), we need to re-implode the
768 // classes array.
769 $variables['classes'] = implode(' ', $variables['classes_array']);
770}
771
772// -----------------------------------------------------------------------
773// Menu callbacks that must be in the .module file.
774
775/**
776 * Determine if the current user has access via a plugin.
777 *
778 * This function is meant to be embedded in the Drupal menu system, and
779 * therefore is in the .module file since sub files can't be loaded, and
780 * takes arguments a little bit more haphazardly than ctools_access().
781 *
782 * @param $access
783 * An access control array which contains the following information:
784 * - 'logic': and or or. Whether all tests must pass or one must pass.
785 * - 'plugins': An array of access plugins. Each contains:
786 * - - 'name': The name of the plugin
787 * - - 'settings': The settings from the plugin UI.
788 * - - 'context': Which context to use.
789 * @param ...
790 * zero or more context arguments generated from argument plugins. These
791 * contexts must have an 'id' attached to them so that they can be
792 * properly associated. The argument plugin system should set this, but
793 * if the context is coming from elsewhere it will need to be set manually.
794 *
795 * @return
796 * TRUE if access is granted, false if otherwise.
797 */
798function ctools_access_menu($access) {
799 // Short circuit everything if there are no access tests.
800 if (empty($access['plugins'])) {
801 return TRUE;
802 }
803
804 $contexts = array();
805 foreach (func_get_args() as $arg) {
806 if (is_object($arg) && get_class($arg) == 'ctools_context') {
807 $contexts[$arg->id] = $arg;
808 }
809 }
810
811 ctools_include('context');
812 return ctools_access($access, $contexts);
813}
814
815/**
816 * Determine if the current user has access via checks to multiple different
817 * permissions.
818 *
819 * This function is a thin wrapper around user_access that allows multiple
820 * permissions to be easily designated for use on, for example, a menu callback.
821 *
822 * @param ...
823 * An indexed array of zero or more permission strings to be checked by
824 * user_access().
825 *
826 * @return
827 * Iff all checks pass will this function return TRUE. If an invalid argument
828 * is passed (e.g., not a string), this function errs on the safe said and
829 * returns FALSE.
830 */
831function ctools_access_multiperm() {
832 foreach (func_get_args() as $arg) {
833 if (!is_string($arg) || !user_access($arg)) {
834 return FALSE;
835 }
836 }
837 return TRUE;
838}
839
840/**
841 * Check to see if the incoming menu item is js capable or not.
842 *
843 * This can be used as %ctools_js as part of a path in hook menu. CTools
844 * ajax functions will automatically change the phrase 'nojs' to 'ajax'
845 * when it attaches ajax to a link. This can be used to autodetect if
846 * that happened.
847 */
848function ctools_js_load($js) {
849 if ($js == 'ajax') {
850 return TRUE;
851 }
852 return 0;
853}
854
855/**
856 * Provides the default value for %ctools_js.
857 *
858 * This allows drupal_valid_path() to work with %ctools_js.
859 */
860function ctools_js_to_arg($arg) {
861 return empty($arg) || $arg == '%' ? 'nojs' : $arg;
862}
863
864/**
865 * Menu _load hook.
866 *
867 * This function will be called to load an object as a replacement for
868 * %ctools_export_ui in menu paths.
869 */
870function ctools_export_ui_load($item_name, $plugin_name) {
871 $return = &drupal_static(__FUNCTION__, FALSE);
872
873 if (!$return) {
874 ctools_include('export-ui');
875 $plugin = ctools_get_export_ui($plugin_name);
876 $handler = ctools_export_ui_get_handler($plugin);
877
878 if ($handler) {
879 return $handler->load_item($item_name);
880 }
881 }
882
883 return $return;
884}
885
886// -----------------------------------------------------------------------
887// Caching callbacks on behalf of export-ui.
888
889/**
890 * Menu access callback for various tasks of export-ui.
891 */
892function ctools_export_ui_task_access($plugin_name, $op, $item = NULL) {
893 ctools_include('export-ui');
894 $plugin = ctools_get_export_ui($plugin_name);
895 $handler = ctools_export_ui_get_handler($plugin);
896
897 if ($handler) {
898 return $handler->access($op, $item);
899 }
900
901 // Deny access if the handler cannot be found.
902 return FALSE;
903}
904
905/**
906 * Callback for access control ajax form on behalf of export ui.
907 *
908 * Returns the cached access config and contexts used.
909 * Note that this is assuming that access will be in $item->access -- if it
910 * is not, an export UI plugin will have to make its own callbacks.
911 */
912function ctools_export_ui_ctools_access_get($argument) {
913 ctools_include('export-ui');
914 list($plugin_name, $key) = explode(':', $argument, 2);
915
916 $plugin = ctools_get_export_ui($plugin_name);
917 $handler = ctools_export_ui_get_handler($plugin);
918
919 if ($handler) {
920 ctools_include('context');
921 $item = $handler->edit_cache_get($key);
922 if (!$item) {
923 $item = ctools_export_crud_load($handler->plugin['schema'], $key);
924 }
925
926 $contexts = ctools_context_load_contexts($item);
927 return array($item->access, $contexts);
928 }
929}
930
931/**
932 * Callback for access control ajax form on behalf of export ui
933 *
934 * Returns the cached access config and contexts used.
935 * Note that this is assuming that access will be in $item->access -- if it
936 * is not, an export UI plugin will have to make its own callbacks.
937 */
938function ctools_export_ui_ctools_access_set($argument, $access) {
939 ctools_include('export-ui');
940 list($plugin_name, $key) = explode(':', $argument, 2);
941
942 $plugin = ctools_get_export_ui($plugin_name);
943 $handler = ctools_export_ui_get_handler($plugin);
944
945 if ($handler) {
946 ctools_include('context');
947 $item = $handler->edit_cache_get($key);
948 if (!$item) {
949 $item = ctools_export_crud_load($handler->plugin['schema'], $key);
950 }
951 $item->access = $access;
952 return $handler->edit_cache_set_key($item, $key);
953 }
954}
955
956/**
957 * Implements hook_menu_local_tasks_alter().
958 */
959function ctools_menu_local_tasks_alter(&$data, $router_item, $root_path) {
960 ctools_include('menu');
961 _ctools_menu_add_dynamic_items($data, $router_item, $root_path);
962}
963
964/**
965 * Implement hook_block_list_alter() to potentially remove blocks.
966 *
967 * This exists in order to replicate Drupal 6's "no blocks" functionality.
968 */
969function ctools_block_list_alter(&$blocks) {
970 $check = drupal_static('ctools_set_no_blocks', TRUE);
971 if (!$check) {
972 foreach ($blocks as $block_id => $block) {
973 // @todo -- possibly we can set configuration for this so that users can
974 // specify which blocks will not get rendered.
975 if (strpos($block->region, 'sidebar') !== FALSE) {
976 unset($blocks[$block_id]);
977 }
978 }
979 }
980}
981
982/**
983 * Implements hook_modules_enabled().
984 *
985 * Clear caches for detecting new plugins.
986 */
987function ctools_modules_enabled($modules) {
988 ctools_include('plugins');
989 ctools_get_plugins_reset();
990 cache_clear_all('ctools_plugin_files:', 'cache', TRUE);
991}
992
993/**
994 * Implements hook_modules_disabled().
995 *
996 * Clear caches for removing disabled plugins.
997 */
998function ctools_modules_disabled($modules) {
999 ctools_include('plugins');
1000 ctools_get_plugins_reset();
1001 cache_clear_all('ctools_plugin_files:', 'cache', TRUE);
1002}
1003
1004/**
1005 * Menu theme callback.
1006 *
1007 * This simply ensures that Panels ajax calls are rendered in the same
1008 * theme as the original page to prevent .css file confusion.
1009 *
1010 * To use this, set this as the theme callback on AJAX related menu
1011 * items. Since the ajax page state won't be sent during ajax requests,
1012 * it should be safe to use even if ajax isn't invoked.
1013 */
1014function ctools_ajax_theme_callback() {
1015 if (!empty($_POST['ajax_page_state']['theme'])) {
1016 return $_POST['ajax_page_state']['theme'];
1017 }
1018}
1019
1020/**
1021 * Implements hook_ctools_entity_context_alter().
1022 */
1023function ctools_ctools_entity_context_alter(&$plugin, &$entity, $plugin_id) {
1024 ctools_include('context');
1025 switch ($plugin_id) {
1026 case 'entity_id:taxonomy_term':
1027 $plugin['no ui'] = TRUE;
1028 break;
1029 case 'entity:user':
1030 $plugin = ctools_get_context('user');
1031 unset($plugin['no ui']);
1032 unset($plugin['no required context ui']);
1033 break;
1034 }
1035
1036 // Apply restrictions on taxonomy term reverse relationships whose
1037 // restrictions are in the settings on the field.
1038 if (!empty($plugin['parent']) &&
1039 $plugin['parent'] == 'entity_from_field' &&
1040 !empty($plugin['reverse']) &&
1041 $plugin['to entity'] == 'taxonomy_term') {
1042 $field = field_info_field($plugin['field name']);
1043 if (isset($field['settings']['allowed_values'][0]['vocabulary'])) {
1044 $plugin['required context']->restrictions = array('vocabulary' => array($field['settings']['allowed_values'][0]['vocabulary']));
1045 }
1046 }
1047}
1048
1049/**
1050 * Implements hook_field_create_field().
1051 */
1052function ctools_field_create_field($field) {
1053 ctools_flush_field_caches();
1054}
1055
1056/**
1057 * Implements hook_field_create_instance().
1058 */
1059function ctools_field_create_instance($instance) {
1060 ctools_flush_field_caches();
1061}
1062/**
1063 * Implements hook_field_delete_field().
1064 */
1065function ctools_field_delete_field($field) {
1066 ctools_flush_field_caches();
1067}
1068/**
1069 * Implements hook_field_delete_instance().
1070 */
1071function ctools_field_delete_instance($instance) {
1072 ctools_flush_field_caches();
1073}
1074/**
1075 * Implements hook_field_update_field().
1076 */
1077function ctools_field_update_field($field, $prior_field, $has_data) {
1078 ctools_flush_field_caches();
1079}
1080
1081/**
1082 * Implements hook_field_update_instance().
1083 */
1084function ctools_field_update_instance($instance, $prior_instance) {
1085 ctools_flush_field_caches();
1086}
1087
1088/**
1089 * Clear field related caches.
1090 */
1091function ctools_flush_field_caches() {
1092 // Clear caches of 'Entity field' content type plugin.
1093 cache_clear_all('ctools_entity_field_content_type_content_types', 'cache');
1094}