summaryrefslogtreecommitdiff
path: root/sites/all/modules/ctools/includes
diff options
context:
space:
mode:
authorCtibor Brančík <ctibor@brancik.cz>2016-03-20 19:27:01 +0100
committerCtibor Brančík <ctibor@brancik.cz>2016-03-20 19:27:01 +0100
commit29a6913890a675ddf1a9239b4407f105e02dc95d (patch)
treedd9ba21b73e9e704952b49d5153616a9dfa9b98f /sites/all/modules/ctools/includes
parent5ddacae6306ce071d4f7e4d438960d6d3a4c6bd8 (diff)
downloadbrdo-29a6913890a675ddf1a9239b4407f105e02dc95d.tar.gz
brdo-29a6913890a675ddf1a9239b4407f105e02dc95d.tar.bz2
Added drupal modules for site
Diffstat (limited to 'sites/all/modules/ctools/includes')
-rw-r--r--sites/all/modules/ctools/includes/action-links.theme.inc33
-rw-r--r--sites/all/modules/ctools/includes/ajax.inc157
-rw-r--r--sites/all/modules/ctools/includes/cache.inc169
-rw-r--r--sites/all/modules/ctools/includes/cache.plugin-type.inc11
-rw-r--r--sites/all/modules/ctools/includes/cleanstring.inc204
-rw-r--r--sites/all/modules/ctools/includes/collapsible.theme.inc79
-rw-r--r--sites/all/modules/ctools/includes/content.inc853
-rw-r--r--sites/all/modules/ctools/includes/content.menu.inc179
-rw-r--r--sites/all/modules/ctools/includes/content.plugin-type.inc17
-rw-r--r--sites/all/modules/ctools/includes/content.theme.inc21
-rw-r--r--sites/all/modules/ctools/includes/context-access-admin.inc486
-rw-r--r--sites/all/modules/ctools/includes/context-admin.inc849
-rw-r--r--sites/all/modules/ctools/includes/context-task-handler.inc540
-rw-r--r--sites/all/modules/ctools/includes/context.inc1602
-rw-r--r--sites/all/modules/ctools/includes/context.menu.inc40
-rw-r--r--sites/all/modules/ctools/includes/context.plugin-type.inc24
-rw-r--r--sites/all/modules/ctools/includes/context.theme.inc344
-rw-r--r--sites/all/modules/ctools/includes/css-cache.inc52
-rw-r--r--sites/all/modules/ctools/includes/css.inc575
-rw-r--r--sites/all/modules/ctools/includes/dependent.inc181
-rw-r--r--sites/all/modules/ctools/includes/dropbutton.theme.inc143
-rw-r--r--sites/all/modules/ctools/includes/dropdown.theme.inc90
-rw-r--r--sites/all/modules/ctools/includes/entity-access.inc150
-rw-r--r--sites/all/modules/ctools/includes/export-ui.inc475
-rw-r--r--sites/all/modules/ctools/includes/export-ui.menu.inc24
-rw-r--r--sites/all/modules/ctools/includes/export-ui.plugin-type.inc20
-rw-r--r--sites/all/modules/ctools/includes/export.inc1267
-rw-r--r--sites/all/modules/ctools/includes/fields.inc357
-rw-r--r--sites/all/modules/ctools/includes/jump-menu.inc150
-rw-r--r--sites/all/modules/ctools/includes/language.inc44
-rw-r--r--sites/all/modules/ctools/includes/math-expr.inc388
-rw-r--r--sites/all/modules/ctools/includes/menu.inc98
-rw-r--r--sites/all/modules/ctools/includes/modal.inc262
-rw-r--r--sites/all/modules/ctools/includes/object-cache.cron.inc16
-rw-r--r--sites/all/modules/ctools/includes/object-cache.inc205
-rw-r--r--sites/all/modules/ctools/includes/page-wizard.inc194
-rw-r--r--sites/all/modules/ctools/includes/page-wizard.menu.inc32
-rw-r--r--sites/all/modules/ctools/includes/plugins-admin.inc208
-rw-r--r--sites/all/modules/ctools/includes/plugins.inc917
-rw-r--r--sites/all/modules/ctools/includes/registry.inc77
-rw-r--r--sites/all/modules/ctools/includes/stylizer.inc1654
-rw-r--r--sites/all/modules/ctools/includes/stylizer.theme.inc28
-rw-r--r--sites/all/modules/ctools/includes/utility.inc31
-rw-r--r--sites/all/modules/ctools/includes/uuid.inc68
-rw-r--r--sites/all/modules/ctools/includes/views.inc26
-rw-r--r--sites/all/modules/ctools/includes/wizard.inc534
-rw-r--r--sites/all/modules/ctools/includes/wizard.theme.inc22
47 files changed, 13896 insertions, 0 deletions
diff --git a/sites/all/modules/ctools/includes/action-links.theme.inc b/sites/all/modules/ctools/includes/action-links.theme.inc
new file mode 100644
index 000000000..3a2398a1f
--- /dev/null
+++ b/sites/all/modules/ctools/includes/action-links.theme.inc
@@ -0,0 +1,33 @@
+<?php
+/**
+ * @file
+ * Theme function for wrapping menu local actions.
+ */
+
+/**
+ * Delegated implementation of hook_theme()
+ */
+function ctools_action_links_theme(&$items) {
+ $items['ctools_menu_local_actions_wrapper'] = array(
+ 'render element' => 'links',
+ 'file' => 'includes/action-links.theme.inc',
+ );
+}
+
+/**
+ * Render a menu local actions wrapper.
+ *
+ * @param $links
+ * Local actions links.
+ * @param $attributes
+ * An array of attributes to append to the wrapper.
+ */
+function theme_ctools_menu_local_actions_wrapper($variables) {
+ $links = drupal_render($variables['links']);
+
+ if (empty($links)) {
+ return;
+ }
+
+ return '<ul class="action-links">' . $links . '</ul>';
+} \ No newline at end of file
diff --git a/sites/all/modules/ctools/includes/ajax.inc b/sites/all/modules/ctools/includes/ajax.inc
new file mode 100644
index 000000000..96f5068f3
--- /dev/null
+++ b/sites/all/modules/ctools/includes/ajax.inc
@@ -0,0 +1,157 @@
+<?php
+
+// Set this so we can tell that the file has been included at some point.
+define('CTOOLS_AJAX_INCLUDED', 1);
+
+/**
+ * @file
+ * Extend core AJAX with some of our own stuff.
+ */
+
+/**
+ * Render an image as a button link. This will automatically apply an AJAX class
+ * to the link and add the appropriate javascript to make this happen.
+ *
+ * @param $image
+ * The path to an image to use that will be sent to theme('image') for rendering.
+ * @param $dest
+ * The destination of the link.
+ * @param $alt
+ * The alt text of the link.
+ * @param $class
+ * Any class to apply to the link. @todo this should be a options array.
+ */
+function ctools_ajax_image_button($image, $dest, $alt, $class = '') {
+ return ctools_ajax_text_button(theme('image', array('path' => $image)), $dest, $alt, $class);
+}
+
+/**
+ * Render text as a link. This will automatically apply an AJAX class
+ * to the link and add the appropriate javascript to make this happen.
+ *
+ * Note: 'html' => true so be sure any text is vetted! Chances are these kinds of buttons will
+ * not use user input so this is a very minor concern.
+ *
+ * @param $text
+ * The text that will be displayed as the link.
+ * @param $dest
+ * The destination of the link.
+ * @param $alt
+ * The alt text of the link.
+ * @param $class
+ * Any class to apply to the link. @todo this should be a options array.
+ * @param $type
+ * A type to use, in case a different behavior should be attached. Defaults
+ * to ctools-use-ajax.
+ */
+function ctools_ajax_text_button($text, $dest, $alt, $class = '', $type = 'use-ajax') {
+ drupal_add_library('system', 'drupal.ajax');
+ return l($text, $dest, array('html' => TRUE, 'attributes' => array('class' => array($type, $class), 'title' => $alt)));
+}
+
+/**
+ * Render an icon and related text as a link. This will automatically apply an AJAX class
+ * to the link and add the appropriate javascript to make this happen.
+ *
+ * Note: 'html' => true so be sure any text is vetted! Chances are these kinds of buttons will
+ * not use user input so this is a very minor concern.
+ *
+ * @param $text
+ * The text that will be displayed as the link.
+ * @param $image
+ * The icon image to include in the link.
+ * @param $dest
+ * The destination of the link.
+ * @param $alt
+ * The title text of the link.
+ * @param $class
+ * Any class to apply to the link. @todo this should be a options array.
+ * @param $type
+ * A type to use, in case a different behavior should be attached. Defaults
+ * to ctools-use-ajax.
+ */
+function ctools_ajax_icon_text_button($text, $image, $dest, $alt, $class = '', $type = 'use-ajax') {
+ drupal_add_library('system', 'drupal.ajax');
+ $rendered_image = theme('image', array('path' => $image));
+ $link_content = $rendered_image . "<span>" . $text . "</span>";
+ return l($link_content, $dest, array('html' => TRUE, 'attributes' => array('class' => array($type, $class), 'title' => $alt)));
+}
+
+/**
+ * Set a single property to a value, on all matched elements.
+ *
+ * @param $selector
+ * The CSS selector. This can be any selector jquery uses in $().
+ * @param $name
+ * The name or key: of the data attached to this selector.
+ * @param $value
+ * The value of the data.
+ */
+function ctools_ajax_command_attr($selector, $name, $value) {
+ ctools_add_js('ajax-responder');
+ return array(
+ 'command' => 'attr',
+ 'selector' => $selector,
+ 'name' => $name,
+ 'value' => $value,
+ );
+ }
+
+/**
+ * Force a client-side redirect.
+ *
+ * @param $url
+ * The url to be redirected to. This can be an absolute URL or a
+ * Drupal path.
+ * @param $delay
+ * A delay before applying the redirection, in milliseconds.
+ * @param $options
+ * An array of options to pass to the url() function.
+ */
+function ctools_ajax_command_redirect($url, $delay = 0, $options = array()) {
+ ctools_add_js('ajax-responder');
+ return array(
+ 'command' => 'redirect',
+ 'url' => url($url, $options),
+ 'delay' => $delay,
+ );
+}
+
+/**
+ * Force a reload of the current page.
+ */
+function ctools_ajax_command_reload() {
+ ctools_add_js('ajax-responder');
+ return array(
+ 'command' => 'reload',
+ );
+}
+
+/**
+ * Submit a form.
+ *
+ * This is useful for submitting a parent form after a child form has finished
+ * processing in a modal overlay.
+ *
+ * @param $selector
+ * The CSS selector to identify the form for submission. This can be any
+ * selector jquery uses in $().
+ */
+function ctools_ajax_command_submit($selector) {
+ ctools_add_js('ajax-responder');
+ return array(
+ 'command' => 'submit',
+ 'selector' => $selector,
+ );
+}
+
+/**
+ * Send an error response back via AJAX and immediately exit.
+ */
+function ctools_ajax_render_error($error = '') {
+ $commands = array();
+ $commands[] = ajax_command_alert($error);
+ print ajax_render($commands);
+ exit;
+}
+
diff --git a/sites/all/modules/ctools/includes/cache.inc b/sites/all/modules/ctools/includes/cache.inc
new file mode 100644
index 000000000..3918683b0
--- /dev/null
+++ b/sites/all/modules/ctools/includes/cache.inc
@@ -0,0 +1,169 @@
+<?php
+
+/**
+ * @file
+ *
+ * Plugins to handle cache-indirection.
+ *
+ * Simple plugin management to allow clients to more tightly control where
+ * object caches are stored.
+ *
+ * CTools provides an object cache mechanism, and it also provides a number
+ * of subsystems that are designed to plug into larger systems. When doing
+ * caching on multi-step forms (in particular during AJAX operations) these
+ * subsystems often need to operate their own cache. In reality, its best
+ * for everyone if they are able to piggyback off of the larger cache.
+ *
+ * This system allows this by registering plugins to control where caches
+ * are actually stored. For the most part, the subsystems could care less
+ * where the data is fetched from and stored to. All it needs to know is
+ * that it can 'get', 'set' and 'clear' caches. Additionally, some caches
+ * might need extra operations such as 'lock' and 'finalize', and other
+ * operations may be needed based upon the specific uses for the cache
+ * plugins.
+ *
+ * To utilize cache plugins, there are two pieces of data. First, there is
+ * the mechanism, which is simply the name of the plugin to use. CTools
+ * provides a 'simple' mechanism which goes straight through to the object
+ * cache. The second piece of data is the 'key' which is a unique identifier
+ * that can be used to find the data needed. Keys can be generated any number
+ * of ways, and the plugin must be agnostic about the key itself.
+ *
+ * That said, the 'mechanism' can be specified as pluginame::data and that
+ * data can be used to derive additional data. For example, it is often
+ * desirable to NOT store any cached data until original data (i.e, user
+ * input) has been received. The data can be used to derive this original
+ * data so that when a 'get' is called, if the cache is missed it can create
+ * the data needed. This can help prevent unwanted cache entries from
+ * building up just by visiting edit UIs without actually modifying anything.
+ *
+ * Modules wishing to implement cache indirection mechanisms need to implement
+ * a plugin of type 'cache' for the module 'ctools' and provide the .inc file.
+ * It should provide callbacks for 'cache set', 'cache get', and 'cache clear'.
+ * It can provide callbacks for 'break' and 'finalize' if these are relevant
+ * to the caching mechanism (i.e, for use with locking caches such as the page
+ * manager cache). Other operations may be utilized but at this time are not part
+ * of CTools.
+ */
+
+/**
+ * Fetch data from an indirect cache.
+ *
+ * @param string $mechanism
+ * A string containing the plugin name, and an optional data element to
+ * send to the plugin separated by two colons.
+ *
+ * @param string $key
+ * The key used to identify the cache.
+ *
+ * @return mixed
+ * The cached data. This can be any format as the plugin does not necessarily
+ * have knowledge of what is being cached.
+ */
+function ctools_cache_get($mechanism, $key) {
+ return ctools_cache_operation($mechanism, $key, 'get');
+}
+
+/**
+ * Store data in an indirect cache.
+ *
+ * @param string $mechanism
+ * A string containing the plugin name, and an optional data element to
+ * send to the plugin separated by two colons.
+ *
+ * @param string $key
+ * The key used to identify the cache.
+ *
+ * @param mixed $object
+ * The data to cache. This can be any format as the plugin does not
+ * necessarily have knowledge of what is being cached.
+ */
+function ctools_cache_set($mechanism, $key, $object) {
+ return ctools_cache_operation($mechanism, $key, 'set', $object);
+}
+
+/**
+ * Clear data from an indirect cache.
+ *
+ * @param string $mechanism
+ * A string containing the plugin name, and an optional data element to
+ * send to the plugin separated by two colons.
+ *
+ * @param string $key
+ * The key used to identify the cache.
+ */
+function ctools_cache_clear($mechanism, $key) {
+ return ctools_cache_operation($mechanism, $key, 'clear');
+
+}
+
+/**
+ * Perform a secondary operation on an indirect cache.
+ *
+ * Additional operations, beyond get, set and clear may be items
+ * such as 'break' and 'finalize', which are needed to support cache
+ * locking. Other operations may be added by users of the indirect
+ * caching functions as needed.
+ *
+ * @param string $mechanism
+ * A string containing the plugin name, and an optional data element to
+ * send to the plugin separated by two colons.
+ *
+ * @param string $key
+ * The key used to identify the cache.
+ *
+ * @param string $op
+ * The operation to call, such as 'break' or 'finalize'.
+ *
+ * @param mixed $object
+ * The cache data being operated on, in case it is necessary. This is
+ * optional so no references should be used.
+ *
+ * @return mixed
+ * The operation may or may not return a value.
+ */
+function ctools_cache_operation($mechanism, $key, $op, $object = NULL) {
+ list($plugin, $data) = ctools_cache_find_plugin($mechanism);
+ if (empty($plugin)) {
+ return;
+ }
+
+ $function = ctools_plugin_get_function($plugin, "cache $op");
+ if (empty($function)) {
+ return;
+ }
+
+ return $function($data, $key, $object, $op);
+}
+
+/**
+ * Take a mechanism and return a plugin and data.
+ *
+ * @param string $mechanism
+ * A string containing the plugin name, and an optional data element to
+ * send to the plugin separated by two colons.
+ *
+ * @return array
+ * An array, the first element will be the plugin and the second element
+ * will be the data. If the plugin could not be found, the $plugin will
+ * be NULL.
+ */
+function ctools_cache_find_plugin($mechanism) {
+ if (strpos($mechanism, '::') !== FALSE) {
+ // use explode(2) to ensure that the data can contain double
+ // colons, just in case.
+ list($name, $data) = explode('::', $mechanism, 2);
+ }
+ else {
+ $name = $mechanism;
+ $data = '';
+ }
+
+ if (empty($name)) {
+ return array(NULL, $data);
+ }
+
+ ctools_include('plugins');
+ $plugin = ctools_get_plugins('ctools', 'cache', $name);
+ return array($plugin, $data);
+}
diff --git a/sites/all/modules/ctools/includes/cache.plugin-type.inc b/sites/all/modules/ctools/includes/cache.plugin-type.inc
new file mode 100644
index 000000000..1ab4cdd13
--- /dev/null
+++ b/sites/all/modules/ctools/includes/cache.plugin-type.inc
@@ -0,0 +1,11 @@
+<?php
+
+/**
+ * @file
+ * Contains plugin type registration information for the cache tool.
+ */
+
+function ctools_cache_plugin_type(&$items) {
+ // There are no options to declare on this plugin at this time.
+ $items['cache'] = array();
+}
diff --git a/sites/all/modules/ctools/includes/cleanstring.inc b/sites/all/modules/ctools/includes/cleanstring.inc
new file mode 100644
index 000000000..56b3e36f4
--- /dev/null
+++ b/sites/all/modules/ctools/includes/cleanstring.inc
@@ -0,0 +1,204 @@
+<?php
+// $Id $
+
+/**
+ * @file
+ * Helper class to clean strings to make them URL safe and translatable.
+ *
+ * This was copied directly from pathauto and put here to be made available
+ * to all, because more things than just pathauto want URL safe strings.
+ *
+ * To use, simply:
+ * @code
+ * ctools_include('cleanstring');
+ * $output = ctools_cleanstring($string);
+ *
+ * You can add a variety of settings as an array in the second argument,
+ * including words to ignore, how to deal with punctuation, length
+ * limits, and more. See the function itself for options.
+ */
+
+/**
+ * Matches Unicode character classes.
+ *
+ * See: http://www.unicode.org/Public/UNIDATA/UCD.html#General_Category_Values
+ *
+ * The index only contains the following character classes:
+ * Lu Letter, Uppercase
+ * Ll Letter, Lowercase
+ * Lt Letter, Titlecase
+ * Lo Letter, Other
+ * Nd Number, Decimal Digit
+ * No Number, Other
+ *
+ * Copied from search.module's PREG_CLASS_SEARCH_EXCLUDE.
+ */
+define('CTOOLS_PREG_CLASS_ALNUM',
+'\x{0}-\x{2f}\x{3a}-\x{40}\x{5b}-\x{60}\x{7b}-\x{bf}\x{d7}\x{f7}\x{2b0}-' .
+'\x{385}\x{387}\x{3f6}\x{482}-\x{489}\x{559}-\x{55f}\x{589}-\x{5c7}\x{5f3}-' .
+'\x{61f}\x{640}\x{64b}-\x{65e}\x{66a}-\x{66d}\x{670}\x{6d4}\x{6d6}-\x{6ed}' .
+'\x{6fd}\x{6fe}\x{700}-\x{70f}\x{711}\x{730}-\x{74a}\x{7a6}-\x{7b0}\x{901}-' .
+'\x{903}\x{93c}\x{93e}-\x{94d}\x{951}-\x{954}\x{962}-\x{965}\x{970}\x{981}-' .
+'\x{983}\x{9bc}\x{9be}-\x{9cd}\x{9d7}\x{9e2}\x{9e3}\x{9f2}-\x{a03}\x{a3c}-' .
+'\x{a4d}\x{a70}\x{a71}\x{a81}-\x{a83}\x{abc}\x{abe}-\x{acd}\x{ae2}\x{ae3}' .
+'\x{af1}-\x{b03}\x{b3c}\x{b3e}-\x{b57}\x{b70}\x{b82}\x{bbe}-\x{bd7}\x{bf0}-' .
+'\x{c03}\x{c3e}-\x{c56}\x{c82}\x{c83}\x{cbc}\x{cbe}-\x{cd6}\x{d02}\x{d03}' .
+'\x{d3e}-\x{d57}\x{d82}\x{d83}\x{dca}-\x{df4}\x{e31}\x{e34}-\x{e3f}\x{e46}-' .
+'\x{e4f}\x{e5a}\x{e5b}\x{eb1}\x{eb4}-\x{ebc}\x{ec6}-\x{ecd}\x{f01}-\x{f1f}' .
+'\x{f2a}-\x{f3f}\x{f71}-\x{f87}\x{f90}-\x{fd1}\x{102c}-\x{1039}\x{104a}-' .
+'\x{104f}\x{1056}-\x{1059}\x{10fb}\x{10fc}\x{135f}-\x{137c}\x{1390}-\x{1399}' .
+'\x{166d}\x{166e}\x{1680}\x{169b}\x{169c}\x{16eb}-\x{16f0}\x{1712}-\x{1714}' .
+'\x{1732}-\x{1736}\x{1752}\x{1753}\x{1772}\x{1773}\x{17b4}-\x{17db}\x{17dd}' .
+'\x{17f0}-\x{180e}\x{1843}\x{18a9}\x{1920}-\x{1945}\x{19b0}-\x{19c0}\x{19c8}' .
+'\x{19c9}\x{19de}-\x{19ff}\x{1a17}-\x{1a1f}\x{1d2c}-\x{1d61}\x{1d78}\x{1d9b}-' .
+'\x{1dc3}\x{1fbd}\x{1fbf}-\x{1fc1}\x{1fcd}-\x{1fcf}\x{1fdd}-\x{1fdf}\x{1fed}-' .
+'\x{1fef}\x{1ffd}-\x{2070}\x{2074}-\x{207e}\x{2080}-\x{2101}\x{2103}-\x{2106}' .
+'\x{2108}\x{2109}\x{2114}\x{2116}-\x{2118}\x{211e}-\x{2123}\x{2125}\x{2127}' .
+'\x{2129}\x{212e}\x{2132}\x{213a}\x{213b}\x{2140}-\x{2144}\x{214a}-\x{2b13}' .
+'\x{2ce5}-\x{2cff}\x{2d6f}\x{2e00}-\x{3005}\x{3007}-\x{303b}\x{303d}-\x{303f}' .
+'\x{3099}-\x{309e}\x{30a0}\x{30fb}-\x{30fe}\x{3190}-\x{319f}\x{31c0}-\x{31cf}' .
+'\x{3200}-\x{33ff}\x{4dc0}-\x{4dff}\x{a015}\x{a490}-\x{a716}\x{a802}\x{a806}' .
+'\x{a80b}\x{a823}-\x{a82b}\x{e000}-\x{f8ff}\x{fb1e}\x{fb29}\x{fd3e}\x{fd3f}' .
+'\x{fdfc}-\x{fe6b}\x{feff}-\x{ff0f}\x{ff1a}-\x{ff20}\x{ff3b}-\x{ff40}\x{ff5b}-' .
+'\x{ff65}\x{ff70}\x{ff9e}\x{ff9f}\x{ffe0}-\x{fffd}');
+
+/**
+ * Clean up a string value provided by a module.
+ *
+ * Resulting string contains only alphanumerics and separators.
+ *
+ * @param $string
+ * A string to clean.
+ * @param $settings
+ * An optional array of settings to use.
+ * - 'clean slash': If set, slashes will be cleaned. Defaults to TRUE,
+ * so you have to explicitly set this to FALSE to not clean the
+ * slashes.
+ * - 'ignore words': Set to an array of words that will be removed
+ * rather than made safe. Defaults to an empty array.
+ * - 'separator': Change spaces and untranslatable characters to
+ * this character. Defaults to '-' .
+ * - 'replacements': An array of direct replacements to be made that will
+ * be implemented via strtr(). Defaults to an empty array.
+ * - 'transliterate': If set, use the transliteration replacements. If set
+ * to an array, use these replacements instead of the defaults in CTools.
+ * Defaults to FALSE.
+ * - 'reduce ascii': If set to TRUE further reduce to ASCII96 only. Defaults
+ * to TRUE.
+ * - 'max length': If set to a number, reduce the resulting string to this
+ * maximum length. Defaults to no maximum length.
+ * - 'lower case': If set to TRUE, convert the result to lower case.
+ * Defaults to false.
+ * These settings will be passed through drupal_alter.
+ *
+ * @return
+ * The cleaned string.
+ */
+function ctools_cleanstring($string, $settings = array()) {
+ $settings += array(
+ 'clean slash' => TRUE,
+ 'ignore words' => array(),
+ 'separator' => '-',
+ 'replacements' => array(),
+ 'transliterate' => FALSE,
+ 'reduce ascii' => TRUE,
+ 'max length' => FALSE,
+ 'lower case' => FALSE,
+ );
+
+ // Allow modules to make other changes to the settings.
+ if (isset($settings['clean id'])) {
+ drupal_alter('ctools_cleanstring_' . $settings['clean id'], $settings);
+ }
+
+ drupal_alter('ctools_cleanstring', $settings);
+
+ $output = $string;
+
+ // Do any replacements the user selected up front.
+ if (!empty($settings['replacements'])) {
+ $output = strtr($output, $settings['replacements']);
+ }
+
+ // Remove slashes if instructed to do so.
+ if ($settings['clean slash']) {
+ $output = str_replace('/', '', $output);
+ }
+
+ if (!empty($settings['transliterate']) && module_exists('transliteration')) {
+ $output = transliteration_get($output);
+ }
+
+ // Reduce to the subset of ASCII96 letters and numbers
+ if ($settings['reduce ascii']) {
+ $pattern = '/[^a-zA-Z0-9\/]+/';
+ $output = preg_replace($pattern, $settings['separator'], $output);
+ }
+
+ // Get rid of words that are on the ignore list
+ if (!empty($settings['ignore words'])) {
+ $ignore_re = '\b' . preg_replace('/,/', '\b|\b', $settings['ignore words']) . '\b';
+
+ if (function_exists('mb_eregi_replace')) {
+ $output = mb_eregi_replace($ignore_re, '', $output);
+ }
+ else {
+ $output = preg_replace("/$ignore_re/i", '', $output);
+ }
+ }
+
+ // Always replace whitespace with the separator.
+ $output = preg_replace('/\s+/', $settings['separator'], $output);
+
+ // In preparation for pattern matching,
+ // escape the separator if and only if it is not alphanumeric.
+ if (isset($settings['separator'])) {
+ if (preg_match('/^[^' . CTOOLS_PREG_CLASS_ALNUM . ']+$/uD', $settings['separator'])) {
+ $seppattern = $settings['separator'];
+ }
+ else {
+ $seppattern = '\\' . $settings['separator'];
+ }
+ // Trim any leading or trailing separators (note the need to
+ $output = preg_replace("/^$seppattern+|$seppattern+$/", '', $output);
+
+ // Replace multiple separators with a single one
+ $output = preg_replace("/$seppattern+/", $settings['separator'], $output);
+ }
+
+ // Enforce the maximum component length
+ if (!empty($settings['max length'])) {
+ $output = ctools_cleanstring_truncate($output, $settings['max length'], $settings['separator']);
+ }
+
+ if (!empty($settings['lower case'])) {
+ $output = drupal_strtolower($output);
+ }
+ return $output;
+}
+
+/**
+ * A friendly version of truncate_utf8.
+ *
+ * @param $string
+ * The string to be truncated.
+ * @param $length
+ * An integer for the maximum desired length.
+ * @param $separator
+ * A string which contains the word boundary such as - or _.
+ *
+ * @return
+ * The string truncated below the maxlength.
+ */
+function ctools_cleanstring_truncate($string, $length, $separator) {
+ if (drupal_strlen($string) > $length) {
+ $string = drupal_substr($string, 0, $length + 1); // leave one more character
+ if ($last_break = strrpos($string, $separator)) { // space exists AND is not on position 0
+ $string = substr($string, 0, $last_break);
+ }
+ else {
+ $string = drupal_substr($string, 0, $length);
+ }
+ }
+ return $string;
+}
diff --git a/sites/all/modules/ctools/includes/collapsible.theme.inc b/sites/all/modules/ctools/includes/collapsible.theme.inc
new file mode 100644
index 000000000..f7bbbb376
--- /dev/null
+++ b/sites/all/modules/ctools/includes/collapsible.theme.inc
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @file
+ * Theme function for the collapsible div tool.
+ *
+ * Call theme('ctools_collapsible', $handle, $content, $collapsed) to draw the
+ * div. The theme function is not necessary; you can add the classes, js and css
+ * yourself if you really want to.
+ */
+
+/**
+ * Delegated implementation of hook_theme()
+ */
+function ctools_collapsible_theme(&$items) {
+ $items['ctools_collapsible'] = array(
+ 'variables' => array('handle' => NULL, 'content' => NULL, 'collapsed' => FALSE),
+ 'file' => 'includes/collapsible.theme.inc',
+ );
+ $items['ctools_collapsible_remembered'] = array(
+ 'variables' => array('id' => NULL, 'handle' => NULL, 'content' => NULL, 'collapsed' => FALSE),
+ 'file' => 'includes/collapsible.theme.inc',
+ );
+}
+
+/**
+ * Render a collapsible div.
+ *
+ * @param $handle
+ * Text to put in the handle/title area of the div.
+ * @param $content
+ * Text to put in the content area of the div, this is what will get
+ * collapsed
+ * @param $collapsed = FALSE
+ * If true, this div will start out collapsed.
+ */
+function theme_ctools_collapsible($vars) {
+ ctools_add_js('collapsible-div');
+ ctools_add_css('collapsible-div');
+
+ $class = $vars['collapsed'] ? ' ctools-collapsed' : '';
+ $output = '<div class="ctools-collapsible-container' . $class . '">';
+ $output .= '<div class="ctools-collapsible-handle">' . $vars['handle'] . '</div>';
+ $output .= '<div class="ctools-collapsible-content">' . $vars['content'] . '</div>';
+ $output .= '</div>';
+
+ return $output;
+}
+
+/**
+ * Render a collapsible div whose state will be remembered.
+ *
+ * @param $id
+ * The CSS id of the container. This is required.
+ * @param $handle
+ * Text to put in the handle/title area of the div.
+ * @param $content
+ * Text to put in the content area of the div, this is what will get
+ * collapsed
+ * @param $collapsed = FALSE
+ * If true, this div will start out collapsed.
+ */
+function theme_ctools_collapsible_remembered($vars) {
+ $id = $vars['id'];
+ $handle = $vars['handle'];
+ $content = $vars['content'];
+ $collapsed = $vars['collapsed'];
+ ctools_add_js('collapsible-div');
+ ctools_add_css('collapsible-div');
+
+ $class = $collapsed ? ' ctools-collapsed' : '';
+ $output = '<div id="' . $id . '" class="ctools-collapsible-remember ctools-collapsible-container' . $class . '">';
+ $output .= '<div class="ctools-collapsible-handle">' . $handle . '</div>';
+ $output .= '<div class="ctools-collapsible-content">' . $content . '</div>';
+ $output .= '</div>';
+
+ return $output;
+}
+
diff --git a/sites/all/modules/ctools/includes/content.inc b/sites/all/modules/ctools/includes/content.inc
new file mode 100644
index 000000000..ae1c6073d
--- /dev/null
+++ b/sites/all/modules/ctools/includes/content.inc
@@ -0,0 +1,853 @@
+<?php
+
+/**
+ * @file
+ * Contains the tools to handle pluggable content that can be used by other
+ * applications such as Panels or Dashboard.
+ *
+ * See the context-content.html file in advanced help for documentation
+ * of this tool.
+ */
+
+/**
+ * Provide defaults for a content type.
+ *
+ * Currently we check for automatically named callbacks to make life a little
+ * easier on the developer.
+ */
+function ctools_content_process(&$plugin, $info) {
+ $function_base = $plugin['module'] . '_' . $plugin['name'] . '_content_type_';
+
+ if (empty($plugin['render callback']) && function_exists($function_base . 'render')) {
+ $plugin['render callback'] = $function_base . 'render';
+ }
+
+ if (empty($plugin['admin title'])) {
+ if (function_exists($function_base . 'admin_title')) {
+ $plugin['admin title'] = $function_base . 'admin_title';
+ }
+ else {
+ $plugin['admin title'] = $plugin['title'];
+ }
+ }
+
+ if (empty($plugin['admin info']) && function_exists($function_base . 'admin_info')) {
+ $plugin['admin info'] = $function_base . 'admin_info';
+ }
+
+ if (!isset($plugin['edit form']) && function_exists($function_base . 'edit_form')) {
+ $plugin['edit form'] = $function_base . 'edit_form';
+ }
+
+ if (!isset($plugin['add form']) && function_exists($function_base . 'add_form')) {
+ $plugin['add form'] = $function_base . 'add_form';
+ }
+
+ if (!isset($plugin['add form']) && function_exists($function_base . 'edit_form')) {
+ $plugin['add form'] = $function_base . 'edit_form';
+ }
+
+ if (!isset($plugin['description'])) {
+ $plugin['description'] = '';
+ }
+
+ if (!isset($plugin['icon'])) {
+ $plugin['icon'] = ctools_content_admin_icon($plugin);
+ }
+
+ // Another ease of use check:
+ if (!isset($plugin['content types'])) {
+ // If a subtype plugin exists, try to use it. Otherwise assume single.
+ if (function_exists($function_base . 'content_types')) {
+ $plugin['content types'] = $function_base . 'content_types';
+ }
+ else {
+ $type = array(
+ 'title' => $plugin['title'],
+ 'description' => $plugin['description'],
+ 'icon' => ctools_content_admin_icon($plugin),
+ 'category' => $plugin['category'],
+ );
+
+ if (isset($plugin['required context'])) {
+ $type['required context'] = $plugin['required context'];
+ }
+ if (isset($plugin['top level'])) {
+ $type['top level'] = $plugin['top level'];
+ }
+ $plugin['content types'] = array($plugin['name'] => $type);
+ if (!isset($plugin['single'])) {
+ $plugin['single'] = TRUE;
+ }
+ }
+ }
+}
+
+/**
+ * Fetch metadata on a specific content_type plugin.
+ *
+ * @param $content type
+ * Name of a panel content type.
+ *
+ * @return
+ * An array with information about the requested panel content type.
+ */
+function ctools_get_content_type($content_type) {
+ ctools_include('context');
+ ctools_include('plugins');
+ return ctools_get_plugins('ctools', 'content_types', $content_type);
+}
+
+/**
+ * Fetch metadata for all content_type plugins.
+ *
+ * @return
+ * An array of arrays with information about all available panel content types.
+ */
+function ctools_get_content_types() {
+ ctools_include('context');
+ ctools_include('plugins');
+ return ctools_get_plugins('ctools', 'content_types');
+}
+
+/**
+ * Get all of the individual subtypes provided by a given content type. This
+ * would be all of the blocks for the block type, or all of the views for
+ * the view type.
+ *
+ * @param $type
+ * The content type to load.
+ *
+ * @return
+ * An array of all subtypes available.
+ */
+function ctools_content_get_subtypes($type) {
+ static $cache = array();
+
+ $subtypes = array();
+
+ if (is_array($type)) {
+ $plugin = $type;
+ }
+ else {
+ $plugin = ctools_get_content_type($type);
+ }
+
+ if (empty($plugin) || empty($plugin['name'])) {
+ return;
+ }
+
+ if (isset($cache[$plugin['name']])) {
+ return $cache[$plugin['name']];
+ }
+
+ if (isset($plugin['content types'])) {
+ $function = $plugin['content types'];
+ if (is_array($function)) {
+ $subtypes = $function;
+ }
+ else if (function_exists($function)) {
+ // Cast to array to prevent errors from non-array returns.
+ $subtypes = (array) $function($plugin);
+ }
+ }
+
+ // Walk through the subtypes and ensure minimal settings are
+ // retained.
+ foreach ($subtypes as $id => $subtype) {
+ // Use exact name since this is a modify by reference.
+ ctools_content_prepare_subtype($subtypes[$id], $plugin);
+ }
+
+ $cache[$plugin['name']] = $subtypes;
+
+ return $subtypes;
+}
+
+/**
+ * Given a content type and a subtype id, return the information about that
+ * content subtype.
+ *
+ * @param $type
+ * The content type being fetched.
+ * @param $subtype_id
+ * The id of the subtype being fetched.
+ *
+ * @return
+ * An array of information describing the content subtype.
+ */
+function ctools_content_get_subtype($type, $subtype_id) {
+ $subtype = array();
+ if (is_array($type)) {
+ $plugin = $type;
+ }
+ else {
+ $plugin = ctools_get_content_type($type);
+ }
+
+ $function = ctools_plugin_get_function($plugin, 'content type');
+ if ($function) {
+ $subtype = $function($subtype_id, $plugin);
+ }
+ else {
+ $subtypes = ctools_content_get_subtypes($type);
+ if (isset($subtypes[$subtype_id])) {
+ $subtype = $subtypes[$subtype_id];
+ }
+ // If there's only 1 and we somehow have the wrong subtype ID, do not
+ // care. Return the proper subtype anyway.
+ if (empty($subtype) && !empty($plugin['single'])) {
+ $subtype = current($subtypes);
+ }
+ }
+
+ if ($subtype) {
+ ctools_content_prepare_subtype($subtype, $plugin);
+ }
+ return $subtype;
+}
+
+/**
+ * Ensure minimal required settings on a content subtype exist.
+ */
+function ctools_content_prepare_subtype(&$subtype, $plugin) {
+ foreach (array('path', 'js', 'css') as $key) {
+ if (!isset($subtype[$key]) && isset($plugin[$key])) {
+ $subtype[$key] = $plugin[$key];
+ }
+ }
+
+ drupal_alter('ctools_content_subtype', $subtype, $plugin);
+}
+
+/**
+ * Get the content from a given content type.
+ *
+ * @param $type
+ * The content type. May be the name or an already loaded content type plugin.
+ * @param $subtype
+ * The name of the subtype being rendered.
+ * @param $conf
+ * The configuration for the content type.
+ * @param $keywords
+ * An array of replacement keywords that come from outside contexts.
+ * @param $args
+ * The arguments provided to the owner of the content type. Some content may
+ * wish to configure itself based on the arguments the panel or dashboard
+ * received.
+ * @param $context
+ * An array of context objects available for use.
+ * @param $incoming_content
+ * Any incoming content, if this display is a wrapper.
+ *
+ * @return
+ * The content as rendered by the plugin. This content should be an array
+ * with the following possible keys:
+ * - title: The safe to render title of the content.
+ * - title_heading: The title heading.
+ * - content: The safe to render HTML content.
+ * - links: An array of links associated with the content suitable for
+ * theme('links').
+ * - more: An optional 'more' link (destination only)
+ * - admin_links: Administrative links associated with the content, suitable
+ * for theme('links').
+ * - feeds: An array of feed icons or links associated with the content.
+ * Each member of the array is rendered HTML.
+ * - type: The content type.
+ * - subtype: The content subtype. These two may be used together as
+ * module-delta for block style rendering.
+ */
+function ctools_content_render($type, $subtype, $conf, $keywords = array(), $args = array(), $context = array(), $incoming_content = '') {
+ if (is_array($type)) {
+ $plugin = $type;
+ }
+ else {
+ $plugin = ctools_get_content_type($type);
+ }
+
+ $subtype_info = ctools_content_get_subtype($plugin, $subtype);
+
+ $function = ctools_plugin_get_function($subtype_info, 'render callback');
+ if (!$function) {
+ $function = ctools_plugin_get_function($plugin, 'render callback');
+ }
+
+ if ($function) {
+ $pane_context = ctools_content_select_context($plugin, $subtype, $conf, $context);
+ if ($pane_context === FALSE) {
+ return;
+ }
+
+ $content = $function($subtype, $conf, $args, $pane_context, $incoming_content);
+
+ if (empty($content)) {
+ return;
+ }
+
+ // Set up some defaults and other massaging on the content before we hand
+ // it back to the caller.
+ if (!isset($content->type)) {
+ $content->type = $plugin['name'];
+ }
+
+ if (!isset($content->subtype)) {
+ $content->subtype = $subtype;
+ }
+
+ // Override the title if configured to
+ if (!empty($conf['override_title'])) {
+ // Give previous title as an available substitution here.
+ $keywords['%title'] = empty($content->title) ? '' : $content->title;
+ $content->original_title = $keywords['%title'];
+ $content->title = $conf['override_title_text'];
+ $content->title_heading = isset($conf['override_title_heading']) ? $conf['override_title_heading'] : 'h2';
+ }
+
+ if (!empty($content->title)) {
+ // Perform substitutions
+ if (!empty($keywords) || !empty($context)) {
+ $content->title = ctools_context_keyword_substitute($content->title, $keywords, $context);
+ }
+
+ // Sterilize the title
+ $content->title = filter_xss_admin($content->title);
+
+ // If a link is specified, populate.
+ if (!empty($content->title_link)) {
+ if (!is_array($content->title_link)) {
+ $url = array('href' => $content->title_link);
+ }
+ else {
+ $url = $content->title_link;
+ }
+ // set defaults so we don't bring up notices
+ $url += array('href' => '', 'attributes' => array(), 'query' => array(), 'fragment' => '', 'absolute' => NULL, 'html' => TRUE);
+ $content->title = l($content->title, $url['href'], $url);
+ }
+ }
+
+ return $content;
+ }
+}
+
+/**
+ * Determine if a content type can be edited or not.
+ *
+ * Some content types simply have their content and no options. This function
+ * lets a UI determine if it should display an edit link or not.
+ */
+function ctools_content_editable($type, $subtype, $conf) {
+ if (empty($type['edit form']) && empty($subtype['edit form'])) {
+ return FALSE;
+ }
+
+ $function = FALSE;
+
+ if (!empty($subtype['check editable'])) {
+ $function = ctools_plugin_get_function($subtype, 'check editable');
+ }
+ elseif (!empty($type['check editable'])) {
+ $function = ctools_plugin_get_function($type, 'check editable');
+ }
+
+ if ($function) {
+ return $function($type, $subtype, $conf);
+ }
+
+ return TRUE;
+}
+
+/**
+ * Get the administrative title from a given content type.
+ *
+ * @param $type
+ * The content type. May be the name or an already loaded content type object.
+ * @param $subtype
+ * The subtype being rendered.
+ * @param $conf
+ * The configuration for the content type.
+ * @param $context
+ * An array of context objects available for use. These may be placeholders.
+ */
+function ctools_content_admin_title($type, $subtype, $conf, $context = NULL) {
+ if (is_array($type)) {
+ $plugin = $type;
+ }
+ else if (is_string($type)) {
+ $plugin = ctools_get_content_type($type);
+ }
+ else {
+ return;
+ }
+
+ if ($function = ctools_plugin_get_function($plugin, 'admin title')) {
+ $pane_context = ctools_content_select_context($plugin, $subtype, $conf, $context);
+ if ($pane_context === FALSE) {
+ if ($plugin['name'] == $subtype) {
+ return t('@type will not display due to missing context', array('@type' => $plugin['name']));
+ }
+ return t('@type:@subtype will not display due to missing context', array('@type' => $plugin['name'], '@subtype' => $subtype));
+ }
+
+ return $function($subtype, $conf, $pane_context);
+ }
+ else if (isset($plugin['admin title'])) {
+ return $plugin['admin title'];
+ }
+ else if (isset($plugin['title'])) {
+ return $plugin['title'];
+ }
+}
+
+/**
+ * Get the proper icon path to use, falling back to default icons if no icon exists.
+ *
+ * $subtype
+ * The loaded subtype info.
+ */
+function ctools_content_admin_icon($subtype) {
+ $icon = '';
+
+ if (isset($subtype['icon'])) {
+ $icon = $subtype['icon'];
+ if (!file_exists($icon)) {
+ $icon = $subtype['path'] . '/' . $icon;
+ }
+ }
+
+ if (empty($icon) || !file_exists($icon)) {
+ $icon = ctools_image_path('no-icon.png');
+ }
+
+ return $icon;
+}
+
+/**
+ * Set up the default $conf for a new instance of a content type.
+ */
+function ctools_content_get_defaults($plugin, $subtype) {
+ if (isset($plugin['defaults'])) {
+ $defaults = $plugin['defaults'];
+ }
+ else if (isset($subtype['defaults'])) {
+ $defaults = $subtype['defaults'];
+ }
+ if (isset($defaults)) {
+ if (is_string($defaults) && function_exists($defaults)) {
+ if ($return = $defaults($pane)) {
+ return $return;
+ }
+ }
+ else if (is_array($defaults)) {
+ return $defaults;
+ }
+ }
+
+ return array();
+}
+
+/**
+ * Get the administrative title from a given content type.
+ *
+ * @param $type
+ * The content type. May be the name or an already loaded content type object.
+ * @param $subtype
+ * The subtype being rendered.
+ * @param $conf
+ * The configuration for the content type.
+ * @param $context
+ * An array of context objects available for use. These may be placeholders.
+ */
+function ctools_content_admin_info($type, $subtype, $conf, $context = NULL) {
+ if (is_array($type)) {
+ $plugin = $type;
+ }
+ else {
+ $plugin = ctools_get_content_type($type);
+ }
+
+ if ($function = ctools_plugin_get_function($plugin, 'admin info')) {
+ $output = $function($subtype, $conf, $context);
+ }
+
+ if (empty($output) || !is_object($output)) {
+ $output = new stdClass();
+ // replace the _ with " " for a better output
+ $subtype = check_plain(str_replace("_", " ", $subtype));
+ $output->title = $subtype;
+ $output->content = t('No info available.');
+ }
+ return $output;
+}
+
+/**
+ * Add the default FAPI elements to the content type configuration form
+ */
+function ctools_content_configure_form_defaults($form, &$form_state) {
+ $plugin = $form_state['plugin'];
+ $subtype = $form_state['subtype'];
+ $contexts = isset($form_state['contexts']) ? $form_state['contexts'] : NULL;
+ $conf = $form_state['conf'];
+
+ $add_submit = FALSE;
+ if (!empty($subtype['required context']) && is_array($contexts)) {
+ $form['context'] = ctools_context_selector($contexts, $subtype['required context'], isset($conf['context']) ? $conf['context'] : array());
+ $add_submit = TRUE;
+ }
+
+ ctools_include('dependent');
+
+ // Unless we're not allowed to override the title on this content type, add this
+ // gadget to all panes.
+ if (empty($plugin['no title override']) && empty($subtype['no title override'])) {
+ $form['aligner_start'] = array(
+ '#markup' => '<div class="option-text-aligner clearfix">',
+ );
+ $form['override_title'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => isset($conf['override_title']) ? $conf['override_title'] : '',
+ '#title' => t('Override title'),
+ '#id' => 'override-title-checkbox',
+ );
+ $form['override_title_text'] = array(
+ '#type' => 'textfield',
+ '#default_value' => isset($conf['override_title_text']) ? $conf['override_title_text'] : '',
+ '#size' => 35,
+ '#id' => 'override-title-textfield',
+ '#dependency' => array('override-title-checkbox' => array(1)),
+ '#dependency_type' => 'hidden',
+ );
+ $form['override_title_heading'] = array(
+ '#type' => 'select',
+ '#default_value' => isset($conf['override_title_heading']) ? $conf['override_title_heading'] : 'h2',
+ '#options' => array(
+ 'h1' => t('h1'),
+ 'h2' => t('h2'),
+ 'h3' => t('h3'),
+ 'h4' => t('h4'),
+ 'h5' => t('h5'),
+ 'h6' => t('h6'),
+ 'div' => t('div'),
+ 'span' => t('span'),
+ ),
+ '#id' => 'override-title-heading',
+ '#dependency' => array('override-title-checkbox' => array(1)),
+ '#dependency_type' => 'hidden',
+ );
+
+ $form['aligner_stop'] = array(
+ '#markup' => '</div>',
+ );
+ if (is_array($contexts)) {
+ $form['override_title_markup'] = array(
+ '#prefix' => '<div class="description">',
+ '#suffix' => '</div>',
+ '#markup' => t('You may use %keywords from contexts, as well as %title to contain the original title.'),
+ );
+ }
+ $add_submit = TRUE;
+ }
+
+ if ($add_submit) {
+ // '#submit' is already set up due to the wizard.
+ $form['#submit'][] = 'ctools_content_configure_form_defaults_submit';
+ }
+ return $form;
+}
+
+/**
+ * Submit handler to store context/title override info.
+ */
+function ctools_content_configure_form_defaults_submit(&$form, &$form_state) {
+ if (isset($form_state['values']['context'])) {
+ $form_state['conf']['context'] = $form_state['values']['context'];
+ }
+ if (isset($form_state['values']['override_title'])) {
+ $form_state['conf']['override_title'] = $form_state['values']['override_title'];
+ $form_state['conf']['override_title_text'] = $form_state['values']['override_title_text'];
+ $form_state['conf']['override_title_heading'] = $form_state['values']['override_title_heading'];
+ }
+}
+
+/**
+ * Get the config form.
+ *
+ * The $form_info and $form_state need to be preconfigured with data you'll need
+ * such as whether or not you're using ajax, or the modal. $form_info will need
+ * your next/submit callbacks so that you can cache your data appropriately.
+ *
+ * @return
+ * If this function returns false, no form exists.
+ */
+function ctools_content_form($op, $form_info, &$form_state, $plugin, $subtype_name, $subtype, &$conf, $step = NULL) {
+ $form_state += array(
+ 'plugin' => $plugin,
+ 'subtype' => $subtype,
+ 'subtype_name' => $subtype_name,
+ 'conf' => &$conf,
+ 'op' => $op,
+ );
+
+ $form_info += array(
+ 'id' => 'ctools_content_form',
+ 'show back' => TRUE,
+ );
+
+ // Turn the forms defined in the plugin into the format the wizard needs.
+ if ($op == 'add') {
+ if (!empty($subtype['add form'])) {
+ _ctools_content_create_form_info($form_info, $subtype['add form'], $subtype, $subtype, $op);
+ }
+ else if (!empty($plugin['add form'])) {
+ _ctools_content_create_form_info($form_info, $plugin['add form'], $plugin, $subtype, $op);
+ }
+ }
+
+ if (empty($form_info['order'])) {
+ // Use the edit form for the add form if add form was completely left off.
+ if (!empty($subtype['edit form'])) {
+ _ctools_content_create_form_info($form_info, $subtype['edit form'], $subtype, $subtype, $op);
+ }
+ else if (!empty($plugin['edit form'])) {
+ _ctools_content_create_form_info($form_info, $plugin['edit form'], $plugin, $subtype, $op);
+ }
+ }
+
+ if (empty($form_info['order'])) {
+ return FALSE;
+ }
+
+ ctools_include('wizard');
+ return ctools_wizard_multistep_form($form_info, $step, $form_state);
+
+}
+
+function _ctools_content_create_form_info(&$form_info, $info, $plugin, $subtype, $op) {
+ if (is_string($info)) {
+ if (empty($subtype['title'])) {
+ $title = t('Configure');
+ }
+ else if ($op == 'add') {
+ $title = t('Configure new !subtype_title', array('!subtype_title' => $subtype['title']));
+ }
+ else {
+ $title = t('Configure !subtype_title', array('!subtype_title' => $subtype['title']));
+ }
+ $form_info['order'] = array('form' => $title);
+ $form_info['forms'] = array(
+ 'form' => array(
+ 'title' => $title,
+ 'form id' => $info,
+ 'wrapper' => 'ctools_content_configure_form_defaults',
+ ),
+ );
+ }
+ else if (is_array($info)) {
+ $form_info['order'] = array();
+ $form_info['forms'] = array();
+ $count = 0;
+ $base = 'step';
+ $wrapper = NULL;
+ foreach ($info as $form_id => $title) {
+ // @todo -- docs say %title can be used to sub for the admin title.
+ $step = $base . ++$count;
+ if (empty($wrapper)) {
+ $wrapper = $step;
+ }
+
+ if (is_array($title)) {
+ if (!empty($title['default'])) {
+ $wrapper = $step;
+ }
+ $title = $title['title'];
+ }
+
+ $form_info['order'][$step] = $title;
+ $form_info['forms'][$step] = array(
+ 'title' => $title,
+ 'form id' => $form_id,
+ );
+ }
+ if ($wrapper) {
+ $form_info['forms'][$wrapper]['wrapper'] = 'ctools_content_configure_form_defaults';
+ }
+ }
+}
+
+/**
+ * Get an array of all available content types that can be fed into the
+ * display editor for the add content list.
+ *
+ * @param $context
+ * If a context is provided, content that requires that context can apepar.
+ * @param $has_content
+ * Whether or not the display will have incoming content
+ * @param $allowed_types
+ * An array of allowed content types (pane types) keyed by content_type . '-' . sub_type
+ * @param $default_types
+ * A default allowed/denied status for content that isn't known about
+ */
+function ctools_content_get_available_types($contexts = NULL, $has_content = FALSE, $allowed_types = NULL, $default_types = NULL) {
+ $plugins = ctools_get_content_types();
+ $available = array();
+
+ foreach ($plugins as $id => $plugin) {
+ foreach (ctools_content_get_subtypes($plugin) as $subtype_id => $subtype) {
+ // exclude items that require content if we're saying we don't
+ // provide it.
+ if (!empty($subtype['requires content']) && !$has_content) {
+ continue;
+ }
+
+ // Check to see if the content type can be used in this context.
+ if (!empty($subtype['required context'])) {
+ if (!ctools_context_match_requirements($contexts, $subtype['required context'])) {
+ continue;
+ }
+ }
+
+ // Check to see if the passed-in allowed types allows this content.
+ if ($allowed_types) {
+ $key = $id . '-' . $subtype_id;
+ if (!isset($allowed_types[$key])) {
+ $allowed_types[$key] = isset($default_types[$id]) ? $default_types[$id] : $default_types['other'];
+ }
+ if (!$allowed_types[$key]) {
+ continue;
+ }
+ }
+
+ // Check if the content type provides an access callback.
+ if (isset($subtype['create content access']) && function_exists($subtype['create content access']) && !$subtype['create content access']($plugin, $subtype)) {
+ continue;
+ }
+
+ // If we made it through all the tests, then we can use this content.
+ $available[$id][$subtype_id] = $subtype;
+ }
+ }
+ return $available;
+}
+
+/**
+ * Get an array of all content types that can be fed into the
+ * display editor for the add content list, regardless of
+ * availability.
+ *
+ */
+function ctools_content_get_all_types() {
+ $plugins = ctools_get_content_types();
+ $available = array();
+
+ foreach ($plugins as $id => $plugin) {
+ foreach (ctools_content_get_subtypes($plugin) as $subtype_id => $subtype) {
+ // If we made it through all the tests, then we can use this content.
+ $available[$id][$subtype_id] = $subtype;
+ }
+ }
+ return $available;
+}
+
+/**
+ * Select the context to be used for a piece of content, based upon config.
+ *
+ * @param $plugin
+ * The content plugin
+ * @param $subtype
+ * The subtype of the content.
+ * @param $conf
+ * The configuration array that should contain the context.
+ * @param $contexts
+ * A keyed array of available contexts.
+ *
+ * @return
+ * The matching contexts or NULL if none or necessary, or FALSE if
+ * requirements can't be met.
+ */
+function ctools_content_select_context($plugin, $subtype, $conf, $contexts) {
+ // Identify which of our possible contexts apply.
+ if (empty($subtype)) {
+ return;
+ }
+
+ $subtype_info = ctools_content_get_subtype($plugin, $subtype);
+ if (empty($subtype_info)) {
+ return;
+ }
+
+ if (!empty($subtype_info['all contexts']) || !empty($plugin['all contexts'])) {
+ return $contexts;
+ }
+
+ // If the content requires a context, fetch it; if no context is returned,
+ // do not display the pane.
+ if (empty($subtype_info['required context'])) {
+ return;
+ }
+
+ // Deal with dynamic required contexts not getting updated in the panes.
+ // For example, Views let you dynamically change context info. While
+ // we cannot be perfect, one thing we can do is if no context at all
+ // was asked for, and then was later added but none is selected, make
+ // a best guess as to what context should be used. THis is right more
+ // than it's wrong.
+ if (is_array($subtype_info['required context'])) {
+ if (empty($conf['context']) || count($subtype_info['required context']) != count($conf['context'])) {
+ foreach ($subtype_info['required context'] as $index => $required) {
+ if (!isset($conf['context'][$index])) {
+ $filtered = ctools_context_filter($contexts, $required);
+ if ($filtered) {
+ $keys = array_keys($filtered);
+ $conf['context'][$index] = array_shift($keys);
+ }
+ }
+ }
+ }
+ }
+ else {
+ if (empty($conf['context'])) {
+ $filtered = ctools_context_filter($contexts, $subtype_info['required context']);
+ if ($filtered) {
+ $keys = array_keys($filtered);
+ $conf['context'] = array_shift($keys);
+ }
+ }
+ }
+
+ if (empty($conf['context'])) {
+ return;
+ }
+
+ $context = ctools_context_select($contexts, $subtype_info['required context'], $conf['context']);
+
+ return $context;
+}
+
+/**
+ * Fetch a piece of content from the addressable content system.
+ *
+ * @param $address
+ * A string or an array representing the address of the content.
+ * @param $type
+ * The type of content to return. The type is dependent on what
+ * the content actually is. The default, 'content' means a simple
+ * string representing the content. However, richer systems may
+ * offer more options. For example, Panels might allow the
+ * fetching of 'display' and 'pane' objects. Page Manager
+ * might allow for the fetching of 'task_handler' objects
+ * (AKA variants).
+ */
+function ctools_get_addressable_content($address, $type = 'content') {
+ if (!is_array($address)) {
+ $address = explode('::', $address);
+ }
+
+ if (!$address) {
+ return;
+ }
+
+ // This removes the module from the address so the
+ // implementor is not responsible for that part.
+ $module = array_shift($address);
+ return module_invoke($module, 'addressable_content', $address, $type);
+}
diff --git a/sites/all/modules/ctools/includes/content.menu.inc b/sites/all/modules/ctools/includes/content.menu.inc
new file mode 100644
index 000000000..f7f934067
--- /dev/null
+++ b/sites/all/modules/ctools/includes/content.menu.inc
@@ -0,0 +1,179 @@
+<?php
+
+/**
+ * @file
+ * Contains menu item registration for the content tool.
+ *
+ * The menu items registered are AJAX callbacks for the things like
+ * autocomplete and other tools needed by the content types.
+ */
+
+function ctools_content_menu(&$items) {
+ $base = array(
+ 'access arguments' => array('access content'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'includes/content.menu.inc',
+ );
+ $items['ctools/autocomplete/%'] = array(
+ 'page callback' => 'ctools_content_autocomplete_entity',
+ 'page arguments' => array(2),
+ ) + $base;
+}
+
+/**
+ * Helper function for autocompletion of entity titles.
+ */
+function ctools_content_autocomplete_entity($entity_type, $string = '') {
+ if ($string != '') {
+ $entity_info = entity_get_info($entity_type);
+
+ if (!module_exists('entity')) {
+ module_load_include('inc', 'ctools', 'includes/entity-access');
+ _ctools_entity_access($entity_info, $entity_type);
+ }
+
+ // We must query all ids, because if every one of the 10 don't have access
+ // the user may never be able to autocomplete a node title.
+ $preg_matches = array();
+ $matches = array();
+ $match = preg_match('/\[id: (\d+)\]/', $string, $preg_matches);
+ if (!$match) {
+ $match = preg_match('/^id: (\d+)/', $string, $preg_matches);
+ }
+ // If an ID match was found, use that ID rather than the whole string.
+ if ($match) {
+ $entity_id = $preg_matches[1];
+ $results = _ctools_getReferencableEntities($entity_type, $entity_info, $entity_id, '=', 1);
+ }
+ else {
+ // We cannot find results if the entity doesn't have a label to search.
+ if (!isset($entity_info['entity keys']['label'])) {
+ drupal_json_output(array("[id: NULL]" => '<span class="autocomplete_title">' . t('Entity Type !entity_type does not support autocomplete search.', array('!entity_type' => $entity_type)) . '</span>'));
+ return;
+ }
+ $results = _ctools_getReferencableEntities($entity_type, $entity_info, $string, 'LIKE', 10);
+ }
+ foreach ($results as $entity_id => $result) {
+ $matches[$result['label'] . " [id: $entity_id]"] = '<span class="autocomplete_title">' . check_plain($result['label']) . '</span>';
+ $matches[$result['label'] . " [id: $entity_id]"] .= isset($result['bundle']) ? ' <span class="autocomplete_bundle">(' . check_plain($result['bundle']) . ')</span>' : '';
+ }
+
+ drupal_json_output($matches);
+ }
+}
+
+/*
+ * Use well known/tested entity reference code to build our search query
+ * From EntityReference_SelectionHandler_Generic class
+ */
+function _ctools_buildQuery($entity_type, $entity_info, $match = NULL, $match_operator = 'CONTAINS') {
+ $base_table = $entity_info['base table'];
+ $label_key = $entity_info['entity keys']['label'];
+ $query = db_select($base_table)
+ ->fields($base_table, array($entity_info['entity keys']['id']));
+
+ if (isset($match)) {
+ if (isset($label_key)) {
+ $query->condition($base_table . '.' . $label_key, '%' . $match . '%', $match_operator);
+ }
+ // This should never happen, but double check just in case.
+ else {
+ return array();
+ }
+ }
+ // Add a generic entity access tag to the query.
+ $query->addTag('ctools');
+
+ // We have to perform two checks. First check is a query alter (with tags)
+ // in an attempt to only return results that have access. However, this is
+ // not full-proof since entities many not implement hook_access query tag.
+ // This is why we have a second check after entity load, before we display
+ // the label of an entity.
+ if ($entity_type == 'comment') {
+ // Adding the 'comment_access' tag is sadly insufficient for comments: core

+ // requires us to also know about the concept of 'published' and

+ // 'unpublished'.

+ if (!user_access('administer comments')) {
+ $query->condition('comment.status', COMMENT_PUBLISHED);
+ }
+
+ // Join to a node if the user does not have node access bypass permissions

+ // to obey node published permissions

+ if (!user_access('bypass node access')) {
+ $node_alias = $query->innerJoin('node', 'n', '%alias.nid = comment.nid');
+ $query->condition($node_alias . '.status', NODE_PUBLISHED);
+ }
+ $query->addTag('node_access');
+ }
+ else {
+ $query->addTag($entity_type . '_access');
+ }
+
+ // Add the sort option.
+ if (isset($label_key)) {
+ $query->orderBy($base_table . '.' . $label_key, 'ASC');
+ }
+
+ return $query;
+}
+
+/**
+ * Private function to get referencable entities. Based on code from the
+ * Entity Reference module.
+ */
+function _ctools_getReferencableEntities($entity_type, $entity_info, $match = NULL, $match_operator = 'LIKE', $limit = 0) {
+ global $user;
+ $account = $user;
+ $options = array();
+ // We're an entity ID, return the id
+ if (is_numeric($match) && $match_operator == '=') {
+ if ($entity = array_shift(entity_load($entity_type, array($match)))) {
+ if (isset($entity_info['access callback']) && function_exists($entity_info['access callback'])) {
+ if ($entity_info['access callback']('view', $entity, $account, $entity_type)) {
+ $label = entity_label($entity_type, $entity);
+ return array(
+ $match => array(
+ 'label' => !empty($label) ? $label : $entity->{$entity_info['entity keys']['id']},
+ 'bundle' => !empty($entity_info['entity keys']['bundle']) ? check_plain($entity->{$entity_info['entity keys']['bundle']}) : NULL,
+ ),
+ );
+ }
+ }
+ }
+ // If you don't have access, or an access callback or a valid entity, just
+ // Return back the Entity ID.
+ return array(
+ $match => array(
+ 'label' => $match,
+ 'bundle' => NULL,
+ ),
+ );
+ }
+
+ // We have matches, build a query to fetch the result.
+ if ($query = _ctools_buildQuery($entity_type, $entity_info, $match, $match_operator)) {
+ if ($limit > 0) {
+ $query->range(0, $limit);
+ }
+
+ $results = $query->execute();
+
+ if (!empty($results)) {
+ foreach ($results as $record) {
+ $entities = entity_load($entity_type, array($record->{$entity_info['entity keys']['id']}));
+ $entity = array_shift($entities);
+ if (isset($entity_info['access callback']) && function_exists($entity_info['access callback'])) {
+ if ($entity_info['access callback']('view', $entity, $account, $entity_type)) {
+ $label = entity_label($entity_type, $entity);
+ $options[$record->{$entity_info['entity keys']['id']}] = array(
+ 'label' => !empty($label) ? $label : $entity->{$entity_info['entity keys']['id']},
+ 'bundle' => !empty($entity_info['entity keys']['bundle']) ? check_plain($entity->{$entity_info['entity keys']['bundle']}) : NULL,
+ );
+ }
+ }
+ }
+ }
+ return $options;
+ }
+ return array();
+}
diff --git a/sites/all/modules/ctools/includes/content.plugin-type.inc b/sites/all/modules/ctools/includes/content.plugin-type.inc
new file mode 100644
index 000000000..a0debc3e5
--- /dev/null
+++ b/sites/all/modules/ctools/includes/content.plugin-type.inc
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @file
+ * Contains plugin type registration information for the content tool.
+ */
+
+function ctools_content_plugin_type(&$items) {
+ $items['content_types'] = array(
+ 'cache' => FALSE,
+ 'process' => array(
+ 'function' => 'ctools_content_process',
+ 'file' => 'content.inc',
+ 'path' => drupal_get_path('module', 'ctools') . '/includes',
+ ),
+ );
+} \ No newline at end of file
diff --git a/sites/all/modules/ctools/includes/content.theme.inc b/sites/all/modules/ctools/includes/content.theme.inc
new file mode 100644
index 000000000..ae4456aa7
--- /dev/null
+++ b/sites/all/modules/ctools/includes/content.theme.inc
@@ -0,0 +1,21 @@
+<?php
+
+/**
+ * @file
+ * Contains theme registry and theme implementations for the content types.
+ */
+
+/**
+ * Implements hook_theme to load all content plugins and pass thru if
+ * necessary.
+ */
+function ctools_content_theme(&$theme) {
+ ctools_include('content');
+
+ $plugins = ctools_get_content_types();
+ foreach ($plugins as $plugin) {
+ if ($function = ctools_plugin_get_function($plugin, 'hook theme')) {
+ $function($theme, $plugin);
+ }
+ }
+}
diff --git a/sites/all/modules/ctools/includes/context-access-admin.inc b/sites/all/modules/ctools/includes/context-access-admin.inc
new file mode 100644
index 000000000..76643cf62
--- /dev/null
+++ b/sites/all/modules/ctools/includes/context-access-admin.inc
@@ -0,0 +1,486 @@
+<?php
+
+/**
+ * @file
+ * Contains administrative screens for the access control plugins.
+ *
+ * Access control can be implemented by creating a list of 0 or more access
+ * plugins, each with settings. This list can be ANDed together or ORed
+ * together. When testing access, each plugin is tested until success
+ * or failure can be determined. We use short circuiting techniques to
+ * ensure we are as efficient as possible.
+ *
+ * Access plugins are part of the context system, and as such can require
+ * contexts to work. That allows the use of access based upon visibility
+ * of an object, or even more esoteric things such as node type, node language
+ * etc. Since a lot of access depends on the logged in user, the logged in
+ * user should always be provided as a context.
+ *
+ * In the UI, the user is presented with a table and a 'add access method' select.
+ * When added, the user will be presented with the config wizard and, when
+ * confirmed, table will be refreshed via AJAX to show the new access method.
+ * Each item in the table will have controls to change the settings or remove
+ * the item. Changing the settings will invoke the modal for update.
+ *
+ * Currently the modal is not degradable, but it could be with only a small
+ * amount of work.
+ *
+ * A simple radio
+ * control is used to let the user pick the and/or logic.
+ *
+ * Access control is stored in an array:
+ * @code
+ * array(
+ * 'plugins' => array(
+ * 0 => array(
+ * 'name' => 'name of access plugin',
+ * 'settings' => array(), // These will be set by the form
+ * ),
+ * // ... as many as needed
+ * ),
+ * 'logic' => 'AND', // or 'OR',
+ * ),
+ * @endcode
+ *
+ * To add this widget to your UI, you need to do a little bit of setup.
+ *
+ * The form will utilize two callbacks, one to get the cached version
+ * of the access settings, and one to store the cached version of the
+ * access settings. These will be used from AJAX forms, so they will
+ * be completely out of the context of this page load and will not have
+ * knowledge of anything sent to this form (the 'module' and 'argument'
+ * will be preserved through the URL only).
+ *
+ * The 'module' is used to determine the location of the callback. It
+ * does not strictly need to be a module, so that if your module defines
+ * multiple systems that use this callback, it can use anything within the
+ * module's namespace it likes.
+ *
+ * When retrieving the cache, the cache may not have already been set up;
+ * In order to efficiently use cache space, we want to cache the stored
+ * settings *only* when they have changed. Therefore, the get access cache
+ * callback should first look for cache, and if it finds nothing, return
+ * the original settings.
+ *
+ * The callbacks:
+ * - $module . _ctools_access_get($argument) -- get the 'access' settings
+ * from cache. Must return array($access, $contexts); This callback can
+ * perform access checking to make sure this URL is not being gamed.
+ * - $module . _ctools_access_set($argument, $access) -- set the 'access'
+ * settings in cache.
+ * - $module . _ctools_access_clear($argument) -- clear the cache.
+ *
+ * The ctools_object_cache is recommended for this purpose, but you can use
+ * any caching mechanism you like. An example:
+ *
+ * @code{
+ * ctools_include('object-cache');
+ * ctools_object_cache_set("$module:argument", $access);
+ * }
+ *
+ * To utilize this form:
+ * @code
+ * ctools_include('context-access-admin');
+ * $form_state = array(
+ * 'access' => $access,
+ * 'module' => 'module name',
+ * 'callback argument' => 'some string',
+ * 'contexts' => $contexts, // an array of contexts. Optional if no contexts.
+ * // 'logged-in-user' will be added if not present as the access system
+ * // requires this context.
+ * ),
+ * $output = drupal_build_form('ctools_access_admin_form', $form_state);
+ * if (!empty($form_state['executed'])) {
+ * // save $form_state['access'] however you like.
+ * }
+ * @endcode
+ *
+ * Additionally, you may add 'no buttons' => TRUE if you wish to embed this
+ * form into your own, and instead call
+ *
+ * @code{
+ * $form = ctools_access_admin_form($form, $form_state);
+ * }
+ *
+ * You'll be responsible for adding a submit button.
+ *
+ * You may use ctools_access($access, $contexts) which will return
+ * TRUE if access is passed or FALSE if access is not passed.
+ */
+
+/**
+ * Administrative form for access control.
+ */
+function ctools_access_admin_form($form, &$form_state) {
+ ctools_include('context');
+ $argument = isset($form_state['callback argument']) ? $form_state['callback argument'] : '';
+ $fragment = $form_state['module'];
+ if ($argument) {
+ $fragment .= '-' . $argument;
+ }
+
+ $contexts = isset($form_state['contexts']) ? $form_state['contexts'] : array();
+
+ $form['access_table'] = array(
+ '#markup' => ctools_access_admin_render_table($form_state['access'], $fragment, $contexts),
+ );
+
+ $form['add-button'] = array(
+ '#theme' => 'ctools_access_admin_add',
+ );
+ // This sets up the URL for the add access modal.
+ $form['add-button']['add-url'] = array(
+ '#attributes' => array('class' => array("ctools-access-add-url")),
+ '#type' => 'hidden',
+ '#value' => url("ctools/context/ajax/access/add/$fragment", array('absolute' => TRUE)),
+ );
+
+ $plugins = ctools_get_relevant_access_plugins($contexts);
+ $options = array();
+ foreach ($plugins as $id => $plugin) {
+ $options[$id] = $plugin['title'];
+ }
+
+ asort($options);
+
+ $form['add-button']['type'] = array(
+ // This ensures that the form item is added to the URL.
+ '#attributes' => array('class' => array("ctools-access-add-url")),
+ '#type' => 'select',
+ '#options' => $options,
+ '#required' => FALSE,
+ );
+
+ $form['add-button']['add'] = array(
+ '#type' => 'submit',
+ '#attributes' => array('class' => array('ctools-use-modal')),
+ '#id' => "ctools-access-add",
+ '#value' => t('Add'),
+ );
+
+ $form['logic'] = array(
+ '#type' => 'radios',
+ '#options' => array(
+ 'and' => t('All criteria must pass.'),
+ 'or' => t('Only one criteria must pass.'),
+ ),
+ '#default_value' => isset($form_state['access']['logic']) ? $form_state['access']['logic'] : 'and',
+ );
+
+ if (empty($form_state['no buttons'])) {
+ $form['buttons']['save'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save'),
+ '#submit' => array('ctools_access_admin_form_submit'),
+ );
+ }
+
+ return $form;
+}
+
+/**
+ * Render the table. This is used both to render it initially and to rerender
+ * it upon ajax response.
+ */
+function ctools_access_admin_render_table($access, $fragment, $contexts) {
+ ctools_include('ajax');
+ ctools_include('modal');
+ $rows = array();
+
+ if (empty($access['plugins'])) {
+ $access['plugins'] = array();
+ }
+
+ foreach ($access['plugins'] as $id => $test) {
+ $row = array();
+ $plugin = ctools_get_access_plugin($test['name']);
+ $title = isset($plugin['title']) ? $plugin['title'] : t('Broken/missing access plugin %plugin', array('%plugin' => $test['name']));
+
+ $row[] = array('data' => $title, 'class' => array('ctools-access-title'));
+
+ $description = ctools_access_summary($plugin, $contexts, $test);
+ $row[] = array('data' => $description, 'class' => array('ctools-access-description'));
+
+ $operations = ctools_modal_image_button(ctools_image_path('icon-configure.png'), "ctools/context/ajax/access/configure/$fragment/$id", t('Configure settings for this item.'));
+ $operations .= ctools_ajax_image_button(ctools_image_path('icon-delete.png'), "ctools/context/ajax/access/delete/$fragment/$id", t('Remove this item.'));
+
+ $row[] = array('data' => $operations, 'class' => array('ctools-access-operations'), 'align' => 'right');
+
+ $rows[] = $row;
+ }
+
+ $header = array(
+ array('data' => t('Title'), 'class' => array('ctools-access-title')),
+ array('data' => t('Description'), 'class' => array('ctools-access-description')),
+ array('data' => '', 'class' => array('ctools-access-operations'), 'align' => 'right'),
+ );
+
+ if (empty($rows)) {
+ $rows[] = array(array('data' => t('No criteria selected, this test will pass.'), 'colspan' => count($header)));
+ }
+
+ ctools_modal_add_js();
+ return theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('id' => 'ctools-access-table')));
+}
+
+/**
+ * Theme the 'add' portion of the access form into a table.
+ */
+function theme_ctools_access_admin_add($vars) {
+ $rows = array(array(drupal_render_children($vars['form'])));
+ $output = '<div class="container-inline">';
+ $output .= theme('table', array('rows' => $rows));
+ $output .= '</div>';
+ return $output;
+}
+
+function ctools_access_admin_form_submit($form, &$form_state) {
+ $form_state['access']['logic'] = $form_state['values']['logic'];
+
+ $function = $form_state['module'] . '_ctools_access_clear';
+ if (function_exists($function)) {
+ $function($form_state['callback argument']);
+ }
+}
+
+// --------------------------------------------------------------------------
+// AJAX menu entry points.
+
+/**
+ * AJAX callback to add a new access test to the list.
+ */
+function ctools_access_ajax_add($fragment = NULL, $name = NULL) {
+ ctools_include('ajax');
+ ctools_include('modal');
+ ctools_include('context');
+
+ if (empty($fragment) || empty($name)) {
+ ctools_ajax_render_error();
+ }
+
+ $plugin = ctools_get_access_plugin($name);
+ if (empty($plugin)) {
+ ctools_ajax_render_error();
+ }
+
+ // Separate the fragment into 'module' and 'argument'
+ if (strpos($fragment, '-') === FALSE) {
+ $module = $fragment;
+ $argument = NULL;
+ }
+ else {
+ list($module, $argument) = explode('-', $fragment, 2);
+ }
+
+ $function = $module . '_ctools_access_get';
+ if (!function_exists($function)) {
+ ctools_ajax_render_error(t('Missing callback hooks.'));
+ }
+
+ list($access, $contexts) = $function($argument);
+
+ // Make sure we have the logged in user context
+ if (!isset($contexts['logged-in-user'])) {
+ $contexts['logged-in-user'] = ctools_access_get_loggedin_context();
+ }
+
+ if (empty($access['plugins'])) {
+ $access['plugins'] = array();
+ }
+
+ $test = ctools_access_new_test($plugin);
+
+ $id = $access['plugins'] ? max(array_keys($access['plugins'])) + 1 : 0;
+ $access['plugins'][$id] = $test;
+
+ $form_state = array(
+ 'plugin' => $plugin,
+ 'id' => $id,
+ 'test' => &$access['plugins'][$id],
+ 'access' => &$access,
+ 'contexts' => $contexts,
+ 'title' => t('Add criteria'),
+ 'ajax' => TRUE,
+ 'modal' => TRUE,
+ 'modal return' => TRUE,
+ );
+
+ $output = ctools_modal_form_wrapper('ctools_access_ajax_edit_item', $form_state);
+ if (!isset($output[0])) {
+ $function = $module . '_ctools_access_set';
+ if (function_exists($function)) {
+ $function($argument, $access);
+ }
+
+ $table = ctools_access_admin_render_table($access, $fragment, $contexts);
+ $output = array();
+ $output[] = ajax_command_replace('table#ctools-access-table', $table);
+ $output[] = ctools_modal_command_dismiss();
+ }
+
+ print ajax_render($output);
+}
+
+/**
+ * AJAX callback to edit an access test in the list.
+ */
+function ctools_access_ajax_edit($fragment = NULL, $id = NULL) {
+ ctools_include('ajax');
+ ctools_include('modal');
+ ctools_include('context');
+
+ if (empty($fragment) || !isset($id)) {
+ ctools_ajax_render_error();
+ }
+
+ // Separate the fragment into 'module' and 'argument'
+ if (strpos($fragment, '-') === FALSE) {
+ $module = $fragment;
+ $argument = NULL;
+ }
+ else {
+ list($module, $argument) = explode('-', $fragment, 2);
+ }
+
+ $function = $module . '_ctools_access_get';
+ if (!function_exists($function)) {
+ ctools_ajax_render_error(t('Missing callback hooks.'));
+ }
+
+ list($access, $contexts) = $function($argument);
+
+ if (empty($access['plugins'][$id])) {
+ ctools_ajax_render_error();
+ }
+
+ // Make sure we have the logged in user context
+ if (!isset($contexts['logged-in-user'])) {
+ $contexts['logged-in-user'] = ctools_access_get_loggedin_context();
+ }
+
+ $plugin = ctools_get_access_plugin($access['plugins'][$id]['name']);
+ $form_state = array(
+ 'plugin' => $plugin,
+ 'id' => $id,
+ 'test' => &$access['plugins'][$id],
+ 'access' => &$access,
+ 'contexts' => $contexts,
+ 'title' => t('Edit criteria'),
+ 'ajax' => TRUE,
+ 'ajax' => TRUE,
+ 'modal' => TRUE,
+ 'modal return' => TRUE,
+ );
+
+ $output = ctools_modal_form_wrapper('ctools_access_ajax_edit_item', $form_state);
+ if (!isset($output[0])) {
+ $function = $module . '_ctools_access_set';
+ if (function_exists($function)) {
+ $function($argument, $access);
+ }
+
+ $table = ctools_access_admin_render_table($access, $fragment, $contexts);
+ $output = array();
+ $output[] = ajax_command_replace('table#ctools-access-table', $table);
+ $output[] = ctools_modal_command_dismiss();
+ }
+
+ print ajax_render($output);
+}
+
+/**
+ * Form to edit the settings of an access test.
+ */
+function ctools_access_ajax_edit_item($form, &$form_state) {
+ $test = &$form_state['test'];
+ $plugin = &$form_state['plugin'];
+ if (isset($plugin['required context'])) {
+ $form['context'] = ctools_context_selector($form_state['contexts'], $plugin['required context'], $test['context']);
+ }
+ $form['settings'] = array('#tree' => TRUE);
+ if ($function = ctools_plugin_get_function($plugin, 'settings form')) {
+ $form = $function($form, $form_state, $test['settings']);
+ }
+
+ $form['not'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Reverse (NOT)'),
+ '#default_value' => !empty($test['not']),
+ );
+
+ $form['save'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save'),
+ );
+
+ return $form;
+}
+
+/**
+ * Validate handler for argument settings.
+ */
+function ctools_access_ajax_edit_item_validate($form, &$form_state) {
+ if ($function = ctools_plugin_get_function($form_state['plugin'], 'settings form validate')) {
+ $function($form, $form_state);
+ }
+}
+
+/**
+ * Submit handler for argument settings.
+ */
+function ctools_access_ajax_edit_item_submit($form, &$form_state) {
+ if ($function = ctools_plugin_get_function($form_state['plugin'], 'settings form submit')) {
+ $function($form, $form_state);
+ }
+
+ $form_state['test']['settings'] = $form_state['values']['settings'];
+ if (isset($form_state['values']['context'])) {
+ $form_state['test']['context'] = $form_state['values']['context'];
+ }
+ $form_state['test']['not'] = !empty($form_state['values']['not']);
+}
+
+/**
+ * AJAX command to remove an access control item.
+ */
+function ctools_access_ajax_delete($fragment = NULL, $id = NULL) {
+ ctools_include('ajax');
+ ctools_include('modal');
+ ctools_include('context');
+
+ if (empty($fragment) || !isset($id)) {
+ ajax_render_error();
+ }
+
+ // Separate the fragment into 'module' and 'argument'
+ if (strpos($fragment, '-') === FALSE) {
+ $module = $fragment;
+ $argument = NULL;
+ }
+ else {
+ list($module, $argument) = explode('-', $fragment, 2);
+ }
+
+ $function = $module . '_ctools_access_get';
+ if (!function_exists($function)) {
+ ajax_render_error(t('Missing callback hooks.'));
+ }
+
+ list($access, $contexts) = $function($argument);
+
+ if (isset($access['plugins'][$id])) {
+ unset($access['plugins'][$id]);
+ }
+
+ // re-cache
+ $function = $module . '_ctools_access_set';
+ if (function_exists($function)) {
+ $function($argument, $access);
+ }
+
+ $table = ctools_access_admin_render_table($access, $fragment, $contexts);
+ $output = array();
+ $output[] = ajax_command_replace('table#ctools-access-table', $table);
+
+ print ajax_render($output);
+}
diff --git a/sites/all/modules/ctools/includes/context-admin.inc b/sites/all/modules/ctools/includes/context-admin.inc
new file mode 100644
index 000000000..821a5b32a
--- /dev/null
+++ b/sites/all/modules/ctools/includes/context-admin.inc
@@ -0,0 +1,849 @@
+<?php
+
+/**
+ * @file includes/common-context.inc
+ * Provide API for adding contexts for modules that embed displays.
+ *
+ * Note that most of this code was directly copied from Panels 2, and as such
+ * a lot of this code is crusty. It could probably stand to be rewritten,
+ * and brought up to date, or at least better commented.
+ */
+
+/**
+ * Provide a list of the ways contexts can be embedded.
+ *
+ * This provides a full list of context types that the tool understands
+ * and can let modules utilize.
+ */
+function ctools_context_info($type = NULL) {
+ static $info = NULL;
+
+ // static doesn't work with functions like t().
+ if (empty($info)) {
+ $info = array(
+ 'argument' => array(
+ 'title' => t('Arguments'),
+ 'singular title' => t('argument'),
+ 'description' => '', // t("Arguments are parsed from the URL and translated into contexts that may be added to the display via the 'content' tab. These arguments are parsed in the order received, and you may use % in your URL to hold the place of an object; the rest of the arguments will come after the URL. For example, if the URL is node/%/panel and your user visits node/1/panel/foo, the first argument will be 1, and the second argument will be foo."),
+ 'add button' => t('Add argument'),
+ 'context function' => 'ctools_get_argument',
+ 'key' => 'arguments', // the key that data will be stored on an object, eg $panel_page
+ 'sortable' => TRUE,
+ 'settings' => 'argument_settings',
+ ),
+ 'relationship' => array(
+ 'title' => t('Relationships'),
+ 'singular title' => t('relationship'),
+ 'description' => '', // t('Relationships are contexts that are created from already existing contexts; the add relationship button will only appear once there is another context available. Relationships can load objects based upon how they are related to each other; for example, the author of a node, or a taxonomy term attached to a node, or the vocabulary of a taxonomy term.'),
+ 'add button' => t('Add relationship'),
+ 'context function' => 'ctools_get_relationship',
+ 'key' => 'relationships',
+ 'sortable' => FALSE,
+ 'settings' => 'relationship_settings',
+ ),
+ 'context' => array(
+ 'title' => t('Contexts'),
+ 'singular title' => t('context'),
+ 'description' => '', // t('Contexts are embedded directly into the panel; you generally must select an object in the panel. For example, you could select node 5, or the term "animals" or the user "administrator"'),
+ 'add button' => t('Add context'),
+ 'context function' => 'ctools_get_context',
+ 'key' => 'contexts',
+ 'sortable' => FALSE,
+ 'settings' => 'context_settings',
+ ),
+ 'requiredcontext' => array(
+ 'title' => t('Required contexts'),
+ 'singular title' => t('required context'),
+ 'description' => '', // t('Required contexts are passed in from some external source, such as a containing panel. If a mini panel has required contexts, it can only appear when that context is available, and therefore will not show up as a standard Drupal block.'),
+ 'add button' => t('Add required context'),
+ 'context function' => 'ctools_get_context',
+ 'key' => 'requiredcontexts',
+ 'sortable' => FALSE,
+ ),
+ );
+ }
+
+ if ($type === NULL) {
+ return $info;
+ }
+
+ return $info[$type];
+}
+
+
+/**
+ * Get the data belonging to a particular context.
+ */
+function ctools_context_get_plugin($type, $name) {
+ $info = ctools_context_info($type);
+ if (function_exists($info['context function'])) {
+ return $info['context function']($name);
+ }
+}
+
+/**
+ * Add the argument table plus gadget plus javascript to the form.
+ */
+function ctools_context_add_argument_form($module, &$form, &$form_state, &$form_location, $object, $cache_key = NULL) {
+ if (empty($cache_key)) {
+ $cache_key = $object->name;
+ }
+
+ $form_location = array(
+ '#prefix' => '<div id="ctools-arguments-table">',
+ '#suffix' => '</div>',
+ '#theme' => 'ctools_context_item_form',
+ '#cache_key' => $cache_key,
+ '#ctools_context_type' => 'argument',
+ '#ctools_context_module' => $module,
+ );
+
+ $args = ctools_get_arguments();
+ $choices = array();
+ foreach ($args as $name => $arg) {
+ if (empty($arg['no ui'])) {
+ $choices[$name] = $arg['title'];
+ }
+ }
+
+ asort($choices);
+
+ if (!empty($choices) || !empty($object->arguments)) {
+ ctools_context_add_item_table('argument', $form_location, $choices, $object->arguments);
+ }
+}
+
+function ctools_context_add_context_form($module, &$form, &$form_state, &$form_location, $object, $cache_key = NULL) {
+ if (empty($cache_key)) {
+ $cache_key = $object->name;
+ }
+
+ $form_location = array(
+ '#prefix' => '<div id="ctools-contexts-table">',
+ '#suffix' => '</div>',
+ '#theme' => 'ctools_context_item_form',
+ '#cache_key' => $cache_key,
+ '#ctools_context_type' => 'context',
+ '#ctools_context_module' => $module,
+ );
+
+ // Store the order the choices are in so javascript can manipulate it.
+ $form_location['markup'] = array(
+ '#markup' => '&nbsp;',
+ );
+
+ $choices = array();
+ foreach (ctools_get_contexts() as $name => $arg) {
+ if (empty($arg['no ui'])) {
+ $choices[$name] = $arg['title'];
+ }
+ }
+
+ asort($choices);
+
+ if (!empty($choices) || !empty($object->contexts)) {
+ ctools_context_add_item_table('context', $form_location, $choices, $object->contexts);
+ }
+
+}
+
+function ctools_context_add_required_context_form($module, &$form, &$form_state, &$form_location, $object, $cache_key = NULL) {
+ if (empty($cache_key)) {
+ $cache_key = $object->name;
+ }
+
+ $form_location = array(
+ '#prefix' => '<div id="ctools-requiredcontexts-table">',
+ '#suffix' => '</div>',
+ '#theme' => 'ctools_context_item_form',
+ '#cache_key' => $cache_key,
+ '#ctools_context_type' => 'requiredcontext',
+ '#ctools_context_module' => $module,
+ );
+
+ // Store the order the choices are in so javascript can manipulate it.
+ $form_location['markup'] = array(
+ '#value' => '&nbsp;',
+ );
+
+ $choices = array();
+ foreach (ctools_get_contexts() as $name => $arg) {
+ if (empty($arg['no required context ui'])) {
+ $choices[$name] = $arg['title'];
+ }
+ }
+
+ asort($choices);
+
+ if (!empty($choices) || !empty($object->contexts)) {
+ ctools_context_add_item_table('requiredcontext', $form_location, $choices, $object->requiredcontexts);
+ }
+}
+
+function ctools_context_add_relationship_form($module, &$form, &$form_state, &$form_location, $object, $cache_key = NULL) {
+ if (empty($cache_key)) {
+ $cache_key = $object->name;
+ }
+
+ $form_location = array(
+ '#prefix' => '<div id="ctools-relationships-table">',
+ '#suffix' => '</div>',
+ '#theme' => 'ctools_context_item_form',
+ '#cache_key' => $cache_key,
+ '#ctools_context_type' => 'relationship',
+ '#ctools_context_module' => $module,
+ );
+
+ // Store the order the choices are in so javascript can manipulate it.
+ $form_location['markup'] = array(
+ '#value' => '&nbsp;',
+ );
+
+ $base_contexts = isset($object->base_contexts) ? $object->base_contexts : array();
+ $available_relationships = ctools_context_get_relevant_relationships(ctools_context_load_contexts($object, TRUE, $base_contexts));
+
+ ctools_context_add_item_table('relationship', $form_location, $available_relationships, $object->relationships);
+}
+
+/**
+ * Include all context administrative include files, css, javascript.
+ */
+function ctools_context_admin_includes() {
+ ctools_include('context');
+ ctools_include('modal');
+ ctools_include('ajax');
+ ctools_include('object-cache');
+ ctools_modal_add_js();
+ ctools_modal_add_plugin_js(ctools_get_contexts());
+ ctools_modal_add_plugin_js(ctools_get_relationships());
+}
+
+/**
+ * Add the context table to the page.
+ */
+function ctools_context_add_item_table($type, &$form, $available_contexts, $items) {
+ $form[$type] = array(
+ '#tree' => TRUE,
+ );
+
+ $module = $form['#ctools_context_module'];
+ $cache_key = $form['#cache_key'];
+
+ if (isset($items) && is_array($items)) {
+ foreach ($items as $position => $context) {
+ ctools_context_add_item_to_form($module, $type, $cache_key, $form[$type][$position], $position, $context);
+ }
+ }
+
+ $type_info = ctools_context_info($type);
+ $form['description'] = array(
+ '#prefix' => '<div class="description">',
+ '#suffix' => '</div>',
+ '#markup' => $type_info['description'],
+ );
+
+ ctools_context_add_item_table_buttons($type, $module, $form, $available_contexts);
+}
+
+function ctools_context_add_item_table_buttons($type, $module, &$form, $available_contexts) {
+ drupal_add_library('system', 'drupal.ajax');
+ $form['buttons'] = array(
+ '#tree' => TRUE,
+ );
+
+ if (!empty($available_contexts)) {
+ $type_info = ctools_context_info($type);
+
+ $module = $form['#ctools_context_module'];
+ $cache_key = $form['#cache_key'];
+
+ // The URL for this ajax button
+ $form['buttons'][$type]['add-url'] = array(
+ '#attributes' => array('class' => array("ctools-$type-add-url")),
+ '#type' => 'hidden',
+ '#value' => url("ctools/context/ajax/add/$module/$type/$cache_key", array('absolute' => TRUE)),
+ );
+
+ asort($available_contexts);
+ // This also will be in the URL.
+ $form['buttons'][$type]['item'] = array(
+ '#attributes' => array('class' => array("ctools-$type-add-url")),
+ '#type' => 'select',
+ '#options' => $available_contexts,
+ '#required' => FALSE,
+ );
+
+ $form['buttons'][$type]['add'] = array(
+ '#type' => 'submit',
+ '#attributes' => array('class' => array('ctools-use-modal')),
+ '#id' => "ctools-$type-add",
+ '#value' => $type_info['add button'],
+ );
+ }
+}
+
+/**
+ * Add a row to the form. Used both in the main form and by
+ * the ajax to add an item.
+ */
+function ctools_context_add_item_to_form($module, $type, $cache_key, &$form, $position, $item) {
+ // This is the single function way to load any plugin by variable type.
+ $info = ctools_context_get_plugin($type, $item['name']);
+ $form['title'] = array(
+ '#markup' => check_plain($item['identifier']),
+ );
+
+ // Relationships not sortable.
+ $type_info = ctools_context_info($type);
+
+ if (!empty($type_info['sortable'])) {
+ $form['position'] = array(
+ '#type' => 'weight',
+ '#default_value' => $position,
+ '#attributes' => array('class' => array('drag-position')),
+ );
+ }
+
+ $form['remove'] = array(
+ '#markup' => ctools_ajax_image_button(ctools_image_path('icon-delete.png'), "ctools/context/ajax/delete/$module/$type/$cache_key/$position", t('Remove this item.')),
+ );
+
+ $form['settings'] = array(
+ '#markup' => ctools_modal_image_button(ctools_image_path('icon-configure.png'), "ctools/context/ajax/configure/$module/$type/$cache_key/$position", t('Configure settings for this item.')),
+ );
+}
+
+
+// ---------------------------------------------------------------------------
+// AJAX forms and stuff.
+
+/**
+ * Ajax entry point to add an context
+ */
+function ctools_context_ajax_item_add($mechanism = NULL, $type = NULL, $cache_key = NULL, $name = NULL, $step = NULL) {
+ ctools_include('ajax');
+ ctools_include('modal');
+ ctools_include('context');
+ ctools_include('cache');
+ ctools_include('plugins-admin');
+
+ if (!$name) {
+ return ctools_ajax_render_error();
+ }
+
+ // Load stored object from cache.
+ if (!($object = ctools_cache_get($mechanism, $cache_key))) {
+ ctools_ajax_render_error(t('Invalid object name.'));
+ }
+
+ // Get info about what we're adding, i.e, relationship, context, argument, etc.
+ $plugin_definition = ctools_context_get_plugin($type, $name);
+ if (empty($plugin_definition)) {
+ ctools_ajax_render_error(t('Invalid context type'));
+ }
+
+ // Set up the $conf array for this plugin
+ if (empty($step) || empty($object->temporary)) {
+ // Create the basis for our new context.
+ $conf = ctools_context_get_defaults($plugin_definition, $object, $type);
+ $object->temporary = &$conf;
+ }
+ else {
+ $conf = &$object->temporary;
+ }
+
+ // Load the contexts that may be used.
+ $base_contexts = isset($object->base_contexts) ? $object->base_contexts : array();
+ $contexts = ctools_context_load_contexts($object, TRUE, $base_contexts);
+
+ $type_info = ctools_context_info($type);
+ $form_state = array(
+ 'ajax' => TRUE,
+ 'modal' => TRUE,
+ 'modal return' => TRUE,
+ 'object' => &$object,
+ 'conf' => &$conf,
+ 'plugin' => $plugin_definition,
+ 'type' => $type,
+ 'contexts' => $contexts,
+ 'title' => t('Add @type "@context"', array('@type' => $type_info['singular title'], '@context' => $plugin_definition['title'])),
+ 'type info' => $type_info,
+ 'op' => 'add',
+ 'step' => $step,
+ );
+
+ $form_info = array(
+ 'path' => "ctools/context/ajax/add/$mechanism/$type/$cache_key/$name/%step",
+ 'show cancel' => TRUE,
+ 'default form' => 'ctools_edit_context_form_defaults',
+ 'auto cache' => TRUE,
+ 'cache mechanism' => $mechanism,
+ 'cache key' => $cache_key,
+ // This is stating what the cache will be referred to in $form_state
+ 'cache location' => 'object',
+ );
+
+ if ($type == 'requiredcontext') {
+ $form_info += array(
+ 'add form name' => 'required context add form',
+ 'edit form name' => 'required context edit form',
+ );
+ }
+
+ $output = ctools_plugin_configure_form($form_info, $form_state);
+
+ if (!empty($form_state['cancel'])) {
+ $output = array(ctools_modal_command_dismiss());
+ }
+ else if (!empty($form_state['complete'])) {
+ // Successful submit -- move temporary data to location.
+
+ // Create a reference to the place our context lives. Since this is fairly
+ // generic, this is the easiest way to get right to the place of the
+ // object without knowing precisely what data we're poking at.
+ $ref = &$object->{$type_info['key']};
+
+ // Figure out the position for our new context.
+ $position = empty($ref) ? 0 : max(array_keys($ref)) + 1;
+
+ $conf['id'] = ctools_context_next_id($ref, $name);
+ $ref[$position] = $conf;
+
+ if (isset($object->temporary)) {
+ unset($object->temporary);
+ }
+
+ ctools_cache_operation($mechanism, $cache_key, 'finalize', $object);
+
+ // Very irritating way to update the form for our contexts.
+ $arg_form_state = form_state_defaults() + array(
+ 'values' => array(),
+ 'process_input' => FALSE,
+ 'complete form' => array(),
+ );
+
+ $rel_form_state = $arg_form_state;
+
+ $arg_form = array(
+ '#post' => array(),
+ '#programmed' => FALSE,
+ '#tree' => FALSE,
+ '#parents' => array(),
+ '#array_parents' => array(),
+ );
+
+ // Build a chunk of the form to merge into the displayed form
+ $arg_form[$type] = array(
+ '#tree' => TRUE,
+ );
+ $arg_form[$type][$position] = array(
+ '#tree' => TRUE,
+ );
+
+ ctools_context_add_item_to_form($mechanism, $type, $cache_key, $arg_form[$type][$position], $position, $ref[$position]);
+ $arg_form = form_builder('ctools_context_form', $arg_form, $arg_form_state);
+
+ // Build the relationships table so we can ajax it in.
+ // This is an additional thing that goes in here.
+ $rel_form = array(
+ '#theme' => 'ctools_context_item_form',
+ '#cache_key' => $cache_key,
+ '#ctools_context_type' => 'relationship',
+ '#ctools_context_module' => $mechanism,
+ '#only_buttons' => TRUE,
+ '#post' => array(),
+ '#programmed' => FALSE,
+ '#tree' => FALSE,
+ '#parents' => array(),
+ '#array_parents' => array(),
+ );
+
+ $rel_form['relationship'] = array(
+ '#tree' => TRUE,
+ );
+
+ // Allow an object to set some 'base' contexts that come from elsewhere.
+ $rel_contexts = isset($object->base_contexts) ? $object->base_contexts : array();
+ $all_contexts = ctools_context_load_contexts($object, TRUE, $rel_contexts);
+ $available_relationships = ctools_context_get_relevant_relationships($all_contexts);
+
+ $output = array();
+ if (!empty($available_relationships)) {
+ ctools_context_add_item_table_buttons('relationship', $mechanism, $rel_form, $available_relationships);
+ $rel_form = form_builder('dummy_form_id', $rel_form, $rel_form_state);
+ $output[] = ajax_command_replace('div#ctools-relationships-table div.buttons', drupal_render($rel_form));
+ }
+
+ $theme_vars = array();
+ $theme_vars['type'] = $type;
+ $theme_vars['form'] = $arg_form[$type][$position];
+ $theme_vars['position'] = $position;
+ $theme_vars['count'] = $position;
+ $text = theme('ctools_context_item_row', $theme_vars);
+ $output[] = ajax_command_append('#' . $type . '-table tbody', $text);
+ $output[] = ajax_command_changed('#' . $type . '-row-' . $position, '.title');
+ $output[] = ctools_modal_command_dismiss();
+ }
+ else {
+ $output = ctools_modal_form_render($form_state, $output);
+ }
+ print ajax_render($output);
+ exit;
+}
+
+/**
+ * Ajax entry point to edit an item
+ */
+function ctools_context_ajax_item_edit($mechanism = NULL, $type = NULL, $cache_key = NULL, $position = NULL, $step = NULL) {
+ ctools_include('ajax');
+ ctools_include('modal');
+ ctools_include('context');
+ ctools_include('cache');
+ ctools_include('plugins-admin');
+
+ if (!isset($position)) {
+ return ctools_ajax_render_error();
+ }
+
+ // Load stored object from cache.
+ if (!($object = ctools_cache_get($mechanism, $cache_key))) {
+ ctools_ajax_render_error(t('Invalid object name.'));
+ }
+
+ $type_info = ctools_context_info($type);
+
+ // Create a reference to the place our context lives. Since this is fairly
+ // generic, this is the easiest way to get right to the place of the
+ // object without knowing precisely what data we're poking at.
+ $ref = &$object->{$type_info['key']};
+
+ if (empty($step) || empty($object->temporary)) {
+ // Create the basis for our new context.
+ $conf = $object->{$type_info['key']}[$position];
+ $object->temporary = &$conf;
+ }
+ else {
+ $conf = &$object->temporary;
+ }
+
+ $name = $ref[$position]['name'];
+ if (empty($name)) {
+ ctools_ajax_render_error();
+ }
+
+ // load the plugin definition
+ $plugin_definition = ctools_context_get_plugin($type, $name);
+ if (empty($plugin_definition)) {
+ ctools_ajax_render_error(t('Invalid context type'));
+ }
+
+ // Load the contexts
+ $base_contexts = isset($object->base_contexts) ? $object->base_contexts : array();
+ $contexts = ctools_context_load_contexts($object, TRUE, $base_contexts);
+
+ $form_state = array(
+ 'ajax' => TRUE,
+ 'modal' => TRUE,
+ 'modal return' => TRUE,
+ 'object' => &$object,
+ 'conf' => &$conf,
+ 'position' => $position,
+ 'plugin' => $plugin_definition,
+ 'type' => $type,
+ 'contexts' => $contexts,
+ 'title' => t('Edit @type "@context"', array('@type' => $type_info['singular title'], '@context' => $plugin_definition['title'])),
+ 'type info' => $type_info,
+ 'op' => 'add',
+ 'step' => $step,
+ );
+
+ $form_info = array(
+ 'path' => "ctools/context/ajax/configure/$mechanism/$type/$cache_key/$position/%step",
+ 'show cancel' => TRUE,
+ 'default form' => 'ctools_edit_context_form_defaults',
+ 'auto cache' => TRUE,
+ 'cache mechanism' => $mechanism,
+ 'cache key' => $cache_key,
+ // This is stating what the cache will be referred to in $form_state
+ 'cache location' => 'object',
+ );
+
+ if ($type == 'requiredcontext') {
+ $form_info += array(
+ 'add form name' => 'required context add form',
+ 'edit form name' => 'required context edit form',
+ );
+ }
+
+ $output = ctools_plugin_configure_form($form_info, $form_state);
+
+ if (!empty($form_state['cancel'])) {
+ $output = array(ctools_modal_command_dismiss());
+ }
+ else if (!empty($form_state['complete'])) {
+ // successful submit
+ $ref[$position] = $conf;
+ if (isset($object->temporary)) {
+ unset($object->temporary);
+ }
+
+ ctools_cache_operation($mechanism, $cache_key, 'finalize', $object);
+
+ $output = array();
+ $output[] = ctools_modal_command_dismiss();
+
+ $arg_form_state = form_state_defaults() + array(
+ 'values' => array(),
+ 'process_input' => FALSE,
+ 'complete form' => array(),
+ );
+
+ $arg_form = array(
+ '#post' => array(),
+ '#parents' => array(),
+ '#array_parents' => array(),
+ '#programmed' => FALSE,
+ '#tree' => FALSE,
+ );
+
+ // Build a chunk of the form to merge into the displayed form
+ $arg_form[$type] = array(
+ '#tree' => TRUE,
+ );
+ $arg_form[$type][$position] = array(
+ '#tree' => TRUE,
+ );
+
+ ctools_context_add_item_to_form($mechanism, $type, $cache_key, $arg_form[$type][$position], $position, $ref[$position]);
+ $arg_form = form_builder('ctools_context_form', $arg_form, $arg_form_state);
+
+ $theme_vars = array();
+ $theme_vars['type'] = $type;
+ $theme_vars['form'] = $arg_form[$type][$position];
+ $theme_vars['position'] = $position;
+ $theme_vars['count'] = $position;
+ $output[] = ajax_command_replace('#' . $type . '-row-' . $position, theme('ctools_context_item_row', $theme_vars));
+ $output[] = ajax_command_changed('#' . $type . '-row-' . $position, '.title');
+ }
+ else {
+ $output = ctools_modal_form_render($form_state, $output);
+ }
+ print ajax_render($output);
+ exit;
+}
+
+/**
+ * Get the defaults for a new instance of a context plugin.
+ *
+ * @param $plugin_definition
+ * The metadata definition of the plugin from ctools_get_plugins().
+ * @param $object
+ * The object the context plugin will be added to.
+ * @param $type
+ * The type of context plugin. i.e, context, requiredcontext, relationship
+ */
+function ctools_context_get_defaults($plugin_definition, $object, $type) {
+ // Fetch the potential id of the plugin so we can append
+ // title and keyword information for new ones.
+ $type_info = ctools_context_info($type);
+ $id = ctools_context_next_id($object->{$type_info['key']}, $plugin_definition['name']);
+
+ $conf = array(
+ 'identifier' => $plugin_definition['title'] . ($id > 1 ? ' ' . $id : ''),
+ 'keyword' => ctools_get_keyword($object, $plugin_definition['keyword']),
+ 'name' => $plugin_definition['name'],
+ );
+
+ if (isset($plugin_definition['defaults'])) {
+ $defaults = $plugin_definition['defaults'];
+ }
+ else if (isset($subtype['defaults'])) {
+ $defaults = $subtype['defaults'];
+ }
+
+ if (isset($defaults)) {
+ if (is_string($defaults) && function_exists($defaults)) {
+ if ($settings = $defaults($plugin_definition)) {
+ $conf += $settings;
+ }
+ }
+ else if (is_array($defaults)) {
+ $conf += $defaults;
+ }
+ }
+
+ return $conf;
+}
+
+/**
+ * Form wrapper for the edit context form.
+ *
+ * @todo: We should uncombine these.
+ */
+function ctools_edit_context_form_defaults($form, &$form_state) {
+ // Basic values required to orient ourselves
+ $object = $form_state['object'];
+ $plugin_definition = $form_state['plugin'];
+ $type_info = $form_state['type info'];
+ $contexts = $form_state['contexts'];
+ $conf = $form_state['conf'];
+
+ if ($type_info['key'] == 'arguments' && !isset($conf['default'])) {
+ $conf['default'] = 'ignore';
+ $conf['title'] = '';
+ }
+
+ $form['description'] = array(
+ '#prefix' => '<div class="description">',
+ '#suffix' => '</div>',
+ '#markup' => check_plain($plugin_definition['description']),
+ );
+
+ if ($type_info['key'] == 'relationships') {
+ $form['context'] = ctools_context_selector($contexts, $plugin_definition['required context'], isset($conf['context']) ? $conf['context'] : '');
+ }
+ if ($type_info['key'] == 'arguments') {
+ $form['default'] = array(
+ '#type' => 'select',
+ '#title' => t('Default'),
+ '#options' => array(
+ 'ignore' => t('Ignore it; content that requires this context will not be available.'),
+ '404' => t('Display page not found or display nothing at all.'),
+ ),
+ '#default_value' => $conf['default'],
+ '#description' => t('If the argument is missing or is not valid, select how this should behave.'),
+ );
+
+ $form['title'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Title'),
+ '#default_value' => $conf['title'],
+ '#description' => t('Enter a title to use when this argument is present. You may use %KEYWORD substitution, where the keyword is specified below.'),
+ );
+ }
+
+ $form['identifier'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Identifier'),
+ '#description' => t('Enter a name to identify this !type on administrative screens.', array('!type' => t('context'))),
+ '#default_value' => $conf['identifier'],
+ );
+
+ $form['keyword'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Keyword'),
+ '#description' => t('Enter a keyword to use for substitution in titles.'),
+ '#default_value' => $conf['keyword'],
+ );
+
+ if ($type_info['key'] == 'requiredcontexts') {
+ $form['optional'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Context is optional'),
+ '#default_value' => !empty($form_state['conf']['optional']),
+ '#description' => t('This context need not be present for the component to function.'),
+ );
+ }
+
+ $form['#submit'][] = 'ctools_edit_context_form_defaults_submit';
+
+ return $form;
+}
+
+/**
+ * Submit handler to store context identifier and keyword info.
+ */
+function ctools_edit_context_form_defaults_submit(&$form, &$form_state) {
+ if ($form_state['type info']['key'] == 'relationships') {
+ $form_state['conf']['context'] = $form_state['values']['context'];
+ }
+ if ($form_state['type info']['key'] == 'arguments') {
+ $form_state['conf']['default'] = $form_state['values']['default'];
+ $form_state['conf']['title'] = $form_state['values']['title'];
+ }
+ if ($form_state['type info']['key'] == 'requiredcontexts') {
+ $form_state['conf']['optional'] = $form_state['values']['optional'];
+ }
+
+ $form_state['conf']['identifier'] = $form_state['values']['identifier'];
+ $form_state['conf']['keyword'] = $form_state['values']['keyword'];
+}
+
+/**
+ * Ajax entry point to edit an item
+ */
+function ctools_context_ajax_item_delete($mechanism = NULL, $type = NULL, $cache_key = NULL, $position = NULL) {
+ ctools_include('ajax');
+ ctools_include('context');
+ ctools_include('cache');
+
+ if (!isset($position)) {
+ return ctools_ajax_render_error();
+ }
+
+ // Load stored object from cache.
+ if (!($object = ctools_cache_get($mechanism, $cache_key))) {
+ ctools_ajax_render_error(t('Invalid object name.'));
+ }
+
+ $type_info = ctools_context_info($type);
+
+ // Create a reference to the place our context lives. Since this is fairly
+ // generic, this is the easiest way to get right to the place of the
+ // object without knowing precisely what data we're poking at.
+ $ref = &$object->{$type_info['key']};
+
+ if (!array_key_exists($position, $ref)) {
+ ctools_ajax_render_error(t('Unable to delete missing item!'));
+ }
+
+ unset($ref[$position]);
+ ctools_cache_operation($mechanism, $cache_key, 'finalize', $object);
+
+ $output = array();
+ $output[] = ajax_command_replace('#' . $type . '-row-' . $position, '');
+ $output[] = ajax_command_restripe("#$type-table");
+ print ajax_render($output);
+ exit;
+}
+
+// --- End of contexts
+
+function ctools_save_context($type, &$ref, $form_values) {
+ $type_info = ctools_context_info($type);
+
+ // Organize arguments
+ $new = array();
+ $order = array();
+
+ foreach ($ref as $id => $context) {
+ $position = $form_values[$type][$id]['position'];
+ $order[$position] = $id;
+ }
+
+ ksort($order);
+ foreach ($order as $id) {
+ $new[] = $ref[$id];
+ }
+ $ref = $new;
+}
+
+function ctools_get_keyword($page, $word) {
+ // Create a complete set of keywords
+ $keywords = array();
+ foreach (array('arguments', 'relationships', 'contexts', 'requiredcontexts') as $type) {
+ if (!empty($page->$type) && is_array($page->$type)) {
+ foreach ($page->$type as $info) {
+ $keywords[$info['keyword']] = TRUE;
+ }
+ }
+ }
+
+ $keyword = $word;
+ $count = 1;
+ while (!empty($keywords[$keyword])) {
+ $keyword = $word . '_' . ++$count;
+ }
+ return $keyword;
+}
+
diff --git a/sites/all/modules/ctools/includes/context-task-handler.inc b/sites/all/modules/ctools/includes/context-task-handler.inc
new file mode 100644
index 000000000..21ceea5dc
--- /dev/null
+++ b/sites/all/modules/ctools/includes/context-task-handler.inc
@@ -0,0 +1,540 @@
+<?php
+
+/**
+ * @file
+ * Support for creating 'context' type task handlers.
+ *
+ * Context task handlers expect the task to provide 0 or more contexts. The
+ * task handler should use those contexts as selection rules, as well as
+ * rendering with them.
+ *
+ * The functions and forms in this file should be common to every context type
+ * task handler made.
+ *
+ * Forms:
+ * - ...
+ */
+
+/**
+ * Render a context type task handler given a list of handlers
+ * attached to a type.
+ *
+ * @param $task
+ * The $task object in use.
+ * @param $subtask
+ * The id of the subtask in use.
+ * @param $contexts
+ * The context objects in use.
+ * @param $args
+ * The raw arguments behind the contexts.
+ * @param $page
+ * If TRUE then this renderer owns the page and can use theme('page')
+ * for no blocks; if false, output is returned regardless of any no
+ * blocks settings.
+ * @return
+ * Either the output or NULL if there was output, FALSE if no handler
+ * accepted the task. If $page is FALSE then the $info block is returned instead.
+ */
+function ctools_context_handler_render($task, $subtask, $contexts, $args) {
+ // Load the landlers, choosing only enabled handlers.
+ $handlers = page_manager_load_sorted_handlers($task, $subtask ? $subtask['name'] : '', TRUE);
+
+ $id = ctools_context_handler_get_render_handler($task, $subtask, $handlers, $contexts, $args);
+ if ($id) {
+ return ctools_context_handler_render_handler($task, $subtask, $handlers[$id], $contexts, $args);
+ }
+
+ return FALSE;
+}
+
+/**
+ * Figure out which of the listed handlers should be used to render.
+ */
+function ctools_context_handler_get_render_handler($task, $subtask, $handlers, $contexts, $args) {
+ // Try each handler.
+ foreach ($handlers as $id => $handler) {
+ $plugin = page_manager_get_task_handler($handler->handler);
+ // First, see if the handler has a tester.
+ $function = ctools_plugin_get_function($plugin, 'test');
+ if ($function) {
+ $test = $function($handler, $contexts, $args);
+ if ($test) {
+ return $id;
+ }
+ }
+ else {
+ // If not, if it's a 'context' type handler, use the default tester.
+ if ($plugin['handler type'] == 'context') {
+ $test = ctools_context_handler_default_test($handler, $contexts, $args);
+ if ($test) {
+ return $id;
+ }
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+/**
+ * Default test function to see if a task handler should be rendered.
+ *
+ * This tests against the standard selection criteria that most task
+ * handlers should be implementing.
+ */
+function ctools_context_handler_default_test($handler, $base_contexts, $args) {
+ ctools_include('context');
+ // Add my contexts
+ $contexts = ctools_context_handler_get_handler_contexts($base_contexts, $handler);
+
+ // Test.
+ return ctools_context_handler_select($handler, $contexts);
+}
+
+/**
+ * Render a task handler.
+ */
+function ctools_context_handler_render_handler($task, $subtask, $handler, $contexts, $args, $page = TRUE) {
+ $function = page_manager_get_renderer($handler);
+ if (!$function) {
+ return NULL;
+ }
+
+ if ($page) {
+ if ($subtask) {
+ $task_name = page_manager_make_task_name($task['name'], $subtask['name']);
+ }
+ else {
+ $task_name = $task['name'];
+ }
+
+ page_manager_get_current_page(array(
+ 'name' => $task_name,
+ 'task' => $task,
+ 'subtask' => $subtask,
+ 'contexts' => $contexts,
+ 'arguments' => $args,
+ 'handler' => $handler,
+ ));
+ }
+
+ $info = $function($handler, $contexts, $args);
+ if (!$info) {
+ return NULL;
+ }
+
+ $context = array(
+ 'args' => $args,
+ 'contexts' => $contexts,
+ 'task' => $task,
+ 'subtask' => $subtask,
+ 'handler' => $handler
+ );
+ drupal_alter('ctools_render', $info, $page, $context);
+
+ // If we don't own the page, let the caller deal with rendering.
+ if (!$page) {
+ return $info;
+ }
+
+ if (!empty($info['response code']) && $info['response code'] != 200) {
+ switch ($info['response code']) {
+ case 403:
+ return MENU_ACCESS_DENIED;
+ case 404:
+ return MENU_NOT_FOUND;
+ case 410:
+ drupal_add_http_header('Status', '410 Gone');
+ drupal_exit();
+ break;
+ case 301:
+ case 302:
+ case 303:
+ case 304:
+ case 305:
+ case 307:
+ $info += array(
+ 'query' => array(),
+ 'fragment' => '',
+ );
+ $options = array(
+ 'query' => $info['query'],
+ 'fragment' => $info['fragment'],
+ );
+ drupal_goto($info['destination'], $options, $info['response code']);
+ // @todo -- should other response codes be supported here?
+ }
+ }
+
+ $plugin = page_manager_get_task_handler($handler->handler);
+
+ if (module_exists('contextual') && user_access('access contextual links') && isset($handler->task)) {
+ // Provide a contextual link to edit this, if we can:
+ $callback = isset($plugin['contextual link']) ? $plugin['contextual link'] : 'ctools_task_handler_default_contextual_link';
+ if ($callback && function_exists($callback)) {
+ $links = $callback($handler, $plugin, $contexts, $args);
+ }
+
+ if (!empty($links) && is_array($links)) {
+ $build = array(
+ '#theme_wrappers' => array('container'),
+ '#attributes' => array('class' => array('contextual-links-region')),
+ );
+
+ if (!is_array($info['content'])) {
+ $build['content']['#markup'] = $info['content'];
+ }
+ else {
+ $build['content'] = $info['content'];
+ }
+
+ $build['contextual_links'] = array(
+ '#prefix' => '<div class="contextual-links-wrapper">',
+ '#suffix' => '</div>',
+ '#theme' => 'links__contextual',
+ '#links' => $links,
+ '#attributes' => array('class' => array('contextual-links')),
+ '#attached' => array(
+ 'library' => array(array('contextual', 'contextual-links')),
+ ),
+ );
+ $info['content'] = $build;
+ }
+ }
+
+ foreach (ctools_context_handler_get_task_arguments($task, $subtask) as $id => $argument) {
+ $plugin = ctools_get_argument($argument['name']);
+ $cid = ctools_context_id($argument, 'argument');
+ if (!empty($contexts[$cid]) && ($function = ctools_plugin_get_function($plugin, 'breadcrumb'))) {
+ $function($argument['settings'], $contexts[$cid]);
+ }
+ }
+
+ if (isset($info['title'])) {
+ drupal_set_title($info['title'], PASS_THROUGH);
+ }
+
+ // Only directly output if $page was set to true.
+ if (!empty($info['no_blocks'])) {
+ ctools_set_no_blocks(FALSE);
+ }
+ return $info['content'];
+}
+
+/**
+ * Default function to provide contextual link for a task as defined by the handler.
+ *
+ * This provides a simple link to th main content operation and is suitable
+ * for most normal handlers. Setting 'contextual link' to a function overrides
+ * this and setting it to FALSE will prevent a contextual link from appearing.
+ */
+function ctools_task_handler_default_contextual_link($handler, $plugin, $contexts, $args) {
+ if (!user_access('administer page manager')) {
+ return;
+ }
+
+ $task = page_manager_get_task($handler->task);
+
+ $title = !empty($task['tab title']) ? $task['tab title'] : t('Edit @type', array('@type' => $plugin['title']));
+ $trail = array();
+ if (!empty($plugin['tab operation'])) {
+ if (is_array($plugin['tab operation'])) {
+ $trail = $plugin['tab operation'];
+ }
+ else if (function_exists($plugin['tab operation'])) {
+ $trail = $plugin['tab operation']($handler, $contexts, $args);
+ }
+ }
+ $path = page_manager_edit_url(page_manager_make_task_name($handler->task, $handler->subtask), $trail);
+
+ $links = array(array(
+ 'href' => $path,
+ 'title' => $title,
+ 'query' => drupal_get_destination(),
+ ));
+
+ return $links;
+}
+
+/**
+ * Called to execute actions that should happen before a handler is rendered.
+ */
+function ctools_context_handler_pre_render($handler, $contexts, $args) { }
+
+/**
+ * Compare arguments to contexts for selection purposes.
+ *
+ * @param $handler
+ * The handler in question.
+ * @param $contexts
+ * The context objects provided by the task.
+ *
+ * @return
+ * TRUE if these contexts match the selection rules. NULL or FALSE
+ * otherwise.
+ */
+function ctools_context_handler_select($handler, $contexts) {
+ if (empty($handler->conf['access'])) {
+ return TRUE;
+ }
+
+ ctools_include('context');
+ return ctools_access($handler->conf['access'], $contexts);
+}
+
+/**
+ * Get the array of summary strings for the arguments.
+ *
+ * These summary strings are used to communicate to the user what
+ * arguments the task handlers are selecting.
+ *
+ * @param $task
+ * The loaded task plugin.
+ * @param $subtask
+ * The subtask id.
+ * @param $handler
+ * The handler to be checked.
+ */
+function ctools_context_handler_summary($task, $subtask, $handler) {
+ if (empty($handler->conf['access']['plugins'])) {
+ return array();
+ }
+
+ ctools_include('context');
+ $strings = array();
+ $contexts = ctools_context_handler_get_all_contexts($task, $subtask, $handler);
+
+ foreach ($handler->conf['access']['plugins'] as $test) {
+ $plugin = ctools_get_access_plugin($test['name']);
+ if ($string = ctools_access_summary($plugin, $contexts, $test)) {
+ $strings[] = $string;
+ }
+ }
+
+ return $strings;
+}
+
+// --------------------------------------------------------------------------
+// Tasks and Task handlers can both have their own sources of contexts.
+// Sometimes we need all of these contexts at once (when editing
+// the task handler, for example) but sometimes we need them separately
+// (when a task has contexts loaded and is trying out the task handlers,
+// for example). Therefore there are two paths we can take to getting contexts.
+
+/**
+ * Load the contexts for a task, using arguments.
+ *
+ * This creates the base array of contexts, loaded from arguments, suitable
+ * for use in rendering.
+ */
+function ctools_context_handler_get_task_contexts($task, $subtask, $args) {
+ $contexts = ctools_context_handler_get_base_contexts($task, $subtask);
+ $arguments = ctools_context_handler_get_task_arguments($task, $subtask);
+ ctools_context_get_context_from_arguments($arguments, $contexts, $args);
+
+ return $contexts;
+}
+
+/**
+ * Load the contexts for a task handler.
+ *
+ * This expands a base set of contexts passed in from a task with the
+ * contexts defined on the task handler. The contexts from the task
+ * must already have been loaded.
+ */
+function ctools_context_handler_get_handler_contexts($contexts, $handler) {
+ $object = ctools_context_handler_get_handler_object($handler);
+ return ctools_context_load_contexts($object, FALSE, $contexts);
+}
+
+/**
+ * Load the contexts for a task and task handler together.
+ *
+ * This pulls the arguments from a task and everything else from a task
+ * handler and loads them as a group. Since there is no data, this loads
+ * the contexts as placeholders.
+ */
+function ctools_context_handler_get_all_contexts($task, $subtask, $handler) {
+ $contexts = array();
+
+ $object = ctools_context_handler_get_task_object($task, $subtask, $handler);
+ $contexts = ctools_context_load_contexts($object, TRUE, $contexts);
+ ctools_context_handler_set_access_restrictions($task, $subtask, $handler, $contexts);
+ return $contexts;
+}
+
+/**
+ * Create an object suitable for use with the context system that kind of
+ * expects things in a certain, kind of clunky format.
+ */
+function ctools_context_handler_get_handler_object($handler) {
+ $object = new stdClass;
+ $object->name = $handler->name;
+ $object->contexts = isset($handler->conf['contexts']) ? $handler->conf['contexts'] : array();
+ $object->relationships = isset($handler->conf['relationships']) ? $handler->conf['relationships'] : array();
+
+ return $object;
+}
+
+/**
+ * Create an object suitable for use with the context system that kind of
+ * expects things in a certain, kind of clunky format. This one adds in
+ * arguments from the task.
+ */
+function ctools_context_handler_get_task_object($task, $subtask, $handler) {
+ $object = new stdClass;
+ $object->name = !empty($handler->name) ? $handler->name : 'temp';
+ $object->base_contexts = ctools_context_handler_get_base_contexts($task, $subtask, TRUE);
+ $object->arguments = ctools_context_handler_get_task_arguments($task, $subtask);
+ $object->contexts = isset($handler->conf['contexts']) ? $handler->conf['contexts'] : array();
+ $object->relationships = isset($handler->conf['relationships']) ? $handler->conf['relationships'] : array();
+
+ return $object;
+}
+
+/**
+ * Get base contexts from a task, if it has any.
+ *
+ * Tasks can get their contexts either from base contexts or arguments; base
+ * contexts extract their information from the environment.
+ */
+function ctools_context_handler_get_base_contexts($task, $subtask, $placeholders = FALSE) {
+ if ($function = ctools_plugin_get_function($task, 'get base contexts')) {
+ return $function($task, $subtask, $placeholders);
+ }
+
+ return array();
+}
+
+/**
+ * Get the arguments from a task that are used to load contexts.
+ */
+function ctools_context_handler_get_task_arguments($task, $subtask) {
+ if ($function = ctools_plugin_get_function($task, 'get arguments')) {
+ return $function($task, $subtask);
+ }
+
+ return array();
+}
+
+/**
+ * Set any access restrictions on the contexts for a handler.
+ *
+ * Both the task and the handler could add restrictions to the contexts
+ * based upon the access control. These restrictions might be useful
+ * to limit what kind of content appears in the add content dialog;
+ * for example, if we have an access item that limits a node context
+ * to only 'story' and 'page' types, there is no need for content that
+ * only applies to the 'poll' type to appear.
+ */
+function ctools_context_handler_set_access_restrictions($task, $subtask, $handler, &$contexts) {
+ // First, for the task:
+ if ($function = ctools_plugin_get_function($task, 'access restrictions')) {
+ $function($task, $subtask, $contexts);
+ }
+
+ // Then for the handler:
+ if (isset($handler->conf['access'])) {
+ ctools_access_add_restrictions($handler->conf['access'], $contexts);
+ }
+}
+
+/**
+ * Form to choose context based selection rules for a task handler.
+ *
+ * The configuration will be assumed to go simply in $handler->conf and
+ * will be keyed by the argument ID.
+ */
+function ctools_context_handler_edit_criteria($form, &$form_state) {
+ if (!isset($form_state['handler']->conf['access'])) {
+ $form_state['handler']->conf['access'] = array();
+ }
+
+ ctools_include('context');
+ ctools_include('modal');
+ ctools_include('ajax');
+ ctools_modal_add_plugin_js(ctools_get_access_plugins());
+ ctools_include('context-access-admin');
+ $form_state['module'] = (isset($form_state['module'])) ? $form_state['module'] : 'page_manager_task_handler';
+ // Encode a bunch of info into the argument so we can get our cache later
+ $form_state['callback argument'] = $form_state['task_name'] . '*' . $form_state['handler']->name;
+ $form_state['access'] = $form_state['handler']->conf['access'];
+ $form_state['no buttons'] = TRUE;
+ $form_state['contexts'] = ctools_context_handler_get_all_contexts($form_state['task'], $form_state['subtask'], $form_state['handler']);
+
+ $form['markup'] = array(
+ '#markup' => '<div class="description">' .
+ t('If there is more than one variant on a page, when the page is visited each variant is given an opportunity to be displayed. Starting from the first variant and working to the last, each one tests to see if its selection rules will pass. The first variant that meets its criteria (as specified below) will be used.') .
+ '</div>',
+ );
+ $form = ctools_access_admin_form($form, $form_state);
+ return $form;
+}
+
+/**
+ * Submit handler for rules selection
+ */
+function ctools_context_handler_edit_criteria_submit(&$form, &$form_state) {
+ $form_state['handler']->conf['access']['logic'] = $form_state['values']['logic'];
+}
+
+/**
+ * Edit contexts that go with this panel.
+ */
+function ctools_context_handler_edit_context($form, &$form_state) {
+ ctools_include('context-admin');
+ ctools_context_admin_includes();
+
+ $handler = $form_state['handler'];
+ $page = $form_state['page'];
+ $cache_name = $handler->name ? $handler->name : 'temp';
+ if (isset($page->context_cache[$cache_name])) {
+ $cache = $page->context_cache[$cache_name];
+ }
+ else {
+ $cache = ctools_context_handler_get_task_object($form_state['task'], $form_state['subtask'], $form_state['handler']);
+ $form_state['page']->context_cache[$cache_name] = $cache;
+ }
+
+ $form['right'] = array(
+ '#prefix' => '<div class="clearfix"><div class="right-container">',
+ '#suffix' => '</div>',
+ );
+
+ $form['left'] = array(
+ '#prefix' => '<div class="left-container">',
+ '#suffix' => '</div></div>',
+ );
+
+ $module = 'page_manager_context::' . $page->task_name;
+ ctools_context_add_context_form($module, $form, $form_state, $form['right']['contexts_table'], $cache);
+ ctools_context_add_relationship_form($module, $form, $form_state, $form['right']['relationships_table'], $cache);
+
+ $theme_vars = array();
+ $theme_vars['object'] = $cache;
+ $theme_vars['header'] = t('Summary of contexts');
+ $form['left']['summary'] = array(
+ '#prefix' => '<div class="page-manager-contexts">',
+ '#suffix' => '</div>',
+ '#markup' => theme('ctools_context_list', $theme_vars),
+ );
+
+ $form_state['context_object'] = &$cache;
+ return $form;
+}
+
+/**
+ * Process submission of the context edit form.
+ */
+function ctools_context_handler_edit_context_submit(&$form, &$form_state) {
+ $handler = &$form_state['handler'];
+
+ $cache_name = $handler->name ? $handler->name : 'temp';
+
+ $handler->conf['contexts'] = $form_state['context_object']->contexts;
+ $handler->conf['relationships'] = $form_state['context_object']->relationships;
+ if (isset($form_state['page']->context_cache[$cache_name])) {
+ unset($form_state['page']->context_cache[$cache_name]);
+ }
+}
+
diff --git a/sites/all/modules/ctools/includes/context.inc b/sites/all/modules/ctools/includes/context.inc
new file mode 100644
index 000000000..1f9c1e457
--- /dev/null
+++ b/sites/all/modules/ctools/includes/context.inc
@@ -0,0 +1,1602 @@
+<?php
+
+/**
+ * @file
+ *
+ * Contains code related to the ctools system of 'context'.
+ *
+ * Context, originally from Panels, is a method of packaging objects into
+ * a more generic bundle and providing a plugin system so that a UI can
+ * take advantage of them. The idea is that the context objects
+ * represent 'the context' that a given operation (usually a page view)
+ * is operating in or on.
+ *
+ * For example, when viewing a page, the 'context' is a node object. When
+ * viewing a user, the 'context' is a user object. Contexts can also
+ * have related contexts. For example, when viewing a 'node' you may need
+ * to know something about the node author. Therefore, the node author
+ * is a related context.
+ */
+
+/**
+ * The context object is largely a wrapper around some other object, with
+ * an interface to finding out what is contained and getting to both
+ * the object and information about the object.
+ *
+ * Each context object has its own information, but some things are very
+ * common, such as titles, data, keywords, etc. In particulare, the 'type'
+ * of the context is important.
+ */
+class ctools_context {
+ var $type = NULL;
+ var $data = NULL;
+ // The title of this object.
+ var $title = '';
+ // The title of the page if this object exists
+ var $page_title = '';
+ // The identifier (in the UI) of this object
+ var $identifier = '';
+ var $argument = NULL;
+ var $keyword = '';
+ var $original_argument = NULL;
+ var $restrictions = array();
+ var $empty = FALSE;
+
+ function ctools_context($type = 'none', $data = NULL) {
+ $this->type = $type;
+ $this->data = $data;
+ $this->title = t('Unknown context');
+ }
+
+ function is_type($type) {
+ if ($type == 'any' || $this->type == 'any') {
+ return TRUE;
+ }
+
+ $a = is_array($type) ? $type : array($type);
+ $b = is_array($this->type) ? $this->type : array($this->type);
+ return (bool) array_intersect($a, $b);
+ }
+
+ function get_argument() {
+ return $this->argument;
+ }
+
+ function get_original_argument() {
+ if (!is_null($this->original_argument)) {
+ return $this->original_argument;
+ }
+ return $this->argument;
+ }
+
+ function get_keyword() {
+ return $this->keyword;
+ }
+
+ function get_identifier() {
+ return $this->identifier;
+ }
+
+ function get_title() {
+ return $this->title;
+ }
+
+ function get_page_title() {
+ return $this->page_title;
+ }
+}
+
+/**
+ * Used to create a method of comparing if a list of contexts
+ * match a required context type.
+ */
+class ctools_context_required {
+ var $keywords = '';
+
+ /**
+ * If set, the title will be used in the selector to identify
+ * the context. This is very useful when multiple contexts
+ * are required to inform the user will be used for what.
+ */
+ var $title = NULL;
+
+ /**
+ * Test to see if this context is required.
+ */
+ var $required = TRUE;
+
+ /**
+ * If TRUE, skip the check in ctools_context_required::select()
+ * for contexts whose names may have changed.
+ */
+ var $skip_name_check = FALSE;
+
+ /**
+ *
+ * @param $title
+ * The first parameter should be the 'title' of the context for use
+ * in UYI selectors when multiple contexts qualify.
+ * @param ...
+ * One or more keywords to use for matching which contexts are allowed.
+ */
+ function ctools_context_required($title) {
+ $args = func_get_args();
+ $this->title = array_shift($args);
+
+ // If we have a boolean value at the end for $skip_name_check, store it
+ if (is_bool(end($args))) {
+ $this->skip_name_check = array_pop($args);
+ }
+
+ // If we were given restrictions at the end, store them.
+ if (count($args) > 1 && is_array(end($args))) {
+ $this->restrictions = array_pop($args);
+ }
+
+ if (count($args) == 1) {
+ $args = array_shift($args);
+ }
+ $this->keywords = $args;
+ }
+
+ function filter($contexts) {
+ $result = array();
+
+ // See which of these contexts are valid
+ foreach ((array) $contexts as $cid => $context) {
+ if ($context->is_type($this->keywords)) {
+ // Compare to see if our contexts were met.
+ if (!empty($this->restrictions) && !empty($context->restrictions)) {
+ foreach ($this->restrictions as $key => $values) {
+ // If we have a restriction, the context must either not have that
+ // restriction listed, which means we simply don't know what it is,
+ // or there must be an intersection of the restricted values on
+ // both sides.
+ if (!is_array($values)) {
+ $values = array($values);
+ }
+ if (!empty($context->restrictions[$key]) && !array_intersect($values, $context->restrictions[$key])) {
+ continue 2;
+ }
+ }
+ }
+ $result[$cid] = $context;
+ }
+ }
+
+ return $result;
+ }
+
+ function select($contexts, $context) {
+ if (!is_array($contexts)) {
+ if (is_object($contexts) && $contexts instanceof ctools_context) {
+ $contexts = array($contexts->id => $contexts);
+ }
+ else {
+ $contexts = array($contexts);
+ }
+ }
+
+ // If we had requested a $context but that $context doesn't exist
+ // in our context list, there is a good chance that what happened
+ // is our context IDs changed. See if there's another context
+ // that satisfies our requirements.
+ if (!$this->skip_name_check && !empty($context) && !isset($contexts[$context])) {
+ $choices = $this->filter($contexts);
+
+ // If we got a hit, take the first one that matches.
+ if ($choices) {
+ $keys = array_keys($choices);
+ $context = reset($keys);
+ }
+ }
+
+ if (empty($context) || empty($contexts[$context])) {
+ return FALSE;
+ }
+ return $contexts[$context];
+ }
+}
+
+/**
+ * Used to compare to see if a list of contexts match an optional context. This
+ * can produce empty contexts to use as placeholders.
+ */
+class ctools_context_optional extends ctools_context_required {
+ var $required = FALSE;
+ function ctools_context_optional() {
+ $args = func_get_args();
+ call_user_func_array(array($this, 'ctools_context_required'), $args);
+ }
+
+ /**
+ * Add the 'empty' context which is possible for optional
+ */
+ function add_empty(&$contexts) {
+ $context = new ctools_context('any');
+ $context->title = t('No context');
+ $context->identifier = t('No context');
+ $contexts['empty'] = $context;
+ }
+
+ function filter($contexts) {
+ $this->add_empty($contexts);
+ return parent::filter($contexts);
+ }
+
+ function select($contexts, $context) {
+ $this->add_empty($contexts);
+ if (empty($context)) {
+ return $contexts['empty'];
+ }
+
+ $result = parent::select($contexts, $context);
+
+ // Don't flip out if it can't find the context; this is optional, put
+ // in an empty.
+ if ($result == FALSE) {
+ $result = $contexts['empty'];
+ }
+ return $result;
+ }
+}
+
+/**
+ * Return a keyed array of context that match the given 'required context'
+ * filters.
+ *
+ * Functions or systems that require contexts of a particular type provide a
+ * ctools_context_required or ctools_context_optional object. This function
+ * examines that object and an array of contexts to determine which contexts
+ * match the filter.
+ *
+ * Since multiple contexts can be required, this function will accept either
+ * an array of all required contexts, or just a single required context object.
+ *
+ * @param $contexts
+ * A keyed array of all available contexts.
+ * @param $required
+ * A ctools_context_required or ctools_context_optional object, or an array
+ * of such objects.
+ *
+ * @return
+ * A keyed array of contexts that match the filter.
+ */
+function ctools_context_filter($contexts, $required) {
+ if (is_array($required)) {
+ $result = array();
+ foreach ($required as $r) {
+ $result = array_merge($result, _ctools_context_filter($contexts, $r));
+ }
+ return $result;
+ }
+
+ return _ctools_context_filter($contexts, $required);
+}
+
+function _ctools_context_filter($contexts, $required) {
+ $result = array();
+
+ if (is_object($required)) {
+ $result = $required->filter($contexts);
+ }
+
+ return $result;
+}
+
+/**
+ * Create a select box to choose possible contexts.
+ *
+ * This only creates a selector if there is actually a choice; if there
+ * is only one possible context, that one is silently assigned.
+ *
+ * If an array of required contexts is provided, one selector will be
+ * provided for each context.
+ *
+ * @param $contexts
+ * A keyed array of all available contexts.
+ * @param $required
+ * The required context object or array of objects.
+ *
+ * @return
+ * A form element, or NULL if there are no contexts that satisfy the
+ * requirements.
+ */
+function ctools_context_selector($contexts, $required, $default) {
+ if (is_array($required)) {
+ $result = array('#tree' => TRUE);
+ $count = 1;
+ foreach ($required as $id => $r) {
+ $result[] = _ctools_context_selector($contexts, $r, isset($default[$id]) ? $default[$id] : '', $count++);
+ }
+ return $result;
+ }
+
+ return _ctools_context_selector($contexts, $required, $default);
+}
+
+function _ctools_context_selector($contexts, $required, $default, $num = 0) {
+ $filtered = ctools_context_filter($contexts, $required);
+ $count = count($filtered);
+
+ $form = array();
+
+ if ($count >= 1) {
+ // If there's more than one to choose from, create a select widget.
+ foreach ($filtered as $cid => $context) {
+ $options[$cid] = $context->get_identifier();
+ }
+ if (!empty($required->title)) {
+ $title = $required->title;
+ }
+ else {
+ $title = $num ? t('Context %count', array('%count' => $num)) : t('Context');
+ }
+
+ $form = array(
+ '#type' => 'select',
+ '#options' => $options,
+ '#title' => $title,
+ '#default_value' => $default,
+ );
+ }
+
+ return $form;
+}
+
+/**
+ * Are there enough contexts for a plugin?
+ *
+ * Some plugins can have a 'required contexts' item which can either
+ * be a context requirement object or an array of them. When contexts
+ * are required, items that do not have enough contexts should not
+ * appear. This tests an item to see if it has enough contexts
+ * to actually appear.
+ *
+ * @param $contexts
+ * A keyed array of all available contexts.
+ * @param $required
+ * The required context object or array of objects.
+ *
+ * @return
+ * TRUE if there are enough contexts, FALSE if there are not.
+ */
+function ctools_context_match_requirements($contexts, $required) {
+ if (!is_array($required)) {
+ $required = array($required);
+ }
+
+ // Get the keys to avoid bugs in PHP 5.0.8 with keys and loops.
+ // And use it to remove optional contexts.
+ $keys = array_keys($required);
+ foreach ($keys as $key) {
+ if (empty($required[$key]->required)) {
+ unset($required[$key]);
+ }
+ }
+
+ $count = count($required);
+ return (count(ctools_context_filter($contexts, $required)) >= $count);
+}
+
+/**
+ * Create a select box to choose possible contexts.
+ *
+ * This only creates a selector if there is actually a choice; if there
+ * is only one possible context, that one is silently assigned.
+ *
+ * If an array of required contexts is provided, one selector will be
+ * provided for each context.
+ *
+ * @param $contexts
+ * A keyed array of all available contexts.
+ * @param $required
+ * The required context object or array of objects.
+ *
+ * @return
+ * A form element, or NULL if there are no contexts that satisfy the
+ * requirements.
+ */
+function ctools_context_converter_selector($contexts, $required, $default) {
+ if (is_array($required)) {
+ $result = array('#tree' => TRUE);
+ $count = 1;
+ foreach ($required as $id => $r) {
+ $result[] = _ctools_context_converter_selector($contexts, $r, isset($default[$id]) ? $default[$id] : '', $count++);
+ }
+ return $result;
+ }
+
+ return _ctools_context_converter_selector($contexts, $required, $default);
+}
+
+function _ctools_context_converter_selector($contexts, $required, $default, $num = 0) {
+ $filtered = ctools_context_filter($contexts, $required);
+ $count = count($filtered);
+
+ $form = array();
+
+ if ($count > 1) {
+ // If there's more than one to choose from, create a select widget.
+ $options = array();
+ foreach ($filtered as $cid => $context) {
+ if ($context->type == 'any') {
+ $options[''] = t('No context');
+ continue;
+ }
+ $key = $context->get_identifier();
+ if ($converters = ctools_context_get_converters($cid . '.', $context)) {
+ $options[$key] = $converters;
+ }
+ }
+ if (empty($options)) {
+ return array(
+ '#type' => 'value',
+ '#value' => 'any',
+ );
+ }
+ if (!empty($required->title)) {
+ $title = $required->title;
+ }
+ else {
+ $title = $num ? t('Context %count', array('%count' => $num)) : t('Context');
+ }
+
+ return array(
+ '#type' => 'select',
+ '#options' => $options,
+ '#title' => $title,
+ '#description' => t('Please choose which context and how you would like it converted.'),
+ '#default_value' => $default,
+ );
+ }
+}
+
+/**
+ * Get a list of converters available for a given context.
+ */
+function ctools_context_get_converters($cid, $context) {
+ if (empty($context->plugin)) {
+ return array();
+ }
+
+ return _ctools_context_get_converters($cid, $context->plugin);
+}
+
+/**
+ * Get a list of converters available for a given context.
+ */
+function _ctools_context_get_converters($id, $plugin_name) {
+ $plugin = ctools_get_context($plugin_name);
+ if (empty($plugin['convert list'])) {
+ return array();
+ }
+
+ $converters = array();
+ if (is_array($plugin['convert list'])) {
+ $converters = $plugin['convert list'];
+ }
+ else if ($function = ctools_plugin_get_function($plugin, 'convert list')) {
+ $converters = (array) $function($plugin);
+ }
+
+ foreach (module_implements('ctools_context_convert_list_alter') as $module) {
+ $function = $module . '_ctools_context_convert_list_alter';
+ $function($plugin, $converters);
+ }
+
+ // Now, change them all to include the plugin:
+ $return = array();
+ foreach ($converters as $key => $title) {
+ $return[$id . $key] = $title;
+ }
+
+ natcasesort($return);
+ return $return;
+}
+
+/**
+ * Get a list of all contexts + converters available.
+ */
+function ctools_context_get_all_converters() {
+ $contexts = ctools_get_contexts();
+ $converters = array();
+ foreach ($contexts as $name => $context) {
+ if (empty($context['no required context ui'])) {
+ $context_converters = _ctools_context_get_converters($name . '.', $name);
+ if ($context_converters) {
+ $converters[$context['title']] = $context_converters;
+ }
+ }
+ }
+
+ return $converters;
+}
+
+/**
+ * Let the context convert an argument based upon the converter that was given.
+ *
+ * @param $context
+ * The context object
+ * @param $converter
+ * The converter to use, which should be a string provided by the converter list.
+ * @param $converter_options
+ * A n array of options to pass on to the generation function. For contexts
+ * that use token module, of particular use is 'sanitize' => FALSE which can
+ * get raw tokens. This should ONLY be used in values that will later be
+ * treated as unsafe user input since these values are by themselves unsafe.
+ * It is particularly useful to get raw values from Field API.
+ */
+function ctools_context_convert_context($context, $converter, $converter_options = array()) {
+ // Contexts without plugins might be optional placeholders.
+ if (empty($context->plugin)) {
+ return;
+ }
+
+ $value = $context->argument;
+ $plugin = ctools_get_context($context->plugin);
+ if ($function = ctools_plugin_get_function($plugin, 'convert')) {
+ $value = $function($context, $converter, $converter_options);
+ }
+
+ foreach (module_implements('ctools_context_converter_alter') as $module) {
+ $function = $module . '_ctools_context_converter_alter';
+ $function($context, $converter, $value, $converter_options);
+ }
+
+ return $value;
+}
+
+/**
+ * Choose a context or contexts based upon the selection made via
+ * ctools_context_filter.
+ *
+ * @param $contexts
+ * A keyed array of all available contexts
+ * @param $required
+ * The required context object provided by the plugin
+ * @param $context
+ * The selection made using ctools_context_selector
+ */
+function ctools_context_select($contexts, $required, $context) {
+ if (is_array($required)) {
+ $result = array();
+ foreach ($required as $id => $r) {
+ if (empty($required[$id])) {
+ continue;
+ }
+
+ if (($result[] = _ctools_context_select($contexts, $r, $context[$id])) === FALSE) {
+ return FALSE;
+ }
+ }
+ return $result;
+ }
+
+ return _ctools_context_select($contexts, $required, $context);
+}
+
+function _ctools_context_select($contexts, $required, $context) {
+ if (!is_object($required)) {
+ return FALSE;
+ }
+
+ return $required->select($contexts, $context);
+}
+
+/**
+ * Create a new context object.
+ *
+ * @param $type
+ * The type of context to create; this loads a plugin.
+ * @param $data
+ * The data to put into the context.
+ * @param $empty
+ * Whether or not this context is specifically empty.
+ * @param $conf
+ * A configuration structure if this context was created via UI.
+ *
+ * @return
+ * A $context or NULL if one could not be created.
+ */
+function ctools_context_create($type, $data = NULL, $conf = FALSE) {
+ ctools_include('plugins');
+ $plugin = ctools_get_context($type);
+
+ if ($function = ctools_plugin_get_function($plugin, 'context')) {
+ return $function(FALSE, $data, $conf, $plugin);
+ }
+}
+
+/**
+ * Create an empty context object.
+ *
+ * Empty context objects are primarily used as placeholders in the UI where
+ * the actual contents of a context object may not be known. It may have
+ * additional text embedded to give the user clues as to how the context
+ * is used.
+ *
+ * @param $type
+ * The type of context to create; this loads a plugin.
+ *
+ * @return
+ * A $context or NULL if one could not be created.
+ */
+function ctools_context_create_empty($type) {
+ $plugin = ctools_get_context($type);
+ if ($function = ctools_plugin_get_function($plugin, 'context')) {
+ $context = $function(TRUE, NULL, FALSE, $plugin);
+ if (is_object($context)) {
+ $context->empty = TRUE;
+ }
+
+ return $context;
+ }
+}
+
+/**
+ * Perform keyword and context substitutions.
+ */
+function ctools_context_keyword_substitute($string, $keywords, $contexts, $converter_options = array()) {
+ // Ensure a default keyword exists:
+ $keywords['%%'] = '%';
+
+ // Match contexts to the base keywords:
+ $context_keywords = array();
+ foreach ($contexts as $context) {
+ if (isset($context->keyword)) {
+ $context_keywords[$context->keyword] = $context;
+ }
+ }
+
+ // Look for context matches we we only have to convert known matches.
+ $matches = array();
+ if (preg_match_all('/%(%|[a-zA-Z0-9_-]+(?:\:[a-zA-Z0-9_-]+)*)/us', $string, $matches)) {
+ foreach ($matches[1] as $keyword) {
+ // Ignore anything it finds with %%.
+ if ($keyword[0] == '%') {
+ continue;
+ }
+
+ // If the keyword is already set by something passed in, don't try to
+ // overwrite it.
+ if (!empty($keywords['%' . $keyword])) {
+ continue;
+ }
+
+ // Figure out our keyword and converter, if specified.
+ if (strpos($keyword, ':')) {
+ list($context, $converter) = explode(':', $keyword, 2);
+ }
+ else {
+ $context = $keyword;
+ if (isset($context_keywords[$keyword])) {
+ $plugin = ctools_get_context($context_keywords[$context]->plugin);
+
+ // Fall back to a default converter, if specified.
+ if ($plugin && !empty($plugin['convert default'])) {
+ $converter = $plugin['convert default'];
+ }
+ }
+ }
+
+ if (empty($context_keywords[$context]) || !empty($context_keywords[$context]->empty)) {
+ $keywords['%' . $keyword] = '';
+ }
+ else if (!empty($converter)) {
+ $keywords['%' . $keyword] = ctools_context_convert_context($context_keywords[$context], $converter, $converter_options);
+ }
+ else {
+ $keywords['%' . $keyword] = $context_keywords[$keyword]->title;
+ }
+ }
+ }
+ return strtr($string, $keywords);
+}
+
+/**
+ * Determine a unique context ID for a context
+ *
+ * Often contexts of many different types will be placed into a list. This
+ * ensures that even though contexts of multiple types may share IDs, they
+ * are unique in the final list.
+ */
+function ctools_context_id($context, $type = 'context') {
+ if (!$context['id']) {
+ $context['id'] = 1;
+ }
+
+ return $type . '_' . $context['name'] . '_' . $context['id'];
+}
+
+/**
+ * Get the next id available given a list of already existing objects.
+ *
+ * This finds the next id available for the named object.
+ *
+ * @param $objects
+ * A list of context descriptor objects, i.e, arguments, relationships, contexts, etc.
+ * @param $name
+ * The name being used.
+ */
+function ctools_context_next_id($objects, $name) {
+ $id = 0;
+ // Figure out which instance of this argument we're creating
+ if (!$objects) {
+ return $id + 1;
+ }
+
+ foreach ($objects as $object) {
+ if (isset($object['name']) && $object['name'] == $name) {
+ if ($object['id'] > $id) {
+ $id = $object['id'];
+ }
+ }
+ }
+
+ return $id + 1;
+}
+
+
+// ---------------------------------------------------------------------------
+// Functions related to contexts from arguments.
+
+/**
+ * Fetch metadata on a specific argument plugin.
+ *
+ * @param $argument
+ * Name of an argument plugin.
+ *
+ * @return
+ * An array with information about the requested argument plugin.
+ */
+function ctools_get_argument($argument) {
+ ctools_include('plugins');
+ return ctools_get_plugins('ctools', 'arguments', $argument);
+}
+
+/**
+ * Fetch metadata for all argument plugins.
+ *
+ * @return
+ * An array of arrays with information about all available argument plugins.
+ */
+function ctools_get_arguments() {
+ ctools_include('plugins');
+ return ctools_get_plugins('ctools', 'arguments');
+}
+
+/**
+ * Get a context from an argument.
+ *
+ * @param $argument
+ * The configuration of an argument. It must contain the following data:
+ * - name: The name of the argument plugin being used.
+ * - argument_settings: The configuration based upon the plugin forms.
+ * - identifier: The human readable identifier for this argument, usually
+ * defined by the UI.
+ * - keyword: The keyword used for this argument for substitutions.
+ *
+ * @param $arg
+ * The actual argument received. This is expected to be a string from a URL but
+ * this does not have to be the only source of arguments.
+ * @param $empty
+ * If true, the $arg will not be used to load the context. Instead, an empty
+ * placeholder context will be loaded.
+ *
+ * @return
+ * A context object if one can be loaded.
+ */
+function ctools_context_get_context_from_argument($argument, $arg, $empty = FALSE) {
+ ctools_include('plugins');
+ if (empty($argument['name'])) {
+ return;
+ }
+
+ if ($function = ctools_plugin_load_function('ctools', 'arguments', $argument['name'], 'context')) {
+ // Backward compatibility: Merge old style settings into new style:
+ if (!empty($argument['settings'])) {
+ $argument += $argument['settings'];
+ unset($argument['settings']);
+ }
+
+ $context = $function($arg, $argument, $empty);
+
+ if (is_object($context)) {
+ $context->identifier = $argument['identifier'];
+ $context->page_title = isset($argument['title']) ? $argument['title'] : '';
+ $context->keyword = $argument['keyword'];
+ $context->id = ctools_context_id($argument, 'argument');
+ $context->original_argument = $arg;
+
+ if (!empty($context->empty)) {
+ $context->placeholder = array(
+ 'type' => 'argument',
+ 'conf' => $argument,
+ );
+ }
+ }
+ return $context;
+ }
+}
+
+/**
+ * Retrieve a list of empty contexts for all arguments.
+ */
+function ctools_context_get_placeholders_from_argument($arguments) {
+ $contexts = array();
+ foreach ($arguments as $argument) {
+ $context = ctools_context_get_context_from_argument($argument, NULL, TRUE);
+ if ($context) {
+ $contexts[ctools_context_id($argument, 'argument')] = $context;
+ }
+ }
+ return $contexts;
+}
+
+/**
+ * Load the contexts for a given list of arguments.
+ *
+ * @param $arguments
+ * The array of argument definitions.
+ * @param &$contexts
+ * The array of existing contexts. New contexts will be added to this array.
+ * @param $args
+ * The arguments to load.
+ *
+ * @return
+ * FALSE if an argument wants to 404.
+ */
+function ctools_context_get_context_from_arguments($arguments, &$contexts, $args) {
+ foreach ($arguments as $argument) {
+ // pull the argument off the list.
+ $arg = array_shift($args);
+ $id = ctools_context_id($argument, 'argument');
+
+ // For % arguments embedded in the URL, our context is already loaded.
+ // There is no need to go and load it again.
+ if (empty($contexts[$id])) {
+ if ($context = ctools_context_get_context_from_argument($argument, $arg)) {
+ $contexts[$id] = $context;
+ }
+ }
+ else {
+ $context = $contexts[$id];
+ }
+
+ if ((empty($context) || empty($context->data)) && !empty($argument['default']) && $argument['default'] == '404') {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+// ---------------------------------------------------------------------------
+// Functions related to contexts from relationships.
+
+/**
+ * Fetch metadata on a specific relationship plugin.
+ *
+ * @param $content type
+ * Name of a panel content type.
+ *
+ * @return
+ * An array with information about the requested relationship.
+ */
+function ctools_get_relationship($relationship) {
+ ctools_include('plugins');
+ return ctools_get_plugins('ctools', 'relationships', $relationship);
+}
+
+/**
+ * Fetch metadata for all relationship plugins.
+ *
+ * @return
+ * An array of arrays with information about all available relationships.
+ */
+function ctools_get_relationships() {
+ ctools_include('plugins');
+ return ctools_get_plugins('ctools', 'relationships');
+}
+
+/**
+ *
+ * @param $relationship
+ * The configuration of a relationship. It must contain the following data:
+ * - name: The name of the relationship plugin being used.
+ * - relationship_settings: The configuration based upon the plugin forms.
+ * - identifier: The human readable identifier for this relationship, usually
+ * defined by the UI.
+ * - keyword: The keyword used for this relationship for substitutions.
+ *
+ * @param $source_context
+ * The context this relationship is based upon.
+ *
+ * @param $placeholders
+ * If TRUE, placeholders are acceptable.
+ *
+ * @return
+ * A context object if one can be loaded.
+ */
+function ctools_context_get_context_from_relationship($relationship, $source_context, $placeholders = FALSE) {
+ ctools_include('plugins');
+ if ($function = ctools_plugin_load_function('ctools', 'relationships', $relationship['name'], 'context')) {
+ // Backward compatibility: Merge old style settings into new style:
+ if (!empty($relationship['relationship_settings'])) {
+ $relationship += $relationship['relationship_settings'];
+ unset($relationship['relationship_settings']);
+ }
+
+ $context = $function($source_context, $relationship, $placeholders);
+ if ($context) {
+ $context->identifier = $relationship['identifier'];
+ $context->page_title = isset($relationship['title']) ? $relationship['title'] : '';
+ $context->keyword = $relationship['keyword'];
+ if (!empty($context->empty)) {
+ $context->placeholder = array(
+ 'type' => 'relationship',
+ 'conf' => $relationship,
+ );
+ }
+ return $context;
+ }
+ }
+}
+
+/**
+ * Fetch all relevant relationships.
+ *
+ * Relevant relationships are any relationship that can be created based upon
+ * the list of existing contexts. For example, the 'node author' relationship
+ * is relevant if there is a 'node' context, but makes no sense if there is
+ * not one.
+ *
+ * @param $contexts
+ * An array of contexts used to figure out which relationships are relevant.
+ *
+ * @return
+ * An array of relationship keys that are relevant for the given set of
+ * contexts.
+ */
+function ctools_context_get_relevant_relationships($contexts) {
+ $relevant = array();
+ $relationships = ctools_get_relationships();
+
+ // Go through each relationship
+ foreach ($relationships as $rid => $relationship) {
+ // For each relationship, see if there is a context that satisfies it.
+ if (empty($relationship['no ui']) && ctools_context_filter($contexts, $relationship['required context'])) {
+ $relevant[$rid] = $relationship['title'];
+ }
+ }
+
+ return $relevant;
+}
+
+/**
+ * Fetch all active relationships
+ *
+ * @param $relationships
+ * An keyed array of relationship data including:
+ * - name: name of relationship
+ * - context: context id relationship belongs to. This will be used to
+ * identify which context in the $contexts array to use to create the
+ * relationship context.
+ *
+ * @param $contexts
+ * A keyed array of contexts used to figure out which relationships
+ * are relevant. New contexts will be added to this.
+ *
+ * @param $placeholders
+ * If TRUE, placeholders are acceptable.
+ */
+function ctools_context_get_context_from_relationships($relationships, &$contexts, $placeholders = FALSE) {
+ $return = array();
+
+ foreach ($relationships as $rdata) {
+ if (!isset($rdata['context'])) {
+ continue;
+ }
+
+ if (is_array($rdata['context'])) {
+ $rcontexts = array();
+ foreach ($rdata['context'] as $cid) {
+ if (empty($contexts[$cid])) {
+ continue 2;
+ }
+ $rcontexts[] = $contexts[$cid];
+ }
+ }
+ else {
+ if (empty($contexts[$rdata['context']])) {
+ continue;
+ }
+ $rcontexts = $contexts[$rdata['context']];
+ }
+
+ $cid = ctools_context_id($rdata, 'relationship');
+ if ($context = ctools_context_get_context_from_relationship($rdata, $rcontexts)) {
+ $contexts[$cid] = $context;
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+// Functions related to loading contexts from simple context definitions.
+
+/**
+ * Fetch metadata on a specific context plugin.
+ *
+ * @param $context
+ * Name of a context.
+ *
+ * @return
+ * An array with information about the requested panel context.
+ */
+function ctools_get_context($context) {
+ static $gate = array();
+ ctools_include('plugins');
+ $plugin = ctools_get_plugins('ctools', 'contexts', $context);
+ if (empty($gate['context']) && !empty($plugin['superceded by'])) {
+ // This gate prevents infinite loops.
+ $gate[$context] = TRUE;
+ $new_plugin = ctools_get_plugins('ctools', 'contexts', $plugin['superceded by']);
+ $gate[$context] = FALSE;
+
+ // If a new plugin was returned, return it. Otherwise fall through and
+ // return the original we fetched.
+ if ($new_plugin) {
+ return $new_plugin;
+ }
+ }
+
+ return $plugin;
+}
+
+/**
+ * Fetch metadata for all context plugins.
+ *
+ * @return
+ * An array of arrays with information about all available panel contexts.
+ */
+function ctools_get_contexts() {
+ ctools_include('plugins');
+ return ctools_get_plugins('ctools', 'contexts');
+}
+
+/**
+ *
+ * @param $context
+ * The configuration of a context. It must contain the following data:
+ * - name: The name of the context plugin being used.
+ * - context_settings: The configuration based upon the plugin forms.
+ * - identifier: The human readable identifier for this context, usually
+ * defined by the UI.
+ * - keyword: The keyword used for this context for substitutions.
+ * @param $type
+ * This is either 'context' which indicates the context will be loaded
+ * from data in the settings, or 'required_context' which means the
+ * context must be acquired from an external source. This is the method
+ * used to pass pure contexts from one system to another.
+ *
+ * @return
+ * A context object if one can be loaded.
+ */
+function ctools_context_get_context_from_context($context, $type = 'context', $argument = NULL) {
+ ctools_include('plugins');
+ $plugin = ctools_get_context($context['name']);
+ if ($function = ctools_plugin_get_function($plugin, 'context')) {
+ // Backward compatibility: Merge old style settings into new style:
+ if (!empty($context['context_settings'])) {
+ $context += $context['context_settings'];
+ unset($context['context_settings']);
+ }
+
+ if (isset($argument) && isset($plugin['placeholder name'])) {
+ $context[$plugin['placeholder name']] = $argument;
+ }
+
+ $return = $function($type == 'requiredcontext', $context, TRUE, $plugin);
+ if ($return) {
+ $return->identifier = $context['identifier'];
+ $return->page_title = isset($context['title']) ? $context['title'] : '';
+ $return->keyword = $context['keyword'];
+
+ if (!empty($context->empty)) {
+ $context->placeholder = array(
+ 'type' => 'context',
+ 'conf' => $context,
+ );
+ }
+
+ return $return;
+ }
+ }
+}
+
+/**
+ * Retrieve a list of base contexts based upon a simple 'contexts' definition.
+ *
+ * For required contexts this will always retrieve placeholders.
+ *
+ * @param $contexts
+ * The list of contexts defined in the UI.
+ * @param $type
+ * Either 'context' or 'requiredcontext', which indicates whether the contexts
+ * are loaded from internal data or copied from an external source.
+ * @param $placeholders
+ * If true, placeholders are acceptable.
+ */
+function ctools_context_get_context_from_contexts($contexts, $type = 'context', $placeholders = FALSE) {
+ $return = array();
+ foreach ($contexts as $context) {
+ $ctext = ctools_context_get_context_from_context($context, $type);
+ if ($ctext) {
+ if ($placeholders) {
+ $ctext->placeholder = TRUE;
+ }
+ $return[ctools_context_id($context, $type)] = $ctext;
+ }
+ }
+ return $return;
+}
+
+/**
+ * Match up external contexts to our required contexts.
+ *
+ * This function is used to create a list of contexts with proper
+ * IDs based upon a list of required contexts.
+ *
+ * These contexts passed in should match the numeric positions of the
+ * required contexts. The caller must ensure this has already happened
+ * correctly as this function will not detect errors here.
+ *
+ * @param $required
+ * A list of required contexts as defined by the UI.
+ * @param $contexts
+ * A list of matching contexts as passed in from the calling system.
+ */
+function ctools_context_match_required_contexts($required, $contexts) {
+ $return = array();
+ if (!is_array($required)) {
+ return $return;
+ }
+
+ foreach ($required as $r) {
+ $context = clone(array_shift($contexts));
+ $context->identifier = $r['identifier'];
+ $context->page_title = isset($r['title']) ? $r['title'] : '';
+ $context->keyword = $r['keyword'];
+ $return[ctools_context_id($r, 'requiredcontext')] = $context;
+ }
+
+ return $return;
+}
+
+/**
+ * Load a full array of contexts for an object.
+ *
+ * Not all of the types need to be supported by this object.
+ *
+ * This function is not used to load contexts from external data, but may
+ * be used to load internal contexts and relationships. Otherwise it can also
+ * be used to generate a full set of placeholders for UI purposes.
+ *
+ * @param $object
+ * An object that contains some or all of the following variables:
+ *
+ * - requiredcontexts: A list of UI configured contexts that are required
+ * from an external source. Since these require external data, they will
+ * only be added if $placeholders is set to TRUE, and empty contexts will
+ * be created.
+ * - arguments: A list of UI configured arguments that will create contexts.
+ * Since these require external data, they will only be added if $placeholders
+ * is set to TRUE.
+ * - contexts: A list of UI configured contexts that have no external source,
+ * and are essentially hardcoded. For example, these might configure a
+ * particular node or a particular taxonomy term.
+ * - relationships: A list of UI configured contexts to be derived from other
+ * contexts that already exist from other sources. For example, these might
+ * be used to get a user object from a node via the node author relationship.
+ * @param $placeholders
+ * If TRUE, this will generate placeholder objects for types this function
+ * cannot load.
+ * @param $contexts
+ * An array of pre-existing contexts that will be part of the return value.
+ */
+function ctools_context_load_contexts($object, $placeholders = TRUE, $contexts = array()) {
+ if (!empty($object->base_contexts)) {
+ $contexts += $object->base_contexts;
+ }
+
+ if ($placeholders) {
+ // This will load empty contexts as placeholders for arguments that come
+ // from external sources. If this isn't set, it's assumed these context
+ // will already have been matched up and loaded.
+ if (!empty($object->requiredcontexts) && is_array($object->requiredcontexts)) {
+ $contexts += ctools_context_get_context_from_contexts($object->requiredcontexts, 'requiredcontext', $placeholders);
+ }
+
+ if (!empty($object->arguments) && is_array($object->arguments)) {
+ $contexts += ctools_context_get_placeholders_from_argument($object->arguments);
+ }
+ }
+
+ if (!empty($object->contexts) && is_array($object->contexts)) {
+ $contexts += ctools_context_get_context_from_contexts($object->contexts, 'context', $placeholders);
+ }
+
+ // add contexts from relationships
+ if (!empty($object->relationships) && is_array($object->relationships)) {
+ ctools_context_get_context_from_relationships($object->relationships, $contexts, $placeholders);
+ }
+
+ return $contexts;
+}
+
+/**
+ * Return the first context with a form id from a list of contexts.
+ *
+ * This function is used to figure out which contexts represents 'the form'
+ * from a list of contexts. Only one contexts can actually be 'the form' for
+ * a given page, since the @code{<form>} tag can not be embedded within
+ * itself.
+ */
+function ctools_context_get_form($contexts) {
+ if (!empty($contexts)) {
+ foreach ($contexts as $id => $context) {
+ // if a form shows its id as being a 'required context' that means the
+ // the context is external to this display and does not count.
+ if (!empty($context->form_id) && substr($id, 0, 15) != 'requiredcontext') {
+ return $context;
+ }
+ }
+ }
+}
+
+/**
+ * Replace placeholders with real contexts using data extracted from a form
+ * for the purposes of previews.
+ *
+ * @param $contexts
+ * All of the contexts, including the placeholders.
+ * @param $arguments
+ * The arguments. These will be acquired from $form_state['values'] and the
+ * keys must match the context IDs.
+ *
+ * @return
+ * A new $contexts array containing the replaced contexts. Not all contexts
+ * may be replaced if, for example, an argument was unable to be converted
+ * into a context.
+ */
+function ctools_context_replace_placeholders($contexts, $arguments) {
+ foreach ($contexts as $cid => $context) {
+ if (empty($context->empty)) {
+ continue;
+ }
+
+ $new_context = NULL;
+ switch ($context->placeholder['type']) {
+ case 'relationship':
+ $relationship = $context->placeholder['conf'];
+ if (isset($contexts[$relationship['context']])) {
+ $new_context = ctools_context_get_context_from_relationship($relationship, $contexts[$relationship['context']]);
+ }
+ break;
+ case 'argument':
+ if (isset($arguments[$cid]) && $arguments[$cid] !== '') {
+ $argument = $context->placeholder['conf'];
+ $new_context = ctools_context_get_context_from_argument($argument, $arguments[$cid]);
+ }
+ break;
+ case 'context':
+ if (!empty($arguments[$cid])) {
+ $context_info = $context->placeholder['conf'];
+ $new_context = ctools_context_get_context_from_context($context_info, 'requiredcontext', $arguments[$cid]);
+ }
+ break;
+ }
+
+ if ($new_context && empty($new_context->empty)) {
+ $contexts[$cid] = $new_context;
+ }
+ }
+
+ return $contexts;
+}
+
+/**
+ * Provide a form array for getting data to replace placeholder contexts
+ * with real data.
+ */
+function ctools_context_replace_form(&$form, $contexts) {
+ foreach ($contexts as $cid => $context) {
+ if (empty($context->empty)) {
+ continue;
+ }
+
+ // Get plugin info from the context which should have been set when the
+ // empty context was created.
+ $info = NULL;
+ $plugin = NULL;
+ $settings = NULL;
+ switch ($context->placeholder['type']) {
+ case 'argument':
+ $info = $context->placeholder['conf'];
+ $plugin = ctools_get_argument($info['name']);
+ break;
+
+ case 'context':
+ $info = $context->placeholder['conf'];
+ $plugin = ctools_get_context($info['name']);
+ break;
+ }
+
+ // Ask the plugin where the form is.
+ if ($plugin && isset($plugin['placeholder form'])) {
+ if (is_array($plugin['placeholder form'])) {
+ $form[$cid] = $plugin['placeholder form'];
+ }
+ else if (function_exists($plugin['placeholder form'])) {
+ $widget = $plugin['placeholder form']($info);
+ if ($widget) {
+ $form[$cid] = $widget;
+ }
+ }
+
+ if (!empty($form[$cid])) {
+ $form[$cid]['#title'] = t('@identifier (@keyword)', array('@keyword' => '%' . $context->keyword, '@identifier' => $context->identifier));
+ }
+ }
+ }
+}
+
+// ---------------------------------------------------------------------------
+// Functions related to loading access control plugins
+
+/**
+ * Fetch metadata on a specific access control plugin.
+ *
+ * @param $name
+ * Name of a plugin.
+ *
+ * @return
+ * An array with information about the requested access control plugin.
+ */
+function ctools_get_access_plugin($name) {
+ ctools_include('plugins');
+ return ctools_get_plugins('ctools', 'access', $name);
+}
+
+/**
+ * Fetch metadata for all access control plugins.
+ *
+ * @return
+ * An array of arrays with information about all available access control plugins.
+ */
+function ctools_get_access_plugins() {
+ ctools_include('plugins');
+ return ctools_get_plugins('ctools', 'access');
+}
+
+/**
+ * Fetch a list of access plugins that are available for a given list of
+ * contexts.
+ *
+ * if 'logged-in-user' is not in the list of contexts, it will be added as
+ * this is required.
+ */
+function ctools_get_relevant_access_plugins($contexts) {
+ if (!isset($contexts['logged-in-user'])) {
+ $contexts['logged-in-user'] = ctools_access_get_loggedin_context();
+ }
+
+ $all_plugins = ctools_get_access_plugins();
+ $plugins = array();
+ foreach ($all_plugins as $id => $plugin) {
+ if (!empty($plugin['required context']) && !ctools_context_match_requirements($contexts, $plugin['required context'])) {
+ continue;
+ }
+ $plugins[$id] = $plugin;
+ }
+
+ return $plugins;
+}
+
+/**
+ * Create a context for the logged in user.
+ */
+function ctools_access_get_loggedin_context() {
+ $context = ctools_context_create('entity:user', array('type' => 'current'), TRUE);
+ $context->identifier = t('Logged in user');
+ $context->keyword = 'viewer';
+ $context->id = 0;
+
+ return $context;
+}
+
+/**
+ * Get a summary of an access plugin's settings.
+ */
+function ctools_access_summary($plugin, $contexts, $test) {
+ if (!isset($contexts['logged-in-user'])) {
+ $contexts['logged-in-user'] = ctools_access_get_loggedin_context();
+ }
+
+ $description = '';
+ if ($function = ctools_plugin_get_function($plugin, 'summary')) {
+ $required_context = isset($plugin['required context']) ? $plugin['required context'] : array();
+ $context = isset($test['context']) ? $test['context'] : array();
+ $description = $function($test['settings'], ctools_context_select($contexts, $required_context, $context), $plugin);
+ }
+
+ if (!empty($test['not'])) {
+ $description = "NOT ($description)";
+ }
+
+ return $description;
+}
+
+/**
+ * Get a summary of a group of access plugin's settings.
+ */
+function ctools_access_group_summary($access, $contexts) {
+ if (empty($access['plugins'])) {
+ return;
+ }
+
+ $descriptions = array();
+ foreach ($access['plugins'] as $id => $test) {
+ $plugin = ctools_get_access_plugin($test['name']);
+ $descriptions[] = ctools_access_summary($plugin, $contexts, $test);
+ }
+
+ $separator = (isset($access['logic']) && $access['logic'] == 'and') ? t(', and ') : t(', or ');
+ return implode($separator, $descriptions);
+}
+
+/**
+ * Determine if the current user has access via plugin.
+ *
+ * @param $settings
+ * An array of settings theoretically set by the user.
+ * @param $contexts
+ * An array of zero or more contexts that may be used to determine if
+ * the user has access.
+ *
+ * @return
+ * TRUE if access is granted, false if otherwise.
+ */
+function ctools_access($settings, $contexts = array()) {
+ if (empty($settings['plugins'])) {
+ return TRUE;
+ }
+
+ if (!isset($settings['logic'])) {
+ $settings['logic'] = 'and';
+ }
+
+ if (!isset($contexts['logged-in-user'])) {
+ $contexts['logged-in-user'] = ctools_access_get_loggedin_context();
+ }
+
+ foreach ($settings['plugins'] as $test) {
+ $pass = FALSE;
+ $plugin = ctools_get_access_plugin($test['name']);
+ if ($plugin && $function = ctools_plugin_get_function($plugin, 'callback')) {
+ // Do we need just some contexts or all of them?
+ if (!empty($plugin['all contexts'])) {
+ $test_contexts = $contexts;
+ }
+ else {
+ $required_context = isset($plugin['required context']) ? $plugin['required context'] : array();
+ $context = isset($test['context']) ? $test['context'] : array();
+ $test_contexts = ctools_context_select($contexts, $required_context, $context);
+ }
+
+ $pass = $function($test['settings'], $test_contexts, $plugin);
+ if (!empty($test['not'])) {
+ $pass = !$pass;
+ }
+ }
+
+ if ($pass && $settings['logic'] == 'or') {
+ // Pass if 'or' and this rule passed.
+ return TRUE;
+ }
+ else if (!$pass && $settings['logic'] == 'and') {
+ // Fail if 'and' and this rule failed.
+ return FALSE;
+ }
+ }
+
+ // Return TRUE if logic was and, meaning all rules passed.
+ // Return FALSE if logic was or, meaning no rule passed.
+ return $settings['logic'] == 'and';
+}
+
+/**
+ * Create default settings for a new access plugin.
+ *
+ * @param $plugin
+ * The access plugin being used.
+ *
+ * @return
+ * A default configured test that should be placed in $access['plugins'];
+ */
+function ctools_access_new_test($plugin) {
+ $test = array(
+ 'name' => $plugin['name'],
+ 'settings' => array(),
+ );
+
+ // Set up required context defaults.
+ if (isset($plugin['required context'])) {
+ if (is_object($plugin['required context'])) {
+ $test['context'] = '';
+ }
+ else {
+ $test['context'] = array();
+ foreach ($plugin['required context'] as $required) {
+ $test['context'][] = '';
+ }
+ }
+ }
+
+
+ $default = NULL;
+ if (isset($plugin['default'])) {
+ $default = $plugin['default'];
+ }
+ elseif (isset($plugin['defaults'])) {
+ $default = $plugin['defaults'];
+ }
+
+ // Setup plugin defaults.
+ if (isset($default)) {
+ if (is_array($default)) {
+ $test['settings'] = $default;
+ }
+ else if (function_exists($default)) {
+ $test['settings'] = $default();
+ }
+ else {
+ $test['settings'] = array();
+ }
+ }
+
+ return $test;
+}
+
+/**
+ * Apply restrictions to contexts based upon the access control configured.
+ *
+ * These restrictions allow the UI to not show content that may not
+ * be relevant to all types of a particular context.
+ */
+function ctools_access_add_restrictions($settings, $contexts) {
+ if (empty($settings['plugins'])) {
+ return;
+ }
+
+ if (!isset($settings['logic'])) {
+ $settings['logic'] = 'and';
+ }
+
+ // We're not going to try to figure out restrictions on the or.
+ if ($settings['logic'] == 'or' && count($settings['plugins']) > 1) {
+ return;
+ }
+
+ foreach ($settings['plugins'] as $test) {
+ $plugin = ctools_get_access_plugin($test['name']);
+ if ($plugin && $function = ctools_plugin_get_function($plugin, 'restrictions')) {
+ $required_context = isset($plugin['required context']) ? $plugin['required context'] : array();
+ $context = isset($test['context']) ? $test['context'] : array();
+ $contexts = ctools_context_select($contexts, $required_context, $context);
+ if ($contexts !== FALSE) {
+ $function($test['settings'], $contexts);
+ }
+ }
+ }
+}
diff --git a/sites/all/modules/ctools/includes/context.menu.inc b/sites/all/modules/ctools/includes/context.menu.inc
new file mode 100644
index 000000000..ee227cb74
--- /dev/null
+++ b/sites/all/modules/ctools/includes/context.menu.inc
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Contains menu item registration for the context tool.
+ *
+ * The menu items registered are AJAX callbacks for the context configuration
+ * popups. They are kept separately for organizational purposes.
+ */
+
+function ctools_context_menu(&$items) {
+ $base = array(
+ 'access arguments' => array('access content'),
+ 'type' => MENU_CALLBACK,
+ 'file' => 'includes/context-admin.inc',
+ 'theme callback' => 'ajax_base_page_theme',
+ );
+ $items['ctools/context/ajax/add'] = array(
+ 'page callback' => 'ctools_context_ajax_item_add',
+ ) + $base;
+ $items['ctools/context/ajax/configure'] = array(
+ 'page callback' => 'ctools_context_ajax_item_edit',
+ ) + $base;
+ $items['ctools/context/ajax/delete'] = array(
+ 'page callback' => 'ctools_context_ajax_item_delete',
+ ) + $base;
+
+ // For the access system
+ $base['file'] = 'includes/context-access-admin.inc';
+ $items['ctools/context/ajax/access/add'] = array(
+ 'page callback' => 'ctools_access_ajax_add',
+ ) + $base;
+ $items['ctools/context/ajax/access/configure'] = array(
+ 'page callback' => 'ctools_access_ajax_edit',
+ ) + $base;
+ $items['ctools/context/ajax/access/delete'] = array(
+ 'page callback' => 'ctools_access_ajax_delete',
+ ) + $base;
+
+}
diff --git a/sites/all/modules/ctools/includes/context.plugin-type.inc b/sites/all/modules/ctools/includes/context.plugin-type.inc
new file mode 100644
index 000000000..866def2e0
--- /dev/null
+++ b/sites/all/modules/ctools/includes/context.plugin-type.inc
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * @file
+ * Contains plugin type registration information for the context tool.
+ *
+ * Don't actually need to declare anything for these plugin types right now,
+ * apart from the fact that they exist. So, an empty array.
+ */
+
+function ctools_context_plugin_type(&$items) {
+ $items['contexts'] = array(
+ 'child plugins' => TRUE,
+ );
+ $items['arguments'] = array(
+ 'child plugins' => TRUE,
+ );
+ $items['relationships'] = array(
+ 'child plugins' => TRUE,
+ );
+ $items['access'] = array(
+ 'child plugins' => TRUE,
+ );
+}
diff --git a/sites/all/modules/ctools/includes/context.theme.inc b/sites/all/modules/ctools/includes/context.theme.inc
new file mode 100644
index 000000000..8f660b8c0
--- /dev/null
+++ b/sites/all/modules/ctools/includes/context.theme.inc
@@ -0,0 +1,344 @@
+<?php
+
+/**
+ * @file
+ * Contains theme registry and theme implementations for the context tool.
+ */
+
+/**
+ * Implements hook_theme()
+ */
+function ctools_context_theme(&$theme) {
+ $theme['ctools_context_list'] = array(
+ 'variables' => array('object' => NULL),
+ 'file' => 'includes/context.theme.inc',
+ );
+ $theme['ctools_context_list_no_table'] = array(
+ 'variables' => array('object' => NULL),
+ 'file' => 'includes/context.theme.inc',
+ );
+ $theme['ctools_context_item_form'] = array(
+ 'render element' => 'form',
+// 'variables' => array('form' => NULL),
+ 'file' => 'includes/context.theme.inc',
+ );
+ $theme['ctools_context_item_row'] = array(
+ 'variables' => array('type' => NULL, 'form' => NULL, 'position' => NULL, 'count' => NULL, 'with_tr' => TRUE),
+ 'file' => 'includes/context.theme.inc',
+ );
+
+ // For the access plugin
+ $theme['ctools_access_admin_add'] = array(
+ 'render element' => 'form',
+ 'file' => 'includes/context-access-admin.inc',
+ );
+}
+
+/**
+ * Theme the form item for the context entry.
+ */
+function theme_ctools_context_item_row($vars) {
+ $type = $vars['type'];
+ $form = $vars['form'];
+ $position = $vars['position'];
+ $count = $vars['count'];
+ $with_tr = $vars['with_tr'];
+ $output = '<td class="title">&nbsp;' . render($form['title']) . '</td>';
+ if (!empty($form['position'])) {
+ $output .= '<td class="position">&nbsp;' . render($form['position']) . '</td>';
+ }
+ $output .= '<td class="operation">' . render($form['settings']);
+ $output .= render($form['remove']) . '</td>';
+
+ if ($with_tr) {
+ $output = '<tr id="' . $type . '-row-' . $position . '" class="draggable ' . $type . '-row ' . ($count % 2 ? 'even' : 'odd') . '">' . $output . '</tr>';
+ }
+ return $output;
+}
+
+/**
+ * Display the context item.
+ */
+function theme_ctools_context_item_form($vars) {
+ $form = $vars['form'];
+
+ $output = '';
+ $type = $form['#ctools_context_type'];
+ $module = $form['#ctools_context_module'];
+ $cache_key = $form['#cache_key'];
+
+ $type_info = ctools_context_info($type);
+
+ if (!empty($form[$type]) && empty($form['#only_buttons'])) {
+ $count = 0;
+ $rows = '';
+ foreach (array_keys($form[$type]) as $id) {
+ if (!is_numeric($id)) {
+ continue;
+ }
+ $theme_vars = array();
+ $theme_vars['type'] = $type;
+ $theme_vars['form'] = $form[$type][$id];
+ $theme_vars['position'] = $id;
+ $theme_vars['count'] = $count++;
+ $rows .= theme('ctools_context_item_row', $theme_vars);
+ }
+
+ $output .= '<table id="' . $type . '-table">';
+ $output .= '<thead>';
+ $output .= '<tr>';
+ $output .= '<th class="title">' . $type_info['title'] . '</th>';
+ if (!empty($type_info['sortable']) && $count) {
+ $output .= '<th class="position">' . t('Weight') . '</th>';
+ }
+ $output .= '<th class="operation">' . t('Operation') . '</th>';
+ $output .= '</tr>';
+ $output .= '</thead>';
+ $output .= '<tbody>';
+
+ $output .= $rows;
+
+ $output .= '</tbody>';
+ $output .= '</table>';
+ }
+
+ if (!empty($form['buttons'])) {
+ // Display the add context item.
+ $row = array();
+ $row[] = array('data' => render($form['buttons'][$type]['item']), 'class' => array('title'));
+ $row[] = array('data' => render($form['buttons'][$type]['add']), 'class' => array('add'), 'width' => "60%");
+ $output .= '<div class="buttons">';
+ $output .= render($form['buttons'][$type]);
+ $theme_vars = array();
+ $theme_vars['header'] = array();
+ $theme_vars['rows'] = array($row);
+ $theme_vars['attributes'] = array('id' => $type . '-add-table');
+ $output .= theme('table', $theme_vars);
+ $output .= '</div>';
+ }
+ if (!empty($form['description'])) {
+ $output .= render($form['description']);
+ }
+
+ if (!empty($type_info['sortable'])) {
+ drupal_add_tabledrag($type . '-table', 'order', 'sibling', 'drag-position');
+ }
+
+ return $output;
+}
+
+/**
+ * Create a visible list of all the contexts available on an object.
+ * Assumes arguments, relationships and context objects.
+ *
+ * Contexts must be preloaded.
+ */
+function theme_ctools_context_list($vars) {
+ $object = $vars['object'];
+ $header = $vars['header'];
+ $description = (!empty($vars['description'])) ? $vars['description'] : NULL;
+ $titles = array();
+ $output = '';
+ $count = 1;
+
+ $contexts = ctools_context_load_contexts($object);
+
+ // Describe 'built in' contexts.
+ if (!empty($object->base_contexts)) {
+ foreach ($object->base_contexts as $id => $context) {
+ $output .= '<tr>';
+ $output .= '<td valign="top"><em>' . t('Built in context') . '</em></td>';
+ $desc = check_plain($context->identifier);
+ if (isset($context->keyword)) {
+ $desc .= '<div class="description">' . t('Keyword: %@keyword', array('@keyword' => $context->keyword));
+ foreach (ctools_context_get_converters('%' . $context->keyword . ':', $context) as $keyword => $title) {
+ $desc .= '<br />' . t('@keyword --&gt; @title', array('@keyword' => $keyword, '@title' => $title));
+ }
+ $desc .= '</div>';
+
+ }
+ if (isset($context->description)) {
+ $desc .= '<div class="description">' . filter_xss_admin($context->description) . '</div>';
+ }
+ $output .= '<td>' . $desc . '</td>';
+ $output .= '</tr>';
+ $titles[$id] = $context->identifier;
+ }
+ }
+
+ // First, make a list of arguments. Arguments are pretty simple.
+ if (!empty($object->arguments)) {
+ foreach ($object->arguments as $argument) {
+ $output .= '<tr>';
+ $output .= '<td valign="top"><em>' . t('Argument @count', array('@count' => $count)) . '</em></td>';
+ $desc = check_plain($argument['identifier']);
+ if (isset($argument['keyword'])) {
+ $desc .= '<div class="description">' . t('Keyword: %@keyword', array('@keyword' => $argument['keyword']));
+ if (isset($contexts[ctools_context_id($argument, 'argument')])) {
+ foreach (ctools_context_get_converters('%' . $argument['keyword'] . ':', $contexts[ctools_context_id($argument, 'argument')]) as $keyword => $title) {
+ $desc .= '<br />' . t('@keyword --&gt; @title', array('@keyword' => $keyword, '@title' => $title));
+ }
+ }
+ $desc .= '</div>';
+ }
+ $output .= '<td>' . $desc . '</td>';
+ $output .= '</tr>';
+ $titles[ctools_context_id($argument, 'argument')] = $argument['identifier'];
+ $count++;
+ }
+ }
+
+ $count = 1;
+ // Then, make a nice list of contexts.
+ if (!empty($object->contexts)) {
+ foreach ($object->contexts as $context) {
+ $output .= '<tr>';
+ $output .= '<td valign="top"><em>' . t('Context @count', array('@count' => $count)) . '</em></td>';
+ $desc = check_plain($context['identifier']);
+ if (isset($context['keyword'])) {
+ $desc .= '<div class="description">' . t('Keyword: %@keyword', array('@keyword' => $context['keyword']));
+ foreach (ctools_context_get_converters('%' . $context['keyword'] . ':', $contexts[ctools_context_id($context, 'context')]) as $keyword => $title) {
+ $desc .= '<br />' . t('@keyword --&gt; @title', array('@keyword' => $keyword, '@title' => $title));
+ }
+ $desc .= '</div>';
+ }
+ $output .= '<td>' . $desc . '</td>';
+ $output .= '</tr>';
+ $titles[ctools_context_id($context)] = $context['identifier'];
+ $count++;
+ }
+ }
+
+ // And relationships
+ if (!empty($object->relationships)) {
+ foreach ($object->relationships as $relationship) {
+ $output .= '<tr>';
+ if (is_array($relationship['context'])) {
+ $rtitles = array();
+ foreach ($relationship['context'] as $cid) {
+ $rtitles[$cid] = $titles[$cid];
+ }
+ $title = implode(' + ', $rtitles);
+ }
+ else {
+ $title = $titles[$relationship['context']];
+ }
+ $output .= '<td valign="top"><em>' . t('From "@title"', array('@title' => $title)) . '</em></td>';
+ $desc = check_plain($relationship['identifier']);
+ if (isset($relationship['keyword'])) {
+ $desc .= '<div class="description">' . t('Keyword: %@keyword', array('@keyword' => $relationship['keyword']));
+ foreach (ctools_context_get_converters('%' . $relationship['keyword'] . ':', $contexts[ctools_context_id($relationship, 'relationship')]) as $keyword => $title) {
+ $desc .= '<br />' . t('@keyword --&gt; @title', array('@keyword' => $keyword, '@title' => $title));
+ }
+ $desc .= '</div>';
+ }
+ $output .= '<td>' . $desc . '</td>';
+ $output .= '</tr>';
+ $titles[ctools_context_id($relationship, 'relationship')] = $relationship['identifier'];
+ $count++;
+ }
+ }
+
+ $head = '';
+ if ($header) {
+ if ($description) {
+ $header .= '<div class="description">' . $description . '</div>';
+ }
+ $head .= '<thead><tr>';
+ $head .= '<th colspan="2">' . $header . '</th>';
+ $head .= '</tr></thead>';
+ }
+
+ return $output ? "<table>$head<tbody>$output</tbody></table>\n" : "<table>$head</table>\n";
+}
+
+/**
+ * ctools_context_list() but not in a table format because tabledrag
+ * won't let us have tables within tables and still drag.
+ */
+function theme_ctools_context_list_no_table($vars) {
+ $object = $vars['object'];
+ ctools_add_css('context');
+ $titles = array();
+ $output = '';
+ $count = 1;
+ // Describe 'built in' contexts.
+ if (!empty($object->base_contexts)) {
+ foreach ($object->base_contexts as $id => $context) {
+ $output .= '<div class="ctools-context-holder clearfix">';
+ $output .= '<div class="ctools-context-title">' . t('Built in context') . '</div>';
+ $desc = check_plain($context->identifier);
+ if (isset($context->keyword)) {
+ $desc .= '<div class="description">' . t('Keyword: %@keyword', array('@keyword' => $context->keyword)) . '</div>';
+ }
+ if (isset($context->description)) {
+ $desc .= '<div class="description">' . filter_xss_admin($context->description) . '</div>';
+ }
+ $output .= '<div class="ctools-context-content">' . $desc . '</div>';
+ $output .= '</div>';
+ $titles[$id] = $context->identifier;
+ $count++;
+ }
+ }
+
+ // First, make a list of arguments. Arguments are pretty simple.
+ if (!empty($object->arguments)) {
+ foreach ($object->arguments as $argument) {
+ $output .= '<div class="ctools-context-holder clearfix">';
+ $output .= '<div class="ctools-context-title">' . t('Argument @count', array('@count' => $count)) . '</div>';
+ $desc = check_plain($argument['identifier']);
+ if (isset($argument['keyword'])) {
+ $desc .= '<div class="description">' . t('Keyword: %@keyword', array('@keyword' => $argument['keyword'])) . '</div>';
+ }
+ $output .= '<div class="ctools-context-content">' . $desc . '</div>';
+ $output .= '</div>';
+ $titles[ctools_context_id($argument, 'argument')] = $argument['identifier'];
+ $count++;
+ }
+ }
+ $count = 1;
+ // Then, make a nice list of contexts.
+ if (!empty($object->contexts)) {
+ foreach ($object->contexts as $context) {
+ $output .= '<div class="ctools-context-holder clearfix">';
+ $output .= '<div class="ctools-context-title">' . t('Context @count', array('@count' => $count)) . '</div>';
+ $desc = check_plain($context['identifier']);
+ if (isset($context['keyword'])) {
+ $desc .= '<div class="description">' . t('Keyword: %@keyword', array('@keyword' => $context['keyword'])) . '</div>';
+ }
+ $output .= '<div class="ctools-context-content">' . $desc . '</div>';
+ $output .= '</div>';
+ $titles[ctools_context_id($context)] = $context['identifier'];
+ $count++;
+ }
+ }
+ // And relationships
+ if (!empty($object->relationships)) {
+ foreach ($object->relationships as $relationship) {
+ $output .= '<div class="ctools-context-holder clearfix">';
+ if (is_array($relationship['context'])) {
+ $rtitles = array();
+ foreach ($relationship['context'] as $cid) {
+ $rtitles[$cid] = $titles[$cid];
+ }
+ $title = implode(' + ', $rtitles);
+ }
+ else {
+ $title = $titles[$relationship['context']];
+ }
+
+ $output .= '<div class="ctools-context-title">' . t('From "@title"', array('@title' => $title)) . '</div>';
+ $desc = check_plain($relationship['identifier']);
+ if (isset($relationship['keyword'])) {
+ $desc .= '<div class="description">' . t('Keyword: %@keyword', array('@keyword' => $relationship['keyword'])) . '</div>';
+ }
+ $output .= '<div class="ctools-context-content">' . $desc . '</div>';
+ $output .= '</div>';
+ $titles[ctools_context_id($relationship, 'relationship')] = $relationship['identifier'];
+ $count++;
+ }
+ }
+
+ return $output;
+}
+
diff --git a/sites/all/modules/ctools/includes/css-cache.inc b/sites/all/modules/ctools/includes/css-cache.inc
new file mode 100644
index 000000000..d88160b5b
--- /dev/null
+++ b/sites/all/modules/ctools/includes/css-cache.inc
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ * Custom cache implementation for the CTools CSS cache.
+ */
+
+class CToolsCssCache implements DrupalCacheInterface {
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear($cid = NULL, $wildcard = FALSE) {
+ // Only clear the caches if the wildcard is set, this ensures that the cache
+ // is only cleared when the full caches are cleared manually (eg by invoking
+ // drupal_flush_all_caches()), and not on a cron run.
+ // @see drupal_flush_all_caches()
+ // @see system_cron()
+ if ($wildcard) {
+ ctools_include('css');
+ ctools_css_flush_caches();
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get($cid) {
+ return FALSE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getMultiple(&$cids) {
+ return array();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isEmpty() {
+ return FALSE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function set($cid, $data, $expire = CACHE_PERMANENT) {
+ }
+
+}
diff --git a/sites/all/modules/ctools/includes/css.inc b/sites/all/modules/ctools/includes/css.inc
new file mode 100644
index 000000000..8cf5ed407
--- /dev/null
+++ b/sites/all/modules/ctools/includes/css.inc
@@ -0,0 +1,575 @@
+<?php
+
+/*
+ * @file
+ * CSS filtering functions. Contains a disassembler, filter, compressor, and
+ * decompressor.
+ *
+ * The general usage of this tool is:
+ *
+ * To simply filter CSS:
+ * @code
+ * $filtered_css = ctools_css_filter($css, TRUE);
+ * @endcode
+ *
+ * In the above, if the second argument is TRUE, the returned CSS will
+ * be compressed. Otherwise it will be returned in a well formatted
+ * syntax.
+ *
+ * To cache unfiltered CSS in a file, which will be filtered:
+ *
+ * @code
+ * $filename = ctools_css_cache($css, TRUE);
+ * @endcode
+ *
+ * In the above, if the second argument is FALSE, the CSS will not be filtered.
+ *
+ * This file will be cached within the Drupal files system. This system cannot
+ * detect when this file changes, so it is YOUR responsibility to remove and
+ * re-cache this file when the CSS is changed. Your system should also contain
+ * a backup method of re-generating the CSS cache in case it is removed, so
+ * that it is easy to force a re-cache by simply deleting the contents of the
+ * directory.
+ *
+ * Finally, if for some reason your application cannot store the filename
+ * (which is true of Panels where the style can't force the display to
+ * resave unconditionally) you can use the ctools storage mechanism. You
+ * simply have to come up with a unique Id:
+ *
+ * @code
+ * $filename = ctools_css_store($id, $css, TRUE);
+ * @endcode
+ *
+ * Then later on:
+ * @code
+ * $filename = ctools_css_retrieve($id);
+ * drupal_add_css($filename);
+ * @endcode
+ *
+ * The CSS that was generated will be stored in the database, so even if the
+ * file was removed the cached CSS will be used. If the CSS cache is
+ * cleared you may be required to regenerate your CSS. This will normally
+ * only be cleared by an administrator operation, not during normal usage.
+ *
+ * You may remove your stored CSS this way:
+ *
+ * @code
+ * ctools_css_clear($id);
+ * @endcode
+ */
+
+/**
+ * Store CSS with a given id and return the filename to use.
+ *
+ * This function associates a piece of CSS with an id, and stores the
+ * cached filename and the actual CSS for later use with
+ * ctools_css_retrieve.
+ */
+function ctools_css_store($id, $css, $filter = TRUE) {
+ $filename = db_query('SELECT filename FROM {ctools_css_cache} WHERE cid = :cid', array(':cid' => $id))->fetchField();
+ if ($filename && file_exists($filename)) {
+ file_unmanaged_delete($filename);
+ }
+ // Remove any previous records.
+ db_delete('ctools_css_cache')
+ ->condition('cid', $id)
+ ->execute();
+
+ $filename = ctools_css_cache($css, $filter);
+
+ db_merge('ctools_css_cache')
+ ->key(array('cid' => $id))
+ ->fields(array(
+ 'filename' => $filename,
+ 'css' => $css,
+ 'filter' => intval($filter),
+ ))
+ ->execute();
+
+ return $filename;
+}
+
+/**
+ * Retrieve a filename associated with an id of previously cached CSS.
+ *
+ * This will ensure the file still exists and, if not, create it.
+ */
+function ctools_css_retrieve($id) {
+ $cache = db_query('SELECT * FROM {ctools_css_cache} WHERE cid = :cid', array(':cid' => $id))->fetchObject();
+ if (!$cache) {
+ return;
+ }
+
+ if (!file_exists($cache->filename)) {
+ $filename = ctools_css_cache($cache->css, $cache->filter);
+ if ($filename != $cache->filename) {
+ db_update('ctools_css_cache')
+ ->fields(array('filename' => $filename))
+ ->condition('cid', $id)
+ ->execute();
+ $cache->filename = $filename;
+ }
+ }
+
+ return $cache->filename;
+}
+
+/**
+ * Remove stored CSS and any associated file.
+ */
+function ctools_css_clear($id) {
+ $cache = db_query('SELECT * FROM {ctools_css_cache} WHERE cid = :cid', array(':cid' => $id))->fetchObject();
+ if (!$cache) {
+ return;
+ }
+
+ if (file_exists($cache->filename)) {
+ file_unmanaged_delete($cache->filename);
+ // If we remove an existing file, there may be cached pages that refer
+ // to it. We must get rid of them: FIXME same format in D7?
+ cache_clear_all();
+ }
+
+ db_delete('ctools_css_cache')
+ ->condition('cid', $id)
+ ->execute();
+}
+
+/**
+ * Write a chunk of CSS to a temporary cache file and return the file name.
+ *
+ * This function optionally filters the CSS (always compressed, if so) and
+ * generates a unique filename based upon md5. It returns that filename that
+ * can be used with drupal_add_css(). Note that as a cache file, technically
+ * this file is volatile so it should be checked before it is used to ensure
+ * that it exists.
+ *
+ * You can use file_exists() to test for the file and file_delete() to remove
+ * it if it needs to be cleared.
+ *
+ * @param $css
+ * A chunk of well-formed CSS text to cache.
+ * @param $filter
+ * If TRUE the css will be filtered. If FALSE the text will be cached
+ * as-is.
+ *
+ * @return $filename
+ * The filename the CSS will be cached in.
+ */
+function ctools_css_cache($css, $filter = TRUE) {
+ if ($filter) {
+ $css = ctools_css_filter($css);
+ }
+
+ // Create the css/ within the files folder.
+ $path = 'public://ctools/css';
+ if (!file_prepare_directory($path, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
+// if (!file_prepare_directory($path, FILE_CREATE_DIRECTORY)) {
+ drupal_set_message(t('Unable to create CTools CSS cache directory. Check the permissions on your files directory.'), 'error');
+ return;
+ }
+
+ // @todo Is this slow? Does it matter if it is?
+ $filename = $path . '/' . md5($css) . '.css';
+
+ // Generally md5 is considered unique enough to sign file downloads.
+ // So this replaces already existing files based on the assumption that two
+ // files with the same hash are identical content wise.
+ // If we rename, the cache folder can potentially fill up with thousands of
+ // files with the same content.
+ $filename = file_unmanaged_save_data($css, $filename, FILE_EXISTS_REPLACE);
+
+ return $filename;
+}
+
+/**
+ * Filter a chunk of CSS text.
+ *
+ * This function disassembles the CSS into a raw format that makes it easier
+ * for our tool to work, then runs it through the filter and reassembles it.
+ * If you find that you want the raw data for some reason or another, you
+ * can use the disassemble/assemble functions yourself.
+ *
+ * @param $css
+ * The CSS text to filter.
+ * @param $compressed
+ * If true, generate compressed output; if false, generate pretty output.
+ * Defaults to TRUE.
+ */
+function ctools_css_filter($css, $compressed = TRUE) {
+ $css_data = ctools_css_disassemble($css);
+
+ // Note: By using this function yourself you can control the allowed
+ // properties and values list.
+ $filtered = ctools_css_filter_css_data($css_data);
+
+ return $compressed ? ctools_css_compress($filtered) : ctools_css_assemble($filtered);
+}
+
+/**
+ * Re-assemble a css string and format it nicely.
+ *
+ * @param array $css_data
+ * An array of css data, as produced by @see ctools_css_disassemble()
+ * disassembler and the @see ctools_css_filter_css_data() filter.
+ *
+ * @return string $css
+ * css optimized for human viewing.
+ */
+function ctools_css_assemble($css_data) {
+ // Initialize the output.
+ $css = '';
+ // Iterate through all the statements.
+ foreach ($css_data as $selector_str => $declaration) {
+ // Add the selectors, separating them with commas and line feeds.
+ $css .= strpos($selector_str, ',') === FALSE ? $selector_str : str_replace(", ", ",\n", $selector_str);
+ // Add the opening curly brace.
+ $css .= " {\n";
+ // Iterate through all the declarations.
+ foreach ($declaration as $property => $value) {
+ $css .= " " . $property . ": " . $value . ";\n";
+ }
+ // Add the closing curly brace.
+ $css .= "}\n\n";
+ }
+ // Return the output.
+ return $css;
+}
+
+/**
+ * Compress css data (filter it first!) to optimize for use on view.
+ *
+ * @param array $css_data
+ * An array of css data, as produced by @see ctools_css_disassemble()
+ * disassembler and the @see ctools_css_filter_css_data() filter.
+ *
+ * @return string $css
+ * css optimized for use.
+ */
+function ctools_css_compress($css_data) {
+ // Initialize the output.
+ $css = '';
+ // Iterate through all the statements.
+ foreach ($css_data as $selector_str => $declaration) {
+ if (empty($declaration)) {
+ // Skip this statement if filtering removed all parts of the declaration.
+ continue;
+ }
+ // Add the selectors, separating them with commas.
+ $css .= $selector_str;
+ // And, the opening curly brace.
+ $css .= "{";
+ // Iterate through all the statement properties.
+ foreach ($declaration as $property => $value) {
+ $css .= $property . ':' . $value . ';';
+ }
+ // Add the closing curly brace.
+ $css .= "}";
+ }
+ // Return the output.
+ return $css;
+}
+
+/**
+ * Disassemble the css string.
+ *
+ * Strip the css of irrelevant characters, invalid/malformed selectors and
+ * declarations, and otherwise prepare it for processing.
+ *
+ * @param string $css
+ * A string containing the css to be disassembled.
+ *
+ * @return array $disassembled_css
+ * An array of disassembled, slightly cleaned-up/formatted css statements.
+ */
+function ctools_css_disassemble($css) {
+ $disassembled_css = array();
+ // Remove comments.
+ $css = preg_replace("/\/\*(.*)?\*\//Usi", "", $css);
+ // Split out each statement. Match either a right curly brace or a semi-colon
+ // that precedes a left curly brace with no right curly brace separating them.
+ $statements = preg_split('/}|;(?=[^}]*{)/', $css);
+
+ // If we have any statements, parse them.
+ if (!empty($statements)) {
+ // Iterate through all of the statements.
+ foreach ($statements as $statement) {
+ // Get the selector(s) and declaration.
+ if (empty($statement) || !strpos($statement, '{')) {
+ continue;
+ }
+
+ list($selector_str, $declaration) = explode('{', $statement);
+
+ // If the selector exists, then disassemble it, check it, and regenerate
+ // the selector string.
+ $selector_str = empty($selector_str) ? FALSE : _ctools_css_disassemble_selector($selector_str);
+ if (empty($selector_str)) {
+ // No valid selectors. Bomb out and start the next item.
+ continue;
+ }
+
+ // Disassemble the declaration, check it and tuck it into an array.
+ if (!isset($disassembled_css[$selector_str])) {
+ $disassembled_css[$selector_str] = array();
+ }
+ $disassembled_css[$selector_str] += _ctools_css_disassemble_declaration($declaration);
+ }
+ }
+ return $disassembled_css;
+}
+
+function _ctools_css_disassemble_selector($selector_str) {
+ // Get all selectors individually.
+ $selectors = explode(",", trim($selector_str));
+ // Iterate through all the selectors, sanity check them and return if they
+ // pass. Note that this handles 0, 1, or more valid selectors gracefully.
+ foreach ($selectors as $key => $selector) {
+ // Replace un-needed characters and do a little cleanup.
+ $selector = preg_replace("/[\n|\t|\\|\s]+/", ' ', trim($selector));
+ // Make sure this is still a real selector after cleanup.
+ if (!empty($selector)) {
+ $selectors[$key] = $selector;
+ }
+ else {
+ // Selector is no good, so we scrap it.
+ unset($selectors[$key]);
+ }
+ }
+ // Check for malformed selectors; if found, we skip this declaration.
+ if (empty($selectors)) {
+ return FALSE;
+ }
+ return implode(', ', $selectors);
+}
+
+function _ctools_css_disassemble_declaration($declaration) {
+ $formatted_statement = array();
+ $propval_pairs = explode(";", $declaration);
+ // Make sure we actually have some properties to work with.
+ if (!empty($propval_pairs)) {
+ // Iterate through the remains and parse them.
+ foreach ($propval_pairs as $key => $propval_pair) {
+ // Check that we have a ':', otherwise it's an invalid pair.
+ if (strpos($propval_pair, ':') === FALSE) {
+ continue;
+ }
+ // Clean up the current property-value pair.
+ $propval_pair = preg_replace("/[\n|\t|\\|\s]+/", ' ', trim($propval_pair));
+ // Explode the remaining fragements some more, but clean them up first.
+ list($property, $value) = explode(':', $propval_pair, 2);
+ // If the property survived, toss it onto the stack.
+ if (!empty($property)) {
+ $formatted_statement[trim($property)] = trim($value);
+ }
+ }
+ }
+ return $formatted_statement;
+}
+
+/**
+ * Run disassembled $css through the filter.
+ *
+ * @param $css
+ * CSS code disassembled by ctools_dss_disassemble().
+ * @param $allowed_properties
+ * A list of properties that are allowed by the filter. If empty
+ * ctools_css_filter_default_allowed_properties() will provide the
+ * list.
+ * @param $allowed_values
+ * A list of values that are allowed by the filter. If empty
+ * ctools_css_filter_default_allowed_values() will provide the
+ * list.
+ *
+ * @return
+ * An array of disassembled, filtered CSS.
+ */
+function ctools_css_filter_css_data($css, $allowed_properties = array(), $allowed_values = array(), $allowed_values_regex = '', $disallowed_values_regex = '') {
+//function ctools_css_filter_css_data($css, &$filtered = NULL, $allowed_properties = array(), $allowed_values = array(), $allowed_values_regex = '', $disallowed_values_regex = '') {
+ // Retrieve the default list of allowed properties if none is provided.
+ $allowed_properties = !empty($allowed_properties) ? $allowed_properties : ctools_css_filter_default_allowed_properties();
+ // Retrieve the default list of allowed values if none is provided.
+ $allowed_values = !empty($allowed_values) ? $allowed_values : ctools_css_filter_default_allowed_values();
+ // Define allowed values regex if none is provided.
+ $allowed_values_regex = !empty($allowed_values_regex) ? $allowed_values_regex : '/(#[0-9a-f]+|rgb\(\d+%?,\d*%?,?\d*%?\)?|\d{0,2}\.?\d{0,2}(cm|em|ex|in|mm|pc|pt|px|%|,|\))?)/';
+ // Define disallowed url() value contents, if none is provided.
+ // $disallowed_values_regex = !empty($disallowed_values_regex) ? $disallowed_values_regex : '/[url|expression]\s*\(\s*[^\s)]+?\s*\)\s*/';
+ $disallowed_values_regex = !empty($disallowed_values_regex) ? $disallowed_values_regex : '/(url|expression)/';
+
+ foreach ($css as $selector_str => $declaration) {
+ foreach ($declaration as $property => $value) {
+ if (!in_array($property, $allowed_properties)) {
+ // $filtered['properties'][$selector_str][$property] = $value;
+ unset($css[$selector_str][$property]);
+ continue;
+ }
+ $value = str_replace('!important', '', $value);
+ if (preg_match($disallowed_values_regex, $value) || !(in_array($value, $allowed_values) || preg_match($allowed_values_regex, $value))) {
+ // $filtered['values'][$selector_str][$property] = $value;
+ unset($css[$selector_str][$property]);
+ continue;
+ }
+ }
+ }
+ return $css;
+}
+
+/**
+ * Provide a deafult list of allowed properties by the filter.
+ */
+function ctools_css_filter_default_allowed_properties() {
+ return array(
+ 'azimuth',
+ 'background',
+ 'background-color',
+ 'background-image',
+ 'background-repeat',
+ 'background-attachment',
+ 'background-position',
+ 'border',
+ 'border-top-width',
+ 'border-right-width',
+ 'border-bottom-width',
+ 'border-left-width',
+ 'border-width',
+ 'border-top-color',
+ 'border-right-color',
+ 'border-bottom-color',
+ 'border-left-color',
+ 'border-color',
+ 'border-top-style',
+ 'border-right-style',
+ 'border-bottom-style',
+ 'border-left-style',
+ 'border-style',
+ 'border-top',
+ 'border-right',
+ 'border-bottom',
+ 'border-left',
+ 'clear',
+ 'color',
+ 'cursor',
+ 'direction',
+ 'display',
+ 'elevation',
+ 'float',
+ 'font',
+ 'font-family',
+ 'font-size',
+ 'font-style',
+ 'font-variant',
+ 'font-weight',
+ 'height',
+ 'letter-spacing',
+ 'line-height',
+ 'margin',
+ 'margin-top',
+ 'margin-right',
+ 'margin-bottom',
+ 'margin-left',
+ 'overflow',
+ 'padding',
+ 'padding-top',
+ 'padding-right',
+ 'padding-bottom',
+ 'padding-left',
+ 'pause',
+ 'pause-after',
+ 'pause-before',
+ 'pitch',
+ 'pitch-range',
+ 'richness',
+ 'speak',
+ 'speak-header',
+ 'speak-numeral',
+ 'speak-punctuation',
+ 'speech-rate',
+ 'stress',
+ 'text-align',
+ 'text-decoration',
+ 'text-indent',
+ 'text-transform',
+ 'unicode-bidi',
+ 'vertical-align',
+ 'voice-family',
+ 'volume',
+ 'white-space',
+ 'width',
+ 'fill',
+ 'fill-opacity',
+ 'fill-rule',
+ 'stroke',
+ 'stroke-width',
+ 'stroke-linecap',
+ 'stroke-linejoin',
+ 'stroke-opacity',
+ );
+}
+
+/**
+ * Provide a default list of allowed values by the filter.
+ */
+function ctools_css_filter_default_allowed_values() {
+ return array(
+ 'auto',
+ 'aqua',
+ 'black',
+ 'block',
+ 'blue',
+ 'bold',
+ 'both',
+ 'bottom',
+ 'brown',
+ 'capitalize',
+ 'center',
+ 'collapse',
+ 'dashed',
+ 'dotted',
+ 'fuchsia',
+ 'gray',
+ 'green',
+ 'italic',
+ 'inherit',
+ 'left',
+ 'lime',
+ 'lowercase',
+ 'maroon',
+ 'medium',
+ 'navy',
+ 'normal',
+ 'nowrap',
+ 'olive',
+ 'pointer',
+ 'purple',
+ 'red',
+ 'right',
+ 'solid',
+ 'silver',
+ 'teal',
+ 'top',
+ 'transparent',
+ 'underline',
+ 'uppercase',
+ 'white',
+ 'yellow',
+ );
+}
+
+/**
+ * Delegated implementation of hook_flush_caches()
+ */
+function ctools_css_flush_caches() {
+ // Remove all generated files.
+ // @see http://drupal.org/node/573292
+ // file_unmanaged_delete_recursive('public://render');
+ $filedir = file_default_scheme() . '://ctools/css';
+ if (drupal_realpath($filedir) && file_exists($filedir)) {
+ // We use the @ because it's possible that files created by the webserver
+ // cannot be deleted while using drush to clear the cache. We don't really
+ // care that much about that, to be honest, so we use the @ to suppress
+ // the error message.
+ @file_unmanaged_delete_recursive($filedir);
+ }
+
+ db_delete('ctools_css_cache')->execute();
+}
diff --git a/sites/all/modules/ctools/includes/dependent.inc b/sites/all/modules/ctools/includes/dependent.inc
new file mode 100644
index 000000000..74de91971
--- /dev/null
+++ b/sites/all/modules/ctools/includes/dependent.inc
@@ -0,0 +1,181 @@
+<?php
+
+/**
+ * @file
+ * Provide dependent checkboxes that can be easily used in forms.
+ *
+ * This system will ensure that form items are invisible if the dependency is
+ * not met. What this means is that you set the #dependency of an item to a
+ * list of form ids that must be set, and the list of values that qualify.
+ *
+ * For a simple use, setting an item to be dependent upon a select box, if
+ * any of the listed values are selected, the item will be visible. Otherwise,
+ * the item will be invisible.
+ *
+ * If dependent upon multiple items, use #dependency_count = X to set the
+ * number of items that must be set in order to make this item visible. This
+ * defaults to 1. If set to 2, then at least 2 form items in the list must
+ * have their items set for the item to become visible.
+ *
+ * When hiding checkboxes and radios you need to add their id in a div
+ * manually via #prefix and #suffix since they don't have their own id. You
+ * actually need to add TWO divs because it's the parent that gets hidden.
+ *
+ * Fieldsets can not be hidden by default. Adding '#input' => TRUE to the
+ * fieldset works around that.
+ *
+ * For radios, because they are selected a little bit differently, instead of
+ * using the CSS id, use: radio:NAME where NAME is the #name of the property.
+ * This can be quickly found by looking at the HTML of the generated form, but
+ * it is usually derived from the array which contains the item. For example,
+ * $form['menu']['type'] would have a name of menu[type]. This name is the same
+ * field that is used to determine where in $form_state['values'] you will find
+ * the value of the form.
+ *
+ * The item that is dependent on, should be set to #tree = TRUE.
+ *
+ * Usage:
+ *
+ * First, ensure this tool is loaded:
+ * @code { ctools_include('dependent'); }
+ *
+ * On any form item, add
+ * - @code '#dependency' => array('id-of-form-without-the-#' => array(list, of, values, that, make, this, gadget, visible)), @endcode
+ *
+ * A fuller example, that hides the menu title when no menu is selected:
+ * @code
+ *function ctools_dependent_example() {
+ * $form = array();
+ * $form['menu'] = array(
+ * '#type' => 'fieldset',
+ * '#title' => t('Menu settings'),
+ * '#tree' => TRUE,
+ * );
+ * $form['menu']['type'] = array(
+ * '#title' => t('Menu type'),
+ * '#type' => 'radios',
+ * '#options' => array(
+ * 'none' => t('No menu entry'),
+ * 'normal' => t('Normal menu entry'),
+ * 'tab' => t('Menu tab'),
+ * 'default tab' => t('Default menu tab'),
+ * ),
+ * '#default_value' => 'none',
+ * );
+ *
+ * $form['menu']['title'] = array(
+ * '#title' => t('Title'),
+ * '#type' => 'textfield',
+ * '#default_value' => '',
+ * '#description' => t('If set to normal or tab, enter the text to use for the menu item.'),
+ * '#dependency' => array('radio:menu[type]' => array('normal', 'tab', 'default tab')),
+ * );
+ *
+ * return system_settings_form($form);
+ *}
+ * @endcode
+ *
+ * An example for hiding checkboxes using #prefix and #suffix:
+ * @code
+ *function ctools_dependent_example_checkbox() {
+ * $form = array();
+ * $form['object'] = array(
+ * '#type' => 'fieldset',
+ * '#title' => t('Select object type'),
+ * '#tree' => TRUE,
+ * );
+ * $form['object']['type'] = array(
+ * '#title' => t('Object type'),
+ * '#type' => 'radios',
+ * '#options' => array(
+ * 'view' => t('View'),
+ * 'node' => t('Node'),
+ * 'field' => t('Field'),
+ * 'term' => t('Term'),
+ * ),
+ * '#default_value' => 'view',
+ * );
+ *
+ * $form['object']['elements'] = array(
+ * '#title' => t('Select the elements to load from the node.'),
+ * '#type' => 'checkboxes',
+ * '#prefix' => '<div id="edit-elements-wrapper"><div id="edit-elements">',
+ * '#suffix' => '</div></div>',
+ * '#dependency' => array('radio:menu[type]' => array('node')),
+ * '#options' => array(
+ * 'body' => t('Body'),
+ * 'fields' => t('Fields'),
+ * 'taxonomy' => t('Taxonomy'),
+ * ),
+ * '#default_value' => array('body', 'fields'),
+ * );
+ *
+ * return system_settings_form($form);
+ *}
+ * @endcode
+ *
+ * Deprecated:
+ *
+ * You no longer use ctools_dependent_process(), and it should be removed
+ * completely.
+ *
+ * If you have a form element which isn't listed in ctools_dependent_element_info_alter
+ * you have to add [#pre_render'][] => 'ctools_dependent_pre_render' to your form.
+ */
+
+/**
+ * Process callback to add dependency to form items.
+ *
+ */
+function ctools_dependent_process($element, &$form_state, &$form) {
+ return $element;
+}
+
+function ctools_dependent_pre_render($element) {
+ // Preprocess only items with #dependency set.
+ if (isset($element['#dependency'])) {
+ if (!isset($element['#dependency_count'])) {
+ $element['#dependency_count'] = 1;
+ }
+ if (!isset($element['#dependency_type'])) {
+ $element['#dependency_type'] = 'hide';
+ }
+
+ $js = array(
+ 'values' => $element['#dependency'],
+ 'num' => $element['#dependency_count'],
+ 'type' => $element['#dependency_type'],
+ );
+
+ // Add a additional wrapper id around fieldsets, textareas to support depedency on it.
+ if (in_array($element['#type'], array('textarea', 'fieldset', 'text_format'))) {
+ $element['#theme_wrappers'][] = 'container';
+ $element['#attributes']['id'] = $element['#id'] . '-wrapper';
+ }
+
+ // Text formats need to unset the dependency on the textarea
+ // or it gets applied twice.
+ if ($element['#type'] == 'text_format') {
+ unset($element['value']['#dependency']);
+ }
+
+ $element['#attached']['js'][] = ctools_attach_js('dependent');
+ $options['CTools']['dependent'][$element['#id']] = $js;
+ $element['#attached']['js'][] = array('type' => 'setting', 'data' => $options);
+
+ }
+
+ return $element;
+}
+
+/**
+ * CTools alters the element_info to be able to add #process functions to
+ * every major form element to make it much more handy to use #dependency,
+ * because you don't have to add #process.
+ */
+function ctools_dependent_element_info_alter(&$type) {
+ $form_elements = array('checkbox', 'checkboxes', 'date', 'fieldset', 'item', 'machine_name', 'markup', 'radio', 'radios', 'select', 'textarea', 'textfield', 'text_format');
+ foreach ($form_elements as $element) {
+ $type[$element]['#pre_render'][] = 'ctools_dependent_pre_render';
+ }
+}
diff --git a/sites/all/modules/ctools/includes/dropbutton.theme.inc b/sites/all/modules/ctools/includes/dropbutton.theme.inc
new file mode 100644
index 000000000..fcdd5a37a
--- /dev/null
+++ b/sites/all/modules/ctools/includes/dropbutton.theme.inc
@@ -0,0 +1,143 @@
+<?php
+
+/**
+ * @file
+ * Provide a javascript based dropbutton menu.
+ *
+ * An example are the edit/disable/delete links on the views listing page.
+ *
+ * The dropbutton menu will show up as a button with a clickable twisty pointer
+ * to the right. When clicked the button will expand, showing the list of links.
+ *
+ * The dropbutton will stay open until either the user has moved the mouse
+ * away from the box for > .5 seconds, or can be immediately closed by
+ * clicking the twisty again. The code is smart enough that if the mouse
+ * moves away and then back within the .5 second window, it will not
+ * re-close.
+ *
+ * Multiple dropbuttons can be placed per page.
+ *
+ * If only one link is passed to the theme function, the function will render
+ * a ctools-button with no twisty. The twisty is only rendered when 2 or more
+ * links are provided. The wrapping element will be classed with both
+ * ctools-button and ctools-dropbutton when a dropbutton is rendered.
+ *
+ * If the user does not have javascript enabled, the link will not appear,
+ * and instead by default the list of links will appear as a normal inline
+ * list.
+ *
+ * The menu is minimally styled by default, and to make it look different
+ * will require your own CSS. You can apply your own class to the
+ * dropbutton to help style it.
+ *
+ * The twisty is rendered as a border on a widthless and heightless element.
+ * There is no image for the twisty.
+ * The color of the twisty is the color of the link by default. To adjust the
+ * size of the twisty, adjust the border widths on .ctools-twisty. The adjust
+ * the color of the twisty, assign a new color to the .ctools-button class or
+ * assign a color to .ctools-twisty. You shouldn't need to adjust border-color.
+ *
+ * Use the theme() function to render dropbutton e.g.
+ * theme('links__ctools_dropbutton', array()) where array contains a renderable
+ * array of links.
+ */
+
+/**
+ * Delegated implementation of hook_theme()
+ */
+function ctools_dropbutton_theme(&$items) {
+ $items['links__ctools_dropbutton'] = array(
+ 'variables' => array('title' => NULL, 'links' => NULL, 'image' => FALSE, 'class' => NULL),
+ 'file' => 'includes/dropbutton.theme.inc',
+ );
+}
+
+/**
+ * Create a dropbutton menu.
+ *
+ * @param $title
+ * The text to place in the clickable area to activate the dropbutton. This
+ * text is indented to -9999px by default.
+ * @param $links
+ * A list of links to provide within the dropbutton, suitable for use
+ * in via Drupal's theme('links').
+ * @param $image
+ * If true, the dropbutton link is an image and will not get extra decorations
+ * that a text dropbutton link will.
+ * @param $class
+ * An optional class to add to the dropbutton's container div to allow you
+ * to style a single dropbutton however you like without interfering with
+ * other dropbuttons.
+ */
+function theme_links__ctools_dropbutton($vars) {
+ // Check to see if the number of links is greater than 1;
+ // otherwise just treat this like a button.
+ if (!empty($vars['links'])) {
+ $is_drop_button = (count($vars['links']) > 1);
+
+ // Add needed files
+ if ($is_drop_button) {
+ ctools_add_js('dropbutton');
+ ctools_add_css('dropbutton');
+ }
+ ctools_add_css('button');
+
+ // Provide a unique identifier for every button on the page.
+ static $id = 0;
+ $id++;
+
+ // Wrapping div
+ $class = 'ctools-no-js';
+ $class .= ($is_drop_button) ? ' ctools-dropbutton' : '';
+ $class .= ' ctools-button';
+ if (!empty($vars['class'])) {
+ $class .= ($vars['class']) ? (' ' . implode(' ', $vars['class'])) : '';
+ }
+
+ $output = '';
+
+ $output .= '<div class="' . $class . '" id="ctools-button-' . $id . '">';
+
+ // Add a twisty if this is a dropbutton
+ if ($is_drop_button) {
+ $vars['title'] = ($vars['title'] ? check_plain($vars['title']) : t('open'));
+
+ $output .= '<div class="ctools-link">';
+ if ($vars['image']) {
+ $output .= '<a href="#" class="ctools-twisty ctools-image">' . $vars['title'] . '</a>';
+ }
+ else {
+ $output .= '<a href="#" class="ctools-twisty ctools-text">' . $vars['title'] . '</a>';
+ }
+ $output .= '</div>'; // ctools-link
+ }
+
+ // The button content
+ $output .= '<div class="ctools-content">';
+
+ // Check for attributes. theme_links expects an array().
+ $vars['attributes'] = (!empty($vars['attributes'])) ? $vars['attributes'] : array();
+
+ // Remove the inline and links classes from links if they exist.
+ // These classes are added and styled by Drupal core and mess up the default
+ // styling of any link list.
+ if (!empty($vars['attributes']['class'])) {
+ $classes = $vars['attributes']['class'];
+ foreach ($classes as $key => $class) {
+ if ($class === 'inline' || $class === 'links') {
+ unset($vars['attributes']['class'][$key]);
+ }
+ }
+ }
+
+ // Call theme_links to render the list of links.
+ $output .= theme_links(array('links' => $vars['links'], 'attributes' => $vars['attributes'], 'heading' => ''));
+ $output .= '</div>'; // ctools-content
+ $output .= '</div>'; // ctools-dropbutton
+ return $output;
+ }
+ else {
+ return '';
+ }
+}
+
diff --git a/sites/all/modules/ctools/includes/dropdown.theme.inc b/sites/all/modules/ctools/includes/dropdown.theme.inc
new file mode 100644
index 000000000..7e748f5e9
--- /dev/null
+++ b/sites/all/modules/ctools/includes/dropdown.theme.inc
@@ -0,0 +1,90 @@
+<?php
+
+/**
+ * @file
+ * Provide a javascript based dropdown menu.
+ *
+ * An example are the dropdown settings in the panels ui, like for adding
+ * new panes.
+ *
+ * The dropdown menu will show up as a clickable link; when clicked,
+ * a small menu will slide down beneath it, showing the list of links.
+ *
+ * The dropdown will stay open until either the user has moved the mouse
+ * away from the box for > .5 seconds, or can be immediately closed by
+ * clicking the link again. The code is smart enough that if the mouse
+ * moves away and then back within the .5 second window, it will not
+ * re-close.
+ *
+ * Multiple dropdowns can be placed per page.
+ *
+ * If the user does not have javascript enabled, the link will not appear,
+ * and instead by default the list of links will appear as a normal inline
+ * list.
+ *
+ * The menu is heavily styled by default, and to make it look different
+ * will require a little bit of CSS. You can apply your own class to the
+ * dropdown to help ensure that your CSS can override the dropdown's CSS.
+ *
+ * In particular, the text, link, background and border colors may need to
+ * be changed. Please see dropdown.css for information about the existing
+ * styling.
+ */
+
+/**
+ * Delegated implementation of hook_theme()
+ */
+function ctools_dropdown_theme(&$items) {
+ $items['ctools_dropdown'] = array(
+ 'variables' => array('title' => NULL, 'links' => NULL, 'image' => FALSE, 'class' => ''),
+ 'file' => 'includes/dropdown.theme.inc',
+ );
+}
+
+/**
+ * Create a dropdown menu.
+ *
+ * @param $title
+ * The text to place in the clickable area to activate the dropdown.
+ * @param $links
+ * A list of links to provide within the dropdown, suitable for use
+ * in via Drupal's theme('links').
+ * @param $image
+ * If true, the dropdown link is an image and will not get extra decorations
+ * that a text dropdown link will.
+ * @param $class
+ * An optional class to add to the dropdown's container div to allow you
+ * to style a single dropdown however you like without interfering with
+ * other dropdowns.
+ */
+function theme_ctools_dropdown($vars) {
+ // Provide a unique identifier for every dropdown on the page.
+ static $id = 0;
+ $id++;
+
+ $class = 'ctools-dropdown-no-js ctools-dropdown' . ($vars['class'] ? (' ' . $vars['class']) : '');
+
+ ctools_add_js('dropdown');
+ ctools_add_css('dropdown');
+
+ $output = '';
+
+ $output .= '<div class="' . $class . '" id="ctools-dropdown-' . $id . '">';
+ $output .= '<div class="ctools-dropdown-link-wrapper">';
+ if ($vars['image']) {
+ $output .= '<a href="#" class="ctools-dropdown-link ctools-dropdown-image-link">' . $vars['title'] . '</a>';
+ }
+ else {
+ $output .= '<a href="#" class="ctools-dropdown-link ctools-dropdown-text-link">' . check_plain($vars['title']) . '</a>';
+ }
+
+ $output .= '</div>'; // wrapper
+ $output .= '<div class="ctools-dropdown-container-wrapper">';
+ $output .= '<div class="ctools-dropdown-container">';
+ $output .= theme_links(array('links' => $vars['links'], 'attributes' => array(), 'heading' => ''));
+ $output .= '</div>'; // container
+ $output .= '</div>'; // container wrapper
+ $output .= '</div>'; // dropdown
+ return $output;
+}
+
diff --git a/sites/all/modules/ctools/includes/entity-access.inc b/sites/all/modules/ctools/includes/entity-access.inc
new file mode 100644
index 000000000..972cf13b0
--- /dev/null
+++ b/sites/all/modules/ctools/includes/entity-access.inc
@@ -0,0 +1,150 @@
+<?php
+
+/**
+ * @file
+ * Provides various callbacks for the whole core module integration.
+ * This is a copy of Entity API's functionality for use when Entity API isn't
+ * Enabled, and only works on view functions.
+ */
+
+/**
+ * Core hack to include entity api-esque 'access callback' functions to core
+ * entities without needing to rely on entity api.
+ * Exception: We don't touch file entity. You must have entity API enabled to
+ * view files.
+ */
+function _ctools_entity_access(&$entity_info, $entity_type) {
+ // If the access callback is already set, don't change anything.
+ if (isset($entity_info['access callback'])) {
+ return;
+ }
+
+ switch ($entity_type) {
+ case 'node':
+ // Sad panda, we don't use Entity API, lets manually add access callbacks.
+ $entity_info['access callback'] = 'ctools_metadata_no_hook_node_access';
+ break;
+ case 'user':
+ $entity_info['access callback'] = 'ctools_metadata_user_access';
+ break;
+ case 'comment':
+ if (module_exists('comment')) {
+ $entity_info['access callback'] = 'ctools_metadata_comment_access';
+ }
+ break;
+ case 'taxonomy_term':
+ if (module_exists('taxonomy')) {
+ $entity_info['access callback'] = 'ctools_metadata_taxonomy_access';
+ }
+ break;
+ case 'taxonomy_vocabulary':
+ if (module_exists('taxonomy')) {
+ $entity_info['access callback'] = 'ctools_metadata_taxonomy_access';
+ }
+ break;
+ }
+}
+
+/**
+ * Access callback for the node entity.
+ *
+ * This function does not implement hook_node_access(), thus it may not be
+ * called ctools_metadata_node_access().
+ *
+ * @see entity_access()
+ *
+ * @param $op
+ * The operation being performed. One of 'view', 'update', 'create' or
+ * 'delete'.
+ * @param $node
+ * A node to check access for. Must be a node object. Must have nid,
+ * except in the case of 'create' operations.
+ * @param $account
+ * The user to check for. Leave it to NULL to check for the global user.
+ *
+ * @throws EntityMalformedException
+ *
+ * @return boolean
+ * TRUE if access is allowed, FALSE otherwise.
+ */
+function ctools_metadata_no_hook_node_access($op, $node = NULL, $account = NULL) {
+ // First deal with the case where a $node is provided.
+ if (isset($node)) {
+ // If a non-default revision is given, incorporate revision access.
+ $default_revision = node_load($node->nid);
+ if ($node->vid !== $default_revision->vid) {
+ return _node_revision_access($node, $op, $account);
+ }
+ else {
+ return node_access($op, $node, $account);
+ }
+ }
+ // No node is provided. Check for access to all nodes.
+ if (user_access('bypass node access', $account)) {
+ return TRUE;
+ }
+ if (!user_access('access content', $account)) {
+ return FALSE;
+ }
+ if ($op == 'view' && node_access_view_all_nodes($account)) {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Access callback for the user entity.
+ */
+function ctools_metadata_user_access($op, $entity = NULL, $account = NULL, $entity_type) {
+ $account = isset($account) ? $account : $GLOBALS['user'];
+ // Grant access to the users own user account and to the anonymous one.
+ if (isset($entity) && $op != 'delete' && (($entity->uid == $account->uid && $entity->uid) || (!$entity->uid && $op == 'view'))) {
+ return TRUE;
+ }
+ if (user_access('administer users', $account) || user_access('access user profiles', $account) && $op == 'view' && $entity->status) {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Access callback for the comment entity.
+ */
+function ctools_metadata_comment_access($op, $entity = NULL, $account = NULL) {
+ // When determining access to a comment, if comment has an associated node,
+ // the user must be able to view the node in order to access the comment.
+ if (isset($entity->nid)) {
+ if (!node_access('view', node_load($entity->nid), $account)) {
+ return FALSE;
+ }
+ }
+
+ // Comment administrators are allowed to perform all operations on all
+ // comments.
+ if (user_access('administer comments', $account)) {
+ return TRUE;
+ }
+
+ // Unpublished comments can never be accessed by non-admins.
+ if (isset($entity->status) && $entity->status == COMMENT_NOT_PUBLISHED) {
+ return FALSE;
+ }
+
+ if (user_access('access comments', $account) && $op == 'view') {
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/**
+ * Access callback for the taxonomy entities.
+ */
+function ctools_metadata_taxonomy_access($op, $entity = NULL, $account = NULL, $entity_type) {
+ if ($entity_type == 'taxonomy_vocabulary') {
+ return user_access('administer taxonomy', $account);
+ }
+ if (user_access('administer taxonomy', $account) || user_access('access content', $account) && $op == 'view') {
+ return TRUE;
+ }
+ return FALSE;
+}
diff --git a/sites/all/modules/ctools/includes/export-ui.inc b/sites/all/modules/ctools/includes/export-ui.inc
new file mode 100644
index 000000000..16e57d6ed
--- /dev/null
+++ b/sites/all/modules/ctools/includes/export-ui.inc
@@ -0,0 +1,475 @@
+<?php
+
+/**
+ * @file
+ * Provide a tool for creating UIs for exportable objects.
+ *
+ * See Advanced Help for documentation.
+ */
+
+/**
+ * Process an export-ui plugin to provide it with defaults.
+ */
+function ctools_export_ui_process(&$plugin, $info) {
+ ctools_include('export');
+
+ $plugin += array(
+ 'has menu' => TRUE,
+ 'title' => $plugin['name'],
+ 'export' => array(),
+ 'allowed operations' => array(),
+ 'menu' => array(),
+ 'redirect' => array(),
+ 'form' => array(),
+ 'strings' => array(),
+ 'list' => NULL,
+ 'access' => 'administer site configuration',
+ );
+
+ // Provide CRUD access defaults based on the base 'access' setting:
+ $plugin += array(
+ 'create access' => $plugin['access'],
+ 'delete access' => $plugin['access'],
+ );
+
+ if (empty($plugin['has menu'])) {
+ return;
+ }
+
+ // The following keys are required and the plugin cannot be processed
+ // without them.
+ $keys = array(
+ 'title singular',
+ 'title plural',
+ 'title singular proper',
+ 'title plural proper',
+ 'schema',
+ );
+
+ foreach ($keys as $key) {
+ if (empty($plugin[$key])) {
+ drupal_set_message(t('The plugin definition of @plugin is missing the %key key.', array('%key' => $key, '@plugin' => $plugin['name'])), 'error');
+ }
+ }
+
+ // If we're on the modules page and building a menu, there is a design flaw
+ // in Drupal core that causes modules to be installed but the schema does
+ // not become available until AFTER menu rebuild. This helps smooth that
+ // out. This is a HACK but it should work:
+ $schema = ctools_export_get_schema($plugin['schema']);
+
+ if (empty($schema)) {
+ // If we're updating the schema may not have been read yet, so don't report this error in that case.
+ if (!defined('MAINTENANCE_MODE')) {
+ drupal_set_message(t('The plugin definition of @plugin cannot locate schema %schema.', array('%schema' => $plugin['schema'], '@plugin' => $plugin['name'])), 'error');
+ }
+ return;
+ }
+
+ if (empty($schema['export'])) {
+ drupal_set_message(t('The plugin definition of @plugin uses %schema, but it has no export section.', array('%schema' => $plugin['schema'], '@plugin' => $plugin['name'])), 'error');
+ return;
+ }
+ $plugin['export'] += $schema['export'];
+
+ $plugin['export'] += array(
+ // Add the identifier key from the schema so we don't have to call
+ // ctools_export_get_schema() just for that.
+ 'key' => $schema['export']['key'],
+ );
+
+ // Add some default fields that appear often in exports
+ // If these use different keys they can easily be specified in the
+ // $plugin.
+
+ if (empty($plugin['export']['admin_title']) && !empty($schema['fields']['admin_title'])) {
+ $plugin['export']['admin_title'] = 'admin_title';
+ }
+ if (empty($plugin['export']['admin_description']) && !empty($schema['fields']['admin_description'])) {
+ $plugin['export']['admin_description'] = 'admin_description';
+ }
+
+ // Define allowed operations, and the name of the operations.
+ $plugin['allowed operations'] += array(
+ 'edit' => array('title' => t('Edit')),
+ 'enable' => array('title' => t('Enable'), 'ajax' => TRUE, 'token' => TRUE),
+ 'disable' => array('title' => t('Disable'), 'ajax' => TRUE, 'token' => TRUE),
+ 'revert' => array('title' => t('Revert')),
+ 'delete' => array('title' => t('Delete')),
+ 'clone' => array('title' => t('Clone')),
+ 'import' => array('title' => t('Import')),
+ 'export' => array('title' => t('Export')),
+ );
+
+ $plugin['menu'] += array(
+ 'menu item' => str_replace(' ', '-', $plugin['name']),
+ 'menu prefix' => 'admin/structure',
+ 'menu title' => $plugin['title'],
+ 'menu description' => '',
+ );
+ $base_path = ctools_export_ui_plugin_base_path($plugin);
+ $prefix_count = count(explode('/', $plugin['menu']['menu prefix']));
+
+ $plugin['menu'] += array(
+ // Default menu items that should be declared.
+ 'items' => array(),
+ );
+
+ $plugin['menu']['items'] += array(
+ 'list callback' => array(),
+ 'list' => array(),
+ 'add' => array(),
+ 'edit callback' => array(),
+ 'edit' => array(),
+ );
+
+ $plugin['menu']['items']['list callback'] += array(
+ 'path' => '',
+ // Menu items are translated by the menu system.
+ // TODO: We need more flexibility in title. The title of the admin page
+ // is not necessarily the title of the object, plus we need
+ // plural, singular, proper, not proper, etc.
+ 'title' => $plugin['menu']['menu title'],
+ 'description' => $plugin['menu']['menu description'],
+ 'page callback' => 'ctools_export_ui_switcher_page',
+ 'page arguments' => array($plugin['name'], 'list'),
+ 'access callback' => 'ctools_export_ui_task_access',
+ 'access arguments' => array($plugin['name'], 'list'),
+ 'type' => MENU_NORMAL_ITEM,
+ );
+
+ $plugin['menu']['items']['list'] += array(
+ 'path' => 'list',
+ 'title' => 'List',
+ 'page callback' => 'ctools_export_ui_switcher_page',
+ 'page arguments' => array($plugin['name'], 'list'),
+ 'access callback' => 'ctools_export_ui_task_access',
+ 'access arguments' => array($plugin['name'], 'list'),
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ );
+
+ $plugin['menu']['items']['add'] += array(
+ 'path' => 'add',
+ 'title' => 'Add',
+ 'page callback' => 'ctools_export_ui_switcher_page',
+ 'page arguments' => array($plugin['name'], 'add'),
+ 'access callback' => 'ctools_export_ui_task_access',
+ 'access arguments' => array($plugin['name'], 'add'),
+ 'type' => MENU_LOCAL_ACTION,
+ );
+
+ $plugin['menu']['items']['edit callback'] += array(
+ 'path' => 'list/%ctools_export_ui',
+ 'page callback' => 'ctools_export_ui_switcher_page',
+ 'page arguments' => array($plugin['name'], 'edit', $prefix_count + 2),
+ 'load arguments' => array($plugin['name']),
+ 'access callback' => 'ctools_export_ui_task_access',
+ 'access arguments' => array($plugin['name'], 'edit', $prefix_count + 2),
+ 'type' => MENU_CALLBACK,
+ );
+
+ $plugin['menu']['items']['edit'] += array(
+ 'path' => 'list/%ctools_export_ui/edit',
+ 'title' => 'Edit',
+ 'page callback' => 'ctools_export_ui_switcher_page',
+ 'page arguments' => array($plugin['name'], 'edit', $prefix_count + 2),
+ 'load arguments' => array($plugin['name']),
+ 'access callback' => 'ctools_export_ui_task_access',
+ 'access arguments' => array($plugin['name'], 'edit', $prefix_count + 2),
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => -10,
+ );
+
+ if ($plugin['allowed operations']['import']) {
+ $plugin['menu']['items'] += array('import' => array());
+ $plugin['menu']['items']['import'] += array(
+ 'path' => 'import',
+ 'title' => 'Import',
+ 'page callback' => 'ctools_export_ui_switcher_page',
+ 'page arguments' => array($plugin['name'], 'import'),
+ 'access callback' => 'ctools_export_ui_task_access',
+ 'access arguments' => array($plugin['name'], 'import'),
+ 'type' => MENU_LOCAL_ACTION,
+ );
+ }
+
+ if ($plugin['allowed operations']['export']) {
+ $plugin['menu']['items'] += array('export' => array());
+ $plugin['menu']['items']['export'] += array(
+ 'path' => 'list/%ctools_export_ui/export',
+ 'title' => 'Export',
+ 'page callback' => 'ctools_export_ui_switcher_page',
+ 'page arguments' => array($plugin['name'], 'export', $prefix_count + 2),
+ 'load arguments' => array($plugin['name']),
+ 'access callback' => 'ctools_export_ui_task_access',
+ 'access arguments' => array($plugin['name'], 'export', $prefix_count + 2),
+ 'type' => MENU_LOCAL_TASK,
+ );
+ }
+
+ if ($plugin['allowed operations']['revert']) {
+ $plugin['menu']['items'] += array('revert' => array());
+ $plugin['menu']['items']['revert'] += array(
+ 'path' => 'list/%ctools_export_ui/revert',
+ 'title' => 'Revert',
+ 'page callback' => 'ctools_export_ui_switcher_page',
+ // Note: Yes, 'delete' op is correct.
+ 'page arguments' => array($plugin['name'], 'delete', $prefix_count + 2),
+ 'load arguments' => array($plugin['name']),
+ 'access callback' => 'ctools_export_ui_task_access',
+ 'access arguments' => array($plugin['name'], 'revert', $prefix_count + 2),
+ 'type' => MENU_CALLBACK,
+ );
+ }
+
+ if ($plugin['allowed operations']['delete']) {
+ $plugin['menu']['items'] += array('delete' => array());
+ $plugin['menu']['items']['delete'] += array(
+ 'path' => 'list/%ctools_export_ui/delete',
+ 'title' => 'Delete',
+ 'page callback' => 'ctools_export_ui_switcher_page',
+ 'page arguments' => array($plugin['name'], 'delete', $prefix_count + 2),
+ 'load arguments' => array($plugin['name']),
+ 'access callback' => 'ctools_export_ui_task_access',
+ 'access arguments' => array($plugin['name'], 'delete', $prefix_count + 2),
+ 'type' => MENU_CALLBACK,
+ );
+ }
+
+ if ($plugin['allowed operations']['clone']) {
+ $plugin['menu']['items'] += array('clone' => array());
+ $plugin['menu']['items']['clone'] += array(
+ 'path' => 'list/%ctools_export_ui/clone',
+ 'title' => 'Clone',
+ 'page callback' => 'ctools_export_ui_switcher_page',
+ 'page arguments' => array($plugin['name'], 'clone', $prefix_count + 2),
+ 'load arguments' => array($plugin['name']),
+ 'access callback' => 'ctools_export_ui_task_access',
+ 'access arguments' => array($plugin['name'], 'clone', $prefix_count + 2),
+ 'type' => MENU_CALLBACK,
+ );
+ }
+
+ if ($plugin['allowed operations']['enable']) {
+ $plugin['menu']['items'] += array('enable' => array());
+ $plugin['menu']['items']['enable'] += array(
+ 'path' => 'list/%ctools_export_ui/enable',
+ 'title' => 'Enable',
+ 'page callback' => 'ctools_export_ui_switcher_page',
+ 'page arguments' => array($plugin['name'], 'enable', $prefix_count + 2),
+ 'load arguments' => array($plugin['name']),
+ 'access callback' => 'ctools_export_ui_task_access',
+ 'access arguments' => array($plugin['name'], 'enable', $prefix_count + 2),
+ 'type' => MENU_CALLBACK,
+ );
+ }
+
+ if ($plugin['allowed operations']['disable']) {
+ $plugin['menu']['items'] += array('disable' => array());
+ $plugin['menu']['items']['disable'] += array(
+ 'path' => 'list/%ctools_export_ui/disable',
+ 'title' => 'Disable',
+ 'page callback' => 'ctools_export_ui_switcher_page',
+ 'page arguments' => array($plugin['name'], 'disable', $prefix_count + 2),
+ 'load arguments' => array($plugin['name']),
+ 'access callback' => 'ctools_export_ui_task_access',
+ 'access arguments' => array($plugin['name'], 'disable', $prefix_count + 2),
+ 'type' => MENU_CALLBACK,
+ );
+ }
+
+ // Define some redirects that should happen after edit/add/clone/delete operations.
+ $plugin['redirect'] += array(
+ 'add' => $base_path,
+ 'clone' => $base_path,
+ 'edit' => $base_path,
+ 'delete' => $base_path,
+ 'revert' => $base_path,
+ 'import' => $base_path,
+ );
+
+ // Define form elements.
+ $plugin['form'] += array(
+ 'settings' => function_exists($plugin['name'] . '_form') ? $plugin['name'] . '_form' : '',
+ 'validate' => function_exists($plugin['name'] . '_form_validate') ? $plugin['name'] . '_form_validate' : '',
+ 'submit' => function_exists($plugin['name'] . '_form_submit') ? $plugin['name'] . '_form_submit' : '',
+ );
+
+ // Define strings.
+
+ // For all strings, %title may be filled in at a later time via str_replace
+ // since we do not know the title now.
+ $plugin['strings'] += array(
+ 'title' => array(),
+ 'confirmation' => array(),
+ 'help' => array(),
+ 'message' => array(),
+ );
+
+ // Strings used in drupal_set_title().
+ $plugin['strings']['title'] += array(
+ 'add' => t('Add a new @plugin', array('@plugin' => $plugin['title singular'])),
+ // The "%title" will be replaced in ctools_export_ui_form(), as in this
+ // stage we dont have the specific exportable object.
+ 'edit' => t('Edit @plugin %title', array('@plugin' => $plugin['title singular'])),
+ 'clone' => t('Clone @plugin %title', array('@plugin' => $plugin['title singular'])),
+
+ 'import' => t('Import @plugin', array('@plugin' => $plugin['title singular'])),
+ 'export' => t('Export @plugin %title', array('@plugin' => $plugin['title singular'])),
+ );
+
+ // Strings used in confirmation pages.
+ $plugin['strings']['confirmation'] += array(
+ 'revert' => array(),
+ 'delete' => array(),
+ 'add' => array(),
+ 'edit' => array(),
+ );
+
+ $plugin['strings']['confirmation']['revert'] += array(
+ 'question' => t('Are you sure you want to revert %title?'),
+ 'information' => t('This action will permanently remove any customizations made to this item.'),
+ 'success' => t('The item has been reverted.'),
+ );
+
+ $plugin['strings']['confirmation']['delete'] += array(
+ 'question' => t('Are you sure you want to delete %title?'),
+ 'information' => t('This action will permanently remove this item from your database..'),
+ 'success' => t('The item has been deleted.'),
+ );
+
+ $plugin['strings']['confirmation']['add'] += array(
+ 'success' => t('%title has been created.'),
+ 'fail' => t('%title could not be created.'),
+ );
+
+ $plugin['strings']['confirmation']['edit'] += array(
+ 'success' => t('%title has been updated.'),
+ 'fail' => t('%title could not be updated.'),
+ );
+
+ // Strings used in $forms.
+ $plugin['strings']['help'] += array(
+ 'import' => t('You can import an exported definition by pasting the exported object code into the field below.'),
+ );
+
+ // Strings used in drupal_set_message().
+ $plugin['strings']['message'] += array(
+ 'enable' => t('@plugin %title was enabled.', array('@plugin' => $plugin['title singular proper'])),
+ 'disable' => t('@plugin %title was disabled.', array('@plugin' => $plugin['title singular proper'])),
+ 'no items' => t('There are no @titles to display.', array('@titles' => $plugin['title plural'])),
+ );
+}
+
+/**
+ * Get the class to handle creating a list of exportable items.
+ *
+ * If a plugin does not define a lister class at all, then the default
+ * lister class will be used.
+ *
+ * @return
+ * Either the lister class or FALSE if one could not be had.
+ */
+function ctools_export_ui_get_handler($plugin) {
+ $cache = &drupal_static(__FUNCTION__, array());
+ if (empty($cache[$plugin['name']])) {
+ // If a list class is not specified by the plugin, fall back to the
+ // default ctools_export_ui plugin instead.
+ if (empty($plugin['handler'])) {
+ $default = ctools_get_export_ui('ctools_export_ui');
+ $class = ctools_plugin_get_class($default, 'handler');
+ }
+ else {
+ $class = ctools_plugin_get_class($plugin, 'handler');
+ }
+
+ if ($class) {
+ $cache[$plugin['name']] = new $class();
+ $cache[$plugin['name']]->init($plugin);
+ }
+ }
+ return !empty($cache[$plugin['name']]) ? $cache[$plugin['name']] : FALSE;
+}
+
+/**
+ * Get the base path from a plugin.
+ *
+ * @param $plugin
+ * The plugin.
+ *
+ * @return
+ * The menu path to the plugin's list.
+ */
+function ctools_export_ui_plugin_base_path($plugin) {
+ return $plugin['menu']['menu prefix'] . '/' . $plugin['menu']['menu item'];
+}
+
+/**
+ * Get the path to a specific menu item from a plugin.
+ *
+ * @param $plugin
+ * The plugin name.
+ * @param $item_id
+ * The id in the menu items from the plugin.
+ * @param $export_key
+ * The export key of the item being edited, if it exists.
+ * @return
+ * The menu path to the plugin's list.
+ */
+function ctools_export_ui_plugin_menu_path($plugin, $item_id, $export_key = NULL) {
+ $path = $plugin['menu']['items'][$item_id]['path'];
+ if ($export_key) {
+ $path = str_replace('%ctools_export_ui', $export_key, $path);
+ }
+ return ctools_export_ui_plugin_base_path($plugin) . '/' . $path;
+}
+
+/**
+ * Helper function to include CTools plugins and get an export-ui exportable.
+ *
+ * @param $plugin_name
+ * The plugin that should be laoded.
+ */
+function ctools_get_export_ui($plugin_name) {
+ ctools_include('plugins');
+ return ctools_get_plugins('ctools', 'export_ui', $plugin_name);
+
+}
+
+/**
+ * Helper function to include CTools plugins and get all export-ui exportables.
+ */
+function ctools_get_export_uis() {
+ ctools_include('plugins');
+ return ctools_get_plugins('ctools', 'export_ui');
+}
+
+/**
+ * Main page callback to manipulate exportables.
+ *
+ * This simply loads the object defined in the plugin and hands it off to
+ * a method based upon the name of the operation in use. This can easily
+ * be used to add more ops.
+ */
+function ctools_export_ui_switcher_page($plugin_name, $op) {
+ $args = func_get_args();
+ $js = !empty($_REQUEST['js']);
+
+ // Load the $plugin information
+ $plugin = ctools_get_export_ui($plugin_name);
+ $handler = ctools_export_ui_get_handler($plugin);
+
+ if ($handler) {
+ $method = $op . '_page';
+ if (method_exists($handler, $method)) {
+ // replace the first two arguments:
+ $args[0] = $js;
+ $args[1] = $_POST;
+ return call_user_func_array(array($handler, $method), $args);
+ }
+ }
+ else {
+ return t('Configuration error. No handler found.');
+ }
+}
diff --git a/sites/all/modules/ctools/includes/export-ui.menu.inc b/sites/all/modules/ctools/includes/export-ui.menu.inc
new file mode 100644
index 000000000..d27bf157a
--- /dev/null
+++ b/sites/all/modules/ctools/includes/export-ui.menu.inc
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * Delegated implementation of hook_menu().
+ */
+function ctools_export_ui_menu(&$items) {
+ ctools_include('export-ui');
+
+ // If a menu rebuild is triggered because of module enable/disable,
+ // this might be out of date. Reset the cache.
+ ctools_include('plugins');
+ ctools_get_plugins_reset();
+
+ foreach (ctools_get_export_uis() as $plugin) {
+ // We also need to make sure that the module hasn't been disabled. During
+ // the disable process, the module's plugins still still appear.
+ if ($plugin['has menu'] && module_exists($plugin['module'])) {
+ $handler = ctools_export_ui_get_handler($plugin);
+ if ($handler) {
+ $handler->hook_menu($items);
+ }
+ }
+ }
+}
diff --git a/sites/all/modules/ctools/includes/export-ui.plugin-type.inc b/sites/all/modules/ctools/includes/export-ui.plugin-type.inc
new file mode 100644
index 000000000..f1a751095
--- /dev/null
+++ b/sites/all/modules/ctools/includes/export-ui.plugin-type.inc
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @file
+ * Contains plugin type registration information for the context tool.
+ *
+ * Don't actually need to declare anything for these plugin types right now,
+ * apart from the fact that they exist. So, an empty array.
+ */
+
+function ctools_export_ui_plugin_type(&$items) {
+ $items['export_ui'] = array(
+ 'process' => array(
+ 'function' => 'ctools_export_ui_process',
+ 'file' => 'export-ui.inc',
+ 'path' => drupal_get_path('module', 'ctools') . '/includes',
+ ),
+ 'classes' => array('handler'),
+ );
+} \ No newline at end of file
diff --git a/sites/all/modules/ctools/includes/export.inc b/sites/all/modules/ctools/includes/export.inc
new file mode 100644
index 000000000..0b85c2ef9
--- /dev/null
+++ b/sites/all/modules/ctools/includes/export.inc
@@ -0,0 +1,1267 @@
+<?php
+
+/**
+ * @file
+ * Contains code to make it easier to have exportable objects.
+ *
+ * Documentation for exportable objects is contained in help/export.html
+ */
+
+/**
+ * A bit flag used to let us know if an object is in the database.
+ */
+define('EXPORT_IN_DATABASE', 0x01);
+
+/**
+ * A bit flag used to let us know if an object is a 'default' in code.
+ */
+define('EXPORT_IN_CODE', 0x02);
+
+/**
+ * @defgroup export_crud CRUD functions for export.
+ * @{
+ * export.inc supports a small number of CRUD functions that should always
+ * work for every exportable object, no matter how complicated. These
+ * functions allow complex objects to provide their own callbacks, but
+ * in most cases, the default callbacks will be used.
+ *
+ * Note that defaults are NOT set in the $schema because it is presumed
+ * that a module's personalized CRUD functions will already know which
+ * $table to use and not want to clutter up the arguments with it.
+ */
+
+/**
+ * Create a new object for the given $table.
+ *
+ * @param $table
+ * The name of the table to use to retrieve $schema values. This table
+ * must have an 'export' section containing data or this function
+ * will fail.
+ * @param $set_defaults
+ * If TRUE, which is the default, then default values will be retrieved
+ * from schema fields and set on the object.
+ *
+ * @return
+ * The loaded object.
+ */
+function ctools_export_crud_new($table, $set_defaults = TRUE) {
+ $schema = ctools_export_get_schema($table);
+ $export = $schema['export'];
+
+ if (!empty($export['create callback']) && function_exists($export['create callback'])) {
+ return $export['create callback']($set_defaults);
+ }
+ else {
+ return ctools_export_new_object($table, $set_defaults);
+ }
+}
+
+/**
+ * Load a single exportable object.
+ *
+ * @param $table
+ * The name of the table to use to retrieve $schema values. This table
+ * must have an 'export' section containing data or this function
+ * will fail.
+ * @param $name
+ * The unique ID to load. The field for this ID will be specified by
+ * the export key, which normally defaults to 'name'.
+ *
+ * @return
+ * The loaded object.
+ */
+function ctools_export_crud_load($table, $name) {
+ $schema = ctools_export_get_schema($table);
+ $export = $schema['export'];
+
+ if (!empty($export['load callback']) && function_exists($export['load callback'])) {
+ return $export['load callback']($name);
+ }
+ else {
+ $result = ctools_export_load_object($table, 'names', array($name));
+ if (isset($result[$name])) {
+ return $result[$name];
+ }
+ }
+}
+
+/**
+ * Load multiple exportable objects.
+ *
+ * @param $table
+ * The name of the table to use to retrieve $schema values. This table
+ * must have an 'export' section containing data or this function
+ * will fail.
+ * @param $names
+ * An array of unique IDs to load. The field for these IDs will be specified
+ * by the export key, which normally defaults to 'name'.
+ *
+ * @return
+ * An array of the loaded objects.
+ */
+function ctools_export_crud_load_multiple($table, array $names) {
+ $schema = ctools_export_get_schema($table);
+ $export = $schema['export'];
+
+ $results = array();
+ if (!empty($export['load multiple callback']) && function_exists($export['load multiple callback'])) {
+ $results = $export['load multiple callback']($names);
+ }
+ else {
+ $results = ctools_export_load_object($table, 'names', $names);
+ }
+
+ // Ensure no empty results are returned.
+ return array_filter($results);
+}
+
+/**
+ * Load all exportable objects of a given type.
+ *
+ * @param $table
+ * The name of the table to use to retrieve $schema values. This table
+ * must have an 'export' section containing data or this function
+ * will fail.
+ * @param $reset
+ * If TRUE, the static cache of all objects will be flushed prior to
+ * loading all. This can be important on listing pages where items
+ * might have changed on the page load.
+ * @return
+ * An array of all loaded objects, keyed by the unique IDs of the export key.
+ */
+function ctools_export_crud_load_all($table, $reset = FALSE) {
+ $schema = ctools_export_get_schema($table);
+ if (empty($schema['export'])) {
+ return array();
+ }
+
+ $export = $schema['export'];
+
+ if ($reset) {
+ ctools_export_load_object_reset($table);
+ }
+
+ if (!empty($export['load all callback']) && function_exists($export['load all callback'])) {
+ return $export['load all callback']($reset);
+ }
+ else {
+ return ctools_export_load_object($table, 'all');
+ }
+}
+
+/**
+ * Save a single exportable object.
+ *
+ * @param $table
+ * The name of the table to use to retrieve $schema values. This table
+ * must have an 'export' section containing data or this function
+ * will fail.
+ * @param $object
+ * The fully populated object to save.
+ *
+ * @return
+ * Failure to write a record will return FALSE. Otherwise SAVED_NEW or
+ * SAVED_UPDATED is returned depending on the operation performed. The
+ * $object parameter contains values for any serial fields defined by the $table
+ */
+function ctools_export_crud_save($table, &$object) {
+ $schema = ctools_export_get_schema($table);
+ $export = $schema['export'];
+
+ if (!empty($export['save callback']) && function_exists($export['save callback'])) {
+ return $export['save callback']($object);
+ }
+ else {
+ // Objects should have a serial primary key. If not, simply fail to write.
+ if (empty($export['primary key'])) {
+ return FALSE;
+ }
+
+ $key = $export['primary key'];
+ if ($object->export_type & EXPORT_IN_DATABASE) {
+ // Existing record.
+ $update = array($key);
+ }
+ else {
+ // New record.
+ $update = array();
+ $object->export_type = EXPORT_IN_DATABASE;
+ }
+ return drupal_write_record($table, $object, $update);
+ }
+}
+
+/**
+ * Delete a single exportable object.
+ *
+ * This only deletes from the database, which means that if an item is in
+ * code, then this is actually a revert.
+ *
+ * @param $table
+ * The name of the table to use to retrieve $schema values. This table
+ * must have an 'export' section containing data or this function
+ * will fail.
+ * @param $object
+ * The fully populated object to delete, or the export key.
+ */
+function ctools_export_crud_delete($table, $object) {
+ $schema = ctools_export_get_schema($table);
+ $export = $schema['export'];
+
+ if (!empty($export['delete callback']) && function_exists($export['delete callback'])) {
+ return $export['delete callback']($object);
+ }
+ else {
+ // If we were sent an object, get the export key from it. Otherwise
+ // assume we were sent the export key.
+ $value = is_object($object) ? $object->{$export['key']} : $object;
+ db_delete($table)
+ ->condition($export['key'], $value)
+ ->execute();
+ }
+}
+
+/**
+ * Get the exported code of a single exportable object.
+ *
+ * @param $table
+ * The name of the table to use to retrieve $schema values. This table
+ * must have an 'export' section containing data or this function
+ * will fail.
+ * @param $object
+ * The fully populated object to delete, or the export key.
+ * @param $indent
+ * Any indentation to apply to the code, in case this object is embedded
+ * into another, for example.
+ *
+ * @return
+ * A string containing the executable export of the object.
+ */
+function ctools_export_crud_export($table, $object, $indent = '') {
+ $schema = ctools_export_get_schema($table);
+ $export = $schema['export'];
+
+ if (!empty($export['export callback']) && function_exists($export['export callback'])) {
+ return $export['export callback']($object, $indent);
+ }
+ else {
+ return ctools_export_object($table, $object, $indent);
+ }
+}
+
+/**
+ * Turn exported code into an object.
+ *
+ * Note: If the code is poorly formed, this could crash and there is no
+ * way to prevent this.
+ *
+ * @param $table
+ * The name of the table to use to retrieve $schema values. This table
+ * must have an 'export' section containing data or this function
+ * will fail.
+ * @param $code
+ * The code to eval to create the object.
+ *
+ * @return
+ * An object created from the export. This object will NOT have been saved
+ * to the database. In the case of failure, a string containing all errors
+ * that the system was able to determine.
+ */
+function ctools_export_crud_import($table, $code) {
+ $schema = ctools_export_get_schema($table);
+ $export = $schema['export'];
+
+ if (!empty($export['import callback']) && function_exists($export['import callback'])) {
+ return $export['import callback']($code);
+ }
+ else {
+ ob_start();
+ eval($code);
+ ob_end_clean();
+
+ if (empty(${$export['identifier']})) {
+ $errors = ob_get_contents();
+ if (empty($errors)) {
+ $errors = t('No item found.');
+ }
+ return $errors;
+ }
+
+ $item = ${$export['identifier']};
+
+ // Set these defaults just the same way that ctools_export_new_object sets
+ // them.
+ $item->export_type = NULL;
+ $item->{$export['export type string']} = t('Local');
+
+ return $item;
+ }
+}
+
+/**
+ * Change the status of a certain object.
+ *
+ * @param $table
+ * The name of the table to use to enable a certain object. This table
+ * must have an 'export' section containing data or this function
+ * will fail.
+ * @param $object
+ * The fully populated object to enable, or the machine readable name.
+ * @param $status
+ * The status, in this case, is whether or not it is 'disabled'.
+ */
+function ctools_export_crud_set_status($table, $object, $status) {
+ $schema = ctools_export_get_schema($table);
+ $export = $schema['export'];
+
+ if (!empty($export['status callback']) && function_exists($export['status callback'])) {
+ $export['status callback']($object, $status);
+ }
+ else {
+ if (is_object($object)) {
+ ctools_export_set_object_status($object, $status);
+ }
+ else {
+ ctools_export_set_status($table, $object, $status);
+ }
+ }
+
+}
+
+
+/**
+ * Enable a certain object.
+ *
+ * @param $table
+ * The name of the table to use to enable a certain object. This table
+ * must have an 'export' section containing data or this function
+ * will fail.
+ * @param $object
+ * The fully populated object to enable, or the machine readable name.
+ */
+function ctools_export_crud_enable($table, $object) {
+ return ctools_export_crud_set_status($table, $object, FALSE);
+}
+
+/**
+ * Disable a certain object.
+ *
+ * @param $table
+ * The name of the table to use to disable a certain object. This table
+ * must have an 'export' section containing data or this function
+ * will fail.
+ * @param $object
+ * The fully populated object to disable, or the machine readable name.
+ */
+function ctools_export_crud_disable($table, $object) {
+ return ctools_export_crud_set_status($table, $object, TRUE);
+}
+
+/**
+ * @}
+ */
+
+/**
+ * Load some number of exportable objects.
+ *
+ * This function will cache the objects, load subsidiary objects if necessary,
+ * check default objects in code and properly set them up. It will cache
+ * the results so that multiple calls to load the same objects
+ * will not cause problems.
+ *
+ * It attempts to reduce, as much as possible, the number of queries
+ * involved.
+ *
+ * @param $table
+ * The name of the table to be loaded from. Data is expected to be in the
+ * schema to make all this work.
+ * @param $type
+ * A string to notify the loader what the argument is
+ * - all: load all items. This is the default. $args is unused.
+ * - names: $args will be an array of specific named objects to load.
+ * - conditions: $args will be a keyed array of conditions. The conditions
+ * must be in the schema for this table or errors will result.
+ * @param $args
+ * An array of arguments whose actual use is defined by the $type argument.
+ */
+function ctools_export_load_object($table, $type = 'all', $args = array()) {
+ $cache = &drupal_static(__FUNCTION__);
+ $cache_table_exists = &drupal_static(__FUNCTION__ . '_table_exists', array());
+ $cached_database = &drupal_static('ctools_export_load_object_all');
+
+ if (!array_key_exists($table, $cache_table_exists)) {
+ $cache_table_exists[$table] = db_table_exists($table);
+ }
+
+ $schema = ctools_export_get_schema($table);
+ if (empty($schema) || !$cache_table_exists[$table]) {
+ return array();
+ }
+
+ $export = $schema['export'];
+
+ if (!isset($cache[$table])) {
+ $cache[$table] = array();
+ }
+
+ // If fetching all and cached all, we've done so and we are finished.
+ if ($type == 'all' && !empty($cached_database[$table])) {
+ return $cache[$table];
+ }
+
+ $return = array();
+
+ // Don't load anything we've already cached.
+ if ($type == 'names' && !empty($args)) {
+ foreach ($args as $id => $name) {
+ if (isset($cache[$table][$name])) {
+ $return[$name] = $cache[$table][$name];
+ unset($args[$id]);
+ }
+ }
+
+ // If nothing left to load, return the result.
+ if (empty($args)) {
+ return $return;
+ }
+ }
+
+ // Build the query
+ $query = db_select($table, 't__0')->fields('t__0');
+ $alias_count = 1;
+ if (!empty($schema['join'])) {
+ foreach ($schema['join'] as $join_key => $join) {
+ if ($join_schema = drupal_get_schema($join['table'])) {
+ $query->join($join['table'], 't__' . $alias_count, 't__0.' . $join['left_key'] . ' = ' . 't__' . $alias_count . '.' . $join['right_key']);
+ $query->fields('t__' . $alias_count);
+ $alias_count++;
+
+ // Allow joining tables to alter the query through a callback.
+ if (isset($join['callback']) && function_exists($join['callback'])) {
+ $join['callback']($query, $schema, $join_schema);
+ }
+ }
+ }
+ }
+
+ $conditions = array();
+ $query_args = array();
+
+ // If they passed in names, add them to the query.
+ if ($type == 'names') {
+ $query->condition($export['key'], $args, 'IN');
+ }
+ else if ($type == 'conditions') {
+ foreach ($args as $key => $value) {
+ if (isset($schema['fields'][$key])) {
+ $query->condition($key, $value);
+ }
+ }
+ }
+
+ $result = $query->execute();
+
+ $status = variable_get($export['status'], array());
+ // Unpack the results of the query onto objects and cache them.
+ foreach ($result as $data) {
+ if (isset($schema['export']['object factory']) && function_exists($schema['export']['object factory'])) {
+ $object = $schema['export']['object factory']($schema, $data);
+ }
+ else {
+ $object = _ctools_export_unpack_object($schema, $data, $export['object']);
+ }
+ $object->table = $table;
+ $object->{$export['export type string']} = t('Normal');
+ $object->export_type = EXPORT_IN_DATABASE;
+ // Determine if default object is enabled or disabled.
+ if (isset($status[$object->{$export['key']}])) {
+ $object->disabled = $status[$object->{$export['key']}];
+ }
+
+ $cache[$table][$object->{$export['key']}] = $object;
+ if ($type == 'conditions') {
+ $return[$object->{$export['key']}] = $object;
+ }
+ }
+
+ // Load subrecords.
+ if (isset($export['subrecords callback']) && function_exists($export['subrecords callback'])) {
+ $export['subrecords callback']($cache[$table]);
+ }
+
+ if ($type == 'names' && !empty($args) && !empty($export['cache defaults'])) {
+ $defaults = _ctools_export_get_some_defaults($table, $export, $args);
+ }
+ else {
+ $defaults = _ctools_export_get_defaults($table, $export);
+ }
+
+ if ($defaults) {
+ foreach ($defaults as $object) {
+ if ($type == 'conditions') {
+ // if this does not match all of our conditions, skip it.
+ foreach ($args as $key => $value) {
+ if (!isset($object->$key)) {
+ continue 2;
+ }
+ if (is_array($value)) {
+ if (!in_array($object->$key, $value)) {
+ continue 2;
+ }
+ }
+ else if ($object->$key != $value) {
+ continue 2;
+ }
+ }
+ }
+ else if ($type == 'names') {
+ if (!in_array($object->{$export['key']}, $args)) {
+ continue;
+ }
+ }
+
+ // Determine if default object is enabled or disabled.
+ if (isset($status[$object->{$export['key']}])) {
+ $object->disabled = $status[$object->{$export['key']}];
+ }
+
+ if (!empty($cache[$table][$object->{$export['key']}])) {
+ $cache[$table][$object->{$export['key']}]->{$export['export type string']} = t('Overridden');
+ $cache[$table][$object->{$export['key']}]->export_type |= EXPORT_IN_CODE;
+ $cache[$table][$object->{$export['key']}]->export_module = isset($object->export_module) ? $object->export_module : NULL;
+ if ($type == 'conditions') {
+ $return[$object->{$export['key']}] = $cache[$table][$object->{$export['key']}];
+ }
+ }
+ else {
+ $object->{$export['export type string']} = t('Default');
+ $object->export_type = EXPORT_IN_CODE;
+ $object->in_code_only = TRUE;
+ $object->table = $table;
+
+ $cache[$table][$object->{$export['key']}] = $object;
+ if ($type == 'conditions') {
+ $return[$object->{$export['key']}] = $object;
+ }
+ }
+ }
+ }
+
+ // If fetching all, we've done so and we are finished.
+ if ($type == 'all') {
+ $cached_database[$table] = TRUE;
+ return $cache[$table];
+ }
+
+ if ($type == 'names') {
+ foreach ($args as $name) {
+ if (isset($cache[$table][$name])) {
+ $return[$name] = $cache[$table][$name];
+ }
+ }
+ }
+
+ // For conditions,
+ return $return;
+}
+
+/**
+ * Reset all static caches in ctools_export_load_object() or static caches for
+ * a given table in ctools_export_load_object().
+ *
+ * @param $table
+ * String that is the name of a table. If not defined, all static caches in
+ * ctools_export_load_object() will be reset.
+ */
+function ctools_export_load_object_reset($table = NULL) {
+ // Reset plugin cache to make sure new include files are picked up.
+ ctools_include('plugins');
+ ctools_get_plugins_reset();
+ if (empty($table)) {
+ drupal_static_reset('ctools_export_load_object');
+ drupal_static_reset('ctools_export_load_object_all');
+ drupal_static_reset('_ctools_export_get_defaults');
+ }
+ else {
+ $cache = &drupal_static('ctools_export_load_object');
+ $cached_database = &drupal_static('ctools_export_load_object_all');
+ $cached_defaults = &drupal_static('_ctools_export_get_defaults');
+ unset($cache[$table]);
+ unset($cached_database[$table]);
+ unset($cached_defaults[$table]);
+ }
+}
+
+/**
+ * Get the default version of an object, if it exists.
+ *
+ * This function doesn't care if an object is in the database or not and
+ * does not check. This means that export_type could appear to be incorrect,
+ * because a version could exist in the database. However, it's not
+ * incorrect for this function as it is *only* used for the default
+ * in code version.
+ */
+function ctools_get_default_object($table, $name) {
+ $schema = ctools_export_get_schema($table);
+ $export = $schema['export'];
+
+ if (!$export['default hook']) {
+ return;
+ }
+
+ // Try to load individually from cache if this cache is enabled.
+ if (!empty($export['cache defaults'])) {
+ $defaults = _ctools_export_get_some_defaults($table, $export, array($name));
+ }
+ else {
+ $defaults = _ctools_export_get_defaults($table, $export);
+ }
+
+ $status = variable_get($export['status'], array());
+
+ if (!isset($defaults[$name])) {
+ return;
+ }
+
+ $object = $defaults[$name];
+
+ // Determine if default object is enabled or disabled.
+ if (isset($status[$object->{$export['key']}])) {
+ $object->disabled = $status[$object->{$export['key']}];
+ }
+
+ $object->{$export['export type string']} = t('Default');
+ $object->export_type = EXPORT_IN_CODE;
+ $object->in_code_only = TRUE;
+
+ return $object;
+}
+
+/**
+ * Call the hook to get all default objects of the given type from the
+ * export. If configured properly, this could include loading up an API
+ * to get default objects.
+ */
+function _ctools_export_get_defaults($table, $export) {
+ $cache = &drupal_static(__FUNCTION__, array());
+
+ // If defaults may be cached, first see if we can load from cache.
+ if (!isset($cache[$table]) && !empty($export['cache defaults'])) {
+ $cache[$table] = _ctools_export_get_defaults_from_cache($table, $export);
+ }
+
+ if (!isset($cache[$table])) {
+ // If we're caching, attempt to get a lock. We will wait a short time
+ // on the lock, but not too long, because it's better to just rebuild
+ // and throw away results than wait too long on a lock.
+ if (!empty($export['cache defaults'])) {
+ for ($counter = 0; !($lock = lock_acquire('ctools_export:' . $table)) && $counter > 5; $counter++) {
+ lock_wait('ctools_export:' . $table, 1);
+ ++$counter;
+ }
+ }
+
+ $cache[$table] = array();
+
+ if ($export['default hook']) {
+ if (!empty($export['api'])) {
+ ctools_include('plugins');
+ $info = ctools_plugin_api_include($export['api']['owner'], $export['api']['api'],
+ $export['api']['minimum_version'], $export['api']['current_version']);
+ $modules = array_keys($info);
+ }
+ else {
+ $modules = module_implements($export['default hook']);
+ }
+
+ foreach ($modules as $module) {
+ $function = $module . '_' . $export['default hook'];
+ if (function_exists($function)) {
+ foreach ((array) $function($export) as $name => $object) {
+ // Record the module that provides this exportable.
+ $object->export_module = $module;
+
+ if (empty($export['api'])) {
+ $cache[$table][$name] = $object;
+ }
+ else {
+ // If version checking is enabled, ensure that the object can be used.
+ if (isset($object->api_version) &&
+ version_compare($object->api_version, $export['api']['minimum_version']) >= 0 &&
+ version_compare($object->api_version, $export['api']['current_version']) <= 0) {
+ $cache[$table][$name] = $object;
+ }
+ }
+ }
+ }
+ }
+
+ drupal_alter($export['default hook'], $cache[$table]);
+
+ // If we acquired a lock earlier, cache the results and release the
+ // lock.
+ if (!empty($lock)) {
+ // Cache the index.
+ $index = array_keys($cache[$table]);
+ cache_set('ctools_export_index:' . $table, $index, $export['default cache bin']);
+
+ // Cache each object.
+ foreach ($cache[$table] as $name => $object) {
+ cache_set('ctools_export:' . $table . ':' . $name, $object, $export['default cache bin']);
+ }
+ lock_release('ctools_export:' . $table);
+ }
+ }
+ }
+
+ return $cache[$table];
+}
+
+/**
+ * Attempt to load default objects from cache.
+ *
+ * We can be instructed to cache default objects by the schema. If so
+ * we cache them as an index which is a list of all default objects, and
+ * then each default object is cached individually.
+ *
+ * @return Either an array of cached objects, or NULL indicating a cache
+ * rebuild is necessary.
+ */
+function _ctools_export_get_defaults_from_cache($table, $export) {
+ $data = cache_get('ctools_export_index:' . $table, $export['default cache bin']);
+ if (!$data || !is_array($data->data)) {
+ return;
+ }
+
+ // This is the perfectly valid case where there are no default objects,
+ // and we have cached this state.
+ if (empty($data->data)) {
+ return array();
+ }
+
+ $keys = array();
+ foreach ($data->data as $name) {
+ $keys[] = 'ctools_export:' . $table . ':' . $name;
+ }
+
+ $data = cache_get_multiple($keys, $export['default cache bin']);
+
+ // If any of our indexed keys missed, then we have a fail and we need to
+ // rebuild.
+ if (!empty($keys)) {
+ return;
+ }
+
+ // Now, translate the returned cache objects to actual objects.
+ $cache = array();
+ foreach ($data as $cached_object) {
+ $cache[$cached_object->data->{$export['key']}] = $cached_object->data;
+ }
+
+ return $cache;
+}
+
+/**
+ * Get a limited number of default objects.
+ *
+ * This attempts to load the objects directly from cache. If it cannot,
+ * the cache is rebuilt. This does not disturb the general get defaults
+ * from cache object.
+ *
+ * This function should ONLY be called if default caching is enabled.
+ * It does not check, it is assumed the caller has already done so.
+ */
+function _ctools_export_get_some_defaults($table, $export, $names) {
+ foreach ($names as $name) {
+ $keys[] = 'ctools_export:' . $table . ':' . $name;
+ }
+
+ $data = cache_get_multiple($keys, $export['default cache bin']);
+
+ // Cache hits remove the $key from $keys by reference. Cache
+ // misses do not. A cache miss indicates we may have to rebuild.
+ if (!empty($keys)) {
+ return _ctools_export_get_defaults($table, $export);
+ }
+
+ // Now, translate the returned cache objects to actual objects.
+ $cache = array();
+ foreach ($data as $cached_object) {
+ $cache[$cached_object->data->{$export['key']}] = $cached_object->data;
+ }
+
+ return $cache;
+}
+
+/**
+ * Unpack data loaded from the database onto an object.
+ *
+ * @param $schema
+ * The schema from drupal_get_schema().
+ * @param $data
+ * The data as loaded from the database.
+ * @param $object
+ * If an object, data will be unpacked onto it. If a string
+ * an object of that type will be created.
+ */
+function _ctools_export_unpack_object($schema, $data, $object = 'stdClass') {
+ if (is_string($object)) {
+ if (class_exists($object)) {
+ $object = new $object;
+ }
+ else {
+ $object = new stdClass;
+ }
+ }
+
+ // Go through our schema and build correlations.
+ foreach ($schema['fields'] as $field => $info) {
+ if (isset($data->$field)) {
+ $object->$field = empty($info['serialize']) ? $data->$field : unserialize($data->$field);
+ }
+ else {
+ $object->$field = NULL;
+ }
+ }
+
+ if (isset($schema['join'])) {
+ foreach ($schema['join'] as $join_key => $join) {
+ $join_schema = ctools_export_get_schema($join['table']);
+ if (!empty($join['load'])) {
+ foreach ($join['load'] as $field) {
+ $info = $join_schema['fields'][$field];
+ $object->$field = empty($info['serialize']) ? $data->$field : unserialize($data->$field);
+ }
+ }
+ }
+ }
+
+ return $object;
+}
+
+/**
+ * Unpack data loaded from the database onto an object.
+ *
+ * @param $table
+ * The name of the table this object represents.
+ * @param $data
+ * The data as loaded from the database.
+ */
+function ctools_export_unpack_object($table, $data) {
+ $schema = ctools_export_get_schema($table);
+ return _ctools_export_unpack_object($schema, $data, $schema['export']['object']);
+}
+
+/**
+ * Export a field.
+ *
+ * This is a replacement for var_export(), allowing us to more nicely
+ * format exports. It will recurse down into arrays and will try to
+ * properly export bools when it can, though PHP has a hard time with
+ * this since they often end up as strings or ints.
+ */
+function ctools_var_export($var, $prefix = '') {
+ if (is_array($var)) {
+ if (empty($var)) {
+ $output = 'array()';
+ }
+ else {
+ $output = "array(\n";
+ foreach ($var as $key => $value) {
+ $output .= $prefix . " " . ctools_var_export($key) . " => " . ctools_var_export($value, $prefix . ' ') . ",\n";
+ }
+ $output .= $prefix . ')';
+ }
+ }
+ else if (is_object($var) && get_class($var) === 'stdClass') {
+ // var_export() will export stdClass objects using an undefined
+ // magic method __set_state() leaving the export broken. This
+ // workaround avoids this by casting the object as an array for
+ // export and casting it back to an object when evaluated.
+ $output = '(object) ' . ctools_var_export((array) $var, $prefix);
+ }
+ else if (is_bool($var)) {
+ $output = $var ? 'TRUE' : 'FALSE';
+ }
+ else {
+ $output = var_export($var, TRUE);
+ }
+
+ return $output;
+}
+
+/**
+ * Export an object into code.
+ */
+function ctools_export_object($table, $object, $indent = '', $identifier = NULL, $additions = array(), $additions2 = array()) {
+ $schema = ctools_export_get_schema($table);
+ if (!isset($identifier)) {
+ $identifier = $schema['export']['identifier'];
+ }
+
+ $output = $indent . '$' . $identifier . ' = new ' . get_class($object) . "();\n";
+
+ if ($schema['export']['can disable']) {
+ $output .= $indent . '$' . $identifier . '->disabled = FALSE; /* Edit this to true to make a default ' . $identifier . ' disabled initially */' . "\n";
+ }
+ if (!empty($schema['export']['api']['current_version'])) {
+ $output .= $indent . '$' . $identifier . '->api_version = ' . $schema['export']['api']['current_version'] . ";\n";
+ }
+
+ // Put top additions here:
+ foreach ($additions as $field => $value) {
+ $output .= $indent . '$' . $identifier . '->' . $field . ' = ' . ctools_var_export($value, $indent) . ";\n";
+ }
+
+ $fields = $schema['fields'];
+ if (!empty($schema['join'])) {
+ foreach ($schema['join'] as $join) {
+ if (!empty($join['load'])) {
+ foreach ($join['load'] as $join_field) {
+ $fields[$join_field] = $join['fields'][$join_field];
+ }
+ }
+ }
+ }
+
+ // Go through our schema and joined tables and build correlations.
+ foreach ($fields as $field => $info) {
+ if (!empty($info['no export'])) {
+ continue;
+ }
+ if (!isset($object->$field)) {
+ if (isset($info['default'])) {
+ $object->$field = $info['default'];
+ }
+ else {
+ $object->$field = '';
+ }
+ }
+
+ // Note: This is the *field* export callback, not the table one!
+ if (!empty($info['export callback']) && function_exists($info['export callback'])) {
+ $output .= $indent . '$' . $identifier . '->' . $field . ' = ' . $info['export callback']($object, $field, $object->$field, $indent) . ";\n";
+ }
+ else {
+ $value = $object->$field;
+ if ($info['type'] == 'int') {
+ if (isset($info['size']) && $info['size'] == 'tiny') {
+ $info['boolean'] = (!isset($info['boolean'])) ? $schema['export']['boolean'] : $info['boolean'];
+ $value = ($info['boolean']) ? (bool) $value : (int) $value;
+ }
+ else {
+ $value = (int) $value;
+ }
+ }
+
+ $output .= $indent . '$' . $identifier . '->' . $field . ' = ' . ctools_var_export($value, $indent) . ";\n";
+ }
+ }
+
+ // And bottom additions here
+ foreach ($additions2 as $field => $value) {
+ $output .= $indent . '$' . $identifier . '->' . $field . ' = ' . ctools_var_export($value, $indent) . ";\n";
+ }
+
+ return $output;
+}
+
+/**
+ * Get the schema for a given table.
+ *
+ * This looks for data the export subsystem needs and applies defaults so
+ * that it's easily available.
+ */
+function ctools_export_get_schema($table) {
+ static $drupal_static_fast;
+ if (!isset($drupal_static_fast)) {
+ $drupal_static_fast['cache'] = &drupal_static(__FUNCTION__);
+ }
+ $cache = &$drupal_static_fast['cache'];
+
+ if (empty($cache[$table])) {
+ $schema = drupal_get_schema($table);
+
+ // If our schema isn't loaded, it's possible we're in a state where it
+ // simply hasn't been cached. If we've been asked, let's force the
+ // issue.
+ if (!$schema || empty($schema['export'])) {
+ // force a schema reset:
+ $schema = drupal_get_schema($table, TRUE);
+ }
+
+ if (!isset($schema['export'])) {
+ return array();
+ }
+
+ if (empty($schema['module'])) {
+ return array();
+ }
+
+ // Add some defaults
+ $schema['export'] += array(
+ 'key' => 'name',
+ 'key name' => 'Name',
+ 'object' => 'stdClass',
+ 'status' => 'default_' . $table,
+ 'default hook' => 'default_' . $table,
+ 'can disable' => TRUE,
+ 'identifier' => $table,
+ 'primary key' => !empty($schema['primary key']) ? $schema['primary key'][0] : '',
+ 'bulk export' => TRUE,
+ 'list callback' => "$schema[module]_{$table}_list",
+ 'to hook code callback' => "$schema[module]_{$table}_to_hook_code",
+ 'cache defaults' => FALSE,
+ 'default cache bin' => 'cache',
+ 'export type string' => 'type',
+ 'boolean' => TRUE,
+ );
+
+ // If the export definition doesn't have the "primary key" then the CRUD
+ // save callback won't work.
+ if (empty($schema['export']['primary key']) && user_access('administer site configuration')) {
+ drupal_set_message(t('The export definition of @table is missing the "primary key" property.', array('@table' => $table)), 'error');
+ }
+
+ // Notes:
+ // The following callbacks may be defined to override default behavior
+ // when using CRUD functions:
+ //
+ // create callback
+ // load callback
+ // load multiple callback
+ // load all callback
+ // save callback
+ // delete callback
+ // export callback
+ // import callback
+ //
+ // See the appropriate ctools_export_crud function for details on what
+ // arguments these callbacks should accept. Please do not call these
+ // directly, always use the ctools_export_crud_* wrappers to ensure
+ // that default implementations are honored.
+ $cache[$table] = $schema;
+ }
+
+ return $cache[$table];
+}
+
+/**
+ * Gets the schemas for all tables with ctools object metadata.
+ */
+function ctools_export_get_schemas($for_export = FALSE) {
+ $export_tables = &drupal_static(__FUNCTION__);
+ if (is_null($export_tables)) {
+ $export_tables = array();
+ $schemas = drupal_get_schema();
+ foreach ($schemas as $table => $schema) {
+ if (!isset($schema['export'])) {
+ unset($schemas[$table]);
+ continue;
+ }
+ $export_tables[$table] = ctools_export_get_schema($table);
+ }
+ }
+ return $for_export ? array_filter($export_tables, '_ctools_export_filter_export_tables') : $export_tables;
+}
+
+function _ctools_export_filter_export_tables($schema) {
+ return !empty($schema['export']['bulk export']);
+}
+
+function ctools_export_get_schemas_by_module($modules = array(), $for_export = FALSE) {
+ $export_tables = array();
+ $list = ctools_export_get_schemas($for_export);
+ foreach ($list as $table => $schema) {
+ $export_tables[$schema['module']][$table] = $schema;
+ }
+ return empty($modules) ? $export_tables : array_keys($export_tables, $modules);
+}
+
+/**
+ * Set the status of a default $object as a variable.
+ *
+ * The status, in this case, is whether or not it is 'disabled'.
+ * This function does not check to make sure $object actually
+ * exists.
+ */
+function ctools_export_set_status($table, $name, $new_status = TRUE) {
+ $schema = ctools_export_get_schema($table);
+ $status = variable_get($schema['export']['status'], array());
+
+ $status[$name] = $new_status;
+ variable_set($schema['export']['status'], $status);
+}
+
+/**
+ * Set the status of a default $object as a variable.
+ *
+ * This is more efficient than ctools_export_set_status because it
+ * will actually unset the variable entirely if it's not necessary,
+ * this saving a bit of space.
+ */
+function ctools_export_set_object_status($object, $new_status = TRUE) {
+ $table = $object->table;
+ $schema = ctools_export_get_schema($table);
+ $export = $schema['export'];
+ $status = variable_get($export['status'], array());
+
+ // Compare
+ if (!$new_status && $object->export_type & EXPORT_IN_DATABASE) {
+ unset($status[$object->{$export['key']}]);
+ }
+ else {
+ $status[$object->{$export['key']}] = $new_status;
+ }
+
+ variable_set($export['status'], $status);
+}
+
+/**
+ * Provide a form for displaying an export.
+ *
+ * This is a simple form that should be invoked like this:
+ * @code
+ * $output = drupal_get_form('ctools_export_form', $code, $object_title);
+ * @endcode
+ */
+function ctools_export_form($form, &$form_state, $code, $title = '') {
+ $lines = substr_count($code, "\n");
+ $form['code'] = array(
+ '#type' => 'textarea',
+ '#title' => $title,
+ '#default_value' => $code,
+ '#rows' => $lines,
+ );
+
+ return $form;
+}
+
+/**
+ * Create a new object based upon schema values.
+ *
+ * Because 'default' has ambiguous meaning on some fields, we will actually
+ * use 'object default' to fill in default values if default is not set
+ * That's a little safer to use as it won't cause weird database default
+ * situations.
+ */
+function ctools_export_new_object($table, $set_defaults = TRUE) {
+ $schema = ctools_export_get_schema($table);
+ $export = $schema['export'];
+
+ $object = new $export['object'];
+ foreach ($schema['fields'] as $field => $info) {
+ if (isset($info['object default'])) {
+ $object->$field = $info['object default'];
+ }
+ else if (isset($info['default'])) {
+ $object->$field = $info['default'];
+ }
+ else {
+ $object->$field = NULL;
+ }
+ }
+
+ if ($set_defaults) {
+ // Set some defaults so this data always exists.
+ // We don't set the export_type property here, as this object is not saved
+ // yet. We do give it NULL so we don't generate notices trying to read it.
+ $object->export_type = NULL;
+ $object->{$export['export type string']} = t('Local');
+ }
+ return $object;
+}
+
+/**
+ * Convert a group of objects to code based upon input and return this as a larger
+ * export.
+ */
+function ctools_export_to_hook_code(&$code, $table, $names = array(), $name = 'foo') {
+ $schema = ctools_export_get_schema($table);
+ $export = $schema['export'];
+ // Use the schema-specified function for generating hook code, if one exists
+ if (function_exists($export['to hook code callback'])) {
+ $output = $export['to hook code callback']($names, $name);
+ }
+ // Otherwise, the following code generates basic hook code
+ else {
+ $output = ctools_export_default_to_hook_code($schema, $table, $names, $name);
+ }
+
+ if (!empty($output)) {
+ if (isset($export['api'])) {
+ if (isset($code[$export['api']['owner']][$export['api']['api']]['version'])) {
+ $code[$export['api']['owner']][$export['api']['api']]['version'] = max($code[$export['api']['owner']][$export['api']['api']]['version'], $export['api']['minimum_version']);
+ }
+ else {
+ $code[$export['api']['owner']][$export['api']['api']]['version'] = $export['api']['minimum_version'];
+ $code[$export['api']['owner']][$export['api']['api']]['code'] = '';
+ }
+ $code[$export['api']['owner']][$export['api']['api']]['code'] .= $output;
+ }
+ else {
+ if (empty($code['general'])) {
+ $code['general'] = '';
+ }
+ $code['general'] .= $output;
+ }
+ }
+}
+
+/**
+ * Default function to export objects to code.
+ *
+ * Note that if your module provides a 'to hook code callback' then it will
+ * receive only $names and $name as arguments. Your module is presumed to
+ * already know the rest.
+ */
+function ctools_export_default_to_hook_code($schema, $table, $names, $name) {
+ $export = $schema['export'];
+ $output = '';
+ $objects = ctools_export_crud_load_multiple($table, $names);
+ if ($objects) {
+ $output = "/**\n";
+ $output .= " * Implements hook_{$export['default hook']}().\n";
+ $output .= " */\n";
+ $output .= "function " . $name . "_{$export['default hook']}() {\n";
+ $output .= " \${$export['identifier']}s = array();\n\n";
+ foreach ($objects as $object) {
+ $output .= ctools_export_crud_export($table, $object, ' ');
+ $output .= " \${$export['identifier']}s['" . check_plain($object->$export['key']) . "'] = \${$export['identifier']};\n\n";
+ }
+ $output .= " return \${$export['identifier']}s;\n";
+ $output .= "}\n";
+ }
+
+ return $output;
+}
+/**
+ * Default function for listing bulk exportable objects.
+ */
+function ctools_export_default_list($table, $schema) {
+ $list = array();
+
+ $items = ctools_export_crud_load_all($table);
+ $export_key = $schema['export']['key'];
+ foreach ($items as $item) {
+ // Try a couple of possible obvious title keys:
+ $keys = array('admin_title', 'title');
+ if (isset($schema['export']['admin_title'])) {
+ array_unshift($keys, $schema['export']['admin_title']);
+ }
+
+ $string = '';
+ foreach ($keys as $key) {
+ if (!empty($item->$key)) {
+ $string = $item->$key . " (" . $item->$export_key . ")";
+ break;
+ }
+ }
+
+ if (empty($string)) {
+ $string = $item->$export_key;
+ }
+ $list[$item->$export_key] = check_plain($string);
+ }
+ return $list;
+}
diff --git a/sites/all/modules/ctools/includes/fields.inc b/sites/all/modules/ctools/includes/fields.inc
new file mode 100644
index 000000000..f379f5e9d
--- /dev/null
+++ b/sites/all/modules/ctools/includes/fields.inc
@@ -0,0 +1,357 @@
+<?php
+
+/**
+ * @file
+ * Extend core fields with some helper functions to reduce code complexity within views and ctools plugins.
+ */
+
+
+/**
+ * Fake an instance of a field.
+ *
+ * @param $field_name
+ * The unique name for this field no matter what entity/bundle it may be used on.
+ * @param $view_mode
+ * We're building a new view mode for this function. Defaults to ctools, but we expect developers to actually name this something meaningful.
+ * @param $formatter
+ * The formatter key selected from the options provided by field_ui_formatter_options().
+ * @param $formatter_settings
+ * An array of key value pairs. These will be used as #default_value for the form elements generated by a call to hook_field_formatter_settings_form() for this field type.
+ * Typically we'll pass an empty array to begin with and then pass this information back to ourselves on form submit so that we can set the values for later edit sessions.
+ */
+function ctools_fields_fake_field_instance($field_name, $view_mode = 'ctools', $formatter, $formatter_settings) {
+ $field = field_read_field($field_name);
+
+ $field_type = field_info_field_types($field['type']);
+
+ return array(
+ // Build a fake entity type and bundle.
+ 'field_name' => $field_name,
+ 'entity_type' => 'ctools',
+ 'bundle' => 'ctools',
+
+ // Use the default field settings for settings and widget.
+ 'settings' => field_info_instance_settings($field['type']),
+ 'widget' => array(
+ 'type' => $field_type['default_widget'],
+ 'settings' => array(),
+ ),
+
+ // Build a dummy display mode.
+ 'display' => array(
+ $view_mode => array(
+ 'type' => $formatter,
+ 'settings' => $formatter_settings,
+ ),
+ ),
+
+ // Set the other fields to their default values.
+ // @see _field_write_instance().
+ 'required' => FALSE,
+ 'label' => $field_name,
+ 'description' => '',
+ 'deleted' => 0,
+ );
+}
+
+/**
+ * Helper function for calling hook_field_formatter_settings_form() without needing to load an instance of the field.
+ *
+ * @param $field
+ * A fully loaded field.
+ * @param $formatter_type
+ * The formatter key selected from the options provided by field_ui_formatter_options().
+ * @param $form
+ * The full form from the function that's calling this function.
+ * @param $form_state
+ * The full form_state from the function that's calling this function.
+ * @param $view_mode
+ * We're passing a view mode from this function to the fake instance we're creating. Defaults to ctools, but we expect developers to actually name this something meaningful.
+ */
+function ctools_fields_get_field_formatter_settings_form($field, $formatter_type, &$form, $form_state, $view_mode = 'ctools') {
+ $conf = $form_state['conf'];
+ $formatter = field_info_formatter_types($formatter_type);
+ if (isset($formatter['settings'])) {
+ $conf['formatter_settings'] += $formatter['settings'];
+ }
+ $function = $formatter['module'] . '_field_formatter_settings_form';
+ if (function_exists($function)) {
+ $instance = ctools_fields_fake_field_instance($field['field_name'], $view_mode, $formatter_type, $conf['formatter_settings']);
+ $settings_form = $function($field, $instance, $view_mode, $form, $form_state);
+ if ($settings_form) {
+ // Allow other modules to alter the formatter settings form.
+ $context = array(
+ 'module' => $formatter['module'],
+ 'formatter' => $formatter,
+ 'field' => $field,
+ 'instance' => $instance,
+ 'view_mode' => $view_mode,
+ 'form' => $form,
+ 'form_state' => $form_state,
+ );
+ drupal_alter('field_formatter_settings_form', $settings_form, $context);
+
+ $settings_form['#tree'] = TRUE;
+ $form['ctools_field_list']['#value'][] = $field;
+ $form += $settings_form;
+ }
+ }
+
+ if (isset($field['cardinality']) && $field['cardinality'] != 1) {
+ list($prefix, $suffix) = explode('@count', t('Skip the first @count item(s)'));
+ $form['delta_offset'] = array(
+ '#type' => 'textfield',
+ '#size' => 5,
+ '#field_prefix' => $prefix,
+ '#field_suffix' => $suffix,
+ '#default_value' => isset($conf['delta_offset']) ? $conf['delta_offset'] : 0,
+ );
+
+ list($prefix, $suffix) = explode('@count', t('Then display at most @count item(s)'));
+ $form['delta_limit'] = array(
+ '#type' => 'textfield',
+ '#size' => 5,
+ '#field_prefix' => $prefix,
+ '#field_suffix' => $suffix,
+ '#description' => t('Enter 0 to display all items.'),
+ '#default_value' => isset($conf['delta_limit']) ? $conf['delta_limit'] : 0,
+ );
+
+ $form['delta_reversed'] = array(
+ '#title' => t('Display in reverse order'),
+ '#type' => 'checkbox',
+ '#default_value' => !empty($conf['delta_reversed']),
+ '#description' => t('(start from last values)'),
+ );
+ }
+}
+
+/**
+ * Helper function for generating all the formatter information associated with
+ * any fields.
+ * Especially useful for determining the fields that will be added to form that
+ * executes hook_field_formatter_settings_form().
+ *
+ * @param $fields
+ * An array of fully loaded fields.
+ */
+function ctools_fields_get_field_formatter_info($fields) {
+ $info = array();
+ $field_info = module_invoke_all('field_formatter_info');
+ foreach ($fields as $field) {
+ foreach ($field_info as $format_name => $formatter_info) {
+ if (in_array($field['type'], $formatter_info['field types'])) {
+ $info += array($format_name => $formatter_info);
+ }
+ }
+ }
+ drupal_alter('field_formatter_info', $info);
+ return $info;
+}
+
+/**
+ * Returns the label of a certain field.
+ *
+ * Cribbed from Views.
+ */
+function ctools_field_label($field_name) {
+ $label_counter = array();
+ // Count the amount of instances per label per field.
+ $instances = field_info_instances();
+ foreach ($instances as $entity_type) {
+ foreach ($entity_type as $bundle) {
+ if (isset($bundle[$field_name])) {
+ $label_counter[$bundle[$field_name]['label']] = isset($label_counter[$bundle[$field_name]['label']]) ? ++$label_counter[$bundle[$field_name]['label']] : 1;
+ }
+ }
+ }
+ if (empty($label_counter)) {
+ return $field_name;
+ }
+ // Sort the field lables by it most used label and return the most used one.
+ arsort($label_counter);
+ $label_counter = array_keys($label_counter);
+ return $label_counter[0];
+}
+
+/**
+ * Replacement for core _field_invoke() to invoke on a single field.
+ *
+ * Core only allows invoking field hooks via a private function for all fields
+ * on an entire entity. However, we very often need to invoke our hooks on
+ * a single field as we take things apart and only use little bits.
+ *
+ * @param $field_name
+ * Either a field instance object or the name of the field.
+ * If the 'field' key is populated it will be used as the field
+ * settings.
+ * @param $op
+ * Possible operations include:
+ * - form
+ * - validate
+ * - presave
+ * - insert
+ * - update
+ * - delete
+ * - delete revision
+ * - view
+ * - prepare translation
+ * @param $entity_type
+ * The type of $entity; e.g. 'node' or 'user'.
+ * @param $entity
+ * The fully formed $entity_type entity.
+ * @param $a
+ * - The $form in the 'form' operation.
+ * - The value of $view_mode in the 'view' operation.
+ * - Otherwise NULL.
+ * @param $b
+ * - The $form_state in the 'submit' operation.
+ * - Otherwise NULL.
+ * @param $options
+ * An associative array of additional options, with the following keys:
+ * - 'field_name': The name of the field whose operation should be
+ * invoked. By default, the operation is invoked on all the fields
+ * in the entity's bundle. NOTE: This option is not compatible with
+ * the 'deleted' option; the 'field_id' option should be used
+ * instead.
+ * - 'field_id': The id of the field whose operation should be
+ * invoked. By default, the operation is invoked on all the fields
+ * in the entity's' bundles.
+ * - 'default': A boolean value, specifying which implementation of
+ * the operation should be invoked.
+ * - if FALSE (default), the field types implementation of the operation
+ * will be invoked (hook_field_[op])
+ * - If TRUE, the default field implementation of the field operation
+ * will be invoked (field_default_[op])
+ * Internal use only. Do not explicitely set to TRUE, but use
+ * _field_invoke_default() instead.
+ * - 'deleted': If TRUE, the function will operate on deleted fields
+ * as well as non-deleted fields. If unset or FALSE, only
+ * non-deleted fields are operated on.
+ * - 'language': A language code or an array of language codes keyed by field
+ * name. It will be used to narrow down to a single value the available
+ * languages to act on.
+ *
+ * @see _field_invoke()
+ */
+function ctools_field_invoke_field($field_name, $op, $entity_type, $entity, &$a = NULL, &$b = NULL, $options = array()) {
+ if (is_array($field_name)) {
+ $instance = $field_name;
+ $field = empty($field_name['field']) ? field_info_field($instance['field_name']) : $field_name['field'];
+ $field_name = $instance['field_name'];
+ }
+ else {
+ list(, , $bundle) = entity_extract_ids($entity_type, $entity);
+ $instance = field_info_instance($entity_type, $field_name, $bundle);
+ }
+
+ if (empty($instance)) {
+ return;
+ }
+
+ // Merge default options.
+ $default_options = array(
+ 'default' => FALSE,
+ 'deleted' => FALSE,
+ 'language' => NULL,
+ );
+ $options += $default_options;
+
+ $return = array();
+
+ // Everything from here is unmodified code from _field_invoke() formerly
+ // inside a foreach loop over the instances.
+ $function = $options['default'] ? 'field_default_' . $op : $field['module'] . '_field_' . $op;
+ if (function_exists($function)) {
+ // Determine the list of languages to iterate on.
+ $available_languages = field_available_languages($entity_type, $field);
+ $languages = _field_language_suggestion($available_languages, $options['language'], $field_name);
+
+ foreach ($languages as $langcode) {
+ $items = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : array();
+ $result = $function($entity_type, $entity, $field, $instance, $langcode, $items, $a, $b);
+ if (isset($result)) {
+ // For hooks with array results, we merge results together.
+ // For hooks with scalar results, we collect results in an array.
+ if (is_array($result)) {
+ $return = array_merge($return, $result);
+ }
+ else {
+ $return[] = $result;
+ }
+ }
+
+ // Populate $items back in the field values, but avoid replacing missing
+ // fields with an empty array (those are not equivalent on update).
+ if ($items !== array() || isset($entity->{$field_name}[$langcode])) {
+ $entity->{$field_name}[$langcode] = $items;
+ }
+ }
+ }
+
+ return $return;
+}
+
+/**
+ * Replacement for core _field_invoke_default() to invoke on a single field.
+ *
+ * @see ctools_field_invoke_field()
+ * @see _field_invoke_default()
+ */
+function ctools_field_invoke_field_default($field_name, $op, $entity_type, $entity, &$a = NULL, &$b = NULL, $options = array()) {
+ $options['default'] = TRUE;
+ return ctools_field_invoke_field($field_name, $op, $entity_type, $entity, $a, $b, $options);
+}
+
+/**
+ * Returns a list of field definitions of a specified type.
+ *
+ * @param string $field_type
+ * A field type name; e.g. 'text' or 'date'.
+ *
+ * @return array
+ * An array of field definitions of the specified type, keyed by field name.
+ */
+function ctools_fields_get_fields_by_type($field_type) {
+ $fields = array();
+ foreach (field_info_fields() as $field_name => $field_info) {
+ if ($field_info['type'] == $field_type) {
+ $fields[$field_name] = $field_info;
+ }
+ }
+ return $fields;
+}
+
+/**
+ * Derive the foreign keys that a field provides.
+ *
+ * @param $field_name
+ * The name of the field.
+ *
+ * @return
+ * An array of foreign keys according to Schema API.
+ */
+function ctools_field_foreign_keys($field_name) {
+ $foreign_keys = &drupal_static(__FUNCTION__, array());
+ if (!isset($foreign_keys[$field_name])) {
+ $foreign_keys[$field_name] = array();
+ $field = field_info_field($field_name);
+
+ if (!empty($field['foreign keys'])) {
+ $foreign_keys[$field_name] = $field['foreign keys'];
+ }
+ else {
+ // try to fetch foreign keys from schema, as not everything
+ // stores foreign keys properly in the field info.
+ $module = $field['module'];
+
+ module_load_install($module);
+ $schema = module_invoke($module, 'field_schema', $field);
+ if (!empty($schema['foreign keys'])) {
+ $foreign_keys[$field_name] = $schema['foreign keys'];
+ }
+ }
+ }
+
+ return $foreign_keys[$field_name];
+}
diff --git a/sites/all/modules/ctools/includes/jump-menu.inc b/sites/all/modules/ctools/includes/jump-menu.inc
new file mode 100644
index 000000000..51f45982b
--- /dev/null
+++ b/sites/all/modules/ctools/includes/jump-menu.inc
@@ -0,0 +1,150 @@
+<?php
+
+/**
+ * @file
+ * Provides a simple "jump menu".
+ *
+ * A jump menu is a select box and an optional 'go' button which can be removed
+ * if javascript is in use. Each item is keyed to the href that the button
+ * should go to. With javascript, the page is immediately redirected. Without
+ * javascript, the form is submitted and a drupal_goto() is given.
+ *
+ */
+
+/**
+ * Generate a jump menu form.
+ *
+ * This can either be used with drupal_get_form() or directly added to a
+ * form. The button provides its own submit handler so by default, other
+ * submit handlers will not be called.
+ *
+ * One note: Do not use #tree = TRUE or it will be unable to find the
+ * proper value.
+ *
+ * @code
+ * ctools_include('jump-menu');
+ * $output = drupal_get_form('ctools_jump_menu', $targets, $options);
+ * @endcode
+ *
+ * @param $select
+ * An array suitable for use as the #options. The keys will be the direct
+ * URLs that will be jumped to, so you absolutely must encode these using
+ * url() in order for them to work reliably.
+ *
+ * @param $options
+ * $options may be an array with the following options:
+ * - 'title': The text to display for the #title attribute.
+ * - 'description': The text to display for the #description attribute.
+ * - 'default_value': The text to display for the #default_value attribute.
+ * - 'hide': If TRUE the go button will be set to hide via javascript and
+ * will submit on change.
+ * - 'button': The text to display on the button.
+ * - 'image': If set, an image button will be used instead, and the image
+ * set to this.
+ * - 'inline': If set to TRUE (default) the display will be forced inline.
+ */
+function ctools_jump_menu($form, &$form_state, $select, $options = array()) {
+ $options += array(
+ 'button' => t('Go'),
+ 'choose' => t('- Choose -'),
+ 'inline' => TRUE,
+ 'hide' => TRUE,
+ );
+
+ $form['#attached']['js'][] = ctools_attach_js('jump-menu');
+
+ if (!empty($options['choose'])) {
+ $select = array('' => $options['choose']) + $select;
+ }
+
+ $form['jump'] = array(
+ '#type' => 'select',
+ '#options' => $select,
+ '#attributes' => array(
+ 'class' => array('ctools-jump-menu-select'),
+ ),
+ );
+
+ if (!empty($options['title'])) {
+ $form['jump']['#title'] = $options['title'];
+ }
+
+ if (!empty($options['description'])) {
+ $form['jump']['#description'] = $options['description'];
+ }
+
+ if (!empty($options['default_value'])) {
+ $form['jump']['#default_value'] = $options['default_value'];
+ }
+
+ if (isset($options['image'])) {
+ $form['go'] = array(
+ '#type' => 'image_button',
+ '#src' => $options['image'],
+ '#submit' => array('ctools_jump_menu_submit'),
+ '#attributes' => array(
+ 'class' => array('ctools-jump-menu-button'),
+ ),
+ );
+ }
+ else {
+ $form['go'] = array(
+ '#type' => 'submit',
+ '#value' => $options['button'],
+ '#submit' => array('ctools_jump_menu_submit'),
+ '#attributes' => array(
+ 'class' => array('ctools-jump-menu-button'),
+ ),
+ );
+ }
+
+ if ($options['inline']) {
+ $form['jump']['#prefix'] = '<div class="container-inline">';
+ $form['go']['#suffix'] = '</div>';
+ }
+
+ if ($options['hide']) {
+ $form['jump']['#attributes']['class'][] = 'ctools-jump-menu-change';
+ $form['go']['#attributes']['class'][] = 'ctools-jump-menu-hide';
+ }
+
+ return $form;
+}
+
+/**
+ * Submit handler for the jump menu.
+ *
+ * This is normally only invoked upon submit without javascript enabled.
+ */
+function ctools_jump_menu_submit($form, &$form_state) {
+ if ($form_state['values']['jump'] === '') {
+ // We have nothing to do when the user has not selected any value.
+ return;
+ }
+
+ // If the path we are redirecting to contains the string :: then treat the
+ // the string after the double colon as the path to redirect to.
+ // This allows duplicate paths to be used in jump menus for multiple options.
+ $redirect_array = explode("::", $form_state['values']['jump']);
+
+ if(isset($redirect_array[1]) && !empty($redirect_array[1])){
+ $redirect = $redirect_array[1];
+ }
+ else {
+ $redirect = $form_state['values']['jump'];
+ }
+
+ // If the path we are redirecting to starts with the base path (for example,
+ // "/somepath/node/1"), we need to strip the base path off before passing it
+ // to $form_state['redirect'].
+ $base_path = base_path();
+ if (strpos($redirect, $base_path) === 0) {
+ $redirect = substr($redirect, strlen($base_path));
+ }
+
+ // Parse the URL so that query strings and fragments are preserved in the
+ // redirect.
+ $redirect = drupal_parse_url($redirect);
+ $redirect['path'] = urldecode($redirect['path']);
+ $form_state['redirect'] = array($redirect['path'], $redirect);
+}
diff --git a/sites/all/modules/ctools/includes/language.inc b/sites/all/modules/ctools/includes/language.inc
new file mode 100644
index 000000000..9a7850b77
--- /dev/null
+++ b/sites/all/modules/ctools/includes/language.inc
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * Returns array of language names.
+ *
+ * This is a one to one copy of locale_language_list because we can't rely on enabled locale module.
+ *
+ * @param $field
+ * 'name' => names in current language, localized
+ * 'native' => native names
+ * @param $all
+ * Boolean to return all languages or only enabled ones
+ *
+ * @see locale_language_list
+ */
+function ctools_language_list($field = 'name', $all = FALSE) {
+ if ($all) {
+ $languages = language_list();
+ }
+ else {
+ $languages = language_list('enabled');
+ $languages = $languages[1];
+ }
+ $list = array();
+ foreach ($languages as $language) {
+ $list[$language->language] = ($field == 'name') ? t($language->name) : $language->$field;
+ }
+ return $list;
+}
+
+/**
+ * Returns an array of language names similar to ctools_language_list() except
+ * that additional choices have been added for ease of use.
+ */
+
+function ctools_language_list_all() {
+ $languages = array(
+ '***CURRENT_LANGUAGE***' => t("Current user's language"),
+ '***DEFAULT_LANGUAGE***' => t("Default site language"),
+ LANGUAGE_NONE => t('Language neutral'),
+ );
+ $languages = array_merge($languages, ctools_language_list());
+ return $languages;
+} \ No newline at end of file
diff --git a/sites/all/modules/ctools/includes/math-expr.inc b/sites/all/modules/ctools/includes/math-expr.inc
new file mode 100644
index 000000000..eeb184d86
--- /dev/null
+++ b/sites/all/modules/ctools/includes/math-expr.inc
@@ -0,0 +1,388 @@
+<?php
+
+/*
+================================================================================
+
+ctools_math_expr - PHP Class to safely evaluate math expressions
+Copyright (C) 2005 Miles Kaufmann <http://www.twmagic.com/>
+
+================================================================================
+
+NAME
+ ctools_math_expr - safely evaluate math expressions
+
+SYNOPSIS
+ include('ctools_math_expr.class.php');
+ $m = new ctools_math_expr;
+ // basic evaluation:
+ $result = $m->evaluate('2+2');
+ // supports: order of operation; parentheses; negation; built-in functions
+ $result = $m->evaluate('-8(5/2)^2*(1-sqrt(4))-8');
+ // create your own variables
+ $m->evaluate('a = e^(ln(pi))');
+ // or functions
+ $m->evaluate('f(x,y) = x^2 + y^2 - 2x*y + 1');
+ // and then use them
+ $result = $m->evaluate('3*f(42,a)');
+
+DESCRIPTION
+ Use the ctools_math_expr class when you want to evaluate mathematical expressions
+ from untrusted sources. You can define your own variables and functions,
+ which are stored in the object. Try it, it's fun!
+
+METHODS
+ $m->evalute($expr)
+ Evaluates the expression and returns the result. If an error occurs,
+ prints a warning and returns false. If $expr is a function assignment,
+ returns true on success.
+
+ $m->e($expr)
+ A synonym for $m->evaluate().
+
+ $m->vars()
+ Returns an associative array of all user-defined variables and values.
+
+ $m->funcs()
+ Returns an array of all user-defined functions.
+
+PARAMETERS
+ $m->suppress_errors
+ Set to true to turn off warnings when evaluating expressions
+
+ $m->last_error
+ If the last evaluation failed, contains a string describing the error.
+ (Useful when suppress_errors is on).
+
+AUTHOR INFORMATION
+ Copyright 2005, Miles Kaufmann.
+
+LICENSE
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ 1 Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. The name of the author may not be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
+ ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ POSSIBILITY OF SUCH DAMAGE.
+
+*/
+
+class ctools_math_expr {
+ var $suppress_errors = false;
+ var $last_error = null;
+
+ var $v = array('e'=>2.71,'pi'=>3.14); // variables (and constants)
+ var $f = array(); // user-defined functions
+ var $vb = array('e', 'pi'); // constants
+ var $fb = array( // built-in functions
+ 'sin','sinh','arcsin','asin','arcsinh','asinh',
+ 'cos','cosh','arccos','acos','arccosh','acosh',
+ 'tan','tanh','arctan','atan','arctanh','atanh',
+ 'pow', 'exp',
+ 'sqrt','abs','ln','log',
+ 'time', 'ceil', 'floor', 'min', 'max', 'round');
+
+ function ctools_math_expr() {
+ // make the variables a little more accurate
+ $this->v['pi'] = pi();
+ $this->v['e'] = exp(1);
+ drupal_alter('ctools_math_expression_functions', $this->fb);
+ }
+
+ function e($expr) {
+ return $this->evaluate($expr);
+ }
+
+ function evaluate($expr) {
+ $this->last_error = null;
+ $expr = trim($expr);
+ if (substr($expr, -1, 1) == ';') $expr = substr($expr, 0, strlen($expr)-1); // strip semicolons at the end
+ //===============
+ // is it a variable assignment?
+ if (preg_match('/^\s*([a-z]\w*)\s*=\s*(.+)$/', $expr, $matches)) {
+ if (in_array($matches[1], $this->vb)) { // make sure we're not assigning to a constant
+ return $this->trigger("cannot assign to constant '$matches[1]'");
+ }
+ if (($tmp = $this->pfx($this->nfx($matches[2]))) === false) return false; // get the result and make sure it's good
+ $this->v[$matches[1]] = $tmp; // if so, stick it in the variable array
+ return $this->v[$matches[1]]; // and return the resulting value
+ //===============
+ // is it a function assignment?
+ } elseif (preg_match('/^\s*([a-z]\w*)\s*\(\s*([a-z]\w*(?:\s*,\s*[a-z]\w*)*)\s*\)\s*=\s*(.+)$/', $expr, $matches)) {
+ $fnn = $matches[1]; // get the function name
+ if (in_array($matches[1], $this->fb)) { // make sure it isn't built in
+ return $this->trigger("cannot redefine built-in function '$matches[1]()'");
+ }
+ $args = explode(",", preg_replace("/\s+/", "", $matches[2])); // get the arguments
+ if (($stack = $this->nfx($matches[3])) === false) return false; // see if it can be converted to postfix
+ for ($i = 0; $i<count($stack); $i++) { // freeze the state of the non-argument variables
+ $token = $stack[$i];
+ if (preg_match('/^[a-z]\w*$/', $token) and !in_array($token, $args)) {
+ if (array_key_exists($token, $this->v)) {
+ $stack[$i] = $this->v[$token];
+ } else {
+ return $this->trigger("undefined variable '$token' in function definition");
+ }
+ }
+ }
+ $this->f[$fnn] = array('args'=>$args, 'func'=>$stack);
+ return true;
+ //===============
+ } else {
+ return $this->pfx($this->nfx($expr)); // straight up evaluation, woo
+ }
+ }
+
+ function vars() {
+ $output = $this->v;
+ unset($output['pi']);
+ unset($output['e']);
+ return $output;
+ }
+
+ function funcs() {
+ $output = array();
+ foreach ($this->f as $fnn=>$dat)
+ $output[] = $fnn . '(' . implode(',', $dat['args']) . ')';
+ return $output;
+ }
+
+ //===================== HERE BE INTERNAL METHODS ====================\\
+
+ // Convert infix to postfix notation
+ function nfx($expr) {
+
+ $index = 0;
+ $stack = new ctools_math_expr_stack;
+ $output = array(); // postfix form of expression, to be passed to pfx()
+ $expr = trim(strtolower($expr));
+
+ $ops = array('+', '-', '*', '/', '^', '_');
+ $ops_r = array('+'=>0,'-'=>0,'*'=>0,'/'=>0,'^'=>1); // right-associative operator?
+ $ops_p = array('+'=>0,'-'=>0,'*'=>1,'/'=>1,'_'=>1,'^'=>2); // operator precedence
+
+ $expecting_op = false; // we use this in syntax-checking the expression
+ // and determining when a - is a negation
+
+ if (preg_match("/[^\w\s+*^\/()\.,-]/", $expr, $matches)) { // make sure the characters are all good
+ return $this->trigger("illegal character '{$matches[0]}'");
+ }
+
+ while(1) { // 1 Infinite Loop ;)
+ $op = substr($expr, $index, 1); // get the first character at the current index
+ // find out if we're currently at the beginning of a number/variable/function/parenthesis/operand
+ $ex = preg_match('/^([a-z]\w*\(?|\d+(?:\.\d*)?|\.\d+|\()/', substr($expr, $index), $match);
+ //===============
+ if ($op == '-' and !$expecting_op) { // is it a negation instead of a minus?
+ $stack->push('_'); // put a negation on the stack
+ $index++;
+ } elseif ($op == '_') { // we have to explicitly deny this, because it's legal on the stack
+ return $this->trigger("illegal character '_'"); // but not in the input expression
+ //===============
+ } elseif ((in_array($op, $ops) or $ex) and $expecting_op) { // are we putting an operator on the stack?
+ if ($ex) { // are we expecting an operator but have a number/variable/function/opening parethesis?
+ $op = '*'; $index--; // it's an implicit multiplication
+ }
+ // heart of the algorithm:
+ while($stack->count > 0 and ($o2 = $stack->last()) and in_array($o2, $ops) and ($ops_r[$op] ? $ops_p[$op] < $ops_p[$o2] : $ops_p[$op] <= $ops_p[$o2])) {
+ $output[] = $stack->pop(); // pop stuff off the stack into the output
+ }
+ // many thanks: http://en.wikipedia.org/wiki/Reverse_Polish_notation#The_algorithm_in_detail
+ $stack->push($op); // finally put OUR operator onto the stack
+ $index++;
+ $expecting_op = false;
+ //===============
+ } elseif ($op == ')' and $expecting_op) { // ready to close a parenthesis?
+ while (($o2 = $stack->pop()) != '(') { // pop off the stack back to the last (
+ if (is_null($o2)) return $this->trigger("unexpected ')'");
+ else $output[] = $o2;
+ }
+ if (preg_match("/^([a-z]\w*)\($/", $stack->last(2), $matches)) { // did we just close a function?
+ $fnn = $matches[1]; // get the function name
+ $arg_count = $stack->pop(); // see how many arguments there were (cleverly stored on the stack, thank you)
+ $output[] = $stack->pop(); // pop the function and push onto the output
+ if (in_array($fnn, $this->fb)) { // check the argument count
+ if($arg_count > 1)
+ return $this->trigger("too many arguments ($arg_count given, 1 expected)");
+ } elseif (array_key_exists($fnn, $this->f)) {
+ if ($arg_count != count($this->f[$fnn]['args']))
+ return $this->trigger("wrong number of arguments ($arg_count given, " . count($this->f[$fnn]['args']) . " expected)");
+ } else { // did we somehow push a non-function on the stack? this should never happen
+ return $this->trigger("internal error");
+ }
+ }
+ $index++;
+ //===============
+ } elseif ($op == ',' and $expecting_op) { // did we just finish a function argument?
+ while (($o2 = $stack->pop()) != '(') {
+ if (is_null($o2)) return $this->trigger("unexpected ','"); // oops, never had a (
+ else $output[] = $o2; // pop the argument expression stuff and push onto the output
+ }
+ // make sure there was a function
+ if (!preg_match("/^([a-z]\w*)\($/", $stack->last(2), $matches))
+ return $this->trigger("unexpected ','");
+ $stack->push($stack->pop()+1); // increment the argument count
+ $stack->push('('); // put the ( back on, we'll need to pop back to it again
+ $index++;
+ $expecting_op = false;
+ //===============
+ } elseif ($op == '(' and !$expecting_op) {
+ $stack->push('('); // that was easy
+ $index++;
+ $allow_neg = true;
+ //===============
+ } elseif ($ex and !$expecting_op) { // do we now have a function/variable/number?
+ $expecting_op = true;
+ $val = $match[1];
+ if (preg_match("/^([a-z]\w*)\($/", $val, $matches)) { // may be func, or variable w/ implicit multiplication against parentheses...
+ if (in_array($matches[1], $this->fb) or array_key_exists($matches[1], $this->f)) { // it's a func
+ $stack->push($val);
+ $stack->push(1);
+ $stack->push('(');
+ $expecting_op = false;
+ } else { // it's a var w/ implicit multiplication
+ $val = $matches[1];
+ $output[] = $val;
+ }
+ } else { // it's a plain old var or num
+ $output[] = $val;
+ }
+ $index += strlen($val);
+ //===============
+ } elseif ($op == ')') { // miscellaneous error checking
+ return $this->trigger("unexpected ')'");
+ } elseif (in_array($op, $ops) and !$expecting_op) {
+ return $this->trigger("unexpected operator '$op'");
+ } else { // I don't even want to know what you did to get here
+ return $this->trigger("an unexpected error occurred");
+ }
+ if ($index == strlen($expr)) {
+ if (in_array($op, $ops)) { // did we end with an operator? bad.
+ return $this->trigger("operator '$op' lacks operand");
+ } else {
+ break;
+ }
+ }
+ while (substr($expr, $index, 1) == ' ') { // step the index past whitespace (pretty much turns whitespace
+ $index++; // into implicit multiplication if no operator is there)
+ }
+
+ }
+ while (!is_null($op = $stack->pop())) { // pop everything off the stack and push onto output
+ if ($op == '(') return $this->trigger("expecting ')'"); // if there are (s on the stack, ()s were unbalanced
+ $output[] = $op;
+ }
+ return $output;
+ }
+
+ // evaluate postfix notation
+ function pfx($tokens, $vars = array()) {
+
+ if ($tokens == false) return false;
+
+ $stack = new ctools_math_expr_stack;
+
+ foreach ($tokens as $token) { // nice and easy
+ // if the token is a binary operator, pop two values off the stack, do the operation, and push the result back on
+ if (in_array($token, array('+', '-', '*', '/', '^'))) {
+ if (is_null($op2 = $stack->pop())) return $this->trigger("internal error");
+ if (is_null($op1 = $stack->pop())) return $this->trigger("internal error");
+ switch ($token) {
+ case '+':
+ $stack->push($op1+$op2); break;
+ case '-':
+ $stack->push($op1-$op2); break;
+ case '*':
+ $stack->push($op1*$op2); break;
+ case '/':
+ if ($op2 == 0) return $this->trigger("division by zero");
+ $stack->push($op1/$op2); break;
+ case '^':
+ $stack->push(pow($op1, $op2)); break;
+ }
+ // if the token is a unary operator, pop one value off the stack, do the operation, and push it back on
+ } elseif ($token == "_") {
+ $stack->push(-1*$stack->pop());
+ // if the token is a function, pop arguments off the stack, hand them to the function, and push the result back on
+ } elseif (preg_match("/^([a-z]\w*)\($/", $token, $matches)) { // it's a function!
+ $fnn = $matches[1];
+ if (in_array($fnn, $this->fb)) { // built-in function:
+ if (is_null($op1 = $stack->pop())) return $this->trigger("internal error");
+ $fnn = preg_replace("/^arc/", "a", $fnn); // for the 'arc' trig synonyms
+ if ($fnn == 'ln') $fnn = 'log';
+ eval('$stack->push(' . $fnn . '($op1));'); // perfectly safe eval()
+ } elseif (array_key_exists($fnn, $this->f)) { // user function
+ // get args
+ $args = array();
+ for ($i = count($this->f[$fnn]['args'])-1; $i >= 0; $i--) {
+ if (is_null($args[$this->f[$fnn]['args'][$i]] = $stack->pop())) return $this->trigger("internal error");
+ }
+ $stack->push($this->pfx($this->f[$fnn]['func'], $args)); // yay... recursion!!!!
+ }
+ // if the token is a number or variable, push it on the stack
+ } else {
+ if (is_numeric($token)) {
+ $stack->push($token);
+ } elseif (array_key_exists($token, $this->v)) {
+ $stack->push($this->v[$token]);
+ } elseif (array_key_exists($token, $vars)) {
+ $stack->push($vars[$token]);
+ } else {
+ return $this->trigger("undefined variable '$token'");
+ }
+ }
+ }
+ // when we're out of tokens, the stack should have a single element, the final result
+ if ($stack->count != 1) return $this->trigger("internal error");
+ return $stack->pop();
+ }
+
+ // trigger an error, but nicely, if need be
+ function trigger($msg) {
+ $this->last_error = $msg;
+ if (!$this->suppress_errors) trigger_error($msg, E_USER_WARNING);
+ return false;
+ }
+}
+
+// for internal use
+class ctools_math_expr_stack {
+
+ var $stack = array();
+ var $count = 0;
+
+ function push($val) {
+ $this->stack[$this->count] = $val;
+ $this->count++;
+ }
+
+ function pop() {
+ if ($this->count > 0) {
+ $this->count--;
+ return $this->stack[$this->count];
+ }
+ return null;
+ }
+
+ function last($n=1) {
+ return !empty($this->stack[$this->count-$n]) ? $this->stack[$this->count-$n] : NULL;
+ }
+}
+
diff --git a/sites/all/modules/ctools/includes/menu.inc b/sites/all/modules/ctools/includes/menu.inc
new file mode 100644
index 000000000..e725ea407
--- /dev/null
+++ b/sites/all/modules/ctools/includes/menu.inc
@@ -0,0 +1,98 @@
+<?php
+
+/**
+ * @file
+ * General menu helper functions.
+ */
+
+/**
+ * Dynamically add a tab to the current path.
+ *
+ * This function is a simplified interface for adding tabs to the current path.
+ * Some considerations when doing this:
+ *
+ * - First, if there is only 1 tab, Drupal will not show it. Therefore, if
+ * you are only adding one tab, you should find a way to make sure there is
+ * already tab, or instead add 2.
+ * - Second, the caller is responsible for providing access control to these
+ * links.
+ *
+ * @param $link
+ * An array describing this link. It must contain:
+ * - 'title': The printed title of the link.
+ * - 'href': The path of the link. This is an argument to l() so it has all
+ * of those features and limitations.
+ * - 'options': Any options that go to l, including query, fragment and html
+ * options necessary.
+ * - 'weight': The weight to use in ordering the tabs.
+ * - 'type': Optional. If set to MENU_DEFAULT_LOCAL_TASK this can be used to
+ * add a fake 'default' local task, which is useful if you have to add
+ * tabs to a page that has none.
+ */
+function ctools_menu_add_tab($link = NULL) {
+ $links = &drupal_static(__FUNCTION__, array());
+ if (isset($link)) {
+ $links[$link['href']] = $link;
+ }
+
+ return $links;
+}
+
+/**
+ * Re-sort menu items after we have modified them.
+ */
+function ctools_menu_sort($a, $b) {
+ $a_weight = (is_array($a) && isset($a['#link']['weight'])) ? $a['#link']['weight'] : 0;
+ $b_weight = (is_array($b) && isset($b['#link']['weight'])) ? $b['#link']['weight'] : 0;
+ if ($a_weight == $b_weight) {
+ $a_title = (is_array($a) && isset($a['#link']['title'])) ? $a['#link']['title'] : 0;
+ $b_title = (is_array($b) && isset($b['#link']['title'])) ? $b['#link']['title'] : 0;
+ if ($a_title == $b_title) {
+ return 0;
+ }
+
+ return ($a_title < $b_title) ? -1 : 1;
+ }
+
+ return ($a_weight < $b_weight) ? -1 : 1;
+}
+
+function _ctools_menu_add_dynamic_items(&$data, &$router_item, &$root_path) {
+ if ($additions = ctools_menu_add_tab()) {
+ // If none of the static local tasks are active allow one of the dynamic
+ // active tasks to be marked as such.
+ $has_active = FALSE;
+ if (!empty($data['tabs'][0]['output'])) {
+ foreach ($data['tabs'][0]['output'] as $element) {
+ if (!empty($element['#link']['#active'])) {
+ $has_active = TRUE;
+ }
+ }
+ }
+ foreach ($additions as $addition) {
+ $addition['localized_options'] = isset($addition['options']) ? $addition['options'] : array();
+ if (isset($addition['type']) && $addition['type'] == MENU_LOCAL_ACTION) {
+ $data['actions']['output'][] = array(
+ '#theme' => 'menu_local_action',
+ '#link' => $addition,
+ );
+ }
+ else {
+ $data['tabs'][0]['output'][] = array(
+ '#theme' => 'menu_local_task',
+ '#link' => $addition,
+ '#active' => (!$has_active && $root_path === $addition['href']),
+ );
+ }
+ }
+ if (!empty($data['tabs'][0]['output'])) {
+ uasort($data['tabs'][0]['output'], 'ctools_menu_sort');
+ $data['tabs'][0]['count'] = count($data['tabs'][0]['output']);
+ }
+
+ if (!empty($data['actions']['output'])) {
+ uasort($data['actions']['output'], 'ctools_menu_sort');
+ $data['actions']['count'] = count($data['actions']['output']);
+ }
+ }
+}
diff --git a/sites/all/modules/ctools/includes/modal.inc b/sites/all/modules/ctools/includes/modal.inc
new file mode 100644
index 000000000..fc9901594
--- /dev/null
+++ b/sites/all/modules/ctools/includes/modal.inc
@@ -0,0 +1,262 @@
+<?php
+
+/**
+ * @file
+ * Implement a modal form using AJAX.
+ *
+ * The modal form is implemented primarily from mc.js; this contains the
+ * Drupal specific stuff to use it. The modal is fairly generic and can
+ * be activated mostly by setting up the right classes, but if you are
+ * using the modal you must include links to the images in settings,
+ * because the javascript does not inherently know where the images are.
+ *
+ * You can accomplish this with this PHP code:
+ * @code {
+ * ctools_include('modal');
+ * ctools_modal_add_js();
+ * }
+ *
+ * You can have links and buttons bound to use the modal by adding the
+ * class ctools-use-modal.
+ *
+ * For links, the href of the link will be the destination, with any
+ * appearance of /nojs/ converted to /ajax/.
+ *
+ * For submit buttons, however, the URL is found a different, slightly
+ * more complex way. The ID of the item is taken and -url is appended to
+ * it to derive a class name. Then, all form elements that contain that
+ * class name are founded and their values put together to form a URL.
+ *
+ * For example, let's say you have an 'add' button, and it has a select
+ * form item that tells your system what widget it is adding. If the id
+ * of the add button is edit-add, you would place a hidden input with
+ * the base of your URL in the form and give it a class of 'edit-add-url'.
+ * You would then add 'edit-add-url' as a class to the select widget
+ * allowing you to convert this value to the form without posting.
+ *
+ * If no URL is found, the action of the form will be used and the entire
+ * form posted to it.
+ */
+
+function ctools_modal_add_js() {
+ // Provide a gate so we only do this once.
+ static $done = FALSE;
+ if ($done) {
+ return;
+ }
+
+ $settings = array(
+ 'CToolsModal' => array(
+ 'loadingText' => t('Loading...'),
+ 'closeText' => t('Close Window'),
+ 'closeImage' => theme('image', array(
+ 'path' => ctools_image_path('icon-close-window.png'),
+ 'title' => t('Close window'),
+ 'alt' => t('Close window'),
+ )),
+ 'throbber' => theme('image', array(
+ 'path' => ctools_image_path('throbber.gif'),
+ 'title' => t('Loading...'),
+ 'alt' => t('Loading'),
+ )),
+ ),
+ );
+
+ drupal_add_js($settings, 'setting');
+ drupal_add_library('system', 'jquery.form');
+ drupal_add_library('system', 'drupal.progress');
+ drupal_add_library('system', 'drupal.ajax');
+ drupal_add_library('system', 'ui');
+ ctools_add_js('modal');
+
+ ctools_add_css('modal');
+ $done = TRUE;
+}
+
+/**
+ * @todo this is deprecated
+ */
+function ctools_modal_add_plugin_js($plugins) {
+ $css = array();
+ $js = array(drupal_get_path('module', 'ctools') . '/js/dependent.js' => TRUE);
+ foreach ($plugins as $subtype) {
+ if (isset($subtype['js'])) {
+ foreach ($subtype['js'] as $file) {
+ if (file_exists($file)) {
+ $js[$file] = TRUE;
+ }
+ else if (file(exists($subtype['path'] . '/' . $file))) {
+ $js[$subtype['path'] . '/' . $file] = TRUE;
+ }
+ }
+ }
+ if (isset($subtype['css'])) {
+ foreach ($subtype['css'] as $file) {
+ if (file_exists($file)) {
+ $css[$file] = TRUE;
+ }
+ else if (file(exists($subtype['path'] . '/' . $file))) {
+ $css[$subtype['path'] . '/' . $file] = TRUE;
+ }
+ }
+ }
+ }
+
+ foreach (array_keys($js) as $file) {
+ drupal_add_js($file);
+ }
+ foreach (array_keys($css) as $file) {
+ drupal_add_css($file);
+ }
+}
+
+/**
+ * Place HTML within the modal.
+ *
+ * @param $title
+ * The title of the modal.
+ * @param $html
+ * The html to place within the modal.
+ */
+function ctools_modal_command_display($title, $html) {
+ if (is_array($html)) {
+ $html = drupal_render($html);
+ }
+
+ return array(
+ 'command' => 'modal_display',
+ 'title' => $title,
+ 'output' => $html,
+ );
+}
+
+/**
+ * Dismiss the modal.
+ */
+function ctools_modal_command_dismiss() {
+ return array(
+ 'command' => 'modal_dismiss',
+ );
+}
+
+/**
+ * Display loading screen in the modal
+ */
+function ctools_modal_command_loading() {
+ return array(
+ 'command' => 'modal_loading',
+ );
+}
+
+/**
+ * Render an image as a button link. This will automatically apply an AJAX class
+ * to the link and add the appropriate javascript to make this happen.
+ *
+ * @param $image
+ * The path to an image to use that will be sent to theme('image') for rendering.
+ * @param $dest
+ * The destination of the link.
+ * @param $alt
+ * The alt text of the link.
+ * @param $class
+ * Any class to apply to the link. @todo this should be a options array.
+ */
+function ctools_modal_image_button($image, $dest, $alt, $class = '') {
+ return ctools_ajax_text_button(theme('image', array('path' => $image)), $dest, $alt, $class, 'ctools-use-modal');
+}
+
+/**
+ * Render text as a link. This will automatically apply an AJAX class
+ * to the link and add the appropriate javascript to make this happen.
+ *
+ * Note: 'html' => true so be sure any text is vetted! Chances are these kinds of buttons will
+ * not use user input so this is a very minor concern.
+ *
+ * @param $text
+ * The text that will be displayed as the link.
+ * @param $dest
+ * The destination of the link.
+ * @param $alt
+ * The alt text of the link.
+ * @param $class
+ * Any class to apply to the link. @todo this should be a options array.
+ */
+function ctools_modal_text_button($text, $dest, $alt, $class = '') {
+ return ctools_ajax_text_button($text, $dest, $alt, $class, 'ctools-use-modal');
+}
+
+/**
+ * Wrap a form so that we can use it properly with AJAX. Essentially if the
+ * form wishes to render, it automatically does that, otherwise it returns
+ * the render array so we can see submission results.
+
+ * @param array $form
+ * An associative array containing the structure of the form.
+ * @param array $form_state
+ * An associative array containing the current state of the form.
+ * If the 'reset_html_ids' key is set to TRUE, it will prevent HTML IDs in
+ * forms from being incremented.
+ *
+ * @return mixed
+ * The output of the form, if it was rendered. If $form_state['ajax']
+ * is set, this will use ctools_modal_form_render so it will be
+ * a $command object suitable for ajax_render already.
+ *
+ * If the form was not rendered, the raw render array will be returned.
+ *
+ * If ajax is set the form will never be redirected.
+ */
+function ctools_modal_form_wrapper($form_id, &$form_state) {
+ // Since this will run again on form rebuild while still in the modal, prevent
+ // form IDs from being incremented.
+ // @todo https://drupal.org/node/1305882
+ if (!empty($form_state['reset_html_ids']) && !empty($_POST['ajax_html_ids'])) {
+ unset($_POST['ajax_html_ids']);
+ }
+
+ // This won't override settings already in.
+ $form_state += array(
+ 're_render' => FALSE,
+ 'no_redirect' => !empty($form_state['ajax']),
+ );
+
+ $output = drupal_build_form($form_id, $form_state);
+ if (!empty($form_state['ajax']) && (!$form_state['executed'] || $form_state['rebuild'])) {
+ return ctools_modal_form_render($form_state, $output);
+ }
+
+ return $output;
+}
+
+/**
+ * Render a form into an AJAX display.
+ */
+function ctools_modal_form_render($form_state, $output) {
+ if (is_array($output)) {
+ $output = drupal_render($output);
+ }
+
+ $title = empty($form_state['title']) ? drupal_get_title() : $form_state['title'];
+
+ // If there are messages for the form, render them.
+ if ($messages = theme('status_messages')) {
+ $output = $messages . $output;
+ }
+
+ $commands = array();
+ // If the form has not yet been rendered, render it.
+ $commands[] = ctools_modal_command_display($title, $output);
+ return $commands;
+}
+
+/**
+ * Perform a simple modal render and immediately exit.
+ *
+ * This is primarily used for error displays, since usually modals will
+ * contain forms.
+ */
+function ctools_modal_render($title, $output) {
+ $commands = array();
+ $commands[] = ctools_modal_command_display($title, $output);
+ print ajax_render($commands);
+}
diff --git a/sites/all/modules/ctools/includes/object-cache.cron.inc b/sites/all/modules/ctools/includes/object-cache.cron.inc
new file mode 100644
index 000000000..99f2276ca
--- /dev/null
+++ b/sites/all/modules/ctools/includes/object-cache.cron.inc
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * @file
+ * Contains cron hooks for the object cache tool.
+ *
+ * We use this to clean up old object caches.
+ */
+
+function ctools_object_cache_cron() {
+ if (variable_get('ctools_last_cron', 0) < time() - 86400) {
+ variable_set('ctools_last_cron', time());
+ ctools_include('object-cache');
+ ctools_object_cache_clean();
+ }
+}
diff --git a/sites/all/modules/ctools/includes/object-cache.inc b/sites/all/modules/ctools/includes/object-cache.inc
new file mode 100644
index 000000000..29225b05b
--- /dev/null
+++ b/sites/all/modules/ctools/includes/object-cache.inc
@@ -0,0 +1,205 @@
+<?php
+
+/**
+ * @file
+ * The non-volatile object cache is used to store an object while it is
+ * being edited, so that we don't have to save until we're completely
+ * done. The cache should be 'cleaned' on a regular basis, meaning to
+ * remove old objects from the cache, but otherwise the data in this
+ * cache must remain stable, as it includes unsaved changes.
+ */
+
+/**
+ * Get an object from the non-volatile ctools cache.
+ *
+ * This function caches in memory as well, so that multiple calls to this
+ * will not result in multiple database reads.
+ *
+ * @param $obj
+ * A 128 character or less string to define what kind of object is being
+ * stored; primarily this is used to prevent collisions.
+ * @param $name
+ * The name of the object being stored.
+ * @param $skip_cache
+ * Skip the memory cache, meaning this must be read from the db again.
+ * @param $sid
+ * The session id, allowing someone to use Session API or their own solution;
+ * defaults to session_id().
+ *
+ * @deprecated $skip_cache is deprecated in favor of drupal_static*
+ * @return
+ * The data that was cached.
+ */
+function ctools_object_cache_get($obj, $name, $skip_cache = FALSE, $sid = NULL) {
+ $cache = &drupal_static(__FUNCTION__, array());
+ $key = "$obj:$name";
+ if ($skip_cache) {
+ unset($cache[$key]);
+ }
+
+ if (!$sid) {
+ $sid = session_id();
+ }
+
+ if (!array_key_exists($key, $cache)) {
+ $data = db_query('SELECT * FROM {ctools_object_cache} WHERE sid = :session_id AND obj = :object AND name = :name', array(':session_id' => $sid, ':object' => $obj, ':name' => $name))
+ ->fetchObject();
+ if ($data) {
+ $cache[$key] = unserialize($data->data);
+ }
+ }
+ return isset($cache[$key]) ? $cache[$key] : NULL;
+}
+
+/**
+ * Store an object in the non-volatile ctools cache.
+ *
+ * @param $obj
+ * A 128 character or less string to define what kind of object is being
+ * stored; primarily this is used to prevent collisions.
+ * @param $name
+ * The name of the object being stored.
+ * @param $cache
+ * The object to be cached. This will be serialized prior to writing.
+ * @param $sid
+ * The session id, allowing someone to use Session API or their own solution;
+ * defaults to session_id().
+ */
+function ctools_object_cache_set($obj, $name, $cache, $sid = NULL) {
+ // Store the CTools session id in the user session to force a
+ // session for anonymous users in Drupal 7 and Drupal 6 Pressflow.
+ // see http://drupal.org/node/562374, http://drupal.org/node/861778
+ if (empty($GLOBALS['user']->uid) && empty($_SESSION['ctools_session_id'])) {
+ $_SESSION['ctools_hold_session'] = TRUE;
+ }
+
+ ctools_object_cache_clear($obj, $name, $sid);
+
+ if (!$sid) {
+ $sid = session_id();
+ }
+
+ db_insert('ctools_object_cache')
+ ->fields(array(
+ 'sid' => $sid,
+ 'obj' => $obj,
+ 'name' => $name,
+ 'data' => serialize($cache),
+ 'updated' => REQUEST_TIME,
+ ))
+ ->execute();
+}
+
+/**
+ * Remove an object from the non-volatile ctools cache
+ *
+ * @param $obj
+ * A 128 character or less string to define what kind of object is being
+ * stored; primarily this is used to prevent collisions.
+ * @param $name
+ * The name of the object being removed.
+ * @param $sid
+ * The session id, allowing someone to use Session API or their own solution;
+ * defaults to session_id().
+ */
+function ctools_object_cache_clear($obj, $name, $sid = NULL) {
+
+ if (!$sid) {
+ $sid = session_id();
+ }
+
+ db_delete('ctools_object_cache')
+ ->condition('sid', $sid)
+ ->condition('obj', $obj)
+ ->condition('name', $name)
+ ->execute();
+ // Ensure the static cache is emptied of this obj:name set.
+ drupal_static_reset('ctools_object_cache_get');
+}
+
+
+/**
+ * Determine if another user has a given object cached.
+ *
+ * This is very useful for 'locking' objects so that only one user can
+ * modify them.
+ *
+ * @param $obj
+ * A 128 character or less string to define what kind of object is being
+ * stored; primarily this is used to prevent collisions.
+ * @param $name
+ * The name of the object being removed.
+ * @param $sid
+ * The session id, allowing someone to use Session API or their own solution;
+ * defaults to session_id().
+ *
+ * @return
+ * An object containing the UID and updated date if found; NULL if not.
+ */
+function ctools_object_cache_test($obj, $name, $sid = NULL) {
+
+ if (!$sid) {
+ $sid = session_id();
+ }
+
+ return db_query('SELECT s.uid, c.updated FROM {ctools_object_cache} c INNER JOIN {sessions} s ON c.sid = s.sid WHERE s.sid <> :session_id AND c.obj = :obj AND c.name = :name ORDER BY c.updated ASC', array(':session_id' => $sid, ':obj' => $obj, ':name' => $name))
+ ->fetchObject();
+}
+
+/**
+ * Get the cache status of a group of objects.
+ *
+ * This is useful for displaying lock status when listing a number of objects
+ * an an administration UI.
+ *
+ * @param $obj
+ * A 128 character or less string to define what kind of object is being
+ * stored; primarily this is used to prevent collisions.
+ * @param $names
+ * An array of names of objects
+ *
+ * @return
+ * An array of objects containing the UID and updated date for each name found.
+ */
+function ctools_object_cache_test_objects($obj, $names) {
+ return db_query("SELECT c.name, s.uid, c.updated FROM {ctools_object_cache} c INNER JOIN {sessions} s ON c.sid = s.sid WHERE c.obj = :obj AND c.name IN (:names) ORDER BY c.updated ASC", array(':obj' => $obj, ':names' => $names))
+ ->fetchAllAssoc('name');
+}
+
+/**
+ * Remove an object from the non-volatile ctools cache for all session IDs.
+ *
+ * This is useful for clearing a lock.
+ *
+ * @param $obj
+ * A 128 character or less string to define what kind of object is being
+ * stored; primarily this is used to prevent collisions.
+ * @param $name
+ * The name of the object being removed.
+ */
+function ctools_object_cache_clear_all($obj, $name) {
+ db_delete('ctools_object_cache')
+ ->condition('obj', $obj)
+ ->condition('name', $name)
+ ->execute();
+ // Ensure the static cache is emptied of this obj:name set.
+ $cache = &drupal_static('ctools_object_cache_get', array());
+ unset($cache["$obj:$name"]);
+}
+
+/**
+ * Remove all objects in the object cache that are older than the
+ * specified age.
+ *
+ * @param $age
+ * The minimum age of objects to remove, in seconds. For example, 86400 is
+ * one day. Defaults to 7 days.
+ */
+function ctools_object_cache_clean($age = NULL) {
+ if (empty($age)) {
+ $age = 86400 * 7; // 7 days
+ }
+ db_delete('ctools_object_cache')
+ ->condition('updated', REQUEST_TIME - $age, '<')
+ ->execute();
+}
diff --git a/sites/all/modules/ctools/includes/page-wizard.inc b/sites/all/modules/ctools/includes/page-wizard.inc
new file mode 100644
index 000000000..a211361b2
--- /dev/null
+++ b/sites/all/modules/ctools/includes/page-wizard.inc
@@ -0,0 +1,194 @@
+<?php
+
+/**
+ * Fetch metadata on a specific page_wizard plugin.
+ *
+ * @param $page_wizard
+ * Name of a panel page_wizard.
+ *
+ * @return
+ * An array with information about the requested panel page_wizard.
+ */
+function page_manager_get_page_wizard($page_wizard) {
+ ctools_include('plugins');
+ return ctools_get_plugins('page_manager', 'page_wizards', $page_wizard);
+}
+
+/**
+ * Fetch metadata for all page_wizard plugins.
+ *
+ * @return
+ * An array of arrays with information about all available panel page_wizards.
+ */
+function page_manager_get_page_wizards() {
+ ctools_include('plugins');
+ return ctools_get_plugins('page_manager', 'page_wizards');
+}
+
+/**
+ * Get the cached changes to a given wizard.
+ *
+ * @return
+ * A $cache object or a clean cache object if none could be loaded.
+ */
+function page_manager_get_wizard_cache($plugin) {
+ if (is_string($plugin)) {
+ $plugin = page_manager_get_page_wizard($plugin);
+ }
+
+ if (empty($plugin)) {
+ return;
+ }
+
+ ctools_include('object-cache');
+
+ // Since contexts might be cache, include this so they load.
+ ctools_include('context');
+ $cache = ctools_object_cache_get('page_manager_page_wizard', $plugin['name']);
+ if (!$cache) {
+ $cache = page_manager_make_wizard_cache($plugin);
+ }
+
+ return $cache;
+}
+
+function page_manager_make_wizard_cache($plugin) {
+ $cache = new stdClass;
+ $cache->plugin = $plugin;
+ if ($function = ctools_plugin_get_function($plugin, 'default cache')) {
+ $function($cache);
+ }
+
+ return $cache;
+}
+
+/**
+ * Store changes to a task handler in the object cache.
+ */
+function page_manager_set_wizard_cache($cache) {
+ ctools_include('object-cache');
+ ctools_object_cache_set('page_manager_page_wizard', $cache->plugin['name'], $cache);
+}
+
+/**
+ * Remove an item from the object cache.
+ */
+function page_manager_clear_wizard_cache($name) {
+ ctools_include('object-cache');
+ ctools_object_cache_clear('page_manager_page_wizard', $name);
+}
+
+/**
+ * Menu callback for the page wizard.
+ */
+function page_manager_page_wizard($name, $step = NULL) {
+ $plugin = page_manager_get_page_wizard($name);
+ if (!$plugin) {
+ return MENU_NOT_FOUND;
+ }
+
+ // Check for simple access string on plugin.
+ if (!empty($plugin['access']) && !user_access($plugin['access'])) {
+ return MENU_ACCESS_DENIED;
+ }
+
+ // Check for possibly more complex access callback on plugin.
+ if ($function = ctools_plugin_get_function($plugin, 'access callback') && !$function($plugin)) {
+ return MENU_ACCESS_DENIED;
+ }
+
+ // Create a basic wizard.in form info array and merge it with the
+ // plugin's.
+ $form_info = array(
+ 'id' => 'page_manager_page_wizard',
+ 'show trail' => TRUE,
+ 'show back' => TRUE,
+ 'show return' => FALSE,
+ 'show cancel' => FALSE,
+ 'next callback' => 'page_manager_page_wizard_next',
+ 'finish callback' => 'page_manager_page_wizard_finish',
+
+ 'path' => "admin/structure/pages/wizard/$name/%step",
+ );
+
+ $form_info = array_merge_recursive($form_info, $plugin['form info']);
+
+ // If step is unset, go with the basic step.
+ if (!isset($step)) {
+ $step = current(array_keys($form_info['order']));
+ $cache = page_manager_make_wizard_cache($plugin);
+ }
+ else {
+ $cache = page_manager_get_wizard_cache($plugin);
+ }
+
+ ctools_include('wizard');
+ $form_state = array(
+ 'plugin' => $plugin,
+ 'wizard cache' => $cache,
+ 'type' => 'edit',
+ 'rerender' => TRUE,
+ 'step' => $step,
+ );
+
+ if (isset($plugin['page title'])) {
+ drupal_set_title($plugin['page title']);
+ }
+
+ if ($function = ctools_plugin_get_function($form_state['plugin'], 'start')) {
+ $function($form_info, $step, $form_state);
+ }
+
+ $output = ctools_wizard_multistep_form($form_info, $step, $form_state);
+ return $output;
+}
+
+/**
+ * Callback generated when the add page process is finished.
+ */
+function page_manager_page_wizard_finish(&$form_state) {
+ if ($function = ctools_plugin_get_function($form_state['plugin'], 'finish')) {
+ $function($form_state);
+ }
+
+ page_manager_clear_wizard_cache($form_state['wizard cache']->plugin['name']);
+}
+
+/**
+ * Callback generated when the 'next' button is clicked.
+ *
+ * All we do here is store the cache.
+ */
+function page_manager_page_wizard_next(&$form_state) {
+ if ($function = ctools_plugin_get_function($form_state['plugin'], 'next')) {
+ $function($form_state);
+ }
+
+ page_manager_set_wizard_cache($form_state['wizard cache']);
+}
+
+/**
+ * Provide a simple administrative list of all wizards.
+ *
+ * This is called as a page callback, but can also be used by any module
+ * that wants to get a list of wizards for its type.
+ */
+function page_manager_page_wizard_list($type = NULL) {
+ $plugins = page_manager_get_page_wizards();
+ if (empty($plugins)) {
+ return '<p>' . t('There are no wizards available at this time.') . '</p>';
+ }
+
+ uasort($plugins, 'ctools_plugin_sort');
+
+ $output = '<dl class="page-manager-wizards">';
+ foreach ($plugins as $id => $plugin) {
+ if (!$type || (isset($plugin['type']) && $plugin['type'] == $type)) {
+ $output .= '<dt>' . l($plugin['title'], 'admin/structure/pages/wizard/' . $id) . '</dt>';
+ $output .= '<dd class="description">' . $plugin['description'] . '</dd>';
+ }
+ }
+ $output .= '</dl>';
+
+ return $output;
+}
diff --git a/sites/all/modules/ctools/includes/page-wizard.menu.inc b/sites/all/modules/ctools/includes/page-wizard.menu.inc
new file mode 100644
index 000000000..7ed932ed1
--- /dev/null
+++ b/sites/all/modules/ctools/includes/page-wizard.menu.inc
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Contains menu item registration for the page manager page wizards tool.
+ */
+
+function ctools_page_wizard_menu(&$items) {
+ if (!module_exists('page_manager')) {
+ return;
+ }
+
+ $base = array(
+ 'access arguments' => array('use page manager'),
+ 'file' => 'includes/page-wizard.inc',
+ 'type' => MENU_CALLBACK,
+ );
+
+ $items['admin/structure/pages/wizard'] = array(
+ 'title' => 'Wizards',
+ 'page callback' => 'page_manager_page_wizard_list',
+ 'page arguments' => array(4),
+ 'weight' => -5,
+ 'type' => MENU_LOCAL_TASK,
+ ) + $base;
+
+ $items['admin/structure/pages/wizard/%'] = array(
+ 'title' => 'Wizard',
+ 'page callback' => 'page_manager_page_wizard',
+ 'page arguments' => array(4),
+ ) + $base;
+}
diff --git a/sites/all/modules/ctools/includes/plugins-admin.inc b/sites/all/modules/ctools/includes/plugins-admin.inc
new file mode 100644
index 000000000..d4ead0a48
--- /dev/null
+++ b/sites/all/modules/ctools/includes/plugins-admin.inc
@@ -0,0 +1,208 @@
+<?php
+
+/**
+ * @file
+ * Contains generic plugin administration functions.
+ *
+ * CTools includes the ability to (relatively) easily provide wizard based
+ * configuration for plugins, meaning plugins that need configuration can
+ * automatically allow multi-step forms.
+ *
+ * Implementing this
+ */
+/**
+ * Get a plugin configuration form.
+ *
+ * The $form_info and $form_state need to be preconfigured with data you'll need
+ * such as whether or not you're using ajax, or the modal. $form_info will need
+ * your next/submit callbacks so that you can cache your data appropriately.
+ *
+ * @param array $form_info
+ * This form_info *must* contain at least the path. next and finish callbacks
+ * are advisable but not necessary if using wizard's auto caching. Setting
+ * a cache mechanism is advisable. If not using auto caching, next and finish
+ * callbacks will be necessary.
+ *
+ * Also be sure to set up things such as AJAX settings and any temporary
+ * data you will need. Simply setting 'modal' => TRUE and
+ * 'modal return' => TRUE should make this system work well in the modal.
+ *
+ * In addition to standard wizard fields, this supports one extra field:
+ * - 'default form': A callback to a 'wrapper' that will be applied to either
+ * the first or a marked form. This is useful for adding global features that
+ * are applicable to all instances of a plugin, such as identifiers, or
+ * contexts, or the like.
+ *
+ * @param array &$form_state
+ * This is a standard form state array. This system imposes some requirements
+ * on what will be in the form state:
+ *
+ * - 'plugin': The plugin definition being edited.
+ * - 'conf': The configuration that is being edited, presumed to be an array.
+ * Ultimately at the end, this is where the modified config will be
+ * found.
+ * - 'op': The operation, either 'add' or 'edit'. This is used to derive form
+ * names and can also be used to derive default values from the plugin.
+ * - 'step': The current step. May be null if it is the 'first' step, but
+ * generally needs to be defined in the path.
+ *
+ * @param string $default_form
+ * An optional default form that can be added.
+ *
+ * @return
+ * If this function returns false, no form exists.
+ */
+function ctools_plugin_configure_form($form_info, &$form_state) {
+ // Turn the forms defined in the plugin into the format the wizard needs.
+ _ctools_plugin_configure_create_form_info($form_info, $form_state['plugin'], $form_state['op']);
+
+ if (empty($form_info['order'])) {
+ return FALSE;
+ }
+
+ ctools_include('wizard');
+ return ctools_wizard_multistep_form($form_info, $form_state['step'], $form_state);
+}
+
+function _ctools_plugin_configure_create_form_info(&$form_info, $plugin_definition, $op) {
+ // Provide a few extra defaults.
+ $form_info += array(
+ 'id' => 'ctools_plugin_configure_form',
+ 'show back' => TRUE,
+ );
+
+ $add_form = isset($form_info['add form name']) ? $form_info['add form name'] : 'add form';
+ $edit_form = isset($form_info['edit form name']) ? $form_info['edit form name'] : 'edit form';
+
+ // Figure out what the forms should actually be. Since they can be specified
+ // in a couple of different ways (in order to support simple declarations for
+ // the minimal forms but more complex declarations for powerful wizards).
+ if ($op == 'add') {
+ if (!empty($plugin_definition[$add_form])) {
+ $info = $plugin_definition[$add_form];
+ }
+ }
+ if (!isset($info) || $op == 'edit') {
+ // Use the edit form for the add form if add form was completely left off.
+ if (!empty($plugin_definition[$edit_form])) {
+ $info = $plugin_definition[$edit_form];
+ }
+ }
+
+ // If there is a default form wrapper, but no form is supplied,
+ // use the wrapper as the form.
+ if (empty($info) && !empty($form_info['default form'])) {
+ $info = $form_info['default form'];
+ }
+
+ // @todo we may want to make these titles more settable?
+ if (is_string($info)) {
+ if (empty($plugin_definition['title'])) {
+ $title = t('Configure');
+ }
+ else if ($op == 'add') {
+ $title = t('Configure new !plugin_title', array('!plugin_title' => $plugin_definition['title']));
+ }
+ else {
+ $title = t('Configure !plugin_title', array('!plugin_title' => $plugin_definition['title']));
+ }
+ if (empty($form_info['order'])) {
+ $form_info['order'] = array();
+ }
+ $form_info['order']['form'] = $title;
+
+ if (empty($form_info['forms'])) {
+ $form_info['forms'] = array();
+ }
+ $form_info['forms']['form'] = array(
+ 'title' => $title,
+ 'form id' => $info,
+ );
+
+ // Add the default form if one is specified.
+ if (!empty($form_info['default form']) && $form_info['forms']['form']['form id'] != $form_info['default form']) {
+ $form_info['forms']['form']['wrapper'] = $form_info['default form'];
+ }
+
+ // If no submit is supplied, supply the default submit which will do the
+ // most obvious task.
+ if (!function_exists($form_info['forms']['form']['form id'] . '_submit')) {
+ // Store the original wrapper so we can chain it.
+ if (!empty($form_info['forms']['form']['wrapper'])) {
+ $form_info['forms']['form']['original wrapper'] = $form_info['forms']['form']['wrapper'];
+ }
+ $form_info['forms']['form']['wrapper'] = 'ctools_plugins_default_form_wrapper';
+ }
+ }
+ else if (is_array($info)) {
+ if (empty($form_info['order'])) {
+ $form_info['order'] = array();
+ }
+ if (empty($form_info['forms'])) {
+ $form_info['forms'] = array();
+ }
+ $count = 0;
+ $base = 'step';
+ $wrapper = NULL;
+ foreach ($info as $form_id => $title) {
+ $step = $base . ++$count;
+ if (empty($wrapper)) {
+ $wrapper = $step;
+ }
+
+ if (is_array($title)) {
+ if (!empty($title['default'])) {
+ $wrapper = $step;
+ }
+ $title = $title['title'];
+ }
+
+ $form_info['order'][$step] = $title;
+ $form_info['forms'][$step] = array(
+ 'title' => $title,
+ 'form id' => $form_id,
+ );
+ }
+ if ($wrapper && !empty($form_info['default form'])) {
+ $form_info['forms'][$wrapper]['wrapper'] = $form_info['default form'];
+ }
+ }
+}
+
+/**
+ * A wrapper to provide a default submit so that plugins don't have to duplicate
+ * a whole bunch of code to do what most of them want to do anyway.
+ */
+function ctools_plugins_default_form_wrapper($form, &$form_state) {
+ $form_info = &$form_state['form_info'];
+ $info = $form_info['forms'][$form_state['step']];
+
+ if (isset($info['original wrapper']) && function_exists($info['original wrapper'])) {
+ $form = $info['original wrapper']($form, $form_state);
+ }
+
+ if (isset($form['buttons']['next'])) {
+ if (empty($form['buttons']['next']['#submit'])) {
+ $form['buttons']['next']['#submit'] = $form['#submit'];
+ }
+ $form['buttons']['next']['#submit'][] = 'ctools_plugins_default_form_wrapper_submit';
+ }
+ if (isset($form['buttons']['return'])) {
+ if (empty($form['buttons']['return']['#submit'])) {
+ $form['buttons']['return']['#submit'] = $form['#submit'];
+ }
+ $form['buttons']['return']['#submit'][] = 'ctools_plugins_default_form_wrapper_submit';
+ }
+ return $form;
+}
+
+/**
+ * Provide a default storage mechanism.
+ */
+function ctools_plugins_default_form_wrapper_submit(&$form, &$form_state) {
+ foreach (array_keys($form_state['plugin']['defaults']) as $key) {
+ if (isset($form_state['values'][$key])) {
+ $form_state['conf'][$key] = $form_state['values'][$key];
+ }
+ }
+}
diff --git a/sites/all/modules/ctools/includes/plugins.inc b/sites/all/modules/ctools/includes/plugins.inc
new file mode 100644
index 000000000..79a6087f3
--- /dev/null
+++ b/sites/all/modules/ctools/includes/plugins.inc
@@ -0,0 +1,917 @@
+<?php
+
+/**
+ * @file
+ *
+ * Contains routines to organize and load plugins. It allows a special
+ * variation of the hook system so that plugins can be kept in separate
+ * .inc files, and can be either loaded all at once or loaded only when
+ * necessary.
+ */
+
+/**
+ * Get an array of information about modules that support an API.
+ *
+ * This will ask each module if they support the given API, and if they do
+ * it will return an array of information about the modules that do.
+ *
+ * This function invokes hook_ctools_api. This invocation is statically
+ * cached, so feel free to call it as often per page run as you like, it
+ * will cost very little.
+ *
+ * This function can be used as an alternative to module_implements and can
+ * thus be used to find a precise list of modules that not only support
+ * a given hook (aka 'api') but also restrict to only modules that use
+ * the given version. This will allow multiple modules moving at different
+ * paces to still be able to work together and, in the event of a mismatch,
+ * either fall back to older behaviors or simply cease loading, which is
+ * still better than a crash.
+ *
+ * @param $owner
+ * The name of the module that controls the API.
+ * @param $api
+ * The name of the api. The api name forms the file name:
+ * $module.$api.inc
+ * @param $minimum_version
+ * The lowest version API that is compatible with this one. If a module
+ * reports its API as older than this, its files will not be loaded. This
+ * should never change during operation.
+ * @param $current_version
+ * The current version of the api. If a module reports its minimum API as
+ * higher than this, its files will not be loaded. This should never change
+ * during operation.
+ *
+ * @return
+ * An array of API information, keyed by module. Each module's information will
+ * contain:
+ * - 'version': The version of the API required by the module. The module
+ * should use the lowest number it can support so that the widest range
+ * of supported versions can be used.
+ * - 'path': If not provided, this will be the module's path. This is
+ * where the module will store any subsidiary files. This differs from
+ * plugin paths which are figured separately.
+ *
+ * APIs can request any other information to be placed here that they might
+ * need. This should be in the documentation for that particular API.
+ */
+function ctools_plugin_api_info($owner, $api, $minimum_version, $current_version) {
+ $cache = &drupal_static(__FUNCTION__, array());
+ if (!isset($cache[$owner][$api])) {
+ $cache[$owner][$api] = array();
+
+ $hook = ctools_plugin_api_get_hook($owner, $api);
+
+ foreach (module_implements($hook) as $module) {
+ $function = $module . '_' . $hook;
+ $info = $function($owner, $api);
+ $version = NULL;
+ // This is added to make hook_views_api() compatible with this, since
+ // views used a different version key.
+ if (isset($info['version'])) {
+ $version = $info['version'];
+ }
+ else if (isset($info['api'])) {
+ $version = $info['api'];
+ }
+
+ if (!isset($version)) {
+ continue;
+ }
+
+ // Only process if version is between minimum and current, inclusive.
+ if (($version == $minimum_version) || ($version == $current_version)
+ || (version_compare($version, $minimum_version, '>=')
+ && version_compare($version, $current_version, '<='))) {
+ if (!isset($info['path'])) {
+ $info['path'] = drupal_get_path('module', $module);
+ }
+ $cache[$owner][$api][$module] = $info;
+ }
+ }
+
+ // And allow themes to implement these as well.
+ $themes = _ctools_list_themes();
+ foreach ($themes as $name => $theme) {
+ if (!empty($theme->info['api'][$owner][$api])) {
+ $info = $theme->info['api'][$owner][$api];
+ if (!isset($info['version'])) {
+ continue;
+ }
+
+ // Only process if version is between minimum and current, inclusive.
+ if (version_compare($info['version'], $minimum_version, '>=') && version_compare($info['version'], $current_version, '<=')) {
+ if (!isset($info['path'])) {
+ $info['path'] = '';
+ }
+ // Because themes can't easily specify full path, we add it here
+ // even though we do not for modules:
+ $info['path'] = drupal_get_path('theme', $name) . '/' . $info['path'];
+ $cache[$owner][$api][$name] = $info;
+ }
+ }
+ }
+
+ // Allow other modules to hook in.
+ drupal_alter($hook, $cache[$owner][$api], $owner, $api);
+ }
+
+ return $cache[$owner][$api];
+}
+
+/**
+ * Load a group of API files.
+ *
+ * This will ask each module if they support the given API, and if they do
+ * it will load the specified file name. The API and the file name
+ * coincide by design.
+ *
+ * @param $owner
+ * The name of the module that controls the API.
+ * @param $api
+ * The name of the api. The api name forms the file name:
+ * $module.$api.inc, though this can be overridden by the module's response.
+ * @param $minimum_version
+ * The lowest version API that is compatible with this one. If a module
+ * reports its API as older than this, its files will not be loaded. This
+ * should never change during operation.
+ * @param $current_version
+ * The current version of the api. If a module reports its minimum API as
+ * higher than this, its files will not be loaded. This should never change
+ * during operation.
+ *
+ * @return
+ * The API information, in case you need it.
+ */
+function ctools_plugin_api_include($owner, $api, $minimum_version, $current_version) {
+ static $already_done = array();
+
+ $info = ctools_plugin_api_info($owner, $api, $minimum_version, $current_version);
+ foreach ($info as $module => $plugin_info) {
+ if (!isset($already_done[$owner][$api][$module])) {
+ if (isset($plugin_info["$api file"])) {
+ $file = $plugin_info["$api file"];
+ }
+ else if (isset($plugin_info['file'])) {
+ $file = $plugin_info['file'];
+ }
+ else {
+ $file = "$module.$api.inc";
+ }
+
+ if (file_exists(DRUPAL_ROOT . "/$plugin_info[path]/$file")) {
+ require_once DRUPAL_ROOT . "/$plugin_info[path]/$file";
+ }
+ else if (file_exists(DRUPAL_ROOT . "/$file")) {
+ require_once DRUPAL_ROOT . "/$file";
+ }
+ $already_done[$owner][$api][$module] = TRUE;
+ }
+ }
+
+ return $info;
+}
+
+/**
+ * Find out what hook to use to determine if modules support an API.
+ *
+ * By default, most APIs will use hook_ctools_plugin_api, but some modules
+ * want sole ownership. This technique lets modules define what hook
+ * to use.
+ */
+function ctools_plugin_api_get_hook($owner, $api) {
+ // Allow modules to use their own hook for this. The only easy way to do
+ // this right now is with a magically named function.
+ if (function_exists($function = $owner . '_' . $api . '_hook_name')) {
+ $hook = $function();
+ }
+ else if (function_exists($function = $owner . '_ctools_plugin_api_hook_name')) {
+ $hook = $function();
+ }
+
+ // Do this last so that if the $function above failed to return, we have a
+ // sane default.
+ if (empty($hook)) {
+ $hook = 'ctools_plugin_api';
+ }
+
+ return $hook;
+}
+
+/**
+ * Fetch a group of plugins by name.
+ *
+ * @param $module
+ * The name of the module that utilizes this plugin system. It will be
+ * used to call hook_ctools_plugin_$plugin() to get more data about the plugin.
+ * @param $type
+ * The type identifier of the plugin.
+ * @param $id
+ * If specified, return only information about plugin with this identifier.
+ * The system will do its utmost to load only plugins with this id.
+ *
+ * @return
+ * An array of information arrays about the plugins received. The contents
+ * of the array are specific to the plugin.
+ */
+function ctools_get_plugins($module, $type, $id = NULL) {
+ // Store local caches of plugins and plugin info so we don't have to do full
+ // lookups every time.
+ static $drupal_static_fast;
+ if (!isset($drupal_static_fast)) {
+ $drupal_static_fast['plugins'] = &drupal_static('ctools_plugins', array());
+ }
+ $plugins = &$drupal_static_fast['plugins'];
+
+ $info = ctools_plugin_get_plugin_type_info();
+
+ // Bail out noisily if an invalid module/type combination is requested.
+ if (!isset($info[$module][$type])) {
+ watchdog('ctools', 'Invalid plugin module/type combination requested: module @module and type @type', array('@module' => $module, '@type' => $type), WATCHDOG_ERROR);
+ return array();
+ }
+
+ // Make sure our plugins array is populated.
+ if (!isset($plugins[$module][$type])) {
+ $plugins[$module][$type] = array();
+ }
+
+ // Attempt to shortcut this whole piece of code if we already have
+ // the requested plugin:
+ if ($id && array_key_exists($id, $plugins[$module][$type])) {
+ return $plugins[$module][$type][$id];
+ }
+
+ // Store the status of plugin loading. If a module plugin type pair is true,
+ // then it is fully loaded and no searching or setup needs to be done.
+ $setup = &drupal_static('ctools_plugin_setup', array());
+
+ // We assume we don't need to build a cache.
+ $build_cache = FALSE;
+
+ // If the plugin info says this can be cached, check cache first.
+ if ($info[$module][$type]['cache'] && empty($setup[$module][$type])) {
+ $cache = cache_get("plugins:$module:$type", $info[$module][$type]['cache table']);
+
+ if (!empty($cache->data)) {
+ // Cache load succeeded so use the cached plugin list.
+ $plugins[$module][$type] = $cache->data;
+ // Set $setup to true so we know things where loaded.
+ $setup[$module][$type] = TRUE;
+ }
+ else {
+ // Cache load failed so store that we need to build and write the cache.
+ $build_cache = TRUE;
+ }
+ }
+
+ // Always load all hooks if we need them. Note we only need them now if the
+ // plugin asks for them. We can assume that if we have plugins we've already
+ // called the global hook.
+ if (!empty($info[$module][$type]['use hooks']) && empty($plugins[$module][$type])) {
+ $plugins[$module][$type] = ctools_plugin_load_hooks($info[$module][$type]);
+ }
+
+ // Then see if we should load all files. We only do this if we
+ // want a list of all plugins or there was a cache miss.
+ if (empty($setup[$module][$type]) && ($build_cache || !$id)) {
+ $setup[$module][$type] = TRUE;
+ $plugins[$module][$type] = array_merge($plugins[$module][$type], ctools_plugin_load_includes($info[$module][$type]));
+ // If the plugin can have child plugins, and we're loading all plugins,
+ // go through the list of plugins we have and find child plugins.
+ if (!$id && !empty($info[$module][$type]['child plugins'])) {
+ // If a plugin supports children, go through each plugin and ask.
+ $temp = array();
+ foreach ($plugins[$module][$type] as $name => $plugin) {
+ // The strpos ensures that we don't try to find children for plugins
+ // that are already children.
+ if (!empty($plugin['get children']) && function_exists($plugin['get children']) && strpos($name, ':') === FALSE) {
+ $temp = array_merge($plugin['get children']($plugin, $name), $temp);
+ }
+ else {
+ $temp[$name] = $plugin;
+ }
+ }
+ $plugins[$module][$type] = $temp;
+ }
+ }
+
+
+ // If we were told earlier that this is cacheable and the cache was
+ // empty, give something back.
+ if ($build_cache) {
+ cache_set("plugins:$module:$type", $plugins[$module][$type], $info[$module][$type]['cache table']);
+ }
+
+ // If no id was requested, we are finished here:
+ if (!$id) {
+ // Use array_filter because looking for unknown plugins could cause NULL
+ // entries to appear in the list later.
+ return array_filter($plugins[$module][$type]);
+ }
+
+ // Check to see if we need to look for the file
+ if (!array_key_exists($id, $plugins[$module][$type])) {
+ // If we can have child plugins, check to see if the plugin name is in the
+ // format of parent:child and break it up if it is.
+ if (!empty($info[$module][$type]['child plugins']) && strpos($id, ':') !== FALSE) {
+ list($parent, $child) = explode(':', $id, 2);
+ }
+ else {
+ $parent = $id;
+ }
+
+ if (!array_key_exists($parent, $plugins[$module][$type])) {
+ $result = ctools_plugin_load_includes($info[$module][$type], $parent);
+ // Set to either what was returned or NULL.
+ $plugins[$module][$type][$parent] = isset($result[$parent]) ? $result[$parent] : NULL;
+ }
+
+ // If we are looking for a child, and have the parent, ask the parent for the child.
+ if (!empty($child) && !empty($plugins[$module][$type][$parent]) && function_exists($plugins[$module][$type][$parent]['get child'])) {
+ $plugins[$module][$type][$id] = $plugins[$module][$type][$parent]['get child']($plugins[$module][$type][$parent], $parent, $child);
+ }
+ }
+
+ // At this point we should either have the plugin, or a NULL.
+ return $plugins[$module][$type][$id];
+}
+
+/**
+ * Return the full list of plugin type info for all plugin types registered in
+ * the current system.
+ *
+ * This function manages its own cache getting/setting, and should always be
+ * used as the way to initially populate the list of plugin types. Make sure you
+ * call this function to properly populate the ctools_plugin_type_info static
+ * variable.
+ *
+ * @return array
+ * A multilevel array of plugin type info, the outer array keyed on module
+ * name and each inner array keyed on plugin type name.
+ */
+function ctools_plugin_get_plugin_type_info($flush = FALSE) {
+ static $drupal_static_fast;
+ if (!isset($drupal_static_fast)) {
+ $drupal_static_fast['info_loaded'] = &drupal_static('ctools_plugin_type_info_loaded', FALSE);
+ $drupal_static_fast['all_type_info'] = &drupal_static('ctools_plugin_type_info', array());
+ }
+ $info_loaded = &$drupal_static_fast['info_loaded'];
+ $all_type_info = &$drupal_static_fast['all_type_info'];
+
+ // Only trigger info loading once.
+ if ($info_loaded && !$flush) {
+ return $all_type_info;
+ }
+ $info_loaded = TRUE;
+
+ $cache = cache_get('ctools_plugin_type_info');
+ if (!empty($cache->data) && !$flush) {
+ // Plugin type info cache is warm, use it.
+ $all_type_info = $cache->data;
+ }
+ else {
+ // Cache expired, refill it.
+ foreach (module_implements('ctools_plugin_type') as $module) {
+ $module_infos = array();
+ $function = $module . '_ctools_plugin_type';
+ $module_infos = $function();
+
+ foreach ($module_infos as $plugin_type_name => $plugin_type_info) {
+ // Apply defaults. Array addition will not overwrite pre-existing keys.
+ $plugin_type_info += array(
+ 'module' => $module,
+ 'type' => $plugin_type_name,
+ 'cache' => FALSE,
+ 'cache table' => 'cache',
+ 'classes' => array(),
+ 'use hooks' => FALSE,
+ 'defaults' => array(),
+ 'process' => '',
+ 'alterable' => TRUE,
+ 'extension' => 'inc',
+ 'info file' => FALSE,
+ 'hook' => $module . '_' . $plugin_type_name,
+ 'load themes' => FALSE,
+ );
+ $all_type_info[$module][$plugin_type_name] = $plugin_type_info;
+ }
+ }
+ cache_set('ctools_plugin_type_info', $all_type_info);
+ }
+
+ return $all_type_info;
+}
+
+/**
+ * Reset all static caches that affect the result of ctools_get_plugins().
+ */
+function ctools_get_plugins_reset() {
+ drupal_static_reset('ctools_plugins');
+ drupal_static_reset('ctools_plugin_setup');
+ drupal_static_reset('ctools_plugin_load_includes');
+ drupal_static_reset('ctools_plugin_api_info');
+}
+
+/**
+ * Load plugins from a directory.
+ *
+ * @param $info
+ * The plugin info as returned by ctools_plugin_get_info()
+ * @param $file
+ * The file to load if we're looking for just one particular plugin.
+ *
+ * @return
+ * An array of information created for this plugin.
+ */
+function ctools_plugin_load_includes($info, $filename = NULL) {
+ // Keep a static array so we don't hit file_scan_directory more than necessary.
+ $all_files = &drupal_static(__FUNCTION__, array());
+
+ // store static of plugin arrays for reference because they can't be reincluded.
+ static $plugin_arrays = array();
+
+ if (!isset($all_files[$info['module']][$info['type']])) {
+ $cache = cache_get("ctools_plugin_files:$info[module]:$info[type]");
+ if ($cache) {
+ $all_files[$info['module']][$info['type']] = $cache->data;
+ }
+ // Do not attempt any file scan even if the cached entry was empty.
+ // A NULL entry here would mean the plugin just does not exists, and we
+ // cannot afford to run file scan on production sites normal run.
+ elseif (!isset($all_files[$info['module']][$info['type']])) {
+ $all_files[$info['module']][$info['type']] = array();
+ // Load all our plugins.
+ $directories = ctools_plugin_get_directories($info);
+ $extension = (empty($info['info file']) || ($info['extension'] != 'inc')) ? $info['extension'] : 'info';
+
+ foreach ($directories as $module => $path) {
+ $all_files[$info['module']][$info['type']][$module] = file_scan_directory($path, '/\.' . $extension . '$/', array('key' => 'name'));
+ }
+
+ cache_set("ctools_plugin_files:$info[module]:$info[type]", $all_files[$info['module']][$info['type']]);
+ }
+ }
+ $file_list = $all_files[$info['module']][$info['type']];
+ $plugins = array();
+
+ // Iterate through all the plugin .inc files, load them and process the hook
+ // that should now be available.
+ foreach (array_filter($file_list) as $module => $files) {
+ if ($filename) {
+ $files = isset($files[$filename]) ? array($filename => $files[$filename]) : array();
+ }
+ foreach ($files as $file) {
+ if (!empty($info['info file'])) {
+ // Parse a .info file
+ $result = ctools_plugin_process_info($info, $module, $file);
+ }
+ else {
+ // Parse a hook.
+ $plugin = NULL; // ensure that we don't have something leftover from earlier.
+
+ if (isset($plugin_arrays[$file->uri])) {
+ $identifier = $plugin_arrays[$file->uri];
+ }
+ else {
+
+ require_once DRUPAL_ROOT . '/' . $file->uri;
+ // .inc files have a special format for the hook identifier.
+ // For example, 'foo.inc' in the module 'mogul' using the plugin
+ // whose hook is named 'borg_type' should have a function named (deep breath)
+ // mogul_foo_borg_type()
+
+ // If, however, the .inc file set the quasi-global $plugin array, we
+ // can use that and not even call a function. Set the $identifier
+ // appropriately and ctools_plugin_process() will handle it.
+ if (isset($plugin)) {
+ $plugin_arrays[$file->uri] = $plugin;
+ $identifier = $plugin;
+ }
+ else {
+ $identifier = $module . '_' . $file->name;
+ }
+ }
+
+ $result = ctools_plugin_process($info, $module, $identifier, dirname($file->uri), basename($file->uri), $file->name);
+ }
+ if (is_array($result)) {
+ $plugins = array_merge($plugins, $result);
+ }
+ }
+ }
+ return $plugins;
+}
+
+/**
+ * Get a list of directories to search for plugins of the given type.
+ *
+ * This utilizes hook_ctools_plugin_directory() to determine a complete list of
+ * directories. Only modules that implement this hook and return a string
+ * value will have their directories included.
+ *
+ * @param $info
+ * The $info array for the plugin as returned by ctools_plugin_get_info().
+ *
+ * @return array $directories
+ * An array of directories to search.
+ */
+function ctools_plugin_get_directories($info) {
+ $directories = array();
+
+ foreach (module_implements('ctools_plugin_directory') as $module) {
+ $function = $module . '_ctools_plugin_directory';
+ $result = $function($info['module'], $info['type']);
+ if ($result && is_string($result)) {
+ $directories[$module] = drupal_get_path('module', $module) . '/' . $result;
+ }
+ }
+
+ if (!empty($info['load themes'])) {
+ $themes = _ctools_list_themes();
+ foreach ($themes as $name => $theme) {
+ if (!empty($theme->info['plugins'][$info['module']][$info['type']])) {
+ $directories[$name] = drupal_get_path('theme', $name) . '/' . $theme->info['plugins'][$info['module']][$info['type']];
+ }
+ }
+ }
+ return $directories;
+}
+
+/**
+ * Helper function to build a ctools-friendly list of themes capable of
+ * providing plugins.
+ *
+ * @return array $themes
+ * A list of themes that can act as plugin providers, sorted parent-first with
+ * the active theme placed last.
+ */
+function _ctools_list_themes() {
+ static $themes;
+ if (is_null($themes)) {
+ $current = variable_get('theme_default', FALSE);
+ $themes = $active = array();
+ $all_themes = list_themes();
+ foreach ($all_themes as $name => $theme) {
+ // Only search from active themes
+ if (empty($theme->status) && $theme->name != $current) {
+ continue;
+ }
+ $active[$name] = $theme;
+ // Prior to drupal 6.14, $theme->base_themes does not exist. Build it.
+ if (!isset($theme->base_themes) && !empty($theme->base_theme)) {
+ $active[$name]->base_themes = ctools_find_base_themes($all_themes, $name);
+ }
+ }
+
+ // Construct a parent-first list of all themes
+ foreach ($active as $name => $theme) {
+ $base_themes = isset($theme->base_themes) ? $theme->base_themes : array();
+ $themes = array_merge($themes, $base_themes, array($name => $theme->info['name']));
+ }
+ // Put the actual theme info objects into the array
+ foreach (array_keys($themes) as $name) {
+ if (isset($all_themes[$name])) {
+ $themes[$name] = $all_themes[$name];
+ }
+ }
+
+ // Make sure the current default theme always gets the last word
+ if ($current_key = array_search($current, array_keys($themes))) {
+ $themes += array_splice($themes, $current_key, 1);
+ }
+ }
+ return $themes;
+}
+
+
+/**
+ * Find all the base themes for the specified theme.
+ *
+ * Themes can inherit templates and function implementations from earlier themes.
+ *
+ * NOTE: this is a verbatim copy of system_find_base_themes(), which was not
+ * implemented until 6.14. It is included here only as a fallback for outdated
+ * versions of drupal core.
+ *
+ * @param $themes
+ * An array of available themes.
+ * @param $key
+ * The name of the theme whose base we are looking for.
+ * @param $used_keys
+ * A recursion parameter preventing endless loops.
+ * @return
+ * Returns an array of all of the theme's ancestors; the first element's value
+ * will be NULL if an error occurred.
+ */
+function ctools_find_base_themes($themes, $key, $used_keys = array()) {
+ $base_key = $themes[$key]->info['base theme'];
+ // Does the base theme exist?
+ if (!isset($themes[$base_key])) {
+ return array($base_key => NULL);
+ }
+
+ $current_base_theme = array($base_key => $themes[$base_key]->info['name']);
+
+ // Is the base theme itself a child of another theme?
+ if (isset($themes[$base_key]->info['base theme'])) {
+ // Do we already know the base themes of this theme?
+ if (isset($themes[$base_key]->base_themes)) {
+ return $themes[$base_key]->base_themes + $current_base_theme;
+ }
+ // Prevent loops.
+ if (!empty($used_keys[$base_key])) {
+ return array($base_key => NULL);
+ }
+ $used_keys[$base_key] = TRUE;
+ return ctools_find_base_themes($themes, $base_key, $used_keys) + $current_base_theme;
+ }
+ // If we get here, then this is our parent theme.
+ return $current_base_theme;
+}
+
+
+/**
+ * Load plugin info for the provided hook; this is handled separately from
+ * plugins from files.
+ *
+ * @param $info
+ * The info array about the plugin as created by ctools_plugin_get_info()
+ *
+ * @return
+ * An array of info supplied by any hook implementations.
+ */
+function ctools_plugin_load_hooks($info) {
+ $hooks = array();
+ foreach (module_implements($info['hook']) as $module) {
+ $result = ctools_plugin_process($info, $module, $module, drupal_get_path('module', $module));
+ if (is_array($result)) {
+ $hooks = array_merge($hooks, $result);
+ }
+ }
+ return $hooks;
+}
+
+/**
+ * Process a single hook implementation of a ctools plugin.
+ *
+ * @param $info
+ * The $info array about the plugin as returned by ctools_plugin_get_info()
+ * @param $module
+ * The module that implements the plugin being processed.
+ * @param $identifier
+ * The plugin identifier, which is used to create the name of the hook
+ * function being called.
+ * @param $path
+ * The path where files utilized by this plugin will be found.
+ * @param $file
+ * The file that was loaded for this plugin, if it exists.
+ * @param $base
+ * The base plugin name to use. If a file was loaded for the plugin, this
+ * is the plugin to assume must be present. This is used to automatically
+ * translate the array to make the syntax more friendly to plugin
+ * implementors.
+ */
+function ctools_plugin_process($info, $module, $identifier, $path, $file = NULL, $base = NULL) {
+ if (is_array($identifier)) {
+ $result = $identifier;
+ }
+ else {
+ $function = $identifier . '_' . $info['hook'];
+ if (!function_exists($function)) {
+ return NULL;
+ }
+ $result = $function($info);
+ if (!isset($result) || !is_array($result)) {
+ return NULL;
+ }
+ }
+
+ // Automatically convert to the proper format that lets plugin implementations
+ // not nest arrays as deeply as they used to, but still support the older
+ // format where they do:
+ if ($base && (!isset($result[$base]) || !is_array($result[$base]))) {
+ $result = array($base => $result);
+ }
+
+ return _ctools_process_data($result, $info, $module, $path, $file);
+}
+
+/**
+ * Fill in default values and run hooks for data loaded for one or
+ * more plugins.
+ */
+function _ctools_process_data($result, $plugin_type_info, $module, $path, $file) {
+ // Fill in global defaults.
+ foreach ($result as $name => $plugin) {
+ $result[$name] += array(
+ 'module' => $module,
+ 'name' => $name,
+ 'path' => $path,
+ 'file' => $file,
+ 'plugin module' => $plugin_type_info['module'],
+ 'plugin type' => $plugin_type_info['type'],
+ );
+
+ // Fill in plugin-specific defaults, if they exist.
+ if (!empty($plugin_type_info['defaults'])) {
+ if (is_array($plugin_type_info['defaults'])) {
+ $result[$name] += $plugin_type_info['defaults'];
+ }
+ }
+
+ // Allow the plugin to be altered before processing.
+ if (!empty($plugin_type_info['alterable']) && $plugin_type_info['alterable']) {
+ drupal_alter('ctools_plugin_pre', $result[$name], $plugin_type_info);
+ }
+
+ // Allow the plugin owner to do additional processing.
+ if (!empty($plugin_type_info['process']) && $function = ctools_plugin_get_function($plugin_type_info, 'process')) {
+ $function($result[$name], $plugin_type_info);
+ }
+
+ // Allow the plugin to be altered after processing.
+ if (!empty($plugin_type_info['alterable']) && $plugin_type_info['alterable']) {
+ drupal_alter('ctools_plugin_post', $result[$name], $plugin_type_info);
+ }
+ }
+ return $result;
+}
+
+
+/**
+ * Process an info file for plugin information, rather than a hook.
+ */
+function ctools_plugin_process_info($info, $module, $file) {
+ $result = drupal_parse_info_file($file->uri);
+ if ($result) {
+ $result = array($file->name => $result);
+ return _ctools_process_data($result, $info, $module, dirname($file->uri), basename($file->uri));
+ }
+}
+
+/**
+ * Ask a module for info about a particular plugin type.
+ */
+function ctools_plugin_get_info($module, $type) {
+ $all_info = ctools_plugin_get_plugin_type_info();
+ return isset($all_info[$module][$type]) ? $all_info[$module][$type] : array();
+}
+
+/**
+ * Get a function from a plugin, if it exists. If the plugin is not already
+ * loaded, try ctools_plugin_load_function() instead.
+ *
+ * @param $plugin_definition
+ * The loaded plugin type.
+ * @param $function_name
+ * The identifier of the function. For example, 'settings form'.
+ *
+ * @return
+ * The actual name of the function to call, or NULL if the function
+ * does not exist.
+ */
+function ctools_plugin_get_function($plugin_definition, $function_name) {
+ // If cached the .inc file may not have been loaded. require_once is quite safe
+ // and fast so it's okay to keep calling it.
+ if (isset($plugin_definition['file'])) {
+ // Plugins that are loaded from info files have the info file as
+ // $plugin['file']. Don't try to run those.
+ $info = ctools_plugin_get_info($plugin_definition['plugin module'], $plugin_definition['plugin type']);
+ if (empty($info['info file'])) {
+ require_once DRUPAL_ROOT . '/' . $plugin_definition['path'] . '/' . $plugin_definition['file'];
+ }
+ }
+
+ if (!isset($plugin_definition[$function_name])) {
+ return;
+ }
+
+ if (is_array($plugin_definition[$function_name]) && isset($plugin_definition[$function_name]['function'])) {
+ $function = $plugin_definition[$function_name]['function'];
+ if (isset($plugin_definition[$function_name]['file'])) {
+ $file = $plugin_definition[$function_name]['file'];
+ if (isset($plugin_definition[$function_name]['path'])) {
+ $file = $plugin_definition[$function_name]['path'] . '/' . $file;
+ }
+ require_once DRUPAL_ROOT . '/' . $file;
+ }
+ }
+ else {
+ $function = $plugin_definition[$function_name];
+ }
+
+ if (function_exists($function)) {
+ return $function;
+ }
+}
+
+/**
+ * Load a plugin and get a function name from it, returning success only
+ * if the function exists.
+ *
+ * @param $module
+ * The module that owns the plugin type.
+ * @param $type
+ * The type of plugin.
+ * @param $id
+ * The id of the specific plugin to load.
+ * @param $function_name
+ * The identifier of the function. For example, 'settings form'.
+ *
+ * @return
+ * The actual name of the function to call, or NULL if the function
+ * does not exist.
+ */
+function ctools_plugin_load_function($module, $type, $id, $function_name) {
+ $plugin = ctools_get_plugins($module, $type, $id);
+ return ctools_plugin_get_function($plugin, $function_name);
+}
+
+/**
+ * Get a class from a plugin, if it exists. If the plugin is not already
+ * loaded, try ctools_plugin_load_class() instead.
+ *
+ * @param $plugin_definition
+ * The loaded plugin type.
+ * @param $class_name
+ * The identifier of the class. For example, 'handler'.
+ *
+ * @return
+ * The actual name of the class to call, or NULL if the class does not exist.
+ */
+function ctools_plugin_get_class($plugin_definition, $class_name) {
+ // If cached the .inc file may not have been loaded. require_once is quite safe
+ // and fast so it's okay to keep calling it.
+ if (isset($plugin_definition['file'])) {
+ // Plugins that are loaded from info files have the info file as
+ // $plugin['file']. Don't try to run those.
+ $info = ctools_plugin_get_info($plugin_definition['plugin module'], $plugin_definition['plugin type']);
+ if (empty($info['info file'])) {
+ require_once DRUPAL_ROOT . '/' . $plugin_definition['path'] . '/' . $plugin_definition['file'];
+ }
+ }
+
+ $return = FALSE;
+ if (!isset($plugin_definition[$class_name])) {
+ return;
+ }
+ else if (is_string($plugin_definition[$class_name])) {
+ // Plugin uses the string form shorthand.
+ $return = $plugin_definition[$class_name];
+ }
+ else if (isset($plugin_definition[$class_name]['class'])) {
+ // Plugin uses the verbose array form.
+ $return = $plugin_definition[$class_name]['class'];
+ }
+ // @todo consider adding an else {watchdog(...)} here
+
+ return ($return && class_exists($return)) ? $return : NULL;
+}
+
+/**
+ * Load a plugin and get a class name from it, returning success only if the
+ * class exists.
+ *
+ * @param $module
+ * The module that owns the plugin type.
+ * @param $type
+ * The type of plugin.
+ * @param $id
+ * The id of the specific plugin to load.
+ * @param $class_name
+ * The identifier of the class. For example, 'handler'.
+ *
+ * @return
+ * The actual name of the class to call, or NULL if the class does not exist.
+ */
+function ctools_plugin_load_class($module, $type, $id, $class_name) {
+ $plugin = ctools_get_plugins($module, $type, $id);
+ return ctools_plugin_get_class($plugin, $class_name);
+}
+
+/**
+ * Sort callback for sorting plugins naturally.
+ *
+ * Sort first by weight, then by title.
+ */
+function ctools_plugin_sort($a, $b) {
+ if (is_object($a)) {
+ $a = (array) $a;
+ }
+ if (is_object($b)) {
+ $b = (array) $b;
+ }
+
+ if (empty($a['weight'])) {
+ $a['weight'] = 0;
+ }
+
+ if (empty($b['weight'])) {
+ $b['weight'] = 0;
+ }
+
+ if ($a['weight'] == $b['weight']) {
+ return strnatcmp(strtolower($a['title']), strtolower($b['title']));
+ }
+ return ($a['weight'] < $b['weight']) ? -1 : 1;
+}
diff --git a/sites/all/modules/ctools/includes/registry.inc b/sites/all/modules/ctools/includes/registry.inc
new file mode 100644
index 000000000..9d4328e69
--- /dev/null
+++ b/sites/all/modules/ctools/includes/registry.inc
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * @file
+ *
+ * Registry magic. In a separate file to minimize unnecessary code loading.
+ */
+
+/**
+ * Implements (via delegation) hook_registry_files_alter().
+ *
+ * Alter the registry of files to automagically include all classes in
+ * class-based plugins.
+ */
+function _ctools_registry_files_alter(&$files, $indexed_modules) {
+ ctools_include('plugins');
+
+ // Remap the passed indexed modules into a useful format.
+ $modules = array();
+ foreach ($indexed_modules as $module_object) {
+ $modules[$module_object->name] = $module_object;
+ }
+
+ $all_type_info = ctools_plugin_get_plugin_type_info(TRUE);
+ foreach ($all_type_info as $module => $plugin_types) {
+ foreach ($plugin_types as $plugin_type_name => $plugin_type_info) {
+ if (empty($plugin_type_info['classes'])) {
+ // This plugin type does not use classes, so skip it.
+ continue;
+ }
+
+ // Retrieve the full list of plugins of this type.
+ $plugins = ctools_get_plugins($module, $plugin_type_name);
+ foreach ($plugins as $plugin_name => $plugin_definition) {
+ foreach ($plugin_type_info['classes'] as $class_key) {
+ if (empty($plugin_definition[$class_key])) {
+ // Plugin doesn't provide a class for this class key, so skip it.
+ continue;
+ }
+
+ if (is_string($plugin_definition[$class_key])) {
+ // Plugin definition uses the shorthand for defining a class name
+ // and location; look for the containing file by following naming
+ // convention.
+ $path = $plugin_definition['path'] . '/' . $plugin_definition[$class_key] . '.class.php';
+ }
+ else {
+ // Plugin uses the verbose definition to indicate where its class
+ // files are.
+ $class = $plugin_definition[$class_key]['class'];
+ // Use the filename if it's explicitly set, else follow the naming
+ // conventions.
+ $filename = isset($plugin_definition[$class_key]['file']) ? $plugin_definition[$class_key]['file'] : $class . '.class.php';
+ $base_path = isset($plugin_definition[$class_key]['path']) ? $plugin_definition[$class_key]['path'] : $plugin_definition['path'];
+ $path = "$base_path/$filename";
+ }
+
+ if (file_exists($path)) {
+ // If the file exists, add it to the files for registry building.
+ $files[$path] = array('module' => $plugin_definition['module'], 'weight' => $modules[$plugin_definition['module']]->weight);
+ }
+ else {
+ // Else, watchdog that we've got some erroneous plugin info.
+ $args = array(
+ '@plugin' => $plugin_definition['name'],
+ '@owner' => $module,
+ '@type' => $plugin_type_name,
+ '@file' => $path,
+ '@class' => $class_key,
+ );
+ watchdog('ctools', 'Plugin @plugin of plugin type @owner:@type points to nonexistent file @file for class handler @class.', $args);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/sites/all/modules/ctools/includes/stylizer.inc b/sites/all/modules/ctools/includes/stylizer.inc
new file mode 100644
index 000000000..5bc8450cb
--- /dev/null
+++ b/sites/all/modules/ctools/includes/stylizer.inc
@@ -0,0 +1,1654 @@
+<?php
+/**
+ * @file
+ * Create customized CSS and images from palettes created by user input.
+ */
+
+/**
+ * Fetch metadata on a specific style_base plugin.
+ *
+ * @param $content type
+ * Name of a panel content type.
+ *
+ * @return
+ * An array with information about the requested stylizer style base.
+ */
+function ctools_get_style_base($style_base) {
+ ctools_include('plugins');
+ return ctools_get_plugins('stylizer', 'style_bases', $style_base);
+}
+
+/**
+ * Fetch metadata for all style_base plugins.
+ *
+ * @return
+ * An array of arrays with information about all available styleizer style bases.
+ */
+function ctools_get_style_bases() {
+ ctools_include('plugins');
+ return ctools_get_plugins('stylizer', 'style_bases');
+}
+
+/**
+ * Fetch metadata about all of the style base types that are available.
+ */
+function ctools_get_style_base_types() {
+ $types = array();
+ foreach (module_implements('ctools_style_base_types') as $module) {
+ $types[$module] = module_invoke($module, 'ctools_style_base_types');
+ }
+
+ return $types;
+}
+
+/**
+ * Render the icon for a style base.
+ */
+function ctools_stylizer_print_style_icon($plugin, $print_title = TRUE) {
+ $file = $plugin['path'] . '/' . $plugin['icon'];
+ $title = $print_title ? $plugin['title'] : '';
+ return theme('ctools_style_icon', array('image' => theme('image', array('path' => $file)), 'title' => $title));
+}
+
+/**
+ * Theme the style icon image
+ */
+function theme_ctools_style_icon($vars) {
+ $image = $vars['image'];
+ ctools_add_css('stylizer');
+ ctools_add_js('stylizer');
+ $output = '<div class="ctools-style-icon">';
+ $output .= $vars['image'];
+ if ($vars['title']) {
+ $output .= '<div class="caption">' . $vars['title'] . '</div>';
+ }
+ $output .= '</div>';
+ return $output;
+}
+
+/**
+ * Add the necessary CSS for a stylizer plugin to the page.
+ *
+ * This will check to see if the images directory and the cached CSS
+ * exists and, if not, will regenerate everything needed.
+ */
+function ctools_stylizer_add_css($plugin, $settings) {
+ if (!file_exists(ctools_stylizer_get_image_path($plugin, $settings, FALSE))) {
+ ctools_stylizer_build_style($plugin, $settings, TRUE);
+ return;
+ }
+
+ ctools_include('css');
+ $filename = ctools_css_retrieve(ctools_stylizer_get_css_id($plugin, $settings));
+ if (!$filename) {
+ ctools_stylizer_build_style($plugin, $settings, TRUE);
+ }
+ else {
+ drupal_add_css($filename);
+ }
+}
+
+/**
+ * Build the files for a stylizer given the proper settings.
+ */
+function ctools_stylizer_build_style($plugin, $settings, $add_css = FALSE) {
+ $path = ctools_stylizer_get_image_path($plugin, $settings);
+ if (!$path) {
+ return;
+ }
+
+ $replacements = array();
+
+ // Set up palette conversions
+ foreach ($settings['palette'] as $key => $color) {
+ $replacements['%' . $key ] = $color;
+ }
+
+ // Process image actions:
+ if (!empty($plugin['actions'])) {
+ $processor = new ctools_stylizer_image_processor;
+ $processor->execute($path, $plugin, $settings);
+
+// @todo -- there needs to be an easier way to get at this.
+// dsm($processor->message_log);
+ // Add filenames to our conversions.
+ }
+
+ // Convert and write the CSS file.
+ $css = file_get_contents($plugin['path'] . '/' . $plugin['css']);
+
+ // Replace %style keyword with our generated class name.
+ // @todo We need one more unique identifier I think.
+ $class = ctools_stylizer_get_css_class($plugin, $settings);
+ $replacements['%style'] = '.' . $class;
+
+ if (!empty($processor) && !empty($processor->paths)) {
+ foreach ($processor->paths as $file => $image) {
+ $replacements[$file] = file_create_url($image);
+ }
+ }
+
+ if (!empty($plugin['build']) && function_exists($plugin['build'])) {
+ $plugin['build']($plugin, $settings, $css, $replacements);
+ }
+
+ $css = strtr($css, $replacements);
+ ctools_include('css');
+ $filename = ctools_css_store(ctools_stylizer_get_css_id($plugin, $settings), $css, FALSE);
+
+ if ($add_css) {
+ drupal_add_css($filename);
+ }
+}
+
+/**
+ * Clean up no longer used files.
+ *
+ * To prevent excess clutter in the files directory, this should be called
+ * whenever a style is going out of use. When being deleted, but also when
+ * the palette is being changed.
+ */
+function ctools_stylizer_cleanup_style($plugin, $settings) {
+ ctools_include('css');
+ $path = ctools_stylizer_get_image_path($plugin, $settings, FALSE);
+ if ($path) {
+ ctools_stylizer_recursive_delete($path);
+ }
+
+ ctools_css_clear(ctools_stylizer_get_css_id($plugin, $settings));
+}
+
+/**
+ * Recursively delete all files and folders in the specified filepath, then
+ * delete the containing folder.
+ *
+ * Note that this only deletes visible files with write permission.
+ *
+ * @param string $path
+ * A filepath relative to file_directory_path.
+ */
+function ctools_stylizer_recursive_delete($path) {
+ if (empty($path)) {
+ return;
+ }
+
+ $listing = $path . '/*';
+
+ foreach (glob($listing) as $file) {
+ if (is_file($file) === TRUE) {
+ @unlink($file);
+ }
+ elseif (is_dir($file) === TRUE) {
+ ctools_stylizer_recursive_delete($file);
+ }
+ }
+
+ @rmdir($path);
+}
+
+/**
+ * Get a safe name for the settings.
+ *
+ * This uses an md5 of the palette if the name is temporary so
+ * that multiple temporary styles on the same page can coexist
+ * safely.
+ */
+function ctools_stylizer_get_settings_name($settings) {
+ if ($settings['name'] != '_temporary') {
+ return $settings['name'];
+ }
+
+ return $settings['name'] . '-' . md5(serialize($settings['palette']));
+}
+
+/**
+ * Get the path where images will be stored for a given style plugin and settings.
+ *
+ * This function will make sure the path exists.
+ */
+function ctools_stylizer_get_image_path($plugin, $settings, $check = TRUE) {
+ $path = 'public://ctools/style/' . $settings['name'] . '/' . md5(serialize($settings['palette']));
+ if (!file_prepare_directory($path, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
+ drupal_set_message(t('Unable to create CTools styles cache directory @path. Check the permissions on your files directory.', array('@path' => $path)), 'error');
+ return;
+ }
+
+ return $path;
+}
+
+/**
+ * Get the id used to cache CSS for a given style plugin and settings.
+ */
+function ctools_stylizer_get_css_id($plugin, $settings) {
+ return 'ctools-stylizer:' . $settings['name'] . ':' . md5(serialize($settings['palette']));
+}
+
+/**
+ * Get the class to use for a stylizer plugin.
+ */
+function ctools_stylizer_get_css_class($plugin, $settings) {
+ ctools_include('cleanstring');
+ return ctools_cleanstring($plugin['name'] . '-' . ctools_stylizer_get_settings_name($settings));
+}
+
+class ctools_stylizer_image_processor {
+ var $workspace = NULL;
+ var $name = NULL;
+
+ var $workspaces = array();
+
+ var $message_log = array();
+ var $error_log = array();
+
+ function execute($path, $plugin, $settings) {
+ $this->path = $path;
+ $this->plugin = $plugin;
+ $this->settings = $settings;
+ $this->palette = $settings['palette'];
+
+ if (is_string($plugin['actions']) && function_exists($plugin['actions'])) {
+ $actions = $plugin['actions']($plugin, $settings);
+ }
+ else if (is_array($plugin['actions'])) {
+ $actions = $plugin['actions'];
+ }
+
+ if (!empty($actions) && is_array($actions)) {
+ foreach ($plugin['actions'] as $action) {
+ $command = 'command_' . array_shift($action);
+ if (method_exists($this, $command)) {
+ call_user_func_array(array($this, $command), $action);
+ }
+ }
+ }
+
+ // Clean up buffers.
+ foreach ($this->workspaces as $name => $workspace) {
+ imagedestroy($this->workspaces[$name]);
+ }
+ }
+
+ function log($message, $type = 'normal') {
+ $this->message_log[] = $message;
+ if ($type == 'error') {
+ $this->error_log[] = $message;
+ }
+ }
+
+ function set_current_workspace($workspace) {
+ $this->log("Set current workspace: $workspace");
+ $this->workspace = &$this->workspaces[$workspace];
+ $this->name = $workspace;
+ }
+
+ /**
+ * Create a new workspace.
+ */
+ function command_new($name, $width, $height) {
+ $this->log("New workspace: $name ($width x $height)");
+ // Clean up if there was already a workspace there.
+ if (isset($this->workspaces[$name])) {
+ imagedestroy($this->workspaces[$name]);
+ }
+
+ $this->workspaces[$name] = imagecreatetruecolor($width, $height);
+ $this->set_current_workspace($name);
+
+ // Make sure the new workspace has a transparent color.
+
+ // Turn off transparency blending (temporarily)
+ imagealphablending($this->workspace, FALSE);
+
+ // Create a new transparent color for image
+ $color = imagecolorallocatealpha($this->workspace, 0, 0, 0, 127);
+
+ // Completely fill the background of the new image with allocated color.
+ imagefill($this->workspace, 0, 0, $color);
+
+ // Restore transparency blending
+ imagesavealpha($this->workspace, TRUE);
+
+ }
+
+ /**
+ * Create a new workspace a file.
+ *
+ * This will make the new workspace the current workspace.
+ */
+ function command_load($name, $file) {
+ $this->log("New workspace: $name (from $file)");
+ if (!file_exists($file)) {
+ // Try it relative to the plugin
+ $file = $this->plugin['path'] . '/' . $file;
+ if (!file_exists($file)) {
+ $this->log("Unable to open $file");
+ return;
+ }
+ }
+
+ // Clean up if there was already a workspace there.
+ if (isset($this->workspaces[$name])) {
+ imagedestroy($this->workspaces[$name]);
+ }
+
+ $this->workspaces[$name] = imagecreatefrompng($file);
+ $this->set_current_workspace($name);
+ }
+
+ /**
+ * Create a new workspace using the properties of an existing workspace
+ */
+ function command_new_from($name, $workspace) {
+ $this->log("New workspace: $name from existing $workspace");
+ if (empty($this->workspaces[$workspace])) {
+ $this->log("Workspace $name does not exist.", 'error');
+ return;
+ }
+
+ // Clean up if there was already a workspace there.
+ if (isset($this->workspaces[$name])) {
+ imagedestroy($this->workspaces[$name]);
+ }
+
+ $this->workspaces[$name] = $this->new_image($this->workspace[$workspace]);
+ $this->set_current_workspace($name);
+ }
+
+ /**
+ * Set the current workspace.
+ */
+ function command_workspace($name) {
+ $this->log("Set workspace: $name");
+ if (empty($this->workspaces[$name])) {
+ $this->log("Workspace $name does not exist.", 'error');
+ return;
+ }
+ $this->set_current_workspace($name);
+ }
+
+ /**
+ * Copy the contents of one workspace into the current workspace.
+ */
+ function command_merge_from($workspace, $x = 0, $y = 0) {
+ $this->log("Merge from: $workspace ($x, $y)");
+ if (empty($this->workspaces[$workspace])) {
+ $this->log("Workspace $name does not exist.", 'error');
+ return;
+ }
+
+ $this->merge($this->workspaces[$workspace], $this->workspace, $x, $y);
+ }
+
+ function command_merge_to($workspace, $x = 0, $y = 0) {
+ $this->log("Merge to: $workspace ($x, $y)");
+ if (empty($this->workspaces[$workspace])) {
+ $this->log("Workspace $name does not exist.", 'error');
+ return;
+ }
+
+ $this->merge($this->workspace, $this->workspaces[$workspace], $x, $y);
+ $this->set_current_workspace($workspace);
+ }
+
+ /**
+ * Blend an image into the current workspace.
+ */
+ function command_merge_from_file($file, $x = 0, $y = 0) {
+ $this->log("Merge from file: $file ($x, $y)");
+ if (!file_exists($file)) {
+ // Try it relative to the plugin
+ $file = $this->plugin['path'] . '/' . $file;
+ if (!file_exists($file)) {
+ $this->log("Unable to open $file");
+ return;
+ }
+ }
+
+ $source = imagecreatefrompng($file);
+
+ $this->merge($source, $this->workspace, $x, $y);
+
+ imagedestroy($source);
+ }
+
+ function command_fill($color, $x, $y, $width, $height) {
+ $this->log("Fill: $color ($x, $y, $width, $height)");
+ imagefilledrectangle($this->workspace, $x, $y, $x + $width, $y + $height, _color_gd($this->workspace, $this->palette[$color]));
+ }
+
+ function command_gradient($from, $to, $x, $y, $width, $height, $direction = 'down') {
+ $this->log("Gradient: $from to $to ($x, $y, $width, $height) $direction");
+
+ if ($direction == 'down') {
+ for ($i = 0; $i < $height; ++$i) {
+ $color = _color_blend($this->workspace, $this->palette[$from], $this->palette[$to], $i / ($height - 1));
+ imagefilledrectangle($this->workspace, $x, $y + $i, $x + $width, $y + $i + 1, $color);
+ }
+ }
+ else {
+ for ($i = 0; $i < $width; ++$i) {
+ $color = _color_blend($this->workspace, $this->palette[$from], $this->palette[$to], $i / ($width - 1));
+ imagefilledrectangle($this->workspace, $x + $i, $y, $x + $i + 1, $y + $height, $color);
+ }
+ }
+ }
+
+ /**
+ * Colorize the current workspace with the given location.
+ *
+ * This uses simple color blending to colorize the image.
+ *
+ * @todo it is possible that this colorize could allow different methods for
+ * determining how to blend colors?
+ */
+ function command_colorize($color, $x = NULL, $y = NULL, $width = NULL, $height = NULL) {
+ if (!isset($x)) {
+ $whole_image = TRUE;
+ $x = $y = 0;
+ $width = imagesx($this->workspace);
+ $height = imagesy($this->workspace);
+ }
+ $this->log("Colorize: $color ($x, $y, $width, $height)");
+
+ $c = _color_unpack($this->palette[$color]);
+
+ imagealphablending($this->workspace, FALSE);
+ imagesavealpha($this->workspace, TRUE);
+
+ // If PHP 5 use the nice imagefilter which is faster.
+ if (!empty($whole_image) && version_compare(phpversion(), '5.2.5', '>=') && function_exists('imagefilter')) {
+ imagefilter($this->workspace, IMG_FILTER_COLORIZE, $c[0], $c[1], $c[2]);
+ }
+ else {
+ // Otherwise we can do it the brute force way.
+ for ($j = 0; $j < $height; $j++) {
+ for ($i = 0; $i < $width; $i++) {
+ $current = imagecolorsforindex($this->workspace, imagecolorat($this->workspace, $i, $j));
+ $new_index = imagecolorallocatealpha($this->workspace, $c[0], $c[1], $c[2], $current['alpha']);
+ imagesetpixel($this->workspace, $i, $j, $new_index);
+ }
+ }
+ }
+ }
+
+ /**
+ * Colorize the current workspace with the given location.
+ *
+ * This uses a color replacement algorithm that retains luminosity but
+ * turns replaces all color with the specified color.
+ */
+ function command_hue($color, $x = NULL, $y = NULL, $width = NULL, $height = NULL) {
+ if (!isset($x)) {
+ $whole_image = TRUE;
+ $x = $y = 0;
+ $width = imagesx($this->workspace);
+ $height = imagesy($this->workspace);
+ }
+ $this->log("Hue: $color ($x, $y, $width, $height)");
+
+ list($red, $green, $blue) = _color_unpack($this->palette[$color]);
+
+ // We will create a monochromatic palette based on the input color
+ // which will go from black to white.
+
+ // Input color luminosity: this is equivalent to the position of the
+ // input color in the monochromatic palette
+ $luminosity_input = round(255 * ($red + $green + $blue) / 765); // 765 = 255 * 3
+
+ // We fill the palette entry with the input color at itscorresponding position
+ $palette[$luminosity_input]['red'] = $red;
+ $palette[$luminosity_input]['green'] = $green;
+ $palette[$luminosity_input]['blue'] = $blue;
+
+ // Now we complete the palette, first we'll do it to the black, and then to
+ // the white.
+
+ // From input to black
+ $steps_to_black = $luminosity_input;
+
+ // The step size for each component
+ if ($steps_to_black) {
+ $step_size_red = $red / $steps_to_black;
+ $step_size_green = $green / $steps_to_black;
+ $step_size_blue = $blue / $steps_to_black;
+
+ for ($i = $steps_to_black; $i >= 0; $i--) {
+ $palette[$steps_to_black-$i]['red'] = $red - round($step_size_red * $i);
+ $palette[$steps_to_black-$i]['green'] = $green - round($step_size_green * $i);
+ $palette[$steps_to_black-$i]['blue'] = $blue - round($step_size_blue * $i);
+ }
+ }
+
+ // From input to white
+ $steps_to_white = 255 - $luminosity_input;
+
+ if ($steps_to_white) {
+ $step_size_red = (255 - $red) / $steps_to_white;
+ $step_size_green = (255 - $green) / $steps_to_white;
+ $step_size_blue = (255 - $blue) / $steps_to_white;
+ }
+ else {
+ $step_size_red=$step_size_green=$step_size_blue=0;
+ }
+
+ // The step size for each component
+ for ($i = ($luminosity_input + 1); $i <= 255; $i++) {
+ $palette[$i]['red'] = $red + round($step_size_red * ($i - $luminosity_input));
+ $palette[$i]['green'] = $green + round($step_size_green * ($i - $luminosity_input));
+ $palette[$i]['blue']= $blue + round($step_size_blue * ($i - $luminosity_input));
+ }
+
+ // Go over the specified area of the image and update the colors.
+ for ($j = $x; $j < $height; $j++) {
+ for ($i = $y; $i < $width; $i++) {
+ $color = imagecolorsforindex($this->workspace, imagecolorat($this->workspace, $i, $j));
+ $luminosity = round(255 * ($color['red'] + $color['green'] + $color['blue']) / 765);
+ $new_color = imagecolorallocatealpha($this->workspace, $palette[$luminosity]['red'], $palette[$luminosity]['green'], $palette[$luminosity]['blue'], $color['alpha']);
+ imagesetpixel($this->workspace, $i, $j, $new_color);
+ }
+ }
+ }
+
+ /**
+ * Take a slice out of the current workspace and save it as an image.
+ */
+ function command_slice($file, $x = NULL, $y = NULL, $width = NULL, $height = NULL) {
+ if (!isset($x)) {
+ $x = $y = 0;
+ $width = imagesx($this->workspace);
+ $height = imagesy($this->workspace);
+ }
+
+ $this->log("Slice: $file ($x, $y, $width, $height)");
+
+ $base = basename($file);
+ $image = $this->path . '/' . $base;
+
+ $slice = $this->new_image($this->workspace, $width, $height);
+ imagecopy($slice, $this->workspace, 0, 0, $x, $y, $width, $height);
+
+ // Make sure alphas are saved:
+ imagealphablending($slice, FALSE);
+ imagesavealpha($slice, TRUE);
+
+ // Save image.
+ $temp_name = drupal_tempnam('temporary://', 'file');
+
+ imagepng($slice, drupal_realpath($temp_name));
+ file_unmanaged_move($temp_name, $image);
+ imagedestroy($slice);
+
+ // Set standard file permissions for webserver-generated files
+ @chmod(realpath($image), 0664);
+
+ $this->paths[$file] = $image;
+ }
+
+ /**
+ * Prepare a new image for being copied or worked on, preserving transparency.
+ */
+ function &new_image(&$source, $width = NULL, $height = NULL) {
+ if (!isset($width)) {
+ $width = imagesx($source);
+ }
+
+ if (!isset($height)) {
+ $height = imagesy($source);
+ }
+
+ $target = imagecreatetruecolor($width, $height);
+ imagealphablending($target, FALSE);
+ imagesavealpha($target, TRUE);
+
+ $transparency_index = imagecolortransparent($source);
+
+ // If we have a specific transparent color
+ if ($transparency_index >= 0) {
+ // Get the original image's transparent color's RGB values
+ $transparent_color = imagecolorsforindex($source, $transparency_index);
+
+ // Allocate the same color in the new image resource
+ $transparency_index = imagecolorallocate($target, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']);
+
+ // Completely fill the background of the new image with allocated color.
+ imagefill($target, 0, 0, $transparency_index);
+
+ // Set the background color for new image to transparent
+ imagecolortransparent($target, $transparency_index);
+ }
+ // Always make a transparent background color for PNGs that don't have one allocated already
+ else {
+ // Create a new transparent color for image
+ $color = imagecolorallocatealpha($target, 0, 0, 0, 127);
+
+ // Completely fill the background of the new image with allocated color.
+ imagefill($target, 0, 0, $color);
+ }
+
+ return $target;
+ }
+
+ /**
+ * Merge two images together, preserving alpha transparency.
+ */
+ function merge(&$from, &$to, $x, $y) {
+ // Blend over template.
+ $width = imagesx($from);
+ $height = imagesy($from);
+
+ // Re-enable alpha blending to make sure transparency merges.
+ imagealphablending($to, TRUE);
+ imagecopy($to, $from, $x, $y, 0, 0, $width, $height);
+ imagealphablending($to, FALSE);
+ }
+}
+
+/**
+ * Get the cached changes to a given task handler.
+ */
+function ctools_stylizer_get_settings_cache($name) {
+ ctools_include('object-cache');
+ return ctools_object_cache_get('ctools_stylizer_settings', $name);
+}
+
+/**
+ * Store changes to a task handler in the object cache.
+ */
+function ctools_stylizer_set_settings_cache($name, $settings) {
+ ctools_include('object-cache');
+ ctools_object_cache_set('ctools_stylizer_settings', $name, $settings);
+}
+
+/**
+ * Remove an item from the object cache.
+ */
+function ctools_stylizer_clear_settings_cache($name) {
+ ctools_include('object-cache');
+ ctools_object_cache_clear('ctools_stylizer_settings', $name);
+}
+
+/**
+ * Add a new style of the specified type.
+ */
+function ctools_stylizer_edit_style(&$info, $js, $step = NULL) {
+ $name = '::new';
+ $form_info = array(
+ 'id' => 'ctools_stylizer_edit_style',
+ 'path' => $info['path'],
+ 'show trail' => TRUE,
+ 'show back' => TRUE,
+ 'show return' => FALSE,
+ 'next callback' => 'ctools_stylizer_edit_style_next',
+ 'finish callback' => 'ctools_stylizer_edit_style_finish',
+ 'return callback' => 'ctools_stylizer_edit_style_finish',
+ 'cancel callback' => 'ctools_stylizer_edit_style_cancel',
+ 'forms' => array(
+ 'choose' => array(
+ 'form id' => 'ctools_stylizer_edit_style_form_choose',
+ ),
+ ),
+ );
+
+ if (empty($info['settings'])) {
+ $form_info['order'] = array(
+ 'choose' => t('Select base style'),
+ );
+ if (empty($step)) {
+ $step = 'choose';
+ }
+
+ if ($step != 'choose') {
+ $cache = ctools_stylizer_get_settings_cache($name);
+ if (!$cache) {
+ $output = t('Missing settings cache.');
+ if ($js) {
+ return ctools_modal_form_render($form_state, $output);
+ }
+ else {
+ return $output;
+ }
+ }
+
+ if (!empty($cache['owner settings'])) {
+ $info['owner settings'] = $cache['owner settings'];
+ }
+ $settings = $cache['settings'];
+ }
+ else {
+ $settings = array(
+ 'name' => '_temporary',
+ 'style_base' => NULL,
+ 'palette' => array(),
+ );
+ ctools_stylizer_clear_settings_cache($name);
+ }
+ $op = 'add';
+ }
+ else {
+ $cache = ctools_stylizer_get_settings_cache($info['settings']['name']);
+
+ if (!empty($cache)) {
+ if (!empty($cache['owner settings'])) {
+ $info['owner settings'] = $cache['owner settings'];
+ }
+ $settings = $cache['settings'];
+ }
+ else {
+ $settings = $info['settings'];
+ }
+ $op = 'edit';
+ }
+
+ if (!empty($info['op'])) {
+ // Allow this to override. Necessary to allow cloning properly.
+ $op = $info['op'];
+ }
+
+ $plugin = NULL;
+ if (!empty($settings['style_base'])) {
+ $plugin = ctools_get_style_base($settings['style_base']);
+ $info['type'] = $plugin['type'];
+ ctools_stylizer_add_plugin_forms($form_info, $plugin, $op);
+ }
+ else {
+ // This is here so the 'finish' button does not show up, and because
+ // we don't have the selected style we don't know what the next form(s)
+ // will be.
+ $form_info['order']['next'] = t('Configure style');
+ }
+
+ if (count($form_info['order']) < 2 || $step == 'choose') {
+ $form_info['show trail'] = FALSE;
+ }
+
+ $form_state = array(
+ 'module' => $info['module'],
+ 'type' => $info['type'],
+ 'owner info' => &$info,
+ 'base_style_plugin' => $plugin,
+ 'name' => $name,
+ 'step' => $step,
+ 'settings' => $settings,
+ 'ajax' => $js,
+ 'op' => $op,
+ );
+
+ if (!empty($info['modal'])) {
+ $form_state['modal'] = TRUE;
+ $form_state['title'] = $info['modal'];
+ $form_state['modal return'] = TRUE;
+ }
+
+ ctools_include('wizard');
+ $output = ctools_wizard_multistep_form($form_info, $step, $form_state);
+
+ if (!empty($form_state['complete'])) {
+ $info['complete'] = TRUE;
+ $info['settings'] = $form_state['settings'];
+ }
+
+ if ($js && !$output && !empty($form_state['clicked_button']['#next'])) {
+ // We have to do a separate redirect here because the formula that adds
+ // stuff to the wizard after being chosen hasn't happened. The wizard
+ // tried to go to the next step which did not exist.
+ return ctools_stylizer_edit_style($info, $js, $form_state['clicked_button']['#next']);
+ }
+
+ if ($js) {
+ return ctools_modal_form_render($form_state, $output);
+ }
+ else {
+ return $output;
+ }
+}
+
+/**
+ * Add wizard forms specific to a style base plugin.
+ *
+ * The plugin can store forms either as a simple 'edit form'
+ * => 'form callback' or if it needs the more complicated wizard
+ * functionality, it can set 'forms' and 'order' with values suitable
+ * for the wizard $form_info array.
+ *
+ * @param &$form_info
+ * The form info to modify.
+ * @param $plugin
+ * The plugin to use.
+ * @param $op
+ * Either 'add' or 'edit' so we can get the right forms.
+ */
+function ctools_stylizer_add_plugin_forms(&$form_info, $plugin, $op) {
+ if (empty($plugin['forms'])) {
+ if ($op == 'add' && isset($plugin['add form'])) {
+ $id = $plugin['add form'];
+ }
+ else if (isset($plugin['edit form'])) {
+ $id = $plugin['edit form'];
+ }
+ else {
+ $id = 'ctools_stylizer_edit_style_form_default';
+ }
+
+ $form_info['forms']['settings'] = array(
+ 'form id' => $id,
+ );
+ $form_info['order']['settings'] = t('Settings');
+ }
+ else {
+ $form_info['forms'] += $plugin['forms'];
+ $form_info['order'] += $plugin['order'];
+ }
+}
+
+/**
+ * Callback generated when the add style process is finished.
+ */
+function ctools_stylizer_edit_style_finish(&$form_state) {
+ $form_state['complete'] = TRUE;
+ ctools_stylizer_clear_settings_cache($form_state['name']);
+
+ if (isset($form_state['settings']['old_settings'])) {
+ unset($form_state['settings']['old_settings']);
+ }
+}
+
+/**
+ * Callback generated when the 'next' button is clicked.
+ */
+function ctools_stylizer_edit_style_next(&$form_state) {
+ $form_state['form_info']['path'] = str_replace('%name', $form_state['name'], $form_state['form_info']['path']);
+ $form_state['redirect'] = ctools_wizard_get_path($form_state['form_info'], $form_state['clicked_button']['#next']);
+
+ // Update the cache with changes.
+ $cache = array('settings' => $form_state['settings']);
+ if (!empty($form_state['owner info']['owner settings'])) {
+ $cache['owner settings'] = $form_state['owner info']['owner settings'];
+ }
+ ctools_stylizer_set_settings_cache($form_state['name'], $cache);
+}
+
+/**
+ * Callback generated when the 'cancel' button is clicked.
+ *
+ * We might have some temporary data lying around. We must remove it.
+ */
+function ctools_stylizer_edit_style_cancel(&$form_state) {
+ if (!empty($form_state['name'])) {
+ ctools_stylizer_clear_settings_cache($form_state['name']);
+ }
+}
+
+/**
+ * Choose which plugin to use to create a new style.
+ */
+function ctools_stylizer_edit_style_form_choose($form, &$form_state) {
+ $plugins = ctools_get_style_bases();
+ $options = array();
+
+ $categories = array();
+ foreach ($plugins as $name => $plugin) {
+ if ($form_state['module'] == $plugin['module'] && $form_state['type'] == $plugin['type']) {
+ $categories[$plugin['category']] = $plugin['category'];
+ $unsorted_options[$plugin['category']][$name] = ctools_stylizer_print_style_icon($plugin, TRUE);
+ }
+ }
+
+ asort($categories);
+
+ foreach ($categories as $category) {
+ $options[$category] = $unsorted_options[$category];
+ }
+
+ $form['style_base'] = array(
+ '#prefix' => '<div class="ctools-style-icons clearfix">',
+ '#suffix' => '</div>',
+ );
+
+ ctools_include('cleanstring');
+ foreach ($options as $category => $radios) {
+ $cat = ctools_cleanstring($category);
+ $form['style_base'][$cat] = array(
+ '#prefix' => '<div class="ctools-style-category clearfix"><label>' . $category . '</label>',
+ '#suffix' => '</div>',
+ );
+
+ foreach ($radios as $key => $choice) {
+ // Generate the parents as the autogenerator does, so we will have a
+ // unique id for each radio button.
+ $form['style_base'][$cat][$key] = array(
+ '#type' => 'radio',
+ '#title' => $choice,
+ '#parents' => array('style_base'),
+ '#id' => drupal_clean_css_identifier('edit-style-base-' . $key),
+ '#return_value' => check_plain($key),
+ );
+ }
+ }
+
+ return $form;
+}
+
+function ctools_stylizer_edit_style_form_choose_submit($form, &$form_state) {
+ $form_state['settings']['style_base'] = $form_state['values']['style_base'];
+
+ // The 'next' form will show up as 'next' but that's not accurate now that
+ // we have a style. Figure out what next really is and update.
+ $plugin = ctools_get_style_base($form_state['settings']['style_base']);
+ if (empty($plugin['forms'])) {
+ $form_state['clicked_button']['#next'] = 'settings';
+ }
+ else {
+ $forms = array_keys($form_info['forms']);
+ $form_state['clicked_button']['#next'] = array_shift($forms);
+ }
+
+ // Fill in the defaults for the settings.
+ if (!empty($plugin['defaults'])) {
+ // @todo allow a callback
+ $form_state['settings'] += $plugin['defaults'];
+ }
+
+ return $form;
+}
+
+/**
+ * The default stylizer style editing form.
+ *
+ * Even when not using this, styles should call through to this form in
+ * their own edit forms.
+ */
+function ctools_stylizer_edit_style_form_default($form, &$form_state) {
+ ctools_add_js('stylizer');
+ ctools_add_css('stylizer');
+ drupal_add_library('system', 'farbtastic');
+
+ $plugin = &$form_state['base_style_plugin'];
+ $settings = &$form_state['settings'];
+
+ $form['top box'] = array(
+ '#prefix' => '<div id="ctools-stylizer-top-box" class="clearfix">',
+ '#suffix' => '</div>',
+ );
+ $form['top box']['left'] = array(
+ '#prefix' => '<div id="ctools-stylizer-left-box">',
+ '#suffix' => '</div>',
+ );
+ $form['top box']['preview'] = array(
+ // We have a copy of the $form_state on $form because form theme functions
+ // do not get $form_state.
+ '#theme' => 'ctools_stylizer_preview_form',
+ '#form_state' => &$form_state,
+ );
+
+ $form['top box']['preview']['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Preview'),
+ );
+
+ if (!empty($plugin['palette'])) {
+ $form['top box']['color'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Color scheme'),
+ '#attributes' => array('id' => 'ctools_stylizer_color_scheme_form', 'class' => array('ctools-stylizer-color-edit')),
+ '#theme' => 'ctools_stylizer_color_scheme_form',
+ );
+
+ $form['top box']['color']['palette']['#tree'] = TRUE;
+
+ foreach ($plugin['palette'] as $key => $color) {
+ if (empty($settings['palette'][$key])) {
+ $settings['palette'][$key] = $color['default_value'];
+ }
+
+ $form['top box']['color']['palette'][$key] = array(
+ '#type' => 'textfield',
+ '#title' => $color['label'],
+ '#default_value' => $settings['palette'][$key],
+ '#size' => 8,
+ );
+ }
+ }
+
+ if (!empty($plugin['settings form']) && function_exists($plugin['settings form'])) {
+ $plugin['settings form']($form, $form_state);
+ }
+
+ if (!empty($form_state['owner info']['owner form']) && function_exists($form_state['owner info']['owner form'])) {
+ $form_state['owner info']['owner form']($form, $form_state);
+ }
+
+ return $form;
+}
+
+/**
+ * Theme the stylizer color scheme form.
+ */
+function theme_ctools_stylizer_color_scheme_form($vars) {
+ $form = &$vars['form'];
+ $output = '';
+
+ // Wrapper
+ $output .= '<div class="color-form clearfix">';
+
+ // Color schemes
+// $output .= drupal_render($form['scheme']);
+
+ // Palette
+ $output .= '<div id="palette" class="clearfix">';
+ foreach (element_children($form['palette']) as $name) {
+ $output .= render($form['palette'][$name]);
+ }
+ $output .= '</div>'; // palette
+
+ $output .= '</div>'; // color form
+
+ return $output;
+}
+
+/**
+ * Theme the stylizer preview form.
+ */
+function theme_ctools_stylizer_preview_form($vars) {
+ $form = &$vars['form'];
+
+ $plugin = $form['#form_state']['base_style_plugin'];
+ $settings = $form['#form_state']['settings'];
+
+ if (!empty($form['#form_state']['settings']['old_settings'])) {
+ ctools_stylizer_cleanup_style($plugin, $form['#form_state']['settings']['old_settings']);
+ }
+ $preview = '';
+ if (!empty($plugin['preview'])) {
+ $preview = $plugin['preview'];
+ }
+ else {
+ $base_types = ctools_get_style_base_types();
+ if (!empty($base_types[$plugin['module']][$plugin['type']]['preview'])) {
+ $preview = $base_types[$plugin['module']][$plugin['type']]['preview'];
+ }
+ }
+
+ if (!empty($preview) && function_exists($preview)) {
+ $output = '<fieldset id="preview"><legend>' . t('Preview') . '</legend>';
+ $output .= $preview($plugin, $settings);
+ $output .= drupal_render_children($form);
+ $output .= '</fieldset>';
+
+ return $output;
+ }
+}
+
+function ctools_stylizer_edit_style_form_default_validate($form, &$form_state) {
+ if (!empty($form_state['owner info']['owner form validate']) && function_exists($form_state['owner info']['owner form validate'])) {
+ $form_state['owner info']['owner form validate']($form, $form_state);
+ }
+
+ if (!empty($form_state['base_style_plugin']['settings form validate']) && function_exists($form_state['base_style_plugin']['settings form validate'])) {
+ $form_state['base_style_plugin']['settings form validate']($form, $form_state);
+ }
+}
+
+function ctools_stylizer_edit_style_form_default_submit($form, &$form_state) {
+ // Store old settings for the purposes of cleaning up.
+ $form_state['settings']['old_settings'] = $form_state['settings'];
+ $form_state['settings']['palette'] = $form_state['values']['palette'];
+
+ if (!empty($form_state['owner info']['owner form submit']) && function_exists($form_state['owner info']['owner form submit'])) {
+ $form_state['owner info']['owner form submit']($form, $form_state);
+ }
+
+ if (!empty($form_state['base_style_plugin']['settings form submit']) && function_exists($form_state['base_style_plugin']['settings form submit'])) {
+ $form_state['base_style_plugin']['settings form submit']($form, $form_state);
+ }
+
+ if ($form_state['clicked_button']['#value'] == t('Preview')) {
+ $form_state['rerender'] = TRUE;
+ // Update the cache with changes.
+ if (!empty($form_state['name'])) {
+ $cache = array('settings' => $form_state['settings']);
+ if (!empty($form_state['owner info']['owner settings'])) {
+ $cache['owner settings'] = $form_state['owner info']['owner settings'];
+ }
+ ctools_stylizer_set_settings_cache($form_state['name'], $cache);
+ }
+ }
+}
+
+// --------------------------------------------------------------------------
+// CSS forms and tools that plugins can use.
+
+/**
+ * Font selector form
+ */
+function ctools_stylizer_font_selector_form(&$form, &$form_state, $label, $settings) {
+ // Family
+ $form['#prefix'] = '<div class="ctools-stylizer-spacing-form clearfix">';
+ $form['#type'] = 'fieldset';
+ $form['#title'] = $label;
+ $form['#suffix'] = '</div>';
+ $form['#tree'] = TRUE;
+
+ $form['font'] = array(
+ '#title' => t('Font family'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['font']) ? $settings['font'] : '',
+ '#options' => array(
+ '' => '',
+ 'Arial, Helvetica, sans-serif' => t('Arial, Helvetica, sans-serif'),
+ 'Times New Roman, Times, serif' => t('Times New Roman, Times, serif'),
+ 'Courier New, Courier, monospace' => t('Courier New, Courier, monospace'),
+ 'Georgia, Times New Roman, Times, serif' => t('Georgia, Times New Roman, Times, serif'),
+ 'Verdana, Arial, Helvetica, sans-serif' => t('Verdana, Arial, Helvetica, sans-serif'),
+ 'Geneva, Arial, Helvetica, sans-serif' => t('Geneva, Arial, Helvetica, sans-serif'),
+ 'Trebuchet MS, Trebuchet, Verdana, sans-serif' => t('Trebuchet MS, Trebuchet, Verdana, sans-serif'),
+ ),
+ );
+
+ // size
+ $form['size'] = array(
+ '#title' => t('Size'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['size']) ? $settings['size'] : '',
+ '#options' => array(
+ '' => '',
+ 'xx-small' => t('XX-Small'),
+ 'x-small' => t('X-Small'),
+ 'small' => t('Small'),
+ 'medium' => t('Medium'),
+ 'large' => t('Large'),
+ 'x-large' => t('X-Large'),
+ 'xx-large' => t('XX-Large'),
+ ),
+ );
+
+ // letter spacing
+ $form['letter_spacing'] = array(
+ '#title' => t('Letter spacing'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['letter_spacing']) ? $settings['letter_spacing'] : '',
+ '#options' => array(
+ '' => '',
+ "-10px" => '10px',
+ "-9px" => '9px',
+ "-8px" => '8px',
+ "-7px" => '7px',
+ "-6px" => '6px',
+ "-5px" => '5px',
+ "-4px" => '4px',
+ "-3px" => '3px',
+ "-2px" => '2px',
+ "-1px" => '1px',
+ "0" => '0',
+ "1px" => '1px',
+ "2px" => '2px',
+ "3px" => '3px',
+ "4px" => '4px',
+ "5px" => '5px',
+ "6px" => '6px',
+ "7px" => '7px',
+ "8px" => '8px',
+ "9px" => '9px',
+ "10px" => '10px',
+ "11px" => '11px',
+ "12px" => '12px',
+ "13px" => '13px',
+ "14px" => '14px',
+ "15px" => '15px',
+ "16px" => '16px',
+ "17px" => '17px',
+ "18px" => '18px',
+ "19px" => '19px',
+ "20px" => '20px',
+ "21px" => '21px',
+ "22px" => '22px',
+ "23px" => '23px',
+ "24px" => '24px',
+ "25px" => '25px',
+ "26px" => '26px',
+ "27px" => '27px',
+ "28px" => '28px',
+ "29px" => '29px',
+ "30px" => '30px',
+ "31px" => '31px',
+ "32px" => '32px',
+ "33px" => '33px',
+ "34px" => '34px',
+ "35px" => '35px',
+ "36px" => '36px',
+ "37px" => '37px',
+ "38px" => '38px',
+ "39px" => '39px',
+ "40px" => '40px',
+ "41px" => '41px',
+ "42px" => '42px',
+ "43px" => '43px',
+ "44px" => '44px',
+ "45px" => '45px',
+ "46px" => '46px',
+ "47px" => '47px',
+ "48px" => '48px',
+ "49px" => '49px',
+ "50px" => '50px',
+ ),
+ );
+
+ // word space
+ $form['word_spacing'] = array(
+ '#title' => t('Word spacing'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['word_spacing']) ? $settings['word_spacing'] : '',
+ '#options' => array(
+ '' => '',
+ "-1em" => '-1em',
+ "-0.95em" => '-0.95em',
+ "-0.9em" => '-0.9em',
+ "-0.85em" => '-0.85em',
+ "-0.8em" => '-0.8em',
+ "-0.75em" => '-0.75em',
+ "-0.7em" => '-0.7em',
+ "-0.65em" => '-0.65em',
+ "-0.6em" => '-0.6em',
+ "-0.55em" => '-0.55em',
+ "-0.5em" => '-0.5em',
+ "-0.45em" => '-0.45em',
+ "-0.4em" => '-0.4em',
+ "-0.35em" => '-0.35em',
+ "-0.3em" => '-0.3em',
+ "-0.25em" => '-0.25em',
+ "-0.2em" => '-0.2em',
+ "-0.15em" => '-0.15em',
+ "-0.1em" => '-0.1em',
+ "-0.05em" => '-0.05em',
+ "normal" => 'normal',
+ "0.05em" => '0.05em',
+ "0.1em" => '0.1em',
+ "0.15em" => '0.15em',
+ "0.2em" => '0.2em',
+ "0.25em" => '0.25em',
+ "0.3em" => '0.3em',
+ "0.35em" => '0.35em',
+ "0.4em" => '0.4em',
+ "0.45em" => '0.45em',
+ "0.5em" => '0.5em',
+ "0.55em" => '0.55em',
+ "0.6em" => '0.6em',
+ "0.65em" => '0.65em',
+ "0.7em" => '0.7em',
+ "0.75em" => '0.75em',
+ "0.8em" => '0.8em',
+ "0.85em" => '0.85em',
+ "0.9em" => '0.9em',
+ "0.95em" => '0.95em',
+ "1em" => '1em',
+ ),
+ );
+
+ // decoration
+ $form['decoration'] = array(
+ '#title' => t('Decoration'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['decoration']) ? $settings['decoration'] : '',
+ '#options' => array(
+ '' => '',
+ 'none' => t('None'),
+ 'underline' => t('Underline'),
+ 'overline' => t('Overline'),
+ 'line-through' => t('Line-through'),
+ ),
+ );
+
+ // weight
+ $form['weight'] = array(
+ '#title' => t('Weight'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['weight']) ? $settings['weight'] : '',
+ '#options' => array(
+ '' => '',
+ 'normal' => t('Normal'),
+ 'bold' => t('Bold'),
+ 'bolder' => t('Bolder'),
+ 'lighter' => t('Lighter'),
+ ),
+ );
+
+ // style
+ $form['style'] = array(
+ '#title' => t('Style'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['style']) ? $settings['style'] : '',
+ '#options' => array(
+ '' => '',
+ 'normal' => t('Normal'),
+ 'italic' => t('Italic'),
+ 'oblique' => t('Oblique'),
+ ),
+ );
+
+ // variant
+ $form['variant'] = array(
+ '#title' => t('Variant'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['variant']) ? $settings['variant'] : '',
+ '#options' => array(
+ '' => '',
+ 'normal' => t('Normal'),
+ 'small-caps' => t('Small-caps'),
+ ),
+ );
+
+ // case
+ $form['case'] = array(
+ '#title' => t('Case'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['case']) ? $settings['case'] : '',
+ '#options' => array(
+ '' => '',
+ 'capitalize' => t('Capitalize'),
+ 'uppercase' => t('Uppercase'),
+ 'lowercase' => t('Lowercase'),
+ 'none' => t('None'),
+ ),
+ );
+
+ // alignment
+ $form['alignment'] = array(
+ '#title' => t('Align'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['alignment']) ? $settings['alignment'] : '',
+ '#options' => array(
+ '' => '',
+ 'justify' => t('Justify'),
+ 'left' => t('Left'),
+ 'right' => t('Right'),
+ 'center' => t('Center'),
+ ),
+ );
+}
+
+/**
+ * Copy font selector information into the settings
+ */
+function ctools_stylizer_font_selector_form_submit(&$form, &$form_state, &$values, &$settings) {
+ $settings = $values;
+}
+
+function ctools_stylizer_font_apply_style(&$stylesheet, $selector, $settings) {
+ $css = '';
+ if (isset($settings['font']) && $settings['font'] !== '') {
+ $css .= ' font-family: ' . $settings['font'] . ";\n";
+ }
+
+ if (isset($settings['size']) && $settings['size'] !== '') {
+ $css .= ' font-size: ' . $settings['size'] . ";\n";
+ }
+
+ if (isset($settings['weight']) && $settings['weight'] !== '') {
+ $css .= ' font-weight: ' . $settings['weight'] . ";\n";
+ }
+
+ if (isset($settings['style']) && $settings['style'] !== '') {
+ $css .= ' font-style: ' . $settings['style'] . ";\n";
+ }
+
+ if (isset($settings['variant']) && $settings['variant'] !== '') {
+ $css .= ' font-variant: ' . $settings['variant'] . ";\n";
+ }
+
+ if (isset($settings['case']) && $settings['case'] !== '') {
+ $css .= ' text-transform: ' . $settings['case'] . ";\n";
+ }
+
+ if (isset($settings['decoration']) && $settings['decoration'] !== '') {
+ $css .= ' text-decoration: ' . $settings['decoration'] . ";\n";
+ }
+
+ if (isset($settings['alignment']) && $settings['alignment'] !== '') {
+ $css .= ' text-align: ' . $settings['alignment'] . ";\n";
+ }
+
+ if (isset($settings['letter_spacing']) && $settings['letter_spacing'] !== '') {
+ $css .= ' letter-spacing: ' . $settings['letter_spacing'] . ";\n";
+ }
+
+ if (isset($settings['word_spacing']) && $settings['word_spacing'] !== '') {
+ $css .= ' word-spacing: ' . $settings['word_spacing'] . ";\n";
+ }
+
+ if ($css) {
+ $stylesheet .= $selector . " {\n" . $css . "}\n";
+ }
+}
+
+/**
+ * Border selector form
+ */
+function ctools_stylizer_border_selector_form(&$form, &$form_state, $label, $settings) {
+ // Family
+ $form['#prefix'] = '<div class="ctools-stylizer-spacing-form clearfix">';
+ $form['#type'] = 'fieldset';
+ $form['#title'] = $label;
+ $form['#suffix'] = '</div>';
+ $form['#tree'] = TRUE;
+
+ $form['thickness'] = array(
+ '#title' => t('Thickness'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['thickness']) ? $settings['thickness'] : '',
+ '#options' => array(
+ '' => '',
+ "none" => t('None'),
+ "1px" => '1px',
+ "2px" => '2px',
+ "3px" => '3px',
+ "4px" => '4px',
+ "5px" => '5px',
+ ),
+ );
+
+ $form['style'] = array(
+ '#title' => t('style'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['style']) ? $settings['style'] : '',
+ '#options' => array(
+ '' => '',
+ 'solid' => t('Solid'),
+ 'dotted' => t('Dotted'),
+ 'dashed' => t('Dashed'),
+ 'double' => t('Double'),
+ 'groove' => t('Groove'),
+ 'ridge' => t('Ridge'),
+ 'inset' => t('Inset'),
+ 'outset' => t('Outset'),
+ ),
+ );
+}
+
+/**
+ * Copy border selector information into the settings
+ */
+function ctools_stylizer_border_selector_form_submit(&$form, &$form_state, &$values, &$settings) {
+ $settings = $values;
+}
+
+function ctools_stylizer_border_apply_style(&$stylesheet, $selector, $settings, $color, $which = NULL) {
+ $border = 'border';
+ if ($which) {
+ $border .= '-' . $which;
+ }
+
+ $css = '';
+ if (isset($settings['thickness']) && $settings['thickness'] !== '') {
+ if ($settings['thickness'] == 'none') {
+ $css .= ' ' . $border . ': none';
+ }
+ else {
+ $css .= ' ' . $border . '-width: ' . $settings['thickness'] . ";\n";
+
+ if (isset($settings['style']) && $settings['style'] !== '') {
+ $css .= ' ' . $border . '-style: ' . $settings['style'] . ";\n";
+ }
+
+ $css .= ' ' . $border . '-color: ' . $color . ";\n";
+ }
+ }
+
+ if ($css) {
+ $stylesheet .= $selector . " {\n" . $css . "}\n";
+ }
+
+}
+
+/**
+ * padding selector form
+ */
+function ctools_stylizer_padding_selector_form(&$form, &$form_state, $label, $settings) {
+ // Family
+ $form['#prefix'] = '<div class="ctools-stylizer-spacing-form clearfix">';
+ $form['#type'] = 'fieldset';
+ $form['#title'] = $label;
+ $form['#suffix'] = '</div>';
+ $form['#tree'] = TRUE;
+
+ $options = array(
+ '' => '',
+ "0.05em" => '0.05em',
+ "0.1em" => '0.1em',
+ "0.15em" => '0.15em',
+ "0.2em" => '0.2em',
+ "0.25em" => '0.25em',
+ "0.3em" => '0.3em',
+ "0.35em" => '0.35em',
+ "0.4em" => '0.4em',
+ "0.45em" => '0.45em',
+ "0.5em" => '0.5em',
+ "0.55em" => '0.55em',
+ "0.6em" => '0.6em',
+ "0.65em" => '0.65em',
+ "0.7em" => '0.7em',
+ "0.75em" => '0.75em',
+ "0.8em" => '0.8em',
+ "0.85em" => '0.85em',
+ "0.9em" => '0.9em',
+ "0.95em" => '0.95em',
+ "1.0em" => '1.0em',
+ "1.05em" => '1.05em',
+ "1.1em" => '1.1em',
+ "1.15em" => '1.15em',
+ "1.2em" => '1.2em',
+ "1.25em" => '1.25em',
+ "1.3em" => '1.3em',
+ "1.35em" => '1.35em',
+ "1.4em" => '1.4em',
+ "1.45em" => '1.45em',
+ "1.5em" => '1.5em',
+ "1.55em" => '1.55em',
+ "1.6em" => '1.6em',
+ "1.65em" => '1.65em',
+ "1.7em" => '1.7em',
+ "1.75em" => '1.75em',
+ "1.8em" => '1.8em',
+ "1.85em" => '1.85em',
+ "1.9em" => '1.9em',
+ "1.95em" => '1.95em',
+ "2.0em" => '2.0em',
+ "2.05em" => '2.05em',
+ "2.1em" => '2.1em',
+ "2.15em" => '2.15em',
+ "2.2em" => '2.2em',
+ "2.25em" => '2.25em',
+ "2.3em" => '2.3em',
+ "2.35em" => '2.35em',
+ "2.4em" => '2.4em',
+ "2.45em" => '2.45em',
+ "2.5em" => '2.5em',
+ "2.55em" => '2.55em',
+ "2.6em" => '2.6em',
+ "2.65em" => '2.65em',
+ "2.7em" => '2.7em',
+ "2.75em" => '2.75em',
+ "2.8em" => '2.8em',
+ "2.85em" => '2.85em',
+ "2.9em" => '2.9em',
+ "2.95em" => '2.95em',
+ "3.0em" => '3.0em',
+ "3.05em" => '3.05em',
+ "3.1em" => '3.1em',
+ "3.15em" => '3.15em',
+ "3.2em" => '3.2em',
+ "3.25em" => '3.25em',
+ "3.3em" => '3.3em',
+ "3.35em" => '3.35em',
+ "3.4em" => '3.4em',
+ "3.45em" => '3.45em',
+ "3.5em" => '3.5em',
+ "3.55em" => '3.55em',
+ "3.6em" => '3.6em',
+ "3.65em" => '3.65em',
+ "3.7em" => '3.7em',
+ "3.75em" => '3.75em',
+ "3.8em" => '3.8em',
+ "3.85em" => '3.85em',
+ "3.9em" => '3.9em',
+ "3.95em" => '3.95em',
+ );
+
+ $form['top'] = array(
+ '#title' => t('Top'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['top']) ? $settings['top'] : '',
+ '#options' => $options,
+ );
+
+ $form['right'] = array(
+ '#title' => t('Right'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['right']) ? $settings['right'] : '',
+ '#options' => $options,
+ );
+
+ $form['bottom'] = array(
+ '#title' => t('Bottom'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['bottom']) ? $settings['bottom'] : '',
+ '#options' => $options,
+ );
+
+ $form['left'] = array(
+ '#title' => t('Left'),
+ '#type' => 'select',
+ '#default_value' => isset($settings['left']) ? $settings['left'] : '',
+ '#options' => $options,
+ );
+}
+
+/**
+ * Copy padding selector information into the settings
+ */
+function ctools_stylizer_padding_selector_form_submit(&$form, &$form_state, &$values, &$settings) {
+ $settings = $values;
+}
+
+function ctools_stylizer_padding_apply_style(&$stylesheet, $selector, $settings) {
+ $css = '';
+
+ if (isset($settings['top']) && $settings['top'] !== '') {
+ $css .= ' padding-top: ' . $settings['top'] . ";\n";
+ }
+
+ if (isset($settings['right']) && $settings['right'] !== '') {
+ $css .= ' padding-right: ' . $settings['right'] . ";\n";
+ }
+
+ if (isset($settings['bottom']) && $settings['bottom'] !== '') {
+ $css .= ' padding-bottom: ' . $settings['bottom'] . ";\n";
+ }
+
+ if (isset($settings['left']) && $settings['left'] !== '') {
+ $css .= ' padding-left: ' . $settings['left'] . ";\n";
+ }
+
+ if ($css) {
+ $stylesheet .= $selector . " {\n" . $css . "}\n";
+ }
+
+}
diff --git a/sites/all/modules/ctools/includes/stylizer.theme.inc b/sites/all/modules/ctools/includes/stylizer.theme.inc
new file mode 100644
index 000000000..85346c155
--- /dev/null
+++ b/sites/all/modules/ctools/includes/stylizer.theme.inc
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file
+ * Contains theme registry and theme implementations for the content types.
+ */
+
+/**
+ * Implementation of hook_theme to load all content plugins and pass thru if
+ * necessary.
+ */
+function ctools_stylizer_theme(&$theme) {
+ $theme['ctools_stylizer_color_scheme_form'] = array(
+ 'render element' => 'form',
+ 'file' => 'includes/stylizer.inc',
+ );
+
+ $theme['ctools_stylizer_preview_form'] = array(
+ 'render element' => 'form',
+ 'file' => 'includes/stylizer.inc',
+ );
+
+ $theme['ctools_style_icon'] = array(
+ 'variables' => array('image' => NULL, 'title' => NULL),
+ 'file' => 'includes/stylizer.inc',
+ );
+}
+
diff --git a/sites/all/modules/ctools/includes/utility.inc b/sites/all/modules/ctools/includes/utility.inc
new file mode 100644
index 000000000..82fc1471a
--- /dev/null
+++ b/sites/all/modules/ctools/includes/utility.inc
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Contains general utility functions for CTools that do not need to be
+ * in the module file.
+ *
+ * In particular, things that are only needed during hook_menu() and
+ * hook_theme() are placed here.
+ */
+
+/**
+ * Provide a hook passthrough to included files.
+ *
+ * To organize things neatly, each CTools tool gets its own toolname.$type.inc
+ * file. If it exists, it's loaded and ctools_$tool_$type() is executed.
+ * To save time we pass the $items array in so we don't need to do array
+ * addition. It modifies the array by reference and doesn't need to return it.
+ */
+function ctools_passthrough($module, $type, &$items) {
+ $files = file_scan_directory(drupal_get_path('module', $module) . '/includes', '/\.' . $type . '\.inc$/', array('key' => 'name'));
+ foreach ($files as $file) {
+ require_once DRUPAL_ROOT . '/' . $file->uri;
+ list($tool) = explode('.', $file->name, 2);
+
+ $function = $module . '_' . str_replace ('-', '_', $tool) . '_' . str_replace('-', '_', $type);
+ if (function_exists($function)) {
+ $function($items);
+ }
+ }
+}
diff --git a/sites/all/modules/ctools/includes/uuid.inc b/sites/all/modules/ctools/includes/uuid.inc
new file mode 100644
index 000000000..6e4c42c32
--- /dev/null
+++ b/sites/all/modules/ctools/includes/uuid.inc
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file
+ * Enables ctools generated modules to use UUIDs without the UUID module enabled.
+ * Per the ctools.module, this file only gets included if UUID doesn't exist.
+ */
+
+/**
+ * Pattern for detecting a valid UUID.
+ */
+define('UUID_PATTERN', '[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}');
+
+/**
+ * Generates a UUID using the Windows internal GUID generator.
+ *
+ * @see http://php.net/com_create_guid
+ */
+function _ctools_uuid_generate_com() {
+ // Remove {} wrapper and make lower case to keep result consistent.
+ return drupal_strtolower(trim(com_create_guid(), '{}'));
+}
+
+/**
+ * Generates an universally unique identifier using the PECL extension.
+ */
+function _ctools_uuid_generate_pecl() {
+ $uuid_type = UUID_TYPE_DEFAULT;
+ return uuid_create($uuid_type);
+}
+
+/**
+ * Generates a UUID v4 using PHP code.
+ *
+ * Based on code from @see http://php.net/uniqid#65879 , but corrected.
+ */
+function _ctools_uuid_generate_php() {
+ // The field names refer to RFC 4122 section 4.1.2.
+ return sprintf('%04x%04x-%04x-4%03x-%04x-%04x%04x%04x',
+ // 32 bits for "time_low".
+ mt_rand(0, 65535), mt_rand(0, 65535),
+ // 16 bits for "time_mid".
+ mt_rand(0, 65535),
+ // 12 bits after the 0100 of (version) 4 for "time_hi_and_version".
+ mt_rand(0, 4095),
+ bindec(substr_replace(sprintf('%016b', mt_rand(0, 65535)), '10', 0, 2)),
+ // 8 bits, the last two of which (positions 6 and 7) are 01, for "clk_seq_hi_res"
+ // (hence, the 2nd hex digit after the 3rd hyphen can only be 1, 5, 9 or d)
+ // 8 bits for "clk_seq_low" 48 bits for "node".
+ mt_rand(0, 65535), mt_rand(0, 65535), mt_rand(0, 65535)
+ );
+}
+
+// This is wrapped in an if block to avoid conflicts with PECL's uuid_is_valid().
+/**
+ * Check that a string appears to be in the format of a UUID.
+ *
+ * @param $uuid
+ * The string to test.
+ *
+ * @return
+ * TRUE if the string is well formed.
+ */
+if (!function_exists('uuid_is_valid')) {
+ function uuid_is_valid($uuid) {
+ return preg_match('/^' . UUID_PATTERN . '$/', $uuid);
+ }
+}
diff --git a/sites/all/modules/ctools/includes/views.inc b/sites/all/modules/ctools/includes/views.inc
new file mode 100644
index 000000000..4ef6439e7
--- /dev/null
+++ b/sites/all/modules/ctools/includes/views.inc
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * Generate new context classes by argument settings on the view.
+ */
+function ctools_views_get_argument_context($argument) {
+ if ($argument['type'] == 'context') {
+ if (strpos($argument['context'], '.')) {
+ list($context, $converter) = explode('.', $argument['context'], 2);
+ }
+ else {
+ // Backwards-compat for before we had a system for delimiting the data
+ // we retrieve out of context objects.
+ $context = $argument['context'];
+ }
+ if ($context == 'term' || $context == 'vocabulary') {
+ $context = 'entity:taxonomy_' . $context;
+ }
+ elseif ($entity = entity_get_info($context)) {
+ $context = 'entity:' . $context;
+ }
+ $class = 'ctools_context_' . (empty($argument['context_optional']) ? 'required' : 'optional');
+ $new_context = new $class($argument['label'], $context);
+ return $new_context;
+ }
+}
diff --git a/sites/all/modules/ctools/includes/wizard.inc b/sites/all/modules/ctools/includes/wizard.inc
new file mode 100644
index 000000000..1a821a586
--- /dev/null
+++ b/sites/all/modules/ctools/includes/wizard.inc
@@ -0,0 +1,534 @@
+<?php
+
+/**
+ * @file
+ * CTools' multi-step form wizard tool.
+ *
+ * This tool enables the creation of multi-step forms that go from one
+ * form to another. The forms themselves can allow branching if they
+ * like, and there are a number of configurable options to how
+ * the wizard operates.
+ *
+ * The wizard can also be friendly to ajax forms, such as when used
+ * with the modal tool.
+ *
+ * The wizard provides callbacks throughout the process, allowing the
+ * owner to control the flow. The general flow of what happens is:
+ *
+ * Generate a form
+ * submit a form
+ * based upon button clicked, 'finished', 'next form', 'cancel' or 'return'.
+ *
+ * Each action has its own callback, so cached objects can be modifed and or
+ * turned into real objects. Each callback can make decisions about where to
+ * go next if it wishes to override the default flow.
+ */
+
+/**
+ * Display a multi-step form.
+ *
+ * Aside from the addition of the $form_info which contains an array of
+ * information and configuration so the multi-step wizard can do its thing,
+ * this function works a lot like drupal_build_form.
+ *
+ * Remember that the form builders for this form will receive
+ * &$form, &$form_state, NOT just &$form_state and no additional args.
+ *
+ * @param $form_info
+ * An array of form info. @todo document the array.
+ * @param $step
+ * The current form step.
+ * @param &$form_state
+ * The form state array; this is a reference so the caller can get back
+ * whatever information the form(s) involved left for it.
+ */
+function ctools_wizard_multistep_form($form_info, $step, &$form_state) {
+ // Make sure 'wizard' always exists for the form when dealing
+ // with form caching.
+ ctools_form_include($form_state, 'wizard');
+
+ // allow order array to be optional
+ if (empty($form_info['order'])) {
+ foreach ($form_info['forms'] as $step_id => $params) {
+ $form_info['order'][$step_id] = $params['title'];
+ }
+ }
+
+ if (!isset($step)) {
+ $keys = array_keys($form_info['order']);
+ $step = array_shift($keys);
+ }
+
+ ctools_wizard_defaults($form_info);
+
+ // If automated caching is enabled, ensure that everything is as it
+ // should be.
+ if (!empty($form_info['auto cache'])) {
+ // If the cache mechanism hasn't been set, default to the simple
+ // mechanism and use the wizard ID to ensure uniqueness so cache
+ // objects don't stomp on each other.
+ if (!isset($form_info['cache mechanism'])) {
+ $form_info['cache mechanism'] = 'simple::wizard::' . $form_info['id'];
+ }
+
+ // If not set, default the cache key to the wizard ID. This is often
+ // a unique ID of the object being edited or similar.
+ if (!isset($form_info['cache key'])) {
+ $form_info['cache key'] = $form_info['id'];
+ }
+
+ // If not set, default the cache location to storage. This is often
+ // somnething like 'conf'.
+ if (!isset($form_info['cache location'])) {
+ $form_info['cache location'] = 'storage';
+ }
+
+ // If absolutely nothing was set for the cache area to work on
+ if (!isset($form_state[$form_info['cache location']])) {
+ ctools_include('cache');
+ $form_state[$form_info['cache location']] = ctools_cache_get($form_info['cache mechanism'], $form_info['cache key']);
+ }
+ }
+
+ $form_state['step'] = $step;
+ $form_state['form_info'] = $form_info;
+
+ // Ensure we have form information for the current step.
+ if (!isset($form_info['forms'][$step])) {
+ return;
+ }
+
+ // Ensure that whatever include file(s) were requested by the form info are
+ // actually included.
+ $info = $form_info['forms'][$step];
+
+ if (!empty($info['include'])) {
+ if (is_array($info['include'])) {
+ foreach ($info['include'] as $file) {
+ ctools_form_include_file($form_state, $file);
+ }
+ }
+ else {
+ ctools_form_include_file($form_state, $info['include']);
+ }
+ }
+
+ // This tells drupal_build_form to apply our wrapper to the form. It
+ // will give it buttons and the like.
+ $form_state['wrapper_callback'] = 'ctools_wizard_wrapper';
+ if (!isset($form_state['rerender'])) {
+ $form_state['rerender'] = FALSE;
+ }
+
+ $form_state['no_redirect'] = TRUE;
+
+ $output = drupal_build_form($info['form id'], $form_state);
+
+ if (empty($form_state['executed']) || !empty($form_state['rerender'])) {
+ if (empty($form_state['title']) && !empty($info['title'])) {
+ $form_state['title'] = $info['title'];
+ }
+
+ if (!empty($form_state['ajax render'])) {
+ // Any include files should already be included by this point:
+ return $form_state['ajax render']($form_state, $output);
+ }
+
+ // Automatically use the modal tool if set to true.
+ if (!empty($form_state['modal']) && empty($form_state['modal return'])) {
+ ctools_include('modal');
+
+ // This overwrites any previous commands.
+ $form_state['commands'] = ctools_modal_form_render($form_state, $output);
+ }
+ }
+
+ if (!empty($form_state['executed'])) {
+ // We use the plugins get_function format because it's powerful and
+ // not limited to just functions.
+ ctools_include('plugins');
+
+ if (isset($form_state['clicked_button']['#wizard type'])) {
+ $type = $form_state['clicked_button']['#wizard type'];
+ // If we have a callback depending upon the type of button that was
+ // clicked, call it.
+ if ($function = ctools_plugin_get_function($form_info, "$type callback")) {
+ $function($form_state);
+ }
+
+ // If auto-caching is on, we need to write the cache on next and
+ // clear the cache on finish.
+ if (!empty($form_info['auto cache'])) {
+ if ($type == 'next') {
+ ctools_include('cache');
+ ctools_cache_set($form_info['cache mechanism'], $form_info['cache key'], $form_state[$form_info['cache location']]);
+ }
+ elseif ($type == 'finish') {
+ ctools_include('cache');
+ ctools_cache_clear($form_info['cache mechanism'], $form_info['cache key']);
+ }
+ }
+
+ // Set a couple of niceties:
+ if ($type == 'finish') {
+ $form_state['complete'] = TRUE;
+ }
+
+ if ($type == 'cancel') {
+ $form_state['cancel'] = TRUE;
+ }
+
+ // If the modal is in use, some special code for it:
+ if (!empty($form_state['modal']) && empty($form_state['modal return'])) {
+ if ($type != 'next') {
+ // Automatically dismiss the modal if we're not going to another form.
+ ctools_include('modal');
+ $form_state['commands'][] = ctools_modal_command_dismiss();
+ }
+ }
+ }
+
+ if (empty($form_state['ajax'])) {
+ // redirect, if one is set.
+ if ($form_state['redirect']) {
+ if (is_array($form_state['redirect'])) {
+ call_user_func_array('drupal_goto', $form_state['redirect']);
+ }
+ else {
+ drupal_goto($form_state['redirect']);
+ }
+ }
+ }
+ else if (isset($form_state['ajax next'])) {
+ // Clear a few items off the form state so we don't double post:
+ $next = $form_state['ajax next'];
+ unset($form_state['ajax next']);
+ unset($form_state['executed']);
+ unset($form_state['post']);
+ unset($form_state['next']);
+ return ctools_wizard_multistep_form($form_info, $next, $form_state);
+ }
+
+ // If the callbacks wanted to do something besides go to the next form,
+ // it needs to have set $form_state['commands'] with something that can
+ // be rendered.
+ }
+
+ // Render ajax commands if we have any.
+ if (isset($form_state['ajax']) && isset($form_state['commands']) && empty($form_state['modal return'])) {
+ return ajax_render($form_state['commands']);
+ }
+
+ // Otherwise, return the output.
+ return $output;
+}
+
+/**
+ * Provide a wrapper around another form for adding multi-step information.
+ */
+function ctools_wizard_wrapper($form, &$form_state) {
+ $form_info = &$form_state['form_info'];
+ $info = $form_info['forms'][$form_state['step']];
+
+ // Determine the next form from this step.
+ // Create a form trail if we're supposed to have one.
+ $trail = array();
+ $previous = TRUE;
+ foreach ($form_info['order'] as $id => $title) {
+ if ($id == $form_state['step']) {
+ $previous = FALSE;
+ $class = 'wizard-trail-current';
+ }
+ elseif ($previous) {
+ $not_first = TRUE;
+ $class = 'wizard-trail-previous';
+ $form_state['previous'] = $id;
+ }
+ else {
+ $class = 'wizard-trail-next';
+ if (!isset($form_state['next'])) {
+ $form_state['next'] = $id;
+ }
+ if (empty($form_info['show trail'])) {
+ break;
+ }
+ }
+
+ if (!empty($form_info['show trail'])) {
+ if (!empty($form_info['free trail'])) {
+ // ctools_wizard_get_path() returns results suitable for
+ // $form_state['redirect] which can only be directly used in
+ // drupal_goto. We have to futz a bit with it.
+ $path = ctools_wizard_get_path($form_info, $id);
+ $options = array();
+ if (!empty($path[1])) {
+ $options = $path[1];
+ }
+ $title = l($title, $path[0], $options);
+ }
+ $trail[] = '<span class="' . $class . '">' . $title . '</span>';
+ }
+ }
+
+ // Display the trail if instructed to do so.
+ if (!empty($form_info['show trail'])) {
+ ctools_add_css('wizard');
+ $form['ctools_trail'] = array(
+ '#markup' => theme(array('ctools_wizard_trail__' . $form_info['id'], 'ctools_wizard_trail'), array('trail' => $trail, 'form_info' => $form_info)),
+ '#weight' => -1000,
+ );
+ }
+
+ if (empty($form_info['no buttons'])) {
+ // Ensure buttons stay on the bottom.
+ $form['buttons'] = array(
+ '#type' => 'actions',
+ '#weight' => 1000,
+ );
+
+ $button_attributes = array();
+ if (!empty($form_state['ajax']) && empty($form_state['modal'])) {
+ $button_attributes = array('class' => array('ctools-use-ajax'));
+ }
+
+ if (!empty($form_info['show back']) && isset($form_state['previous'])) {
+ $form['buttons']['previous'] = array(
+ '#type' => 'submit',
+ '#value' => $form_info['back text'],
+ '#next' => $form_state['previous'],
+ '#wizard type' => 'next',
+ '#weight' => -2000,
+ '#limit_validation_errors' => array(),
+ // hardcode the submit so that it doesn't try to save data.
+ '#submit' => array('ctools_wizard_submit'),
+ '#attributes' => $button_attributes,
+ );
+
+ if (isset($form_info['no back validate']) || isset($info['no back validate'])) {
+ $form['buttons']['previous']['#validate'] = array();
+ }
+ }
+
+ // If there is a next form, place the next button.
+ if (isset($form_state['next']) || !empty($form_info['free trail'])) {
+ $form['buttons']['next'] = array(
+ '#type' => 'submit',
+ '#value' => $form_info['next text'],
+ '#next' => !empty($form_info['free trail']) ? $form_state['step'] : $form_state['next'],
+ '#wizard type' => 'next',
+ '#weight' => -1000,
+ '#attributes' => $button_attributes,
+ );
+ }
+
+ // There are two ways the return button can appear. If this is not the
+ // end of the form list (i.e, there is a next) then it's "update and return"
+ // to be clear. If this is the end of the path and there is no next, we
+ // call it 'Finish'.
+
+ // Even if there is no direct return path (some forms may not want you
+ // leaving in the middle) the final button is always a Finish and it does
+ // whatever the return action is.
+ if (!empty($form_info['show return']) && !empty($form_state['next'])) {
+ $form['buttons']['return'] = array(
+ '#type' => 'submit',
+ '#value' => $form_info['return text'],
+ '#wizard type' => 'return',
+ '#attributes' => $button_attributes,
+ );
+ }
+ else if (empty($form_state['next']) || !empty($form_info['free trail'])) {
+ $form['buttons']['return'] = array(
+ '#type' => 'submit',
+ '#value' => $form_info['finish text'],
+ '#wizard type' => 'finish',
+ '#attributes' => $button_attributes,
+ );
+ }
+
+ // If we are allowed to cancel, place a cancel button.
+ if ((isset($form_info['cancel path']) && !isset($form_info['show cancel'])) || !empty($form_info['show cancel'])) {
+ $form['buttons']['cancel'] = array(
+ '#type' => 'submit',
+ '#value' => $form_info['cancel text'],
+ '#wizard type' => 'cancel',
+ // hardcode the submit so that it doesn't try to save data.
+ '#limit_validation_errors' => array(),
+ '#submit' => array('ctools_wizard_submit'),
+ '#attributes' => $button_attributes,
+ );
+ }
+
+ // Set up optional validate handlers.
+ $form['#validate'] = array();
+ if (function_exists($info['form id'] . '_validate')) {
+ $form['#validate'][] = $info['form id'] . '_validate';
+ }
+ if (isset($info['validate']) && function_exists($info['validate'])) {
+ $form['#validate'][] = $info['validate'];
+ }
+
+ // Set up our submit handler after theirs. Since putting something here will
+ // skip Drupal's autodetect, we autodetect for it.
+
+ // We make sure ours is after theirs so that they get to change #next if
+ // the want to.
+ $form['#submit'] = array();
+ if (function_exists($info['form id'] . '_submit')) {
+ $form['#submit'][] = $info['form id'] . '_submit';
+ }
+ if (isset($info['submit']) && function_exists($info['submit'])) {
+ $form['#submit'][] = $info['submit'];
+ }
+ $form['#submit'][] = 'ctools_wizard_submit';
+ }
+
+ if (!empty($form_state['ajax'])) {
+ $params = ctools_wizard_get_path($form_state['form_info'], $form_state['step']);
+ if (count($params) > 1) {
+ $url = array_shift($params);
+ $options = array();
+
+ $keys = array(0 => 'query', 1 => 'fragment');
+ foreach ($params as $key => $value) {
+ if (isset($keys[$key]) && isset($value)) {
+ $options[$keys[$key]] = $value;
+ }
+ }
+
+ $params = array($url, $options);
+ }
+ $form['#action'] = call_user_func_array('url', $params);
+ }
+
+ if (isset($info['wrapper']) && function_exists($info['wrapper'])) {
+ $form = $info['wrapper']($form, $form_state);
+ }
+
+ if (isset($form_info['wrapper']) && function_exists($form_info['wrapper'])) {
+ $form = $form_info['wrapper']($form, $form_state);
+ }
+ return $form;
+}
+
+/**
+ * On a submit, go to the next form.
+ */
+function ctools_wizard_submit(&$form, &$form_state) {
+ if (isset($form_state['clicked_button']['#wizard type'])) {
+ $type = $form_state['clicked_button']['#wizard type'];
+
+ // if AJAX enabled, we proceed slightly differently here.
+ if (!empty($form_state['ajax'])) {
+ if ($type == 'next') {
+ $form_state['ajax next'] = $form_state['clicked_button']['#next'];
+ }
+ }
+ else {
+ if ($type == 'cancel' && isset($form_state['form_info']['cancel path'])) {
+ $form_state['redirect'] = $form_state['form_info']['cancel path'];
+ }
+ else if ($type == 'next') {
+ $form_state['redirect'] = ctools_wizard_get_path($form_state['form_info'], $form_state['clicked_button']['#next']);
+ if (!empty($_GET['destination'])) {
+ // We don't want drupal_goto redirect this request
+ // back. ctools_wizard_get_path ensures that the destination is
+ // carried over on subsequent pages.
+ unset($_GET['destination']);
+ }
+ }
+ else if (isset($form_state['form_info']['return path'])) {
+ $form_state['redirect'] = $form_state['form_info']['return path'];
+ }
+ else if ($type == 'finish' && isset($form_state['form_info']['cancel path'])) {
+ $form_state['redirect'] = $form_state['form_info']['cancel path'];
+ }
+ }
+ }
+}
+
+/**
+ * Create a path from the form info and a given step.
+ */
+function ctools_wizard_get_path($form_info, $step) {
+ if (is_array($form_info['path'])) {
+ foreach ($form_info['path'] as $id => $part) {
+ $form_info['path'][$id] = str_replace('%step', $step, $form_info['path'][$id]);
+ }
+ $path = $form_info['path'];
+ }
+ else {
+ $path = array(str_replace('%step', $step, $form_info['path']));
+ }
+
+ // If destination is set, carry it over so it'll take effect when
+ // saving. The submit handler will unset destination to avoid drupal_goto
+ // redirecting us.
+ if (!empty($_GET['destination'])) {
+ // Ensure that options is an array.
+ if (!isset($path[1]) || !is_array($path[1])) {
+ $path[1] = array();
+ }
+ // Ensure that the query part of options is an array.
+ $path[1] += array('query' => array());
+ // Add the destination parameter, if not set already.
+ $path[1]['query'] += drupal_get_destination();
+ }
+
+ return $path;
+}
+
+/**
+ * Set default parameters and callbacks if none are given.
+ * Callbacks follows pattern:
+ * $form_info['id']_$hook
+ * $form_info['id']_$form_info['forms'][$step_key]_$hook
+ */
+function ctools_wizard_defaults(&$form_info) {
+ $hook = $form_info['id'];
+ $defaults = array(
+ 'show trail' => FALSE,
+ 'free trail' => FALSE,
+ 'show back' => FALSE,
+ 'show cancel' => FALSE,
+ 'show return' => FALSE,
+ 'next text' => t('Continue'),
+ 'back text' => t('Back'),
+ 'return text' => t('Update and return'),
+ 'finish text' => t('Finish'),
+ 'cancel text' => t('Cancel'),
+ );
+
+ if (!empty($form_info['free trail'])) {
+ $defaults['next text'] = t('Update');
+ $defaults['finish text'] = t('Save');
+ }
+
+ $form_info = $form_info + $defaults;
+ // set form callbacks if they aren't defined
+ foreach ($form_info['forms'] as $step => $params) {
+ if (!$params['form id']) {
+ $form_callback = $hook . '_' . $step . '_form';
+ $form_info['forms'][$step]['form id'] = $form_callback;
+ }
+ }
+
+ // set button callbacks
+ $callbacks = array(
+ 'back callback' => '_back',
+ 'next callback' => '_next',
+ 'return callback' => '_return',
+ 'cancel callback' => '_cancel',
+ 'finish callback' => '_finish',
+ );
+
+ foreach ($callbacks as $key => $callback) {
+ // never overwrite if explicity defined
+ if (empty($form_info[$key])) {
+ $wizard_callback = $hook . $callback;
+ if (function_exists($wizard_callback)) {
+ $form_info[$key] = $wizard_callback;
+ }
+ }
+ }
+}
diff --git a/sites/all/modules/ctools/includes/wizard.theme.inc b/sites/all/modules/ctools/includes/wizard.theme.inc
new file mode 100644
index 000000000..c1a26468d
--- /dev/null
+++ b/sites/all/modules/ctools/includes/wizard.theme.inc
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @file
+ * Themable for the wizard tool.
+ */
+
+function ctools_wizard_theme(&$theme) {
+ $theme['ctools_wizard_trail'] = array(
+ 'variables' => array('trail' => NULL, 'form_info' => NULL, 'divider' => ' » '),
+ 'file' => 'includes/wizard.theme.inc',
+ );
+}
+
+/**
+ * Themable display of the 'breadcrumb' trail to show the order of the forms.
+ */
+function theme_ctools_wizard_trail($vars) {
+ if (!empty($vars['trail'])) {
+ return '<div class="wizard-trail">' . implode($vars['divider'], $vars['trail']) . '</div>';
+ }
+}