load()) { return $space; } } return FALSE; } /** * Remove all override values for a space. */ function spaces_delete($type, $id) { return db_query("DELETE FROM {spaces_overrides} WHERE type = '%s' AND id = '%s'", $type, $id); } /** * Initialize a space and set it to be the current active space. */ function spaces_init_space($type, $id) { $space = spaces_load($type, $id); if ($space) { if ($space->activate()) { // If there is already an active space we need to deactivate it. // Note that this often causes a redirect. if ($active = spaces_get_space()) { if ($active->type != $space->type || $active->id != $space->id) { $active->deactivate(); } } spaces_set_space($space); } else { $space->deactivate(); } } } /** * Wrapper function around spaces_set_space(). Retrieves the current * active space for a given space type. */ 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. * * @return * The active space object or FALSE if there is no active space. */ function spaces_set_space($space = NULL) { static $active_space; if (isset($space)) { $active_space = $space; if (module_exists('context') && $plugin = context_get_plugin('condition', 'spaces_type')) { $plugin->execute($space); } } return isset($active_space) ? $active_space : FALSE; } /** * Implementation of hook_ctools_plugin_api(). */ function spaces_ctools_plugin_api($module, $api) { if ($module == 'spaces' && $api == 'plugins') { return array('version' => 3); } } /** * Implementation of hook_spaces_plugins(). */ function spaces_spaces_plugins() { $plugins = array(); $plugins['space'] = array( 'handler' => array( 'path' => drupal_get_path('module', 'spaces') .'/plugins', 'file' => 'space.inc', 'class' => 'space', ), ); $plugins['space_type'] = array( 'handler' => array( 'path' => drupal_get_path('module', 'spaces') .'/plugins', 'file' => 'space_type.inc', 'class' => 'space_type', 'parent' => 'space', ), ); $plugins['space_type_purl'] = array( 'handler' => array( 'path' => drupal_get_path('module', 'spaces') .'/plugins', 'file' => 'space_type_purl.inc', 'class' => 'space_type_purl', 'parent' => 'space_type', ), ); $plugins['spaces_controller'] = array( 'handler' => array( 'path' => drupal_get_path('module', 'spaces') .'/plugins', 'file' => 'spaces_controller.inc', 'class' => 'spaces_controller', ), ); $plugins['spaces_controller_variable'] = array( 'handler' => array( 'path' => drupal_get_path('module', 'spaces') .'/plugins', 'file' => 'spaces_controller_variable.inc', 'class' => 'spaces_controller_variable', 'parent' => 'spaces_controller', ), ); $plugins['spaces_controller_context'] = array( 'handler' => array( 'path' => drupal_get_path('module', 'spaces') .'/plugins', 'file' => 'spaces_controller_context.inc', 'class' => 'spaces_controller_context', 'parent' => 'spaces_controller', ), ); return $plugins; } /** * Implementation of hook_spaces_registry(). */ function spaces_spaces_registry() { return array( 'controllers' => array( 'variable' => array( 'title' => t('Variable'), 'plugin' => 'spaces_controller_variable', ), 'context' => array( 'title' => t('Context'), 'plugin' => 'spaces_controller_context', ), ), ); } /** * Get the registry of spaces types. */ function spaces_types($reset = FALSE) { return _spaces_registry('types', $reset); } /** * Get the registry of spaces controllers. */ function spaces_controllers($reset = FALSE) { return _spaces_registry('controllers', $reset); } /** * Registry retrieval and caching. */ function _spaces_registry($key = NULL, $reset = FALSE) { static $registry; if (!isset($registry) || $reset) { if (!$reset && $cache = cache_get('spaces_registry', 'cache')) { $registry = $cache->data; } else { $registry = module_invoke_all('spaces_registry'); drupal_alter('spaces_registry', $registry); cache_set('spaces_registry', $registry); } } if (isset($key)) { return isset($registry[$key]) ? $registry[$key] : array(); } return $registry; } /** * Presets ============================================================ */ /** * Preset loader. * * @param $name * Optional name for the preset to load. * @param $name * Optional spaces_type of the the presets to load. * * @return * Returns a fully-loaded preset. */ function spaces_preset_load($name = NULL, $space_type = NULL, $reset = FALSE) { ctools_include('export'); if ($reset) { ctools_export_load_object_reset('spaces_presets'); } if (!isset($name) && !isset($space_type)) { return ctools_export_load_object('spaces_presets', 'all'); } if (isset($name)) { $presets = ctools_export_load_object('spaces_presets', 'names', array($name)); return $presets ? array_shift($presets) : FALSE; } if (isset($space_type)) { return ctools_export_load_object('spaces_presets', 'conditions', array('space_type' => $space_type)); } } /** * Inserts or updates a spaces preset into the database. * * @param $preset * The preset object to be inserted. */ function spaces_preset_save($preset) { $existing = spaces_preset_load($preset->name, NULL, TRUE); if ($existing && ($existing->export_type & EXPORT_IN_DATABASE)) { drupal_write_record('spaces_presets', $preset, 'name'); } else { drupal_write_record('spaces_presets', $preset); } spaces_preset_load(NULL, NULL, TRUE); // Reset static cache return TRUE; } /** * Save a preset's values from a given space object. * * @param $preset * The preset object to be saved. * @param $space * The space object whose values should be used for the preset. */ function spaces_preset_save_from_space($preset, $space) { $space->activate(); foreach (array_keys(spaces_controllers()) as $controller) { $preset->value[$controller] = array_merge( $space->controllers->{$controller}->get(NULL, 'preset'), $space->controllers->{$controller}->get(NULL, 'space') ); } spaces_preset_save($preset); } /** * Deletes an existing preset. * * @param $preset * The preset object to be deleted. * * @return * Returns true on success, false on failure. */ function spaces_preset_delete($preset) { if (isset($preset->name) && ($preset->export_type & EXPORT_IN_DATABASE)) { db_query("DELETE FROM {spaces_presets} WHERE name = '%s'", $preset->name); spaces_preset_load(NULL, NULL, TRUE); return TRUE; } return FALSE; } /** * CTools export function. * * Workaround for var_export() #fail with stdClass objects. See: * http://bugs.php.net/bug.php?id=48016&edit=1. A proper patch is in the * CTools issue queue here: http://drupal.org/node/661332. Once this is * committed & included in a CTools release, this workaround will no * longer be necessary. */ function spaces_export_spaces_presets($object, $indent = '') { $export = ctools_export_object('spaces_presets', $object, $indent); return str_replace('stdClass::__set_state(', '(object) (', $export); } /** * Other spaces actions =============================================== */ /** * Calls the router method on all space types, giving them a chance to * route requests accordingly. * * @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) { static $router; if (!isset($router)) { $router = array(); // Add the active space. if ($space = spaces_get_space()) { $router[$space->type] = $space; } // Iterate over space types and create instances of each. ctools_include('plugins'); $plugins = ctools_get_plugins('spaces', 'plugins'); foreach (spaces_types() as $type => $info) { if (!isset($router[$type]) && isset($plugins[$info['plugin']]) && $class = ctools_plugin_get_class($plugins[$info['plugin']], 'handler')) { $router[$type] = new $class($type); } } } foreach ($router as $space) { $space->router($op, $object); } } /** * Integration with Features ========================================== */ /** * 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 $features; static $by_type; if (!isset($features) || $reset) { $features = array(); $by_type = array(); // Include the 'site' key. $types = array_merge(array_keys(spaces_types()), array('site')); foreach (features_get_features() as $feature) { if (module_exists($feature->name) && !empty($feature->info['spaces']['types'])) { $features[$feature->name] = $feature; foreach ($types as $t) { if ($feature->info['spaces']['types'] === 'all' || (is_array($feature->info) && in_array($t, $feature->info['spaces']['types'], TRUE))) { $by_type[$t][$feature->name] = $feature; } } } } } if (isset($type)) { return !empty($by_type[$type]) ? $by_type[$type] : array(); } return $features; } /** * Caches a map of feature component => feature name. */ function spaces_features_map($type = NULL, $reset = FALSE) { static $map; if (!isset($map) || $reset) { $map = array(); $cache = cache_get('spaces_map', 'cache'); if ($cache && !$reset) { $map = $cache->data; } else { // Generate component to feature map based on feature info files $features = spaces_features(); foreach ($features as $feature_name => $feature) { foreach ($feature->info['features'] as $component => $items) { if (is_array($items)) { foreach ($items as $item) { if (!isset($map[$component][$item])) { $map[$component][$item] = $feature_name; } } } } } cache_set('spaces_map', $map, 'cache'); } } if (!empty($type)) { return isset($map[$type]) ? $map[$type] : array(); } return $map; } /** * Access check for the current space. */ function spaces_access_space($account = NULL) { $space = spaces_get_space(); return $space ? $space->access_space() : TRUE; } /** * Access check for administrative access to the current space. */ function spaces_access_admin($account = NULL) { $space = spaces_get_space(); return $space ? $space->access_admin($account) : user_access('administer site configuration'); } /** * Access check for a given feature in the current space. */ function spaces_access_feature($op = 'view', $feature, $account = NULL) { $space = spaces_get_space(); if ($space) { return $space->access_feature($op, $feature, $account); } // @TODO // Need to determine how to handle spaces[types] = "site" from // feature info files. $features = variable_get('spaces_features', array()); return !empty($features[$feature]); } /** * Access check for a user account page in the current space. */ function spaces_access_user($op = 'view', $account = NULL) { $space = spaces_get_space(); return $space ? $space->access_user($op, $account) : TRUE; } /** * Implemented hooks ================================================== */ /** * Implementation of hook_menu(). */ function spaces_menu() { $items = array(); $items['spaces-access-denied'] = array( 'access callback' => FALSE, 'type' => MENU_CALLBACK, ); $items['features'] = array( 'title' => 'Features', 'description' => 'Configure features for this space.', 'page callback' => 'drupal_get_form', 'page arguments' => array('spaces_features_form'), 'access callback' => 'spaces_access_admin', 'access arguments' => array(), 'type' => MENU_NORMAL_ITEM, 'file' => 'spaces.admin.inc', ); return $items; } /** * Implementation of hook_menu_alter(). */ function spaces_menu_alter(&$items) { $router_items = array( 'node/%node', 'node/%node/edit', 'user/%user/view', 'user/%user_uid_optional', 'user/%user_category/edit', ); foreach (node_get_types('types', NULL, TRUE) as $type) { $type_url_str = str_replace('_', '-', $type->type); $router_items[] = 'node/add/'. $type_url_str; } foreach ($router_items as $path) { if (isset($items[$path])) { $arguments = isset($items[$path]['access arguments']) ? $items[$path]['access arguments'] : array(); $arguments[] = isset($items[$path]['access callback']) ? $items[$path]['access callback'] : NULL; $items[$path]['access callback'] = 'spaces_menu_access'; $items[$path]['access arguments'] = $arguments; } } // Graft space-specific settings pages into each space type's menu tree. $graft = array(); $graft['features'] = $items['features']; foreach ($items as $path => $item) { if (strpos($path, 'features/') === 0) { $graft[$path] = $item; } } foreach (spaces_types() as $type => $info) { if (isset($info['path'])) { $graft['features']['type'] = MENU_LOCAL_TASK; $graft['features']['access callback'] = 'spaces_access_admin'; $graft['features']['access arguments'] = array(); // For any child pages of the graft, increment load arguments by the // number of args present in the path. $argcount = count(explode('/', $info['path'])); foreach ($graft as $path => $item) { $newitem = $item; foreach (array('page arguments', 'access arguments', 'title arguments') as $key) { if (!empty($newitem[$key])) { foreach ($newitem[$key] as $position => $argument) { if (is_numeric($argument)) { $newitem[$key][$position] = $newitem[$key][$position] + $argcount; } } } } $items["{$info['path']}/{$path}"] = $newitem; } } $items["{$info['path']}/overrides"] = array( 'title' => 'Overrides', 'description' => 'Manage override values for this space.', 'page callback' => 'drupal_get_form', 'page arguments' => array('spaces_overrides_form'), 'access callback' => 'spaces_access_admin', 'access arguments' => array(), 'type' => MENU_LOCAL_TASK, 'file' => 'spaces.admin.inc', 'module' => 'spaces', ); } } /** * Spaces menu access callback. Allows space types to manage access as * related to their space workflow. See hook_menu_alter() for how * menu access callbacks / arguments get passed. */ function spaces_menu_access() { // Run the standard Drupal access check. $args = func_get_args(); $access_callback = array_pop($args); if (empty($access_callback) || call_user_func_array($access_callback, $args)) { // Determine the access callback to use by inspecting args. $map = spaces_features_map(); if ($access_callback == 'node_access' && $args[0] == 'create') { $node_type = str_replace('-', '_', $args[1]); $feature = isset($map['node'][$node_type]) ? $map['node'][$node_type] : NULL; if ($feature) { return spaces_access_feature('create', $feature); } } else { foreach ($args as $arg) { if (is_object($arg)) { if (isset($object->nid)) { $feature = isset($map['node'][$object->type]) ? $map['node'][$object->type] : NULL; if ($feature) { return spaces_access_feature('view', $feature); } } else if (isset($object->uid)) { return spaces_access_user('view', $object); } break; } } } return TRUE; } return FALSE; } /** * Implementation of hook_theme(). */ function spaces_theme() { $items = array(); $base = array( 'arguments' => array('form' => array()), 'file' => 'spaces.theme.inc', 'path' => drupal_get_path('module', 'spaces'), ); $items['spaces_features_form'] = $items['spaces_preset_form'] = $items['spaces_overrides_form'] = $base; return $items; } /** * Implementation of hook_views_api(). */ function spaces_views_api() { return array('api' => 2, 'path' => drupal_get_path('module', 'spaces') .'/includes'); } /** * Implementation of hook_perm(). */ function spaces_perm() { return array('administer spaces'); } /** * Implementation of hook_init(). */ function spaces_init() { if (module_exists('spaces_customtext')) { global $language; $language->language = ($language->language === 'spaces_customtext') ? spaces_customtext_cache() : $language->language; } if (!spaces_access_space()) { menu_set_active_item('spaces-access-denied'); } spaces_router('init'); } /** * Implementation of hook_user(). */ function spaces_user($op, &$edit, &$account, $category = NULL) { switch ($op) { case 'view': case 'form': spaces_router('user', $account); break; } } /** * Implementation of hook_nodeapi(). */ function spaces_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) { if ($op == 'view' && $page && menu_get_object() === $node) { spaces_router('node', $node); } if ($op == 'prepare') { spaces_router('node', $node); } } /** * Form alter for the system_module form that adds a submit handler to clear * the space registry. */ function spaces_form_system_modules_alter(&$form, &$form_state) { $form['#submit'][] = 'spaces_form_system_modules_submit'; } /** * Submit handler for clearing the space registry. */ function spaces_form_system_modules_submit($form, &$form_state) { cache_clear_all('spaces_types', 'cache'); }