You are here

spaces.module in Spaces 6

File

spaces.module
View source
<?php

define('SPACES_FEATURE_DISABLED', 0);

/**
 * Implementation of hook_init().
 */
function spaces_init() {
  spaces_router('menu');

  // Rebuild spaces menu on page callbacks where there is the
  // potential for the addition/removal/alteration of features.
  if ($_GET['q'] == 'admin/build/context') {
    spaces_menu_rebuild();
  }
  include_once drupal_get_path('module', 'spaces') . '/spaces_admin.inc';
}

/**
 * Implementation of hook_perm().
 */
function spaces_perm() {
  return array(
    'administer spaces',
    'configure spaces features',
  );
}

/**
 * Implementation of hook_purl_provider().
 */
function spaces_purl_provider() {
  $items = array();
  foreach (spaces_types() as $type => $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) {
    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['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,
  );
  $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' => MENU_CALLBACK,
  );
  $items['admin/build/spaces/presets/enable'] = array(
    'page callback' => '_spaces_preset_enable_page',
    'access callback' => 'user_access',
    'access arguments' => array(
      'administer spaces',
    ),
    '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 "<p>" . t('You can use exported presets in your modules by returning an array of presets in <code>hook_spaces_presets()</code>.') . "</p>";
  }
}

/**
 *  Implementation of hook_theme().
 */
function spaces_theme() {
  $items = array();
  $items['spaces_form'] = array();
  $items['spaces_features_form'] = array();
  $items['spaces_customize_item'] = array();
  $items['spaces_preset_default_form'] = array();
  $items['spaces_form_presets'] = array();
  $items['spaces_block_customizer_settings_form'] = array();
  return $items;
}

/**
 * Implementation of hook_theme_registry_alter().
 */
function spaces_theme_registry_alter(&$theme_registry) {
  if (!in_array('spaces_preprocess_page', $theme_registry['page']['preprocess functions'])) {
    $theme_registry['page']['preprocess functions'][] = 'spaces_preprocess_page';
  }
}

/**
 *  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) {
  switch ($op) {
    case 'view':
      if ($page && !$teaser && arg(0) == 'node' && arg(1) == $node->nid) {
        spaces_router('node view', $node);
      }
      break;
  }
}

/**
 * Implementation of hook_form_alter().
 */
function spaces_form_alter(&$form, $form_state, $form_id) {
  switch ($form_id) {
    case 'system_modules':
      spaces_menu_rebuild();
      break;

    // Blow away the customization form for the spaces menu -- we don't want users messing with this... for now.
    case 'menu_overview_form':
      if ($form['#menu']['menu_name'] == 'spaces') {
        $form = array();
        $form['message'] = array(
          '#type' => 'markup',
          '#value' => '<p>' . 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.') . '</p>',
        );
      }
      break;
    default:
      if ($form['#id'] == 'node-form' && arg(0) . '/' . arg(1) != 'admin/content') {
        spaces_router('node form', $form['#node']);
      }
      break;
  }
}

/**
 * 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_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(),
    'block' => new 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_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);
    }
  }
}

/**
 * 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;
}

/**
 * Implementation of hook_context_reactions().
 */
function spaces_context_reactions() {
  $type_options = array();
  foreach (spaces_types() as $type => $info) {
    $type_options[$type] = $info['title'];
  }
  $items = array();
  $items['spaces'] = array(
    '#title' => t('Spaces feature'),
    '#tree' => 'true',
    'label' => array(
      '#title' => t('Label'),
      '#description' => t('The name of your feature.'),
      '#type' => 'textfield',
    ),
    'description' => array(
      '#title' => t('Description'),
      '#description' => t('A short description to display with your feature.'),
      '#type' => 'textfield',
    ),
    // Support only 1 menu item currently
    'menu' => array(
      '#tree' => TRUE,
      0 => array(
        '#tree' => TRUE,
        'title' => array(
          '#title' => t('Menu link'),
          '#description' => t('The link text for your feature.'),
          '#type' => 'textfield',
        ),
        'href' => array(
          '#title' => t('Menu path'),
          '#description' => t('The path to your feature. <strong>Example:</strong> blog'),
          '#type' => 'textfield',
        ),
      ),
    ),
    'types' => array(
      '#title' => t('Enabled types'),
      '#description' => t('Choose the space types for which this feature can be enabled. Leave empty to allow this feature to be used with any space type.'),
      '#type' => 'checkboxes',
      '#options' => $type_options,
      '#multiple' => TRUE,
    ),
  );
  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);
  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'] && isset($space->features[$f]) && $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'])) {
      $feature_menu = array();

      // Get customized values
      $feature_menu = $f->spaces['menu'];
      $feature_menu = $this
        ->customize($space, $feature, $feature_menu);
      foreach ($feature_menu as $key => $item) {
        $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'],
        );
      }
    }
    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) {
      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;
  }

}

/**
 * Customizer for feature blocks.
 */
class space_customizer_block implements space_customizer {
  var $name = 'Blocks';

  /**
   * Implementation of form().
   */
  function form($space, $feature) {
    $form = array(
      '#theme' => 'spaces_block_customizer_settings_form',
      '#tree' => TRUE,
    );
    $features = spaces_features();
    $f = $features[$feature];
    $customizer = !empty($space->customizer[$feature]['block']) ? $space->customizer[$feature]['block'] : array();
    $info = array();
    global $theme_key;
    init_theme();
    $regions = system_region_list($theme_key);
    if (!empty($f->block)) {
      foreach ($f->block as $block) {

        // @TODO: remove this once context is less confused about itself.
        $block = (array) $block;
        $bid = "{$block['module']}-{$block['delta']}";
        if (!empty($block['region'])) {
          if (!isset($info[$block['module']])) {
            $info[$block['module']] = module_invoke($block['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 ($block['module'] == 'views') {
              foreach ($info[$block['module']] as $k => $v) {
                $viewname = strpos($v['info'], ':');
                if ($viewname !== FALSE) {
                  $v['info'] = substr($v['info'], $viewname + 2);
                  $info[$block['module']][$k] = $v;
                }
              }
            }
          }

          // Sanity check that this region exists
          $region = $block['region'];
          if (!empty($regions[$region])) {
            if (!isset($form[$region])) {
              $form[$region] = array(
                '#title' => $regions[$region],
                '#tree' => TRUE,
              );
            }
            $block_details = module_invoke($block['module'], 'block', 'view', $block['delta']);
            if (!empty($info[$block['module']][$block['delta']])) {
              $default_weight = isset($block['weight']) ? $block['weight'] : 0;
              $default_status = isset($block['status']) ? $block['status'] : 1;
              $default_subject = !empty($block_details['subject']) ? $block_details['subject'] : '';
              $form[$region][$bid] = array(
                '#tree' => TRUE,
                '#weight' => isset($customizer[$region][$bid]['weight']) ? $customizer[$region][$bid]['weight'] : $default_weight,
              );
              $form[$region][$bid]['weight'] = array(
                '#type' => 'weight',
                '#delta' => 25,
                '#default_value' => isset($customizer[$region][$bid]['weight']) ? $customizer[$region][$bid]['weight'] : $default_weight,
              );
              $form[$region][$bid]['status'] = array(
                '#type' => 'checkbox',
                '#default_value' => isset($customizer[$region][$bid]['status']) ? $customizer[$region][$bid]['status'] : $default_status,
              );

              // 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($customizer[$region][$bid]['subject']) ? $customizer[$region][$bid]['subject'] : $default_subject,
                  '#description' => $info[$block['module']][$block['delta']]['info'],
                );
              }
              else {
                $form[$region][$bid]['subject'] = array(
                  '#type' => 'markup',
                  '#value' => $info[$block['module']][$block['delta']]['info'],
                );
              }

              // Pass default values to the submit handler so it can omit them.
              $form[$region][$bid]['default_weight'] = array(
                '#type' => 'value',
                '#value' => $default_weight,
              );
              $form[$region][$bid]['default_status'] = array(
                '#type' => 'value',
                '#value' => $default_status,
              );
              $form[$region][$bid]['default_subject'] = array(
                '#type' => 'value',
                '#value' => $default_subject,
              );
            }
          }
        }
      }
    }
    return $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) {
    foreach ($value as $region => $blocks) {
      foreach ($blocks as $bid => $block) {
        foreach (array(
          'weight',
          'status',
          'subject',
        ) as $key) {
          if (isset($block[$key])) {
            if ($block[$key] == $block["default_{$key}"]) {
              unset($value[$region][$bid][$key]);
            }
          }
          unset($value[$region][$bid]["default_{$key}"]);
        }
      }
    }
    return $value;
  }

  /**
   * Implementation of customize().
   */
  function customize($space, $feature, &$block = NULL) {

    // Unset disabled blocks and change weights based on customizer settings. See spaces_context_active_contexts_alter.
    if (!empty($space->customizer[$feature]['block'])) {
      $customizer = $space->customizer[$feature]['block'];
      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, $feature, &$block) {
    if (!empty($space->customizer[$feature]['block'])) {
      $customizer = $space->customizer[$feature]['block'];
      $bid = "{$block->module}-{$block->delta}";
      if (!empty($customizer[$block->region][$bid]['subject'])) {
        $block->subject = $customizer[$block->region][$bid]['subject'];
      }
    }
  }

}

/**
 * Implementation of hook_context_active_contexts_alter().
 */
function spaces_context_active_contexts_alter(&$contexts) {
  $space = spaces_get_space();
  foreach ($contexts as $context) {
    if ($context->block && $context->attribute == 'feature') {
      $feature = $context->value;
      space_customizer_block::customize($space, $feature, $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->spaces['settings'])) {
        foreach ($feature->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();
    $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;
    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(),
    );
    foreach (context_enabled_contexts('spaces') as $feature) {
      if (!empty($feature->spaces)) {
        if (!empty($feature->spaces['types'])) {
          foreach ($feature->spaces['types'] as $t) {
            $spaces_features[$t][$feature->value] = $feature;
          }
        }
        else {
          $spaces_features['common'][$feature->value] = $feature;
        }
        $spaces_features['all'][$feature->value] = $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 $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(),
    );
    foreach (module_implements('spaces_settings') as $module) {
      $function = $module . '_spaces_settings';
      $settings = $function();
      foreach ($settings as $setting) {
        if (!empty($setting->types)) {
          foreach ($setting->types as $t) {
            if (!isset($spaces_settings[$t])) {
              $spaces_settings[$t] = array();
            }
            $spaces_settings[$t][$setting->id] = $setting;
          }
        }
        else {
          $spaces_settings['common'][$setting->id] = $setting;
        }
        $spaces_settings['all'][$setting->id] = $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 $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 $key => $item) {

        // Use the array key as the menu path if not set explicitly --
        // should allow backwards compatibility with old feature
        // defintions.
        $path = !empty($item['href']) ? $item['href'] : $key;

        // 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($space = NULL, $reset = FALSE) {
  static $cache;
  $cache = !isset($cache) ? array() : $cache;
  $space = empty($space) ? spaces_get_space() : $space;

  // Sanity check on our space object.
  if ($space && !empty($space->sid)) {

    // Cache by space type
    $cache[$space->type] = !isset($cache[$space->type]) ? array() : $cache[$space->type];
    if (!isset($cache[$space->type][$space->sid]) || $reset) {

      // 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
      $menu_cache = cache_get('spaces_menu');
      $menu_cache = !$menu_cache ? spaces_menu_rebuild() : $menu_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 = $menu_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');
      $cache[$space->type][$space->sid] = $menu;
    }
    if (!empty($cache[$space->type][$space->sid])) {
      return $cache[$space->type][$space->sid];
    }
  }
  return array();
}

/**
 * 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;
  if ($space = spaces_get_space()) {
    if (isset($type)) {
      if ($space->type == $type && $space
        ->admin_access()) {
        $return = true;
      }
    }
    else {
      if ($space
        ->admin_access()) {
        $return = true;
      }
    }
  }
  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 ($space = spaces_get_space()) {
    if (!$space
      ->feature_access($feature)) {
      return false;
    }
    if (user_access('access content')) {
      return true;
    }
  }
  return false;
}

/**
 * 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,
      'features',
    ),
    '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()) {
    $space
      ->links($links);
    drupal_alter('space_links', $links);
  }
  return $links;
}

/**
 * Implementation of hook_context_links_alter().
 */

/*
function spaces_context_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 theming for the block customizer settings form.
 */
function theme_spaces_block_customizer_settings_form($form) {

  // Add draggable weights
  drupal_add_js('misc/tableheader.js');
  $output = '';

  // List of block regions that should force an empty display
  $force_empty = array(
    'content',
  );
  global $theme_key;
  init_theme();
  $regions = system_region_list($theme_key);
  foreach ($force_empty as $region) {
    if (empty($form[$region]) && !empty($regions[$region])) {
      $output .= "<div class='region-{$region}'>";
      $output .= "<h3>{$regions[$region]}</h3>";
      $output .= "<div class='spaces-empty'>" . t('There are no options available for this region.') . "</div>";
      $output .= "</div>";
    }
  }
  foreach (element_children($form) as $a) {
    drupal_add_tabledrag('spaces-customizer-blocks-' . $a, 'order', 'sibling', 'block-weight');
    $rows = array();
    uasort($form[$a], 'element_sort');
    foreach (element_children($form[$a]) as $b) {
      $form[$a][$b]['weight']['#attributes'] = array(
        'class' => 'block-weight',
      );
      $row = array(
        'dummy' => '',
        'status' => drupal_render($form[$a][$b]['status']),
        'title' => array(
          'data' => drupal_render($form[$a][$b]['subject']),
          'class' => 'fill',
        ),
        'weight' => drupal_render($form[$a][$b]['weight']),
      );
      $rows[] = array(
        'data' => $row,
        'class' => 'draggable',
      );
    }
    $output .= "<div class='region-{$a}'>";
    $output .= "<h3>{$form[$a]['#title']}</h3>";
    $output .= theme('table', array(), $rows, array(
      'id' => 'spaces-customizer-blocks-' . $a,
    ));
    $output .= "</div>";
  }
  $output .= drupal_render($form);
  return $output;
}

/**
 * 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() {
  $links = context_links();
  $block['content'] = theme('context_links', $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;
}

/**
 * theme_block() preprocessor.
 */
function spaces_preprocess_block(&$vars) {
  $space = spaces_get_space();
  if ($feature = context_get('spaces', 'feature')) {
    if (!empty($vars['block'])) {
      space_customizer_block::customize_subject($space, $feature, $vars['block']);
    }
  }
}

Functions

Namesort descending Description
spaces_access_denied Menu callback for early access_denied calls. Going through the a menu callback allows Drupal bootstrap to complete.
spaces_active_space_menu 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.
spaces_admin_access Menu admin access wrapper.
spaces_block Implementation of hook_block()
spaces_content_types Returns a content type => features map.
spaces_context_active_contexts_alter Implementation of hook_context_active_contexts_alter().
spaces_context_conditions Implementation of hook_context_conditions().
spaces_context_reactions Implementation of hook_context_reactions().
spaces_customizers Retrieve all available customizers.
spaces_delete Deletes a space object's records in the database.
spaces_features Retrieve all available features.
spaces_features_menu 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.
spaces_feature_access Menu feature access wrapper.
spaces_form_alter Implementation of hook_form_alter().
spaces_form_presets Preset options form that can be reused by implementing modules.
spaces_get_space Wrapper function around spaces_set_space(). Retrieves the current active space.
spaces_help Implementation of hook_help().
spaces_init Implementation of hook_init().
spaces_init_context Context prefix provider callback.
spaces_load Load a space.
spaces_menu Implementation of hook_menu().
spaces_menu_rebuild Rebuilds the spaces menu stored in the menu_links tables via the menu API.
spaces_nodeapi Implementation of hook_nodeapi().
spaces_perm Implementation of hook_perm().
spaces_preprocess_block theme_block() preprocessor.
spaces_preprocess_page
spaces_presets Gather an array of spaces presets from the DB.
spaces_preset_enforce Enforces a spaces preset.
spaces_purl_provider Implementation of hook_purl_provider().
spaces_router Wrapper around implementations of $space->router. Provides additional intelligence, including a global killswitch and routing when no spaces are active.
spaces_router_get Sets a static variable that is used to disable spaces routing altogether -- e.g. for install/update scripts, migrations, etc.
spaces_router_set Wrapper around spaces_router_get().
spaces_save Saves a space object's feature/setting values.
spaces_settings Retrieve all available settings.
spaces_set_space Sets the specified space as the current active space. Returns the active space if no space is provided.
spaces_spaces_customizers Implementation of hook_spaces_customizers().
spaces_spaces_settings Implementation of hook_spaces_settings().
spaces_space_links Generates an array of utility links for the current space suitable for use in theme_links().
spaces_theme Implementation of hook_theme().
spaces_theme_registry_alter Implementation of hook_theme_registry_alter().
spaces_types Invokes hook_spaces_types() to gather an array of space types and associated classes.
spaces_user Implementation of hook_user().
spaces_views_api Implementation of hook_views_api().
spaces_views_pre_query Implementation of hook_views_pre_query().
theme_spaces_block_customizer_settings_form Form theming for the block customizer settings form.
theme_spaces_form_presets Form theme function for spaces presets.
_spaces_block_nav Default navigation menu - you can create your own customized version using the spaces_features_menu() and theme_links() functions.
_spaces_block_tools Contextual tools
_spaces_block_utility_links Utility links

Constants

Classes

Namesort descending Description
space_customizer_block Customizer for feature blocks.
space_customizer_menu Customizer for feature menus.
space_customizer_views Customizer for views titles and headers.
space_setting_home Provides a homepage setting for each space.

Interfaces

Namesort descending Description
space Interface for space objects.
space_customizer Interface for space customizers.
space_setting Interface for space settings.