$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 (empty($once)) {
$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'),
'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'),
'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'),
);
$items['admin/build/spaces/presets/edit'] = array(
'page callback' => 'drupal_get_form',
'page arguments' => array('spaces_preset_form', 'edit'),
);
$items['admin/build/spaces/presets/export'] = array(
'title' => t('Export preset'),
'page callback' => 'drupal_get_form',
'page arguments' => array('spaces_preset_export'),
);
$items['admin/build/spaces/presets/delete'] = array(
'page callback' => 'drupal_get_form',
'page arguments' => array('spaces_preset_delete_form', 5, 6),
);
$items['admin/build/spaces/presets/disable'] = array(
'page callback' => '_spaces_preset_disable_page',
);
$items['admin/build/spaces/presets/enable'] = array(
'page callback' => '_spaces_preset_enable_page',
);
foreach ($items as $path => $item) {
$items[$path]['access callback'] = 'user_access';
$items[$path]['access arguments'] = array('administer spaces');
$items[$path]['file'] = 'spaces_admin.inc';
if (!isset($item['type'])) {
$items[$path]['type'] = MENU_CALLBACK;
}
}
$items['spaces-access-denied'] = array(
'title' => t('Access denied'),
'description' => t('Page callback for early-stack spaces router access denied.'),
'page callback' => 'spaces_access_denied',
'page arguments' => array(),
'access callback' => FALSE,
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Menu callback for early access_denied calls. Going through the a menu
* callback allows Drupal bootstrap to complete.
*/
function spaces_access_denied() {
drupal_access_denied();
exit;
}
/**
* 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()
.') ."
";
}
}
/**
* Implementation of hook_theme().
*/
function spaces_theme() {
$items = array();
$items['spaces_features_form'] =
$items['spaces_customize_item'] =
$items['spaces_preset_default_form'] =
$items['spaces_form_presets'] =
$items['spaces_block_customizer_settings_form'] = array(
'file' => 'spaces.theme.inc',
);
return $items;
}
/**
* Implementation of hook_user().
*/
function spaces_user($op, &$edit, &$account, $category = NULL) {
switch ($op) {
case 'view':
spaces_router('user view', $account);
break;
case 'form':
spaces_router('user form', $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 view', $node);
}
}
/**
* Implementation of hook_flush_caches().
*/
function spaces_flush_caches() {
spaces_features_map(NULL, TRUE);
return array();
}
/**
* Implementation of hook_form_alter().
*/
function spaces_form_alter(&$form, $form_state, $form_id) {
switch ($form_id) {
case 'system_modules':
spaces_features_map(NULL, TRUE);
break;
default:
if ($form['#id'] == 'node-form' && (arg(0) .'/'. arg(1) != 'admin/content')) {
spaces_router('node form', $form['#node']);
}
break;
}
}
/**
* Implementation of hook_spaces_settings().
*/
function spaces_spaces_settings() {
return array(
'home' => 'space_setting_home',
);
}
/**
* Implementation of hook_spaces_customizers().
*/
function spaces_spaces_customizers() {
return array(
'menu' => 'space_customizer_menu',
'block' => 'space_customizer_block',
);
}
/**
* Implementation of hook_views_api().
*/
function spaces_views_api() {
return array(
'api' => 2,
'path' => drupal_get_path('module', 'spaces') .'/includes',
);
}
/**
* Implementation of hook_context_conditions().
*/
function spaces_context_conditions() {
$type_options = array();
foreach (spaces_types() as $type => $info) {
$type_options[$type] = $info['title'];
}
$items = array();
$items['spaces_type'] = array(
'#title' => t('Spaces type'),
'#type' => 'checkboxes',
'#options' => $type_options,
'#description' => t('Set this context when the selected space types are active.'),
);
return $items;
}
/**
* 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);
}
/**
* 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 => '---');
$links = features_menu_links();
foreach ($links as $link) {
$options[$link['href']] = $link['title'];
}
$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) {
$menu = spaces_features_items('menu', $feature);
if (!empty($menu)) {
$form = array();
$menu_items = menu_navigation_links('features');
$default_items = $menu_items;
$this->customize($space, $menu_items);
foreach ($menu_items as $key => $item) {
if (in_array($item['href'], $menu)) {
$form[$item['href']] = array(
'#title' => $item['href'],
'#type' => 'fieldset',
'#tree' => TRUE,
);
$form[$item['href']]['title'] = array(
'#title' => t('Title'),
'#type' => 'textfield',
'#size' => 40,
'#maxlength' => 255,
'#default_value' => $item['title'],
);
$form[$item['href']]['default'] = array(
'#type' => 'value',
'#value' => $default_items[$key]['title'],
);
}
}
}
return $form;
}
function validate($space, $feature, $value) {
return;
}
function submit($space, $feature, $value) {
$customizer = $space->customizer['menu'];
foreach ($value as $path => $item) {
if ($item['title'] != $item['default']) {
$customizer[$path] = $item['title'];
}
}
return $customizer;
}
function customize($space, &$menu = NULL) {
foreach ($menu as $k => $item) {
if (!empty($space->customizer['menu'][$item['href']])) {
$menu[$k]['title'] = $space->customizer['menu'][$item['href']];
}
}
}
}
/**
* Customizer for feature blocks.
*/
class space_customizer_block implements space_customizer {
var $name = 'Blocks';
var $info = array();
/**
* Helper method to return the block info.
*/
function get_block_info($module, $delta) {
if (!isset($this->info[$module])) {
$this->info[$module] = module_invoke($module, 'block', 'list');
// If the block is provided by Views, doctor the info to strip out the
// view name leaving only the block's display name.
if ($module == 'views') {
foreach ($this->info[$module] as $k => $v) {
$viewname = strpos($v['info'], ':');
if ($viewname !== FALSE) {
$v['info'] = substr($v['info'], $viewname + 2);
$this->info[$module][$k] = $v;
}
}
}
}
return !empty($this->info[$module][$delta]['info']) ? $this->info[$module][$delta]['info'] : '';
}
/**
* Implementation of form().
*/
function form($space, $feature) {
$master_form = array(
'#theme' => 'spaces_block_customizer_settings_form',
'contexts' => array('#tree' => TRUE),
'defaults' => array('#tree' => TRUE),
);
global $theme_key;
init_theme();
$regions = system_region_list($theme_key);
// Collect all contexts associated with this feature
$feature_contexts = drupal_map_assoc(spaces_features_items('context', $feature));
$contexts = context_enabled_contexts();
$contexts = array_intersect_key($contexts, $feature_contexts);
foreach ($contexts as $identifier => $context) {
if (!empty($context->block)) {
// Get customized values -- unfortunately we can't use our own customization
// methods as block customization is a bit more complex.
$customizer = !empty($space->customizer['block'][$identifer]) ? $space->customizer['block'][$identifier] : array();
$subject = !empty($space->customizer['block']['subject']) ? $space->customizer['block']['subject'] : array();
$form = array( '#title' => $identifier, '#tree' => TRUE);
$defaults = array('#tree' => TRUE);
foreach ($context->block as $i => $block) {
// @TODO: remove this once context is less confused about itself.
$block = (array) $block;
$bid = "{$block['module']}-{$block['delta']}";
// Sanity check that this region exists
if (!empty($block['region']) && !empty($regions[$block['region']])) {
$region = $block['region'];
if (!isset($form[$region])) {
$form[$region] = array('#title' => $regions[$region], '#tree' => TRUE);
}
$block_details = module_invoke($block['module'], 'block', 'view', $block['delta']);
$default_subject = !empty($block_details['subject']) ? $block_details['subject'] : '';
$default_weight = isset($block[$i]['weight']) ? $block[$i]['weight'] : 0;
$default_status = isset($block[$i]['status']) ? $block[$i]['status'] : 1;
// Store defaults to compare against changed values in submit handler
$defaults[$region][$bid]['weight'] = array('#type' => 'value', '#value' => $default_weight);
$defaults[$region][$bid]['status'] = array('#type' => 'value', '#value' => $default_status);
$defaults[$region][$bid]['subject'] = array('#type' => 'value', '#value' => $default_subject);
$form[$region][$bid] = array(
'#tree' => TRUE,
'#weight' => isset($customizer[$region][$bid]['weight']) ? $customizer[$region][$bid]['weight'] : 0,
);
$form[$region][$bid]['weight'] = array(
'#type' => 'weight',
'#delta' => 25,
'#default_value' => isset($customizer[$region][$bid]['weight']) ? $customizer[$region][$bid]['weight'] : 0,
);
$form[$region][$bid]['status'] = array(
'#type' => 'checkbox',
'#default_value' => isset($customizer[$region][$bid]['status']) ? $customizer[$region][$bid]['status'] : 1,
);
// If the block subject is empty, it's likely to be for a good reason
// e.g. the subject is generated dynamically or the block is slimmed
// down. Let's respect that.
if (!empty($default_subject)) {
$form[$region][$bid]['subject'] = array(
'#type' => 'textfield',
'#default_value' => !empty($subject[$bid]) ? $subject[$bid] : $default_subject,
'#description' => $this->get_block_info($block['module'], $block['delta']),
);
}
else {
$form[$region][$bid]['subject'] = array(
'#type' => 'markup',
'#value' => $this->get_block_info($block['module'], $block['delta']),
);
}
}
}
$master_form['contexts'][$identifier] = $form;
$master_form['defaults'][$identifier] = $defaults;
}
}
return $master_form;
}
/**
* Implementation of validate().
*/
function validate($space, $feature, $value) {
return;
}
/**
* Implementation of submit().
* Iterate through and only record the aspects of each block that have been customized.
*/
function submit($space, $feature, $value) {
$customizer = $space->customizer['block'];
foreach ($value['contexts'] as $identifier => $context) {
foreach ($context as $region => $blocks) {
foreach ($blocks as $bid => $block) {
foreach ($block as $k => $v) {
// Clear out values that haven't changed from defaults
if ($v === $value['defaults'][$identifier][$region][$bid][$k]) {
unset($block[$k]);
}
// Store subjects "flatly"
else if ($k == 'subject') {
unset($block[$k]);
$customizer['subject'][$bid] = $v;
}
}
if (!empty($block)) {
$customizer[$identifier][$region][$bid] = $block;
}
}
}
}
return $customizer;
}
/**
* Implementation of customize().
*/
function customize($space, $identifier, &$block = NULL) {
// Unset disabled blocks and change weights based on customizer settings. See spaces_context_active_contexts_alter.
if (!empty($space->customizer['block'][$identifier])) {
$customizer = $space->customizer['block'][$identifier];
foreach ($block as $key => $b) {
// @TODO: remove this once context is less confused about itself.
$b = (array) $b;
$block[$key] = (array) $block[$key];
$bid = "{$b['module']}-{$b['delta']}";
// If the block is not enabled, yank it out of the blocks array
if (!empty($customizer[$b['region']][$bid])) {
if (isset($customizer[$b['region']][$bid]['status']) && $customizer[$b['region']][$bid]['status'] === 0) {
unset($block[$key]);
}
if (isset($customizer[$b['region']][$bid]['weight'])) {
$block[$key]['weight'] = $customizer[$b['region']][$bid]['weight'];
}
}
}
}
}
/**
* Additional method: customize_subject() for customizing a block subject.
*/
function customize_subject($space, &$block) {
$bid = "{$block->module}-{$block->delta}";
if (!empty($space->customizer['block']['subject'][$bid])) {
$block->subject = $space->customizer['block']['subject'][$bid];
}
}
}
/**
* Implementation of hook_context_active_contexts_alter().
*/
function spaces_context_active_contexts_alter(&$contexts) {
$space = spaces_get_space();
foreach ($contexts as $identifier => $context) {
if (!empty($context->block)) {
space_customizer_block::customize($space, $identifier, $context->block);
}
}
}
/**
* 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;
// Load the PURL modifier
if ($modifier = purl_load(array('provider' => "spaces_{$type}", 'id' => $sid))) {
$space->purl = $modifier['value'];
}
// 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 (!empty($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($space->type);
foreach (spaces_features() as $feature) {
if (isset($feature->info['spaces']['settings'])) {
foreach ($feature->info['spaces']['settings'] as $id => $setting) {
$valid_settings[$id] = $setting;
}
}
}
foreach ($space->settings as $setting => $value) {
if (isset($valid_settings[$setting]) && !empty($value)) {
$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_purl = isset($types[$space->type]['custom purl']) && $types[$space->type]['custom purl'];
if ($space->purl && $save_purl) {
// We need to concatenate the type/sid so that collisions between space types do not occur.
$modifier = array(
'provider' => 'spaces_'. $space->type,
'id' => $space->sid,
'value' => $space->purl,
);
purl_save($modifier);
}
// 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);
// Delete the purl modifier
$modifier = array('provider' => 'spaces_'. $space->type, 'id' => $space->sid);
purl_delete($modifier);
// 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, settings, customizer
$keys = array('features', 'settings', 'customizer');
foreach ($keys as $key) {
if (isset($preset[$key]) && is_array($preset[$key])) {
foreach ($preset[$key] as $k => $v) {
if ($preset['locked'][$key][$k] || !isset($space->{$key}[$k])) {
$space->{$key}[$k] = $v;
}
}
}
}
// 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,
* 'purl modifier' => $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 URL modifier for each space. If FALSE,
* the implementing module should implement
* hook_purl_modifiers() in order to provide PURL modifiers programatically.
*
* @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();
$disabled = variable_get('spaces_disabled_presets', array());
// 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) {
$preset['disabled'] = isset($disabled[$preset['type']][$id]);
$presets[$preset['type']][$id] = $preset;
}
}
$result = db_query("SELECT * FROM {spaces_presets}");
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]),
);
}
}
// 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;
if ($space) {
// Context integration with spaces_type setter.
context_set_by_condition('spaces_type', $space->type);
}
}
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')) {
// We use a menu callback here rather than drupal_access_denied()
// because spaces_router() is called at times from hook_init().
// Using drupal_access_denied() can terminate the page request before
// bootstrap completes, leading to all sorts of havoc.
menu_set_active_item('spaces-access-denied');
}
}
}
/**
* 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('all' => array(), 'common' => array());
$features = features_get_features();
foreach ($features as $feature) {
if (!empty($feature->info['spaces'])) {
if (!empty($feature->info['spaces']['types']) && is_array($feature->info['spaces']['types'])) {
foreach ($feature->info['spaces']['types'] as $t) {
$spaces_features[$t][$feature->name] = $feature;
}
}
else {
$spaces_features['common'][$feature->name] = $feature;
}
$spaces_features['all'][$feature->name] = $feature;
}
}
foreach (array_keys($spaces_features) as $t) {
if ($t != 'all' && $t != 'common') {
$spaces_features[$t] = array_merge($spaces_features[$t], $spaces_features['common']);
}
}
}
if ($type) {
return !empty($spaces_features[$type]) ? $spaces_features[$type] : $spaces_features['common'];
}
return $spaces_features['all'];
}
/**
* Retrieve all available settings.
*
* @param $type
* Optional space type to return a subset of settings that only apply
* to the given space type.
* @param $reset
* Optional boolean flag for resetting the static cache.
*
* @return
* Keyed array of potential settings.
*/
function spaces_settings($type = NULL, $reset = FALSE) {
static $spaces_settings;
if (!isset($spaces_settings) || $reset) {
$spaces_settings = array('all' => array(), 'common' => array());
$settings = module_invoke_all('spaces_settings');
foreach ($settings as $setting_name => $class) {
$setting = new $class();
if (!empty($setting->types)) {
foreach ($setting->types as $t) {
if (!isset($spaces_settings[$t])) {
$spaces_settings[$t] = array();
}
$spaces_settings[$t][$setting_name] = $setting;
}
}
else {
$spaces_settings['common'][$setting_name] = $setting;
}
$spaces_settings['all'][$setting_name] = $setting;
}
foreach (array_keys($spaces_settings) as $t) {
if ($t != 'all' && $t != 'common') {
$spaces_settings[$t] = array_merge($spaces_settings[$t], $spaces_settings['common']);
}
}
}
if ($type) {
return !empty($spaces_settings[$type]) ? $spaces_settings[$type] : $spaces_settings['common'];
}
return $spaces_settings['all'];
}
/**
* 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 $spaces_customizers;
if (!isset($spaces_customizers) || $reset) {
$customizers = module_invoke_all('spaces_customizers');
foreach ($customizers as $customizer_name => $class) {
$spaces_customizers[$customizer_name] = new $class();
}
}
return $spaces_customizers;
}
/**
* 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 {
$features = spaces_features();
foreach ($features as $feature) {
foreach ($feature->info['features'] as $component => $items) {
if (is_array($items)) {
foreach ($items as $item) {
$map[$component][$item] = $feature->name;
}
}
}
}
// Go through contexts and add relevant context components in as well
$contexts = context_enabled_contexts();
$components = array('node', 'views');
foreach ($map['context'] as $identifier => $feature_name) {
$context = $contexts[$identifier];
if (!empty($context)) {
foreach ($components as $component) {
if (!empty($context->{$component})) {
foreach ($context->{$component} as $item) {
$map[$component][$item] = $feature_name;
}
}
}
}
}
cache_set('spaces_map', $map, 'cache');
}
}
if (!empty($type)) {
return isset($map[$type]) ? $map[$type] : array();
}
return $map;
}
/**
* Returns an array of items of a given type that belong to a given feature.
*/
function spaces_features_items($type, $feature) {
$features = spaces_features();
if (!empty($features[$feature]) && !empty($features[$feature]->info['features'][$type])) {
return $features[$feature]->info['features'][$type];
}
return array();
}
/**
* Implementation of hook_features_menu_links_alter().
*/
function spaces_features_menu_links_alter(&$links) {
$space = empty($space) ? spaces_get_space() : $space;
// Sanity check on our space object.
if ($space && !empty($space->sid)) {
// Load up the spaces menu links
space_customizer_menu::customize($space, $links);
// Retrieve the menu cache for a path to feature mapping
$map = spaces_features_map('menu');
// 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 (e.g. user 1) makes it nice for these to be
// hidden manually.
$weights = array_flip(array_keys($space->features));
$weighted = array();
foreach ($links as $k => $item) {
$feature = '';
if (!empty($map[$item['href']])) {
$feature = $map[$item['href']];
}
if (!empty($feature) && $space->feature_access($feature)) {
// Retrieve path > feature > weight
$links[$k]['#weight'] = $weights[$feature];
}
else {
unset($links[$k]);
}
}
uasort($links, 'element_sort');
}
}
/**
* 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) {
$presets = spaces_presets($space->type);
if ($presets) {
$default_presets = variable_get('spaces_default_presets', array());
if (isset($space->preset) && !empty($presets[$space->preset])) {
$default_preset = $space->preset;
}
else if (isset($default_presets[$space->type])) {
$default_preset = $default_presets[$space->type];
}
else {
$default_preset = NULL;
}
// 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, $op = NULL) {
$return = false;
$space = spaces_get_space();
if ($space && $type == $space->type) {
$return = $space->admin_access();
}
if ($op == 'features') {
return $return && (user_access('administer spaces') || user_access('configure spaces features'));
}
return $return;
}
/**
* Menu feature access wrapper.
*/
function spaces_feature_access($feature = NULL) {
if (user_access('administer spaces')) {
return TRUE;
}
else if ($space = spaces_get_space()) {
return $space->feature_access($feature);
}
return user_access('access content');
}
/**
* 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 .'/' : '';
$spaces_path = drupal_get_path('module', 'spaces');
if ($local_tasks == FALSE) {
$items[$path_prefix. 'spaces'] = array(
'title' => t('!space_type settings', array('!space_type' => $types[$type]['title'])),
'page arguments' => array('spaces_basic_form'),
'access arguments' => array($type),
'type' => MENU_NORMAL_ITEM,
);
}
else {
$items[$path_prefix. 'spaces'] = array(
'title' => t('Spaces'),
'page arguments' => array('spaces_basic_form'),
'access arguments' => array($type),
'type' => MENU_LOCAL_TASK,
'weight' => 1,
);
}
$items[$path_prefix. 'spaces/setup'] = array(
'title' => t('Basic setup'),
'page arguments' => array('spaces_basic_form'),
'access arguments' => array($type),
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => 0,
);
$items[$path_prefix. 'spaces/features'] = array(
'title' => t('Features'),
'page arguments' => array('spaces_features_form'),
'access arguments' => array($type, 'features'),
'type' => MENU_LOCAL_TASK,
'weight' => 1,
);
$items[$path_prefix. 'spaces/features/%'] = array(
'title' => t('Features'),
'page arguments' => array('spaces_customize_form', NULL, 2),
'access arguments' => array($type, 'features'),
'type' => MENU_LOCAL_TASK,
'weight' => 1,
);
foreach ($items as $path => $item) {
$items[$path]['page callback'] = 'drupal_get_form';
$items[$path]['access callback'] = 'spaces_admin_access';
$items[$path]['file'] = 'spaces_admin.inc';
$items[$path]['file path'] = $spaces_path;
}
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()) {
$space->links($links);
drupal_alter('space_links', $links);
}
return $links;
}
/**
* theme_block() preprocessor.
*/
function spaces_preprocess_block(&$vars) {
$space = spaces_get_space();
if (!empty($vars['block'])) {
space_customizer_block::customize_subject($space, $vars['block']);
}
}