summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--context.module674
-rw-r--r--context_ui/context_ui.admin.inc1009
2 files changed, 1683 insertions, 0 deletions
diff --git a/context.module b/context.module
new file mode 100644
index 0000000..702e1d0
--- /dev/null
+++ b/context.module
@@ -0,0 +1,674 @@
+<?php
+// $Id$
+
+require('context.core.inc');
+
+define('CONTEXT_GET', 0);
+define('CONTEXT_SET', 1);
+define('CONTEXT_ISSET', 2);
+define('CONTEXT_CLEAR', 3);
+
+define('CONTEXT_STORAGE_DEFAULT', 0);
+define('CONTEXT_STORAGE_OVERRIDDEN', 1);
+define('CONTEXT_STORAGE_NORMAL', 2);
+
+define('CONTEXT_STATUS_DISABLED', 0);
+define('CONTEXT_STATUS_ENABLED', 1);
+
+/**
+ * Master context function. Avoid calling this directly -- use one of the helper functions below.
+ *
+ * @param $op
+ * The operation to perform - handled by the context helper functions. Use them.
+ * @param $namespace
+ * A string to be used as the namespace for the context information.
+ * @param $attribute
+ * Usually a string to be used as a key to set/retrieve context information. An array can
+ * also be used when setting context to establish an entire context namespace at once.
+ * (At some point objects may also be accepted, but currently functionaliy isn't complete.)
+ * @param $value
+ * A value to set for the provided key. If omitted the value will be set to true.
+ *
+ * @return
+ * Either the requested value, or false if the operation fails.
+ */
+function context_context($op = CONTEXT_GET, $namespace = null, $attribute = null, $value = null) {
+ static $context;
+ $context = !$context ? array() : $context;
+ switch ($op) {
+ case CONTEXT_GET:
+ // return entire context
+ if (!$namespace) {
+ return $context;
+ }
+ // return entire space if set
+ else if (isset($context[(string) $namespace])) {
+ // return val of key from space
+ if (is_array($context[(string) $namespace]) && isset($context[(string) $namespace][(string) $attribute])) {
+ return $context[(string) $namespace][(string) $attribute];
+ }
+ elseif (!$attribute){
+ return $context[(string) $namespace];
+ }
+ }
+ break;
+ case CONTEXT_SET:
+ // bail if invalid space is specified or context is already set
+ if (is_string($namespace) || is_int($namespace)) {
+ // initialize namespace if no key is specified
+ if (!$attribute) {
+ $context[(string) $namespace] = array();
+ return true;
+ }
+ // set to true if key is a usable identifier. otherwise, allow a key or object to be inserted
+ if (!$value) {
+ if (is_string($attribute) || is_int($attribute)) {
+ $context[(string) $namespace][(string) $attribute] = true;
+ return true;
+ }
+ elseif (is_array($attribute) || is_object($attribute)) {
+ $context[(string) $namespace] = $attribute;
+ return true;
+ }
+ }
+ // set value if key is valid
+ if ((is_string($attribute) || is_int($attribute)) && $value) {
+ $context[$namespace][$attribute] = $value;
+ return true;
+ }
+ }
+ break;
+ case CONTEXT_ISSET:
+ // return entire context
+ if (!$namespace) return false;
+ if (!$attribute) {
+ // return entire space if set
+ return isset($context[$namespace]);
+ }
+ // return val of key from space
+ return isset($context[$namespace][$attribute]);
+ case CONTEXT_CLEAR:
+ $context = array();
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Sets a context by namespace + attribute.
+ */
+function context_set($namespace, $attribute = null, $value = null) {
+ return context_context(CONTEXT_SET, $namespace, $attribute, $value);
+}
+
+/**
+ * Retrieves a context by namespace + (optional) attribute.
+ */
+function context_get($namespace = null, $attribute = null) {
+ return context_context(CONTEXT_GET, $namespace, $attribute, null);
+}
+
+/**
+ * Returns a boolean for whether a context namespace + attribute have been set.
+ */
+function context_isset($namespace = null, $attribute = null) {
+ return context_context(CONTEXT_ISSET, $namespace, $attribute, null);
+}
+
+/**
+ * Deprecated context_exists() function. Retained for backwards
+ * compatibility -- please use context_isset() instead.
+ */
+function context_exists($namespace = null, $attribute = null) {
+ return context_context(CONTEXT_ISSET, $namespace, $attribute, null);
+}
+
+/**
+ * Clears static context array() -- meant only for testing
+ */
+function context_clear() {
+ return context_context(CONTEXT_CLEAR);
+}
+
+/**
+ * Implemented hooks ==================================================
+ */
+
+/**
+ * Implementation of hook_init().
+ */
+function context_init() {
+ // context integration against the sitewide setter
+ context_set_by_condition('sitewide', 1);
+
+ // context integration against paths
+ $path = array();
+ $path['real'] = $_GET['q'];
+ $path['alias'] = drupal_get_path_alias($_GET['q']);
+ if ($path['real'] == $path['alias']) {
+ unset($path['alias']);
+ }
+ // Test each path option incrementally adding args as we go.
+ foreach ($path as $path_option) {
+ $args = explode('/', $path_option);
+ if (count($args)) {
+ $test = array_shift($args);
+ context_set_by_condition('path', $test);
+ while (count($args)) {
+ $test .= '/'. array_shift($args);
+ context_set_by_condition('path', $test);
+ }
+ }
+ }
+}
+
+/**
+ * Implementation of hook_flush_caches().
+ */
+function context_flush_caches() {
+ context_invalidate_cache();
+ return array();
+}
+
+/**
+ * L'CRUD FUNCTIONS ===================================================
+ */
+
+/**
+ * Context loader.
+ *
+ * @param $context
+ * The parameters to use for loading this context. Can be an integer
+ * cid or partial context object.
+ *
+ * @return
+ * Returns a fully-loaded context definition.
+ */
+function context_load_context($context, $reset = FALSE) {
+ static $cache = array();
+ // Argument is a cid
+ if (is_numeric($context)) {
+ $cid = $context;
+ if (!isset($cache[$cid])) {
+ $context = db_fetch_object(db_query("SELECT * FROM {context} WHERE cid = %d", $cid));
+ }
+ else {
+ return $cache[$cid];
+ }
+ }
+ // Context object has an associated cid
+ else if (is_object($context) && isset($context->cid)) {
+ $context = db_fetch_object(db_query("SELECT * FROM {context} WHERE cid = %d", $context->cid));
+ }
+ // Context object has no cid -- we'll try to load by ns/attr/val
+ else if (is_object($context) && $context->namespace && $context->attribute && $context->value) {
+ $args = array($context->namespace, $context->attribute, $context->value);
+ $context = db_fetch_object(db_query("SELECT * FROM {context} WHERE namespace = '%s' AND attribute = '%s' AND value = '%s'", $args));
+ }
+
+ // Unserialize and populate associations
+ if (!empty($context->data)) {
+ $context = context_unpack_context($context);
+ $cache[$context->cid] = $context;
+ return $context;
+ }
+
+ return false;
+}
+
+/**
+ * Inserts or updates a context object into the database.
+ * @TODO: should probably return the new cid on success -- make sure
+ * this doesn't break any checks elsewhere.
+ *
+ * @param $context
+ * The context object to be inserted.
+ *
+ * @return
+ * Returns true on success, false on failure.
+ */
+function context_save_context($context) {
+ // Insert or update the core context definition
+ if (!isset($context->cid)) {
+ $existing = context_load_context($context, TRUE);
+ if ($existing && $existing->cid) {
+ return false;
+ }
+ else {
+ $context = context_pack_context($context);
+ drupal_write_record('context', $context);
+ }
+ }
+ else {
+ $context = context_pack_context($context);
+ drupal_write_record('context', $context, 'cid');
+ }
+
+ // Invalidate context cache
+ cache_clear_all('context', 'cache');
+ return TRUE;
+}
+
+/**
+ * Deletes an existing context.
+ *
+ * @param $context
+ * The context object to be deleted. The $context->cid property is
+ * necessary for this operation.
+ *
+ * @return
+ * Returns true on success, false on failure.
+ */
+function context_delete_context($context) {
+ if ($context = context_load_context($context, TRUE)) {
+ db_query("DELETE FROM {context} WHERE cid = %d", $context->cid);
+
+ // Invalidate context cache
+ cache_clear_all('context', 'cache');
+
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Helper function to pack a context object to be stored in the database.
+ */
+function context_pack_context($context) {
+ $packed = new StdClass();
+
+ $packed->cid = $context->cid;
+ $packed->namespace = $context->namespace;
+ $packed->attribute = $context->attribute;
+ $packed->value = $context->value;
+
+ unset($context->cid);
+ unset($context->namespace);
+ unset($context->attribute);
+ unset($context->value);
+
+ // Clear out storage and status information
+ unset($context->type);
+ unset($context->status);
+
+ // Serialize associations as data array
+ $context = (array) $context;
+ $packed->data = serialize($context);
+
+ return $packed;
+}
+
+/**
+ * Helper function to unpack a context object that has been retrieved from the database.
+ * @TODO: Remove this once ->conditions and ->reactions issue has been resolved.
+ */
+function context_unpack_context($context) {
+ if (!empty($context->data)) {
+ $data = unserialize($context->data);
+ foreach ($data as $k => $v) {
+ $context->{$k} = $v;
+ }
+ unset($context->data);
+ }
+ return $context;
+}
+
+/**
+ * API FUNCTIONS ======================================================
+ */
+
+/**
+ * Provides an array of all contexts provided by modules and in the database.
+ *
+ * @param $reset
+ * Boolean to clear the static cached array of contexts.
+ *
+ * @return
+ * An array of context objects.
+ */
+function context_contexts($reset = FALSE) {
+ static $contexts;
+ if (!$contexts || $reset) {
+ $contexts = array();
+ foreach (module_implements('context_default_contexts') as $module) {
+ $function = $module .'_context_default_contexts';
+ $c = call_user_func($function);
+ if (!empty($c)) {
+ foreach ($c as $context) {
+ $context = (object) $context;
+ $context->type = CONTEXT_STORAGE_DEFAULT;
+ $identifier = "{$context->namespace}-{$context->attribute}-{$context->value}";
+ $contexts[$identifier] = $context;
+ }
+ }
+ }
+ // Allow other modules to alter contexts
+ drupal_alter('context_default_contexts', $contexts);
+
+ // Collect normal & overridden contexts
+ $result = db_query("SELECT * FROM {context} ORDER BY namespace ASC, attribute ASC, value ASC");
+ while ($context = db_fetch_object($result)) {
+ $context = context_unpack_context($context);
+ $key = "{$context->namespace}-{$context->attribute}-{$context->value}";
+ if (isset($contexts[$key])) {
+ $contexts[$key] = $context;
+ $contexts[$key]->type = CONTEXT_STORAGE_OVERRIDDEN;
+ }
+ else {
+ $contexts[$key] = $context;
+ $contexts[$key]->type = CONTEXT_STORAGE_NORMAL;
+ }
+ }
+
+ // Mark the status of each context
+ foreach ($contexts as $key => $context) {
+ $contexts[$key]->status = context_status($context);
+ }
+ }
+ return $contexts;
+}
+
+/**
+ * Wrapper around cache_get() to make it easier for context to pull different
+ * datastores from a single cache row.
+ */
+function context_cache_get($key, $reset = FALSE) {
+ static $cache;
+ if (!isset($cache) || $reset) {
+ $cache = cache_get('context', 'cache');
+ $cache = $cache ? $cache->data : array();
+ }
+ return !empty($cache[$key]) ? $cache[$key] : FALSE;
+}
+
+/**
+ * Wrapper around cache_set() to make it easier for context to write different
+ * datastores to a single cache row.
+ */
+function context_cache_set($key, $value) {
+ $cache = cache_get('context', 'cache');
+ $cache = $cache ? $cache->data : array();
+ $cache[$key] = $value;
+ cache_set('context', $cache);
+}
+
+/**
+ * Retrieves all enabled contexts from the cache.
+ */
+function context_enabled_contexts($namespace = NULL, $reset = FALSE) {
+ static $enabled;
+ static $namespaces;
+ if (!isset($enabled) || $reset) {
+ $enabled = array();
+ $cache = context_cache_get('enabled');
+ if ($cache && !$reset) {
+ $enabled = $cache;
+ }
+ else {
+ $contexts = context_contexts(TRUE);
+
+ foreach ($contexts as $context) {
+ if (context_status($context) == CONTEXT_STATUS_ENABLED) {
+ $identifier = "{$context->namespace}-{$context->attribute}-{$context->value}";
+ $enabled[$identifier] = $context;
+ }
+ }
+
+ // Set the cache
+ context_cache_set('enabled', $enabled);
+ }
+ foreach ($enabled as $identifier => $context) {
+ if (!isset($namespaces[$context->namespace])) {
+ $namespaces[$context->namespace] = array();
+ }
+ $namespaces[$context->namespace][$identifier] = $context;
+ }
+ }
+
+ if (!empty($namespace)) {
+ return !empty($namespaces[$namespace]) ? $namespaces[$namespace] : array();
+ }
+ return $enabled;
+}
+
+/**
+ * Loads any active contexts with associated reactions. This should be run
+ * at a late stage of the page load to ensure that relevant contexts have been set.
+ */
+function context_active_contexts($reset = FALSE) {
+ static $contexts;
+ if (!isset($contexts) || $reset) {
+ $contexts = array();
+ $cache = context_enabled_contexts();
+ foreach ($cache as $context) {
+ if (context_get($context->namespace, $context->attribute) == $context->value) {
+ $identifier = "{$context->namespace}-{$context->attribute}-{$context->value}";
+ $contexts[$identifier] = $context;
+ }
+ }
+ drupal_alter('context_active_contexts', $contexts);
+ }
+ return $contexts;
+}
+
+/**
+ * Loads an associative array of conditions => context identifiers to allow
+ * contexts to be set by different conditions. Called by context_set_by_condition().
+ */
+function context_condition_map($reset = FALSE) {
+ static $condition_map;
+ if (!isset($condition_map) || $reset) {
+ $cache = context_cache_get('condition_map');
+ if ($cache && !$reset) {
+ $condition_map = $cache;
+ }
+ else {
+ $enabled = context_enabled_contexts();
+ foreach (array_keys(context_conditions()) as $condition) {
+ $condition_map[$condition] = array();
+ foreach ($enabled as $identifier => $context) {
+ if (!empty($context->{$condition})) {
+ if (is_array($context->{$condition})) {
+ foreach ($context->{$condition} as $value) {
+ if (!isset($condition_map[$condition][$value])) {
+ $condition_map[$condition][$value] = array();
+ }
+ $condition_map[$condition][$value][] = $identifier;
+ }
+ }
+ else if (is_string($context->{$condition})) {
+ $value = $context->{$condition};
+ if (!isset($condition_map[$condition][$value])) {
+ $condition_map[$condition][$value] = array();
+ }
+ $condition_map[$condition][$value][] = $identifier;
+ }
+ }
+ }
+ }
+ context_cache_set('condition_map', $condition_map);
+ }
+ }
+ return $condition_map;
+}
+
+/**
+ * Builds an array of values based on the current active contexts and group
+ * by conditions/reactions.
+ *
+ * @param $type
+ * The condition or reaction type to pull values for.
+ * @param $reset
+ * Boolean value for resetting the static cache.
+ *
+ * @return
+ * Returns a keyed array of reactions and values.
+ */
+function context_active_values($type = NULL, $reset = FALSE) {
+ static $value_map;
+ if (!isset($value_map) || $reset) {
+ $value_map = array();
+
+ // This sucks... @TODO: fix it!!!
+ $keys = array_merge(array_keys(context_reactions()), array_keys(context_conditions()));
+ $keys[] = 'block';
+
+ foreach ($keys as $key) {
+ foreach (context_active_contexts() as $identifier => $context) {
+ if (!empty($context->{$key})) {
+ if (!isset($value_map[$key])) {
+ $value_map[$key] = array();
+ }
+ if (is_array($context->{$key})) {
+ $value_map[$key] = array_merge($value_map[$key], $context->{$key});
+ }
+ else {
+ $value_map[$key][] = $context->{$key};
+ }
+ }
+ }
+ }
+ }
+ if (!empty($type)) {
+ return !empty($value_map[$type]) ? $value_map[$type] : array();
+ }
+ return $value_map;
+}
+
+/**
+ * Returns status for whether a given context definition is enabled.
+ */
+function context_status($context) {
+ $status = variable_get('context_status', array());
+ $identifier = "{$context->namespace}-{$context->attribute}-{$context->value}";
+ if (isset($status[$identifier]) && !$status[$identifier]) {
+ return CONTEXT_STATUS_DISABLED;
+ }
+ return CONTEXT_STATUS_ENABLED;
+}
+
+/**
+ * Invalidates all context caches().
+ */
+function context_invalidate_cache() {
+ cache_clear_all('context', 'cache');
+}
+
+/**
+ * Invokes hook_context_conditions() to provide an array of conditions that may be associated with a context.
+ */
+function context_conditions($reset = FALSE) {
+ static $conditions;
+ if (!isset($conditions) || $reset) {
+ $cache = context_cache_get('conditions');
+ if ($cache && !$reset) {
+ $conditions = $cache;
+ }
+ else {
+ $conditions = module_invoke_all('context_conditions');
+ context_cache_set('conditions', $conditions);
+ }
+ }
+ return $conditions;
+}
+
+/**
+ * Invokes hook_context_reactions() to provide an array of reactions that may be associated with a context.
+ */
+function context_reactions($reset = FALSE) {
+ static $reactions;
+ if (!isset($reactions) || $reset) {
+ $cache = context_cache_get('reactions');
+ if ($cache && !$reset) {
+ $reactions = $cache;
+ }
+ else {
+ $reactions = module_invoke_all('context_reactions');
+ context_cache_set('reactions', $reactions);
+ }
+ }
+ return $reactions;
+}
+
+/**
+ * Sets a namespace-attribute-value context that has been associated with the provided condition.
+ *
+ * @param $type
+ * The condition type to be matched.
+ * @param $id
+ * A value to match against.
+ * @param $force
+ * Boolean param to force the setting of a context value even if a value for
+ * the found namespace/attribute is already set. Defaults to FALSE.
+ *
+ * @return
+ * True if one or more contexts were set. False if no items/contexts matched.
+ */
+function context_set_by_condition($type, $id, $force = FALSE) {
+ $map = context_condition_map();
+ $set = FALSE;
+ if (!empty($map[$type]) && !empty($map[$type][$id])) {
+ $contexts = context_enabled_contexts();
+
+ $identifiers = $map[$type][$id];
+ foreach ($identifiers as $identifier) {
+ $context = !empty($contexts[$identifier]) ? $contexts[$identifier] : FALSE;
+
+ if ($context) {
+ // If this context already has a value, don't alter it.
+ if (!context_isset($context->namespace, $context->attribute) || $force) {
+ context_set($context->namespace, $context->attribute, $context->value);
+ $set = TRUE;
+ }
+ }
+ }
+ }
+ return $set;
+}
+
+/**
+ * Recursive helper function to determine whether an array and its
+ * children are entirely empty.
+ */
+function context_empty($element) {
+ $empty = TRUE;
+ if (is_array($element)) {
+ foreach ($element as $child) {
+ $empty = $empty && context_empty($child);
+ }
+ }
+ else {
+ $empty = $empty && empty($element);
+ }
+ return $empty;
+}
+
+/**
+ * Export a variable.
+ */
+function context_var_export($var, $prefix = '', $multiple = TRUE) {
+ if (is_array($var)) {
+ if (empty($var)) {
+ $output = 'array()';
+ }
+ else {
+ $output = "array(\n";
+ foreach ($var as $key => $value) {
+ $output .= " '$key' => ". context_var_export($value, $prefix . ($multiple? '' : ' ')) .",\n";
+ }
+ $output .= ')';
+ }
+ }
+ else if (is_bool($var)) {
+ $output = $var ? 'TRUE' : 'FALSE';
+ }
+ else {
+ $output = var_export($var, TRUE);
+ }
+
+ if ($prefix) {
+ $output = str_replace("\n", "\n$prefix", $output);
+ }
+
+ return $output;
+}
diff --git a/context_ui/context_ui.admin.inc b/context_ui/context_ui.admin.inc
new file mode 100644
index 0000000..9fc8692
--- /dev/null
+++ b/context_ui/context_ui.admin.inc
@@ -0,0 +1,1009 @@
+<?php
+// $Id$
+
+/**
+ * Page callback for context_ui admin landing page.
+ */
+function context_ui_admin() {
+ // Add css
+ drupal_add_css(drupal_get_path("module", "context_ui") ."/context_ui.css");
+
+ // rebuild blocks
+ _block_rehash();
+
+ // rebuild context cache
+ context_invalidate_cache();
+
+ // user defined contexts
+ $output = "<h3>". t('Context definitions') ."</h3>";
+ $normal = context_contexts();
+ if ($normal) {
+ ksort($normal);
+ $output .= theme('context_ui_admin', $normal);
+ }
+ else {
+ $output .= "<p>". t('Please !add_context to get started.', array('!add_context' => l(t('add a context'), 'admin/build/context/add'))) ."</p>";
+ }
+
+ return $output;
+}
+
+/**
+ * Recursive function that intelligently populates default values in a
+ * form from a provided array of data.
+ *
+ * @param $form
+ * A form API element to populate with default values.
+ * @param $data
+ * A keyed array of data that matches the tree structure of the
+ * form API branch it should populate.
+ *
+ * @return
+ * A form API element populated with default values.
+ */
+function context_ui_default_values($form, $data) {
+ if (!empty($form['#tree'])) {
+ foreach (element_children($form) as $id) {
+ if (isset($data[$id])) {
+ $form[$id] = context_ui_default_values($form[$id], $data[$id]);
+ }
+ }
+ }
+ else {
+ if (is_array($data) && $form['#type'] == 'checkboxes') {
+ $form['#default_value'] = array();
+ foreach ($data as $value) {
+ $form['#default_value'][$value] = $value;
+ }
+ }
+ else if (is_array($data) && $form['#type'] == 'select' && $form['#multiple'] == true) {
+ $form['#default_value'] = array();
+ foreach ($data as $value) {
+ $form['#default_value'][] = $value;
+ }
+ }
+ else if (is_array($data) && $form['#type'] == 'textarea') {
+ $form['#default_value'] = implode("\n", $data);
+ }
+ else if (is_array($data) && $form['#type'] == 'textfield') {
+ $form['#default_value'] = implode(",", $data);
+ }
+ else {
+ $form['#default_value'] = is_array($data) ? current($data) : $data;
+ }
+ }
+ return $form;
+}
+
+/**
+ * Generates the omnibus context definition editing form.
+ * Note: submission and validation handlers are in context_ui_admin.inc
+ *
+ * @param $op
+ * The type of form to build. Either "add", "view" or "edit"
+ * @param $cid
+ * The db context identifier - required when $op == "edit"
+ *
+ * @return
+ * A Drupal form array.
+ */
+function context_ui_form(&$form_state, $op, $context = NULL) {
+ switch ($op) {
+ case 'add':
+ drupal_set_title(t('Add a new context'));
+ break;
+ case 'edit':
+ if (!$context->system) {
+ drupal_set_title(t('Edit context: %title', array('%title' => $context->value)));
+ }
+ else {
+ drupal_set_title(t('View %title', array('%title' => $context->value)));
+ }
+ break;
+ case 'clone':
+ drupal_set_title(t('Clone context: %title', array('%title' => $context->value)));
+ $context->system = 0;
+ $context->cid = null;
+ $cid = null;
+ break;
+ }
+
+ // Initialize context object if it doesn't already exist
+ $context = !$context ? new StdClass() : $context;
+
+ // Core context definition
+ $form = array(
+ '#base' => 'context_ui_form',
+ '#theme' => 'context_ui_form',
+ );
+
+ $form['cid'] = array(
+ '#type' => 'value',
+ '#value' => isset($context->cid) ? $context->cid : NULL,
+ );
+
+ $form['system'] = array(
+ '#type' => 'value',
+ '#value' => isset($context->system) ? $context->system : 0,
+ );
+
+ foreach (array('value', 'attribute', 'namespace', 'description') as $field) {
+ $form[$field] = array(
+ '#type' => 'textfield',
+ '#required' => true,
+ '#maxlength' => 64,
+ '#size' => 20,
+ '#disabled' => !empty($context->system) ? true : false,
+ '#default_value' => isset($context->{$field}) ? $context->{$field} : '',
+ '#title' => t(ucfirst($field)),
+ );
+ }
+
+ $form['value']['#description'] = t('A system name for this context. May only contain lowercase letters, underscores, and numbers. Example: <strong>science_blog</strong>');
+
+ $form['attribute']['#default_value'] = empty($form['attribute']['#default_value']) ? 'section' : $form['attribute']['#default_value'];
+ $form['attribute']['#description'] = t('The type of context information provided in this namespace. Example: <strong>section</strong>');
+
+ $form['namespace']['#default_value'] = empty($form['namespace']['#default_value']) ? 'context_ui' : $form['namespace']['#default_value'];
+ $form['namespace']['#description'] = t('The namespace for this context definition. Example: <strong>context_ui</strong>');
+
+ $form['description']['#required'] = FALSE;
+ $form['description']['#size'] = 40;
+ $form['description']['#maxlength'] = 255;
+ $form['description']['#description'] = t('The description of this context definition.');
+
+ $form['items'] = array('#tree' => true);
+
+ // We need to initialize theme in order to deal with blocks
+ // and also let themes integrate against context_ui
+ init_theme();
+ $theme_key = variable_get('theme_default', 'garland');
+
+ // Generate settings for context item associations
+ foreach ((context_conditions(TRUE) + context_reactions(TRUE)) as $id => $info) {
+ $form['items'][$id] = $info;
+
+ // Disable element
+ if (isset($context->system) && $context->system) {
+ $form['items'][$id]['#disabled'] = true;
+ }
+
+ // Default values
+ if (isset($context->{$id})) {
+ $form['items'][$id] = context_ui_default_values($form['items'][$id], $context->{$id});
+ }
+ }
+
+ $modules = array();
+ $query = db_query("SELECT name, info FROM {system} WHERE type = '%s'", 'module');
+ while ($result = db_fetch_object($query)) {
+ $info = unserialize($result->info);
+ $modules[$result->name] = $info['name'];
+ }
+
+ // Control block visibility
+ $block_options =
+ $block_defaults = array();
+ $blocks = _context_ui_get_blocks();
+ $regions = system_region_list($theme_key);
+ // $blocks in [0] have not been assigned a region
+ foreach ($blocks[0] as $block) {
+ $block_options[$block->module][$block->bid] = $block->label;
+ }
+ ksort($block_options);
+
+ $form['block'] = array(
+ '#tree' => true,
+ );
+
+ $form['block']['help'] = array(
+ '#type' => 'markup',
+ '#value' => t('Control block visibility using context. Selected blocks will be shown when this context is set provided that custom block visibility settings and/or throttling do not hide them. Grayed out blocks are those provided by Drupal\'s standard block settings. These settings apply to the current theme and any enabled themes with regions in common.'),
+ );
+
+ $form['block']['selector'] = array(
+ '#type' => 'item',
+ '#tree' => true,
+ '#prefix' => '<div class="context-ui-block-selector">',
+ '#suffix' => '</div>',
+ );
+ foreach ($block_options as $module => $module_blocks) {
+ if (!empty($module_blocks)) {
+ $form['block']['selector'][$module] = array(
+ '#type' => 'checkboxes',
+ '#title' => $modules[$module],
+ '#options' => $module_blocks,
+ '#disabled' => isset($context->system) && $context->system ? true : false,
+ );
+ }
+ }
+
+ $form['block']['blocks'] = array(
+ '#tree' => true,
+ '#theme' => 'context_ui_block_ui',
+ );
+ foreach ($regions as $region => $label) {
+ $defaults = array();
+ $midpoint = false;
+ $form['block']['blocks'][$region] = array(
+ '#type' => 'item',
+ '#title' => $label,
+ '#tree' => TRUE,
+ );
+
+ $system = _context_ui_get_blocks($region);
+ if ($system) {
+ $system_blocks = array();
+ foreach ($system as $block) {
+ $system_blocks[] = $block->label;
+ }
+ $system_blocks = "<span class='system-blocks'>". implode(", ", $system_blocks) ."</span";
+ $form['block']['blocks'][$region]['system'] = array(
+ '#type' => 'markup',
+ '#tree' => true,
+ '#value' => $system_blocks,
+ '#weight' => 0,
+ 'weight' => array(
+ '#type' => 'weight',
+ '#delta' => 20,
+ '#default_value' => 0,
+ ),
+ );
+ }
+
+ $i = 0;
+ foreach (_context_ui_get_blocks($region, $context) as $block) {
+ if ($block->type == 'context_ui') {
+ $form['block']['blocks'][$region][$block->bid] = array(
+ '#type' => 'markup',
+ '#tree' => true,
+ '#value' => $block->label,
+ '#weight' => $block->weight,
+ 'weight' => array(
+ '#type' => 'weight',
+ '#delta' => 20,
+ '#default_value' => $block->weight,
+ ),
+ );
+ }
+ $i++;
+ }
+
+ uasort($form['block']['blocks'][$region], 'element_sort');
+
+ $defaults = implode(',', element_children($form['block']['blocks'][$region]));
+ $form['block']['regions'][$region] = array(
+ '#type' => 'hidden',
+ '#default_value' => $defaults,
+ );
+ }
+
+ if (!empty($context->system)) {
+ $form['back'] = array(
+ '#type' => 'item',
+ '#value' => l(t('Back'), 'admin/build/context'),
+ );
+ }
+ else {
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Save'),
+ );
+ }
+
+ if (empty($context->system) && $op == 'edit') {
+ $form['delete'] = array(
+ '#type' => 'submit',
+ '#value' => t('Delete'),
+ );
+ }
+
+ return $form;
+}
+
+/**
+ * Generates an abbreviated list of items for display in the
+ * setter/getter UI.
+ */
+function context_ui_item_display($type, $element) {
+ // We're dealing with an item with options --
+ // try to grab the display-friendly value
+ $items = array();
+ $title = l($element['#title'], $_GET['q'], array('fragment' => $type));
+ if (isset($element['#options'])) {
+ if (isset($element['#default_value'])) {
+ if (is_array($element['#default_value'])) {
+ foreach ($element['#default_value'] as $k) {
+ $items[] = $element['#options'][$k];
+ }
+ }
+ else if (is_string($element['#default_value']) && $k = $element['#default_value']) {
+ if (!empty($element['#options'][$k])) {
+ $items[] = $element['#options'][$k];
+ }
+ else {
+ // Fallback to the actual value
+ $items[] = $k;
+ }
+ }
+ }
+ if (empty($items)) {
+ $items[] = array('data' => '', 'class' => 'empty');
+ }
+ }
+ else if (isset($element['#type']) && in_array($element['#type'], array('textfield', 'textarea'))) {
+ $items[] = !empty($element['#default_value']) ? $element['#default_value'] : array('data' => '', 'class' => 'empty');
+ }
+ $output = '';
+ $output .= theme('item_list', $items, $title, 'ul', array('id' => 'display-'. $type));
+ return $output;
+}
+
+/**
+ * Theme function for context_ui_form()
+ */
+function theme_context_ui_form($form) {
+ drupal_add_css(drupal_get_path("module", "context_ui") ."/context_ui.css");
+ drupal_add_js(drupal_get_path("module", "context_ui") ."/context_ui.js");
+
+ $output = '';
+
+ // Render space / key / value trio in a 3-column table
+ $rows = array(
+ 'trio' => array('class' => 'trio'),
+ 'description' => array()
+ );
+ $rows['trio']['data'][] = array('data' => drupal_render($form['namespace']), 'class' => 'namespace');
+ $rows['trio']['data'][] = array('data' => drupal_render($form['attribute']), 'class' => 'attribute');
+ $rows['trio']['data'][] = array('data' => drupal_render($form['value']), 'class' => 'value');
+ $rows['description'] = array(array('data' => drupal_render($form['description']), 'colspan' => 3));
+ $output .= theme('table', array(), $rows, array('id' => 'context-ui-trio'));
+
+
+ // Render setters / getters as a two column split
+ $widgets = '';
+ $display = "<div class='label'>";
+ $display .= t('Conditions');
+ $display .= "<span class='description'>". t('trigger the activation of this context') ."</span>";
+ $display .= "</div>";
+ foreach (array_keys(context_conditions()) as $id) {
+ $widgets .= "<div id='widget-$id' class='widget'>". drupal_render($form['items'][$id]) ."</div>";
+ $display .= context_ui_item_display($id, $form['items'][$id]);
+ }
+
+ $display .= "<div class='label'>";
+ $display .= t('Reactions');
+ $display .= "<span class='description'>". t('respond when this context is active') ."</span>";
+ $display .= "</div>";
+ foreach (array_keys(context_reactions()) as $id) {
+ $widgets .= "<div id='widget-$id' class='widget'>". drupal_render($form['items'][$id]) ."</div>";
+ $display .= context_ui_item_display($id, $form['items'][$id]);
+ }
+ $rows = array(
+ array(
+ array('data' => $display, 'class' => 'display'),
+ array('data' => $widgets, 'class' => 'widget'),
+ ),
+ );
+ $output .= theme('table', array(), $rows, array('id' => 'context-ui-items'));
+
+
+ // Render block visibility
+ $rows = array(
+ array(
+ array('data' => drupal_render($form['block']['blocks']), 'class' => 'display'),
+ array('data' => drupal_render($form['block']['selector']) . drupal_render($form['block']['help']), 'class' => 'widget'),
+ ),
+ );
+ $output .= theme('table', array(), $rows, array('id' => 'context-ui-blocks'));
+
+ $output .= drupal_render($form);
+ return $output;
+}
+
+/**
+ * Provide a form to confirm one of the provided actions.
+ */
+function context_ui_confirm(&$form_state, $op = 'delete', $context) {
+ $form = array();
+ $form['context'] = array('#type' => 'value', '#value' => $context);
+ $form['action'] = array('#type' => 'value', '#value' => $op);
+ switch ($op) {
+ case 'delete':
+ $action = t('delete');
+ $message = t('This action will remove this context permanently from your site.');
+ break;
+ case 'disable':
+ $action = t('disable');
+ $message = '';
+ break;
+ case 'enable':
+ $action = t('enable');
+ $message = '';
+ break;
+ }
+ $form = confirm_form($form,
+ t('Are you sure you want to !action the context %title?', array('%title' => $context->value, '!action' => $action)),
+ 'admin/build/context',
+ $message,
+ drupal_ucfirst($action), t('Cancel')
+ );
+ return $form;
+}
+
+/**
+ * Submit handler for the context_ui_confirm form.
+ */
+function context_ui_confirm_submit($form, &$form_state) {
+ switch ($form_state['values']['action']) {
+ case 'delete':
+ context_delete_context($form_state['values']['context']);
+ break;
+ case 'disable':
+ case 'enable':
+ $context = $form_state['values']['context'];
+ if ($context) {
+ $status = variable_get('context_status', array());
+ $status["{$context->namespace}-{$context->attribute}-{$context->value}"] = ($form_state['values']['action'] == 'disable' ? CONTEXT_STATUS_DISABLED : CONTEXT_STATUS_ENABLED);
+ variable_set('context_status', $status);
+ context_invalidate_cache();
+ }
+ break;
+ }
+ $form_state['redirect'] = 'admin/build/context';
+}
+
+/**
+ * Page callback for import form. Switches form output to context form
+ * if import submission has occurred.
+ */
+function context_ui_import_page() {
+ if (!empty($_POST) && $_POST['form_id'] == 'context_ui_form') {
+ return drupal_get_form('context_ui_form', 'add');
+ }
+ return drupal_get_form('context_ui_import');
+}
+
+/**
+ * Import form. Provides simple helptext instructions and textarea for
+ * pasting a context definition.
+ */
+function context_ui_import() {
+ drupal_set_title(t('Import context'));
+ $help = t('You can import a context definition by pasting the exported context object code into the field below.');
+ $form = array();
+ $form['help'] = array(
+ '#type' => 'item',
+ '#value' => $help,
+ );
+ $form['import'] = array(
+ '#title' => t('Context Object'),
+ '#type' => 'textarea',
+ '#rows' => 10,
+ '#required' => true,
+ );
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Import'),
+ );
+
+ return $form;
+}
+
+/**
+ * Import form submit handler. Evaluates import code and transfers to
+ * context definition form.
+ */
+function context_ui_import_submit($form, &$form_state) {
+ $items = array();
+ if ($import = $form_state['values']['import']) {
+ if (strpos($import, 'return') !== 0) {
+ $import = "return {$import}";
+ }
+ ob_start();
+ $context = eval($import);
+ ob_end_clean();
+ }
+ if (is_array($context)) {
+ $context = (object) $context;
+ if ($exists = context_load_context($context)) {
+ drupal_set_message(t('A user-defined context definition with this space/key/value identifier already exists. Please remove the existing context before importing this definition.'), 'error');
+ $form_state['redirect'] = 'admin/build/context';
+ }
+ else {
+ drupal_set_title(t('Add context'));
+ $output = drupal_get_form('context_ui_form', 'add', (object) $context);
+ print theme('page', $output);
+ exit;
+ }
+ }
+ else {
+ drupal_set_message(t('An error occurred while importing. Please check your context definition.', 'error'));
+ $form_state['redirect'] = 'admin/build/context';
+ }
+}
+
+/**
+ * Provides a form with an exported context definition for use in modules.
+ *
+ * @param $cid
+ * A context id.
+ *
+ * @return
+ * A FormAPI array.
+ */
+function context_ui_export(&$form_state, $context) {
+ drupal_set_title(t('Export %title', array('%title' => $context->value)));
+
+ // prune system specific information and cast for Drupal's AOP (array oriented programming)
+ $prune = array('cid', 'status', 'system', 'type');
+ foreach ($prune as $key) {
+ if (isset($context->{$key})) {
+ unset($context->{$key});
+ }
+ }
+ $context = (array) $context;
+
+ // clear out empty associations
+ foreach ($context as $key => $item) {
+ if (context_empty($item)) {
+ unset($context[$key]);
+ }
+ }
+
+ // clean up blocks
+ if (!empty($context['block'])) {
+ foreach ($context['block'] as $bid => $block) {
+ unset($block->bid);
+ $context['block'][$bid] = (array) $block;
+ }
+ }
+
+ // build the form
+ $form = array();
+ $form['export'] = array(
+ '#type' => 'textarea',
+ '#rows' => 24,
+ '#value' => $context,
+ '#theme' => 'context_ui_export_form',
+ );
+ return $form;
+}
+
+/**
+ * Themes a context value into an export friendly var_export().
+ */
+function theme_context_ui_export_form($form) {
+ $form['#value'] = $form['#default_value'] = context_var_export($form['#value'], '', FALSE);
+ return theme('textarea', $form);
+}
+
+/**
+ * Export multiple contexts
+ */
+function context_ui_bulk_export(&$form_state) {
+ $form = array();
+ $contexts = context_contexts();
+ if (isset($form_state['storage']['module_name'])) {
+ $code = "function ". $form_state['storage']['module_name'] ."_context_ui_define() {\n";
+ foreach ($form_state['storage']['contexts'] as $id) {
+ $context = (array) $contexts[$id];
+ unset($context['cid']);
+ $code .= ' $items[] = '. context_var_export($context, ' ') .";\n\n";
+ }
+ $code .= " return \$items;\n}";
+ $form['code'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Code'),
+ '#description' => t('Put this in your module\'s .module file.'),
+ '#cols' => 60,
+ '#rows' => 30,
+ '#value' => $code,
+ '#default_value' => $code,
+ );
+ }
+ else {
+ $form['#contexts'] = $contexts;
+ $form['checkboxes']['#tree'] = TRUE;
+
+ foreach ($contexts as $context) {
+ $form['checkboxes']["$context->namespace-$context->attribute-$context->value"] = array(
+ '#title' => $context->value,
+ '#type' => 'checkbox',
+ );
+ }
+
+ $form['module_name'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Module Name'),
+ '#description' => t('The name of the module for which to generate the hook.'),
+ '#size' => 40,
+ '#maxlength' => 255,
+ );
+
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Export'),
+ );
+
+ $form['#theme'] = 'context_ui_bulk_export_table';
+ }
+
+ return $form;
+}
+
+function context_ui_bulk_export_submit($form, &$form_state) {
+ $form_state['storage']['module_name'] = $form_state['values']['module_name'];
+ foreach ($form_state['values']['checkboxes'] as $key => $value) {
+ if ($value) {
+ $form_state['storage']['contexts'][] = $key;
+ }
+ }
+}
+
+function theme_context_ui_bulk_export_table($form) {
+ // Add css
+ drupal_add_css(drupal_get_path("module", "context_ui") ."/context_ui.css");
+
+ $rows = $headings = array();
+ foreach (element_children($form['checkboxes']) as $key) {
+ $context = $form['#contexts'][$key];
+ $row = array();
+
+ $namespace = $context->namespace;
+ $attribute = $context->attribute;
+ $value = $context->value;
+ if (isset($context->cid) && $context->cid) {
+ $identifier = $context->cid;
+ }
+ else {
+ $identifier = $key;
+ }
+
+ // If no heading has been printed for this n/a pair, do so
+ if (!isset($rows["$namespace-$attribute"])) {
+ $row = array('', array('data' => "<span class='context-namespace'>$namespace &gt; $attribute</span>", 'colspan' => 2));
+ $rows["$namespace-$attribute"] = $row;
+ }
+ unset($form['checkboxes'][$key]['#title']);
+
+ $rows[$key] = array(
+ 'data' => array(
+ array(
+ 'data' => drupal_render($form['checkboxes'][$key]),
+ 'class' => 'context-ui-checkbox',
+ ),
+ array(
+ 'data' => '<strong>'. $value .'</strong>',
+ 'class' => 'context-name',
+ ),
+ ),
+ 'class' => 'context-table-row ' . $class,
+ );
+ }
+ $output = theme('table', array(theme('table_select_header_cell'), t('Context')), $rows, array('class' => 'context-ui-bulk-export context-ui-overview'));
+ $output .= drupal_render($form);
+
+ return $output;
+}
+
+/**
+ * Generates the main context_ui admin page with a tiered context listing.
+ */
+function theme_context_ui_admin($contexts) {
+ $rows = $headings = array();
+ foreach ($contexts as $key => $context) {
+ $row = array();
+
+ $namespace = $context->namespace;
+ $attribute = $context->attribute;
+ $value = $context->value;
+ if (isset($context->cid) && $context->cid) {
+ $identifier = $context->cid;
+ }
+ else {
+ $identifier = $key;
+ }
+
+ // If no heading has been printed for this n/a pair, do so
+ if (!isset($rows["$namespace-$attribute"])) {
+ $row = array(array('data' => "<span class='context-namespace'>$namespace &gt; $attribute</span>", 'colspan' => 2));
+ $rows["$namespace-$attribute"] = $row;
+ }
+
+ // Add row for context
+ $links = array();
+ $icon = theme('advanced_help_topic', 'context_ui', 'type');
+ switch ($context->type) {
+ case CONTEXT_STORAGE_DEFAULT:
+ $type = 'Default';
+ $links[0] = l(t('Override'), "admin/build/context/$identifier/clone");
+ $links[2] = l(t('Export'), "admin/build/context/$identifier/export");
+ $links[3] = l(t('Clone'), "admin/build/context/$identifier/clone");
+ break;
+ case CONTEXT_STORAGE_OVERRIDDEN:
+ $type = 'Overridden';
+ $links[0] = l(t('Edit'), "admin/build/context/$identifier");
+ $links[2] = l(t('Export'), "admin/build/context/$identifier/export");
+ $links[3] = l(t('Clone'), "admin/build/context/$identifier/clone");
+ $links[4] = l(t('Revert'), "admin/build/context/$identifier/delete");
+ break;
+ case CONTEXT_STORAGE_NORMAL:
+ $type = 'Normal';
+ $links[0] = l(t('Edit'), "admin/build/context/$identifier");
+ $links[2] = l(t('Export'), "admin/build/context/$identifier/export");
+ $links[3] = l(t('Clone'), "admin/build/context/$identifier/clone");
+ $links[4] = l(t('Delete'), "admin/build/context/$identifier/delete");
+ }
+ switch ($context->status) {
+ case CONTEXT_STATUS_DISABLED:
+ $class = 'disabled';
+ $enable = l(t('Enable'), "admin/build/context/$identifier/enable");
+ break;
+ case CONTEXT_STATUS_ENABLED:
+ $class = 'enabled';
+ $enable = l(t('Disable'), "admin/build/context/$identifier/disable");
+ }
+ $links[1] = $enable;
+ if ($icon) {
+ // These spans are used to work-around advanced help's default styling of
+ // floating the help icon left. We would like to display it after the text,
+ // so we put a series of floats next to each other.
+ $data = "<strong>$value</strong> <em><span>($type</span> $icon<span>)</span></em>";
+ }
+ else {
+ $data = "<strong>$value</strong> <em>($type)</em>";
+ }
+ $description = !empty($context->description) ? '<div class="description">'. filter_xss_admin($context->description) .'</div>' : '';
+
+ ksort($links);
+ $rows[$key] = array(
+ 'data' => array(
+ array(
+ 'data' => $data . $description,
+ 'class' => 'context-name '. ($icon? 'icon' : 'no-icon'),
+ ),
+ implode(' | ', $links),
+ ),
+ 'class' => 'context-table-row ' . $class,
+ );
+ }
+ return theme('table', array(t('Context'), t('Operations')), $rows, array('class' => 'context-ui-overview'));
+}
+
+/**
+ * Generates the AJAX enabled block administration portion of the context_ui admin form.
+ */
+function theme_context_ui_block_ui($form) {
+ // Add draggable weights
+ drupal_add_js('misc/tableheader.js');
+
+ $output = '';
+
+ foreach (element_children($form) as $region) {
+ $table_id = 'context-ui-region-'. str_replace('_', '-', $region);
+ drupal_add_tabledrag($table_id, 'order', 'sibling', 'block-weight', NULL, NULL, FALSE);
+ $rows = array();
+ foreach (element_children($form[$region]) as $id) {
+ $form[$region][$id]['weight']['#attributes'] = array('class' => 'block-weight');
+ $label = $form[$region][$id]['#value'];
+ if ($id == 'system') {
+ $remove = '';
+ }
+ else {
+ $remove = l(t('X'), $_GET['q'], array('fragment' => 'remove', 'attributes' => array('class' => 'remove')));
+ }
+ $rows[] = array(
+ 'data' => array($label . drupal_render($form[$region][$id]['weight']), $remove),
+ 'class' => 'draggable',
+ 'id' => $id,
+ );
+ }
+ $output .= "<div class='label'>";
+ $output .= l(t('+ Add'), $_GET['q'], array('fragment' => $region, 'attributes' => array('class' => 'add-block')));
+ $output .= $form[$region]['#title'];
+ $output .= "</div>";
+ $output .= theme('table', array(), $rows, array('id' => $table_id));
+ }
+ return $output;
+}
+
+/**
+ * hook_validate()
+ */
+function context_ui_form_validate($form, &$form_state) {
+ if ($form_state['clicked_button']['#id'] == 'edit-submit' && $form_state['values']['value']) {
+ // Check for string identifier sanity
+ foreach (array('value', 'attribute', 'namespace') as $elem) {
+ if (!preg_match('!^[a-z0-9_]+$!', $form_state['values'][$elem])) {
+ form_set_error($elem, t('The context !elem can only consist of lowercase letters, underscores, and numbers.', array('!elem' => $elem)));
+ }
+ }
+ if (!isset($form_state['values']['cid'])) {
+ // Check that no other user-defined context definition has taken this identifier already
+ $context = new StdClass();
+ $context->namespace = $form_state['values']['namespace'];
+ $context->attribute = $form_state['values']['attribute'];
+ $context->value = $form_state['values']['value'];
+ if ($exists = context_load_context($context)) {
+ form_set_error($form_state['values']['value'], t('A user-defined context with this space/key/value identifier already exists. Please delete the existing definition before creating a new one.'));
+ }
+ }
+ }
+}
+
+/**
+ * Produces a context object from submitted form values.
+ *
+ * @param $form
+ * A form array with submitted values
+ *
+ * @return
+ * A context object
+ */
+function context_ui_form_process($form) {
+ $context = new stdClass();
+
+ // Context ns/attr/value definition
+ foreach (array('cid', 'system', 'namespace', 'attribute', 'value', 'description') as $item) {
+ $context->$item = isset($form[$item]) ? $form[$item] : NULL;
+ }
+ $context->status = 1; // all user defined contexts have status 1
+
+ // Retrieve values for conditions
+ foreach (array_keys(context_conditions()) as $id) {
+ if (is_array($form['items'][$id])) {
+ foreach ($form['items'][$id] as $option => $value) {
+ if (!empty($value)) {
+ $context->{$id}[] = $option;
+ }
+ }
+ }
+ else if (isset($form['items'][$id]) && !empty($form['items'][$id])) {
+ $context->{$id}[] = $form['items'][$id];
+ }
+ }
+
+ // Retrieve values for reactions -- preserve key => val associations
+ foreach (array_keys(context_reactions()) as $id) {
+ if (isset($form['items'][$id]) && !context_empty($form['items'][$id])) {
+ $context->{$id} = $form['items'][$id];
+ }
+ }
+
+ // Blocks must be done by region
+ $theme_key = variable_get('theme_default', 'garland');
+ // Get list of "valid" available blocks
+ $valid = _context_ui_get_blocks();
+ $valid = $valid[0];
+ foreach (system_region_list($theme_key) as $region => $label) {
+ if ($blocks = $form['block']['regions'][$region]) {
+ $blocks = explode(',', $blocks);
+ $midpoint = array_search('system', $blocks);
+ foreach ($blocks as $position => $bid) {
+ // Don't initialize the block array until we're actually sure
+ // the context contains blocks.
+ if (!isset($context->block)) {
+ $context->block = array();
+ }
+ if ($bid != 'system') {
+ $block = $valid[$bid];
+ $modifier = $position < $midpoint ? -20 : 20;
+ $block->weight = $position - $midpoint + $modifier;
+ $block->region = $region;
+ $block->type = 'context_ui';
+ $context->block[$block->bid] = $block;
+ }
+ }
+ }
+ }
+
+ return $context;
+}
+
+/**
+ * Submit handler for main context_ui form.
+ */
+function context_ui_form_submit($form, &$form_state) {
+ switch ($form_state['clicked_button']['#id']) {
+ // Send user to delete confirmation page
+ case 'edit-delete':
+ $form_state['redirect'] = 'admin/build/context/'. $form_state['values']['cid'] .'/delete';
+ return;
+ // Process form values and save and/or update the context in the db
+ case 'edit-submit':
+ $context = context_ui_form_process($form_state['values']);
+ $result = context_save_context($context);
+ if ($result) {
+ drupal_set_message(t('The context %title was saved successfully.', array('%title' => $context->value)));
+ }
+ else {
+ drupal_set_message(t('An error occurred while attempting to save your context information.'), 'error');
+ }
+ break;
+ }
+ $form_state['redirect'] = 'admin/build/context';
+}
+
+/**
+ * Helper function to generate a list of blocks from a specified region. If provided a context object,
+ * will generate a full list of blocks for that region distinguishing between system blocks and
+ * context-provided blocks.
+ *
+ * @param $region
+ * The string identifier for a theme region. e.g. "left"
+ * @param $context
+ * A context object.
+ *
+ * @return
+ * A keyed (by "module_delta" convention) array of blocks.
+ */
+function _context_ui_get_blocks($region = null, $context = null) {
+ $theme_key = variable_get('theme_default', 'garland');
+ static $block_info, $valid, $system_blocks;
+ // we don't static cache context blocks
+ $context_blocks = $blocks = array();
+
+ if (!$system_blocks) {
+ // initialize regions
+ foreach (system_region_list($theme_key) as $r => $l) {
+ $system_blocks[$r] = array();
+ }
+ // load blocks from database
+ $result = db_query("SELECT module, delta, weight, region, status FROM {blocks} WHERE theme = '%s' ORDER BY weight, module, delta", $theme_key);
+ while ($block = db_fetch_object($result)) {
+ // load block info
+ $block_info[$block->module] = isset($block_info[$block->module]) ? $block_info[$block->module] : module_invoke($block->module, 'block', 'list');
+ $block->label = $block_info[$block->module][$block->delta]['info'];
+ $block->type = 'system';
+ $block->bid = $block->module .'_'. $block->delta;
+ // add block to region
+ if ($block->region && $block->status) {
+ $system_blocks[$block->region][$block->bid] = $block;
+ }
+ else {
+ $system_blocks[0][$block->bid] = $block;
+ }
+ // mark block as available in DB
+ $valid[$block->module ."_". $block->delta] = true;
+ }
+ }
+
+ // load system blocks into main block array
+ $blocks = $system_blocks;
+
+ // load context blocks if provided
+ if (is_object($context) && !empty($context->block)) {
+ // iterate over context-associated blocks
+ foreach ($context->block as $block) {
+ $block = (object) $block;
+ // check that this is a valid block
+ if ($valid[$block->module ."_". $block->delta]) {
+ // if region has been specified, ensure that block belongs to it
+ if (!$region || (isset($region) && $block->region == $region)) {
+ // load block info
+ $block_info[$block->module] = $block_info[$block->module] ? $block_info[$block->module] : module_invoke($block->module, 'block', 'list');
+ $block->label = $block_info[$block->module][$block->delta]['info'];
+ $block->type = 'context_ui';
+ $block->bid = $block->module .'_'. $block->delta;
+ // add block to region
+ if ($block->region) {
+ $blocks[$block->region][$block->bid] = $block;
+ }
+ else {
+ $blocks[0][$block->bid] = $block;
+ }
+ }
+ }
+ }
+ }
+
+ foreach ($blocks as $r => $sort_region) {
+ if ($r !== 0) {
+ uasort($sort_region, create_function('$a, $b', 'return ($a->weight - $b->weight);'));
+ $blocks[$r] = $sort_region;
+ }
+ }
+
+ return $region ? $blocks[$region] : $blocks;
+}