$info) { $items['spaces_'. $type] = array( 'name' => $info['title'], 'description' => t('Sets a spaces context.'), 'callback' => 'spaces_init_context', 'callback arguments' => array($type), 'example' => 'my-space', ); } return $items; } /** * Context prefix provider callback. */ function spaces_init_context($type, $sid) { static $once; if (!isset($once)) { $once = FALSE; } if (!$once) { // @TODO: remove the hardcoded space type here and attempt to // discover through the context prefix param : ( context_set('spaces', 'sid', $sid); $space = spaces_load($type, $sid, TRUE); spaces_set_space($space); $once = TRUE; } } /** * Implementation of hook_menu(). */ function spaces_menu() { $items = array(); $items['admin/build/spaces'] = array( 'title' => t('Spaces presets'), 'description' => t('Create and configure spaces for your site.'), 'page callback' => 'drupal_get_form', 'page arguments' => array('spaces_preset_default_form'), 'access callback' => 'user_access', 'access arguments' => array('administer spaces'), 'file' => 'spaces_admin.inc', 'type' => MENU_NORMAL_ITEM, ); $items['admin/build/spaces/presets'] = array( 'title' => t('Presets'), 'page callback' => 'drupal_get_form', 'page arguments' => array('spaces_preset_default_form'), 'access callback' => 'user_access', 'access arguments' => array('administer spaces'), 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -1, ); $items['admin/build/spaces/presets/add'] = array( 'title' => t('Add'), 'page callback' => 'drupal_get_form', 'page arguments' => array('spaces_preset_form', 'add'), 'access callback' => 'user_access', 'access arguments' => array('administer spaces'), 'type' => MENU_CALLBACK, ); $items['admin/build/spaces/presets/edit'] = array( 'page callback' => 'drupal_get_form', 'page arguments' => array('spaces_preset_form', 'edit'), 'access callback' => 'user_access', 'access arguments' => array('administer spaces'), 'type' => MENU_CALLBACK, ); $items['admin/build/spaces/presets/export'] = array( 'title' => t('Export preset'), 'page callback' => 'drupal_get_form', 'page arguments' => array('spaces_preset_export'), 'access callback' => 'user_access', 'access arguments' => array('administer spaces'), 'type' => MENU_CALLBACK, ); $items['admin/build/spaces/presets/delete'] = array( 'page callback' => 'drupal_get_form', 'page arguments' => array('spaces_preset_delete_form', 5, 6), 'access callback' => 'user_access', 'access arguments' => array('administer spaces'), 'type' => MENU_CALLBACK, ); $items['admin/build/spaces/presets/disable'] = array( 'page callback' => '_spaces_preset_disable_page', 'access callback' => 'user_access', 'access arguments' => array('administer spaces'), 'type' => PAGE_CALLBACK, ); $items['admin/build/spaces/presets/enable'] = array( 'page callback' => '_spaces_preset_enable_page', 'access callback' => 'user_access', 'access arguments' => array('administer spaces'), 'type' => PAGE_CALLBACK, ); return $items; } /** * Implementation of hook_help(). */ function spaces_help($path, $arg) { switch ($path) { case 'admin/build/spaces/presets/export': return "
". t('You can use exported presets in your modules by returning an array of presets in hook_spaces_presets()
.') ."
'. t('The spaces menu is meant to be customized on a per-space basis - you can customize it on the features page of any space.') .'
', ); } } /** * Implementation of hook_block() */ function spaces_block($op = 'list', $delta = 0) { if ($op == 'list') { $blocks[1]['info'] = t('Spaces: Contextual Tools'); $blocks[2]['info'] = t('Spaces: Navigation'); $blocks[3]['info'] = t('Spaces: Utility links'); return $blocks; } else if ($op == 'view') { switch ($delta) { case 1: return _spaces_block_tools(); case 2: return _spaces_block_nav(); case 3: return _spaces_block_utility_links(); } } } /** * Implementation of hook_context_define() * * hook_context_define provides a central method to define contextual behavior. The spaces * module extends this hook in the "spaces" key namespace. Available attributes are: * 'label', 'description', 'options', 'options_function', '#weight' * * @return * Keyed array which defines features. */ function spaces_context_define() { $items = array(); return $items; } /** * Implementation of hook_spaces_settings(). */ function spaces_spaces_settings() { return array( 'home' => new space_setting_home(), ); } /** * Implementation of hook_spaces_customizers(). */ function spaces_spaces_customizers() { return array( 'menu' => new space_customizer_menu(), 'views' => new space_customizer_views(), ); } /** * Implementation of hook_views_api(). */ function spaces_views_api() { return array( 'api' => 2, 'path' => drupal_get_path('module', 'spaces') .'/includes', ); } /** * Implementation of hook_views_pre_query(). */ function spaces_views_pre_query(&$view) { // Make spaces filter'd views non-cacheable foreach ($view->filter as $filter) { if ($filter['field'] == 'spaces.type') { $view->is_cacheable = false; } } if ($view->build_type == 'page' && $space = spaces_get_space()) { if ($feature = context_get('spaces', 'feature')) { $view = space_customizer_views::customize($space, $feature, $view); } } } /** * SPACES API ========================================================= */ /** * Interface for space objects. */ interface space { /** * Core API-related functions */ function __construct($type, $sid = NULL, $is_active = FALSE); function save(); function delete(); /** * Method that defines the feature options available for this space * type. * * @return array * A FormAPI suitable options array of feature options. */ function feature_options(); /** * Method that allows the space type to add to/modify the utility * "links" provided to visitors in a space. * * @param $links * The current space utility links (for use with theme_links) array * passed by reference. */ function links(&$links); /** * Method that provides a space-type specific check for whether the * provided feature is accessible by the current user. * * @param $feature * The feature identifier string. * * @return bool * TRUE if the user has access to this feature. FALSE if not. */ function feature_access($feature = NULL); /** * Method that provides a space-type specific check for whether the * the space can be administered by the current user. * * @return bool * TRUE if the user has admin access to this space. FALSE if not. */ function admin_access(); /** * Master router method that allows the space type to define routing * workflow rules. Currently called at the following hooks: * * hook_menu(), where $may_cache == FALSE * hook_nodeapi(), where $op == 'view' * hook_form_alter(), when editing a node * hook_user(), where $op == 'view' * * @param $op * The current hook from which the router is being called. * Can be one of the following: 'menu', 'node view', 'node form', * 'user view' * @param $object * The object relevant to the current $op called. e.g. when * $op == 'node view', $object is the node object. * @param $is_active * Boolean for whether this router has been called as part of a * fully instantiated space object. If FALSE, the router should * not assume the space has been fully constructed and should take * the appropriate actions as necessary. * * @return bool * TRUE to allow the user to pass through. FALSE to return a * drupal_access_denied() page to the user. */ function router($op, $object = NULL, $is_active = TRUE); /** * Redirect handler that abstracts redirect logic and allows space * types to define their own definitions of various spaces concepts. * * @param $op * The page being redirected to. Currently only $op value is 'home' */ function redirect($op = 'home'); /** * Allows the space type to implement custom form options on the * feature/preset form. * * @return array * A FormAPI element array. It will be included as a child element * of the master space feature form. */ function form(); /** * Validate handler for the space type's custom form. * * @param $values * The submitted values. */ function validate($values); /** * Custom submit handler for the space type's custom form. For * example, allows the space type to process values in preparation * for spaces_save(). * * @param $values * The submitted values. * * @return array * An array of values. */ function submit($values); /** * Views filter callback that allows the space type to filter views * when it is the current active space. * * @param $query * The views query object. */ function views_filter($is_active, &$query); /** * Allows the space type to take additional action when enforcing a * preset against the current space. * * @param $preset * A spaces preset definition. */ function preset_enforce($preset); } /** * Interface for space settings. */ interface space_setting { function __construct($id = NULL); function form($space, $value = array()); function validate($space, $value); function submit($space, $value); } /** * Interface for space customizers. */ interface space_customizer { function form($space, $feature); function validate($space, $feature, $value); function submit($space, $feature, $value); function customize($space, $feature, $object = NULL); } /** * Provides a homepage setting for each space. */ class space_setting_home implements space_setting { var $id; function __construct($id = NULL) { if ($id) { $this->id = $id; } else { $this->id = 'home'; } } function form($space, $value = array()) { $options = array(0 => '---'); foreach (spaces_features($space->type) as $f => $feature) { if ($feature->spaces['menu'] && $space->features[$f] != SPACES_FEATURE_DISABLED) { $options[$f] = $feature->spaces['label']; } } $form = array( '#title' => t('Homepage'), '#description' => t('The default page for this space.'), '#type' => 'select', '#options' => $options, '#validate' => array('spaces_setting_validate' => array($this->id)), '#default_value' => $value ? $value : 0, ); return $form; } function validate($space, $value) { // Exclude space "prototypes" (like that used for the preset form) if ($space->sid) { if (!$value && is_array($space->features) && (array_sum($space->features) != 0)) { form_set_error('settings]['. $this->id, t('You must select a homepage for this space.')); } } } function submit($space, $value) { return $value; } } /** * Customizer for feature menus. */ class space_customizer_menu implements space_customizer { var $name = 'Menu'; function form($space, $feature) { $features = spaces_features(); $f = $features[$feature]; $form = array(); // Customize menus if (isset($f->spaces['menu']) && count($f->spaces['menu'])) { // Get customized values $feature_menu = $f->spaces['menu']; $feature_menu = $this->customize($space, $feature, $feature_menu); foreach ($feature_menu as $path => $item) { $form[$path] = array( '#title' => $path, '#type' => 'fieldset', '#tree' => TRUE, ); $form[$path]['title'] = array( '#title' => t('Title'), '#type' => 'textfield', '#size' => 40, '#maxlength' => 255, '#default_value' => $item['title'], ); } } return $form; } function validate($space, $feature, $value) { return; } function submit($space, $feature, $value) { $features = spaces_features(); $feature_menu = $features[$feature]->spaces['menu']; foreach ($value as $path => $item) { if ($item == $feature_menu[$path]) { unset($value[$path]); } } return $value; } function customize($space, $feature, $menu = NULL) { $customizer = array(); foreach ($space->customizer as $c) { $customizer = array_merge($customizer, $c['menu']); } foreach ($menu as $k => $item) { // @TODO: deprecate key-based paths and require 'href' value // for the menu item path. if (!isset($item['href'])) { $item['href'] = $k; } if (isset($customizer[$item['href']])) { $menu[$k]['title'] = $customizer[$item['href']]['title']; } } return $menu; } } /** * Customizer for views titles and headers. */ class space_customizer_views implements space_customizer { var $name = 'Views'; function form($space, $feature) { $features = spaces_features(); $f = $features[$feature]; $form = array(); if (isset($f->views) && is_array($f->views)) { foreach ($f->views as $view_name) { $view = views_get_view($view_name); $view = $this->customize($space, $feature, $view); // Only allow customization of page views for now // @TODO: add input format checking on headers/footers/etc. if ($view && $view->page == TRUE) { $form[$view_name] = array( '#title' => $view_name, '#type' => 'fieldset', '#tree' => TRUE, ); // $default_page_title = isset($customizer['views'][$view_name]['page_title']) ? $customizer['views'][$view_name]['page_title'] : $view->page_title; $form[$view_name]['page_title'] = array( '#title' => t('Page title'), '#type' => 'textfield', '#size' => 40, '#default_value' => $view->page_title, ); // $default_page_header = isset($customizer['views'][$view_name]['page_header']) ? $customizer['views'][$view_name]['page_header'] : $view->page_header; $form[$view_name]['page_header'] = array( '#title' => t('Page header'), '#type' => 'textarea', '#rows' => 2, '#cols' => 40, '#default_value' => $view->page_header, ); } } } return $form; } function validate($space, $feature, $value) { return; } function submit($space, $feature, $value) { // Would like to prune unset values, but it appears that views // caching makes it unusable... return $value; } function customize($space, $feature, $view = NULL) { if (isset($space->customizer[$feature]['views'][$view->name])) { // Apply customizations to the view object $customizer = $space->customizer[$feature]['views'][$view->name]; foreach ($customizer as $property => $value) { $view->{$property} = $value; } } return $view; } } /** * Load a space. * * @param $type * The type of the space to be loaded. Must be one of the keys in the * array returned by spaces_types(). * @param $sid * The id of the space to be loaded. If omitted, a "prototype" space * will be constructed. * @param $is_active * Optional boolean flag for whether this space is active or not. * Defaults to FALSE. * * @return * The requested space object or FALSE if something went wrong. */ function spaces_load($type, $sid = NULL, $is_active = FALSE) { $types = spaces_types(); if (isset($types[$type])) { $class = $types[$type]['class']; // Create a new space object $space = new $class($type, $sid, $is_active); // Initialize various space variables $space->type = $type; $space->features = array(); $space->settings = array(); $space->customizer = array(); // Initialize space specific settings if $sid is provided if ($sid) { $space->sid = $sid; if ($prefix = context_prefix_api('load', array('provider' => 'spaces_'. $type, 'id' => $sid))) { $space->prefix = $prefix['prefix']; } // Load features $result = db_query('SELECT id, value FROM {spaces_features} WHERE sid = %d AND type = "%s" ORDER BY weight ASC', $sid, $type); while ($row = db_fetch_object($result)) { $space->features[$row->id] = $row->value; } // Load settings $result = db_query('SELECT id, value FROM {spaces_settings} WHERE sid = %d AND type = "%s"', $sid, $type); while ($row = db_fetch_object($result)) { $space->settings[$row->id] = unserialize($row->value); } // Load customizer & preset $row = db_fetch_object(db_query("SELECT customizer, preset FROM {spaces} WHERE sid = %d AND type = '%s'", $space->sid, $space->type)); $space->customizer = $row->customizer ? unserialize($row->customizer) : array(); // Enforce preset or use default if not found $default_presets = variable_get('spaces_default_presets', array()); if ($row->preset) { $space->preset = $row->preset; } else if (empty($space->preset) && isset($default_presets[$type])) { $space->preset = $default_presets[$type]; } if ($space->preset) { spaces_preset_enforce($space); } } return $space; } return false; } /** * Saves a space object's feature/setting values. * * @param $space * The space object to save. * * @return * Returns TRUE on success, FALSE on failure. */ function spaces_save($space) { if ($space->sid) { // Enforce the preset spaces_preset_enforce($space); // Update features db_query("DELETE FROM {spaces_features} WHERE sid = %d AND type = '%s'", $space->sid, $space->type); $valid_features = spaces_features($space->type); $weight = -10; foreach ($space->features as $feature => $value) { if (isset($valid_features[$feature])) { $values = array($space->sid, $space->type, $feature, $value, $weight); db_query('INSERT INTO {spaces_features} (sid, type, id, value, weight) VALUES (%d, "%s", "%s", "%s", %d)', $values); $weight++; } } // Update settings db_query("DELETE FROM {spaces_settings} WHERE sid = %d AND type = '%s'", $space->sid, $space->type); // Build list of valid settings including those associated with features. $valid_settings = spaces_settings(); foreach (spaces_features() as $feature) { if (isset($feature->spaces['settings'])) { foreach ($feature->spaces['settings'] as $id => $setting) { $valid_settings[$id] = $setting; } } } foreach ($space->settings as $setting => $value) { if (isset($valid_settings[$setting])) { $value = serialize($value); $values = array($space->sid, $space->type, $setting, $value); db_query('INSERT INTO {spaces_settings} (sid, type, id, value) VALUES (%d, "%s", "%s", "%s")', $values); } } // Update preset & customizer $exists = db_result(db_query("SELECT count(sid) FROM {spaces} WHERE sid = %d AND type = '%s'", $space->sid, $space->type)); if ($exists) { db_query("UPDATE {spaces} SET preset = '%s', customizer = '%s' WHERE sid = %d AND type = '%s'", $space->preset, serialize($space->customizer), $space->sid, $space->type); } else { db_query("INSERT INTO {spaces} (sid, type, preset, customizer) VALUES(%d, '%s', '%s', '%s')", $space->sid, $space->type, $space->preset, serialize($space->customizer)); } // Save context prefix if space type allows prefix customization $types = spaces_types(); $save_prefix = isset($types[$space->type]['custom prefixes']) && $types[$space->type]['custom prefixes']; if ($space->prefix && $save_prefix) { // We need to concatenate the type/sid so that collisions between // space types do not occur. $prefix = array( 'provider' => 'spaces_'. $space->type, 'id' => $space->sid, ); context_prefix_api('delete', $prefix); $prefix['prefix'] = $space->prefix; context_prefix_api('insert', $prefix); } // Allow space type to do its own saving $space->save(); return true; } return false; } /** * Deletes a space object's records in the database. * * @param $space * The space object to delete. * * @return * Returns TRUE for now. */ function spaces_delete($space) { // Remove all features and settings db_query("DELETE FROM {spaces} WHERE sid = %d AND type = '%s'", $space->sid, $space->type); db_query("DELETE FROM {spaces_features} WHERE sid = %d AND type = '%s'", $space->sid, $space->type); db_query("DELETE FROM {spaces_settings} WHERE sid = %d AND type = '%s'", $space->sid, $space->type); // Clear the prefix path from the context_prefix table $prefix = array('provider' => 'spaces_'. $space->type, 'id' => $space->sid); context_prefix_api('delete', $prefix); // Allow space type to do its own deleting $space->delete(); return true; } /** * Enforces a spaces preset. */ function spaces_preset_enforce(&$space) { $presets = spaces_presets($space->type); if (isset($space->preset) && isset($presets[$space->preset])) { $preset = $presets[$space->preset]['preset']; // Enforce features if (is_array($preset['features'])) { foreach ($preset['features'] as $feature => $value) { if ($preset['locked']['features'][$feature] || !isset($space->features[$feature])) { $space->features[$feature] = $value; } } } // Enforce settings if (is_array($preset['settings'])) { foreach ($preset['settings'] as $setting => $value) { if ($preset['locked']['settings'][$setting] || !isset($space->settings[$setting])) { $space->settings[$setting] = $value; } } } // Type-specific presets $space->preset_enforce($preset); } } /** * Invokes hook_spaces_types() to gather an array of space types and * associated classes. * * Implementing modules should provide an associate array in * hook_spaces_types() of the following format: * * return array( * $id => array( * 'class' => $space_class, * 'title' => $space_type_name, * 'custom prefixes' => $bool, * ), * ); * * Where: * * $id: A unique string identifier for this space type. e.g. "og" * $space_class: The PHP class to construct for this space. e.g. "spaces_og" * $space_type_name: The human-readable name of your space type. e.g. "Group space" * $bool: TRUE or FALSE for whether spaces should provide a UI for * users to provide a custom prefix path for each space. If FALSE, * the implementing module should implement * hook_context_prefix_prefixes() in order to provide prefixes * programmatically. * * @param $reset * Optional reset flag for clearing the static cache. * * @return * An array of space types where $key => $value corresponds to the space type => space class. */ function spaces_types($reset = false) { static $spaces_types; if (!isset($spaces_types) || $reset) { $spaces_types = module_invoke_all('spaces_types'); } return $spaces_types; } /** * Gather an array of spaces presets from the DB. * * @param $type * Optional space type to filter results by. * @param $include_disabled * Optional flag to return all presets, including disabled ones. * @param $reset * Optional reset flag for clearing the static cache. * * @return * An array of space types where $key => $value corresponds to the space type => space class. */ function spaces_presets($type = NULL, $include_disabled = FALSE, $reset = FALSE) { static $presets; if (!isset($presets)) { $presets = array(); $result = db_query("SELECT * FROM {spaces_presets}"); $disabled = variable_get('spaces_disabled_presets', array()); while ($row = db_fetch_object($result)) { $presets[$row->type][$row->id] = array( 'name' => $row->name, 'description' => $row->description, 'preset' => unserialize($row->value), 'disabled' => isset($disabled[$row->type][$row->id]), ); } // Collect presets provided by modules in code foreach(module_implements('spaces_presets') as $module) { $items = call_user_func($module .'_spaces_presets'); foreach($items as $id => $preset) { $presets[$preset['type']][$id] = array( 'name' => $preset['name'], 'description' => $preset['description'], 'preset' => $preset['preset'], 'disabled' => isset($disabled[$preset['type']][$id]), 'module' => $module, ); } } } // Move filtering outside main condition in order to hit the DB only once $return = $presets; if (!$include_disabled) { foreach (array_keys($return) as $preset_type) { foreach ($return[$preset_type] as $id => $preset) { if ($preset['disabled']) { unset($return[$preset_type][$id]); } } } } if ($type) { return isset($return[$type]) ? $return[$type] : array(); } return $return; } /** * Wrapper function around spaces_set_space(). Retrieves the current * active space. */ function spaces_get_space() { return spaces_set_space(); } /** * Sets the specified space as the current active space. Returns the * active space if no space is provided. * * @param $space * The space object to set as the active space. Optional. * @param $reset * Optional flag to reset the static cache. * * @return * The active space object or FALSE if there is no active space. */ function spaces_set_space($space = NULL, $reset = FALSE) { static $current_space; if (!isset($current_space) || $reset) { $current_space = $space; } return $current_space ? $current_space : FALSE; } /** * Wrapper around implementations of $space->router. Provides * additional intelligence, including a global killswitch and routing * when no spaces are active. * * @param $op * The current "hook" or "hook op" identifier for the $space->router * to act on. * @param $object * Optional object to pass to the $space->router. */ function spaces_router($op, $object = NULL) { // Check global killswitch if (spaces_router_get()) { $access = true; $types = spaces_types(); // Run the router for the active space if ($space = spaces_get_space()) { $access = $access && $space->router($op, $object); unset($types[$space->type]); } // Run each non-active space type's router foreach ($types as $type => $info) { $access = $access && call_user_func(array($info['class'], 'router'), $op, $object, FALSE); } if (!$access && !user_access('administer spaces')) { drupal_access_denied(); exit; } } } /** * Wrapper around spaces_router_get(). */ function spaces_router_set($status) { return spaces_router_get($status, TRUE); } /** * Sets a static variable that is used to disable spaces routing * altogether -- e.g. for install/update scripts, migrations, etc. * * @param $enabled * Optional boolean for enabling/disabling spaces routing. * @param $reset * Optional boolean for resetting the static cache. * * @return * Returns a boolean for whether routing is enabled/disabled. */ function spaces_router_get($enabled = 1, $reset = FALSE) { static $status; if (!isset($status) || $reset) { $status = $enabled; } return $status; } /** * Retrieve all available features. * * @param $type * Optional type flag by which to filter available features. * @param $reset * Optional boolean flag for resetting the static cache. * * @return * Keyed array of potential features. */ function spaces_features($type = NULL, $reset = FALSE) { static $spaces_features; if (!isset($spaces_features) || $reset) { $spaces_features = array(); foreach (context_ui_defaults('spaces') as $feature) { if (!empty($feature->spaces)) { // If type is specified, perform additional checks if ($type) { if (isset($feature->spaces['types']) && in_array($type, $feature->spaces['types'])) { $spaces_features[$feature->value] = $feature; } else if (!isset($feature->spaces['types']) || !$feature->spaces['types']) { $spaces_features[$feature->value] = $feature; } } else { $spaces_features[$feature->value] = $feature; } } } } return $spaces_features; } /** * Retrieve all available settings. * * @param $reset * Optional boolean flag for resetting the static cache. * * @return * Keyed array of potential settings. */ function spaces_settings($reset = FALSE) { static $settings; if (!isset($settings) || $reset) { $settings = array(); foreach (module_implements('spaces_settings') as $module) { $function = $module .'_spaces_settings'; $settings = array_merge($settings, $function()); } } return $settings; } /** * Retrieve all available customizers. * * @param $reset * Optional boolean flag for resetting the static cache. * * @return * Keyed array of customizers. */ function spaces_customizers($reset = FALSE) { static $customizers; if (!isset($customizers) || $reset) { $customizers = array(); $customizers = module_invoke_all('spaces_customizers'); } return $customizers; } /** * Returns a content type => features map. * * @param $reset * Optional boolean flag for resetting the static cache. * * @return * Keyed array where $nodetype => $feature. */ function spaces_content_types($reset = FALSE) { static $map; if (!isset($map) || $reset) { $map = array(); $features = spaces_features(); foreach ($features as $id => $feature) { if (is_array($feature->node)) { foreach ($feature->node as $type) { $map[$type] = $id; } } } } return $map; } /** * Rebuilds the spaces menu stored in the menu_links tables via the menu API. */ function spaces_menu_rebuild() { // @TODO there is probably a better API-based way to achieve this ... $spaces_menu = array(); $result = db_query("SELECT * FROM {menu_links} WHERE menu_name = 'spaces' AND module = 'spaces'"); while ($item = db_fetch_object($result)) { $spaces_menu[$item->link_path] = $item; } $cleanup = $spaces_menu; // Loop through features and create or update menu items. $cache = array(); $features = spaces_features(); foreach ($features as $feature) { if (isset($feature->spaces['menu']) && count($feature->spaces['menu'])) { foreach ($feature->spaces['menu'] as $path => $item) { // Remove this item from list of menu items to clean up unset($cleanup[$path]); // Load an existing menu item if it already exists $existing_item = isset($spaces_menu[$path]) ? menu_link_load($spaces_menu[$path]->mlid) : array(); $customized = $existing_item['customized']; $attributes = isset($item['attributes']) ? array('attributes' => $item['attributes']) : array(); $new_item = array( 'link_title' => ($customized && isset($existing_item['title'])) ? $existing_item['title'] : $item['title'], 'link_path' => $path, 'options' => ($customized && isset($existing_item['options'])) ? $existing_item['options'] : $attributes, 'menu_name' => 'spaces', 'module' => 'spaces', ); $new_item = array_merge($existing_item, $new_item); $mlid = menu_link_save($new_item); if ($mlid) { $cache[$new_item['link_path']] = $feature->value; } } } } // Clean up stale items for features that no longer exist foreach ($cleanup as $item) { menu_link_delete($item->mlid); } cache_set('spaces_menu', $cache, 'cache'); return $cache; } /** * Returns a links array in the theme_links() format of the current * space's menu items for features accessible to the current user. Each * item has a keyed array of children items if applicable. * * @return * Array of links. */ function spaces_features_menu($reset = FALSE) { static $menu; if (!isset($menu) || $reset) { $space = spaces_get_space(); if ($space) { // Load up the spaces menu links $menu = menu_navigation_links('spaces'); $menu = space_customizer_menu::customize($space, '', $menu); // Retrieve the menu cache for a path to feature mapping $cache = cache_get('spaces_menu'); $cache = !$cache ? spaces_menu_rebuild() : $cache->data; // Sort the menu by feature weight & hide any items // for features that are not available (this **should**) // be handled by access control on the links' respective // menu callbacks, but awkward permissioning sometimes // makes it nice for these to be hidden manually. $weights = array_flip(array_keys($space->features)); $weighted = array(); foreach ($menu as $k => $item) { $feature = $cache[$item['href']]; if ($space->feature_access($feature)) { // Retrieve path > feature > weight $menu[$k]['#weight'] = $weights[$feature]; } else { unset($menu[$k]); } } uasort($menu, 'element_sort'); } } return $menu; } /** * Preset options form that can be reused by implementing modules. * * @param $space * A space object. * * @return * A FormAPI array structure. */ function spaces_form_presets($space) { $default_presets = variable_get('spaces_default_presets', array()); if (isset($space->preset)) { $default_preset = $space->preset; } else if (isset($default_presets[$space->type])) { $default_preset = $default_presets[$space->type]; } else { $default_preset = NULL; } $presets = spaces_presets($space->type); if ($presets) { // Radios for presets $form = array( '#tree' => false, '#theme' => 'spaces_form_presets', ); $form['preset'] = array( '#title' => t('Preset'), '#type' => 'radios', '#required' => true, '#options' => array(), '#default_value' => $default_preset, ); $form['info'] = array(); foreach ($presets as $id => $preset) { $form['preset']['#options'][$id] = $preset['name']; $form['info'][$id] = array( '#type' => 'item', '#title' => $preset['name'], '#description' => $preset['description'], ); } return $form; } return array(); } /** * Menu admin access wrapper. */ function spaces_admin_access($type = NULL) { if ($space = spaces_get_space()) { if (isset($type)) { if ($space->type == $type && $space->admin_access()) { return true; } } else if ($space->admin_access()) { return true; } } return false; } /** * Menu feature access wrapper. */ function spaces_feature_access($feature = NULL) { if ($space = spaces_get_space()) { if (!$space->feature_access($feature)) { return false; } } return true; } /** * A mild abstraction of hook_menu() items that can be used by * implementing modules to embed/graft relevant spaces items into the * menu tree. Should only be used when the $may_cache argument of * hook_menu() is false. * * @param $space * A space object. * @param $local_tasks * Optional boolean flag for whether the items are fully rendered as * local tasks. * @param $path_prefix * A path to prefix the menu item paths by. * * @return * An array of menu items. */ function spaces_active_space_menu($type, $local_tasks = FALSE, $path_prefix = '') { $types = spaces_types(); $path_prefix = !empty($path_prefix) ? $path_prefix .'/' : ''; if ($local_tasks == FALSE) { $items[$path_prefix. 'spaces'] = array( 'title' => t('!space_type settings', array('!space_type' => $types[$type]['title'])), 'page callback' => 'drupal_get_form', 'page arguments' => array('spaces_basic_form'), 'access callback' => 'spaces_admin_access', 'access arguments' => array($type), 'type' => MENU_NORMAL_ITEM, ); } else { $items[$path_prefix. 'spaces'] = array( 'title' => t('Spaces'), 'page callback' => 'drupal_get_form', 'page arguments' => array('spaces_basic_form'), 'access callback' => 'spaces_admin_access', 'access arguments' => array($type), 'type' => MENU_LOCAL_TASK, 'weight' => 1, ); } $items[$path_prefix. 'spaces/setup'] = array( 'title' => t('Basic setup'), 'page callback' => 'drupal_get_form', 'page arguments' => array('spaces_basic_form'), 'access callback' => 'spaces_admin_access', 'access arguments' => array($type), 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => 0, ); $items[$path_prefix. 'spaces/features'] = array( 'title' => t('Features'), 'page callback' => 'spaces_features_page', 'page arguments' => array(), 'access callback' => 'spaces_admin_access', 'access arguments' => array($type), 'type' => MENU_LOCAL_TASK, 'weight' => 1, ); return $items; } /** * THEME FUNCTIONS ==================================================== */ /** * Generates an array of utility links for the current space suitable * for use in theme_links(). */ function spaces_space_links() { $links = array(); if ($space = spaces_get_space()) { $links = array(); $space->links($links); } return $links; } /** * Implementation of hook_context_ui_node_links_alter(). * @TODO: complain to the author of context_ui that this is too long. */ function spaces_context_ui_node_links_alter(&$links) { $output = ''; // Perform additional logic if a spaces feature is active. if ($feature = context_get('spaces', 'feature')) { $space = spaces_get_space(); // Feature is inaccessible if ($space && !$space->feature_access($feature)) { $features = spaces_features($space->type); if (isset($features[$feature]->node)) { foreach ($features[$feature]->node as $type) { unset($links[$type]); } } } } } /** * Form theme function for spaces presets. */ function theme_spaces_form_presets($form) { $output = ''; // Render presets in a table $rows = array(); foreach (element_children($form['info']) as $id) { unset($form['preset'][$id]['#title']); $row = array( drupal_render($form['preset'][$id]), drupal_render($form['info'][$id]), ); $rows[] = $row; } $output .= theme('table', array(array('data' => $form['preset']['#title'], 'colspan' => 2)), $rows); drupal_render($form['preset']); // Throw out the rest of this element $output .= drupal_render($form); return $output; } function spaces_preprocess_page(&$vars) { if (variable_get('menu_primary_links_source', 'primary-links') == 'spaces') { $vars['primary_links'] = spaces_features_menu(); } if (variable_get('menu_secondary_links_source', 'secondary-links') == 'spaces') { $vars['secondary_links'] = spaces_features_menu(); } } /** * BLOCKS ============================================================= */ /** * Contextual tools */ function _spaces_block_tools() { $block['content'] = theme('context_ui_node_links'); return $block; } /** * Default navigation menu - you can create your own customized version * using the spaces_features_menu() and theme_links() functions. */ function _spaces_block_nav() { $block = array(); if ($space = spaces_get_space()) { $links = spaces_features_menu(); $block['subject'] = $space->title; $block['content'] = theme('links', $links); } return $block; } /** * Utility links */ function _spaces_block_utility_links() { $block = array(); if ($space = spaces_get_space()) { } return $block; }