You are here

party_hat.module in Party 8.2

Same filename and directory in other branches
  1. 7 modules/party_hat/party_hat.module

party_hat.module Provides an extensible access system for parties.

File

modules/party_hat/party_hat.module
View source
<?php

/**
 * @file party_hat.module
 * Provides an extensible access system for parties.
 */

/**
 * Implements hook_menu().
 */
function party_hat_menu() {

  // Extend the normal party/add page to accept random arguments.
  $items['party/add/%'] = array(
    'title callback' => 'party_hat_party_add_title',
    'title arguments' => array(
      2,
    ),
    'page callback' => 'party_hat_party_add_form_wrapper',
    'page arguments' => array(
      2,
    ),
    'file' => 'party_hat.pages.inc',
    'access arguments' => array(
      'create parties',
    ),
  );
  return $items;
}

/**
 * Implements hook_entity_info().
 */
function party_hat_entity_info() {

  // The hat entity.
  $entity_info['party_hat'] = array(
    'label' => t('Hat'),
    'entity class' => 'PartyHat',
    'controller class' => 'PartyHatController',
    // We define this so entity module provides us with basic Views data, while
    // allowing us to define more of our own.
    // See http://drupal.org/node/1307760.
    'views controller class' => 'EntityDefaultViewsController',
    'base table' => 'party_hat',
    'fieldable' => TRUE,
    'module' => 'party_hat',
    'entity keys' => array(
      'id' => 'hid',
      'label' => 'label',
      'name' => 'name',
    ),
    'exportable' => TRUE,
    'access callback' => 'party_hat_access',
    'uri callback' => 'party_hat_uri',
    'label callback' => 'entity_class_label',
    'admin ui' => array(
      'controller class' => 'PartyHatUIController',
      'path' => 'admin/community/hats',
      'file' => 'party_hat.admin.inc',
    ),
    'bundles' => array(
      'hat' => array(
        'label' => t('Hat'),
        'admin' => array(
          'path' => 'admin/community/hats',
          'access arguments' => array(
            'administer hats',
          ),
        ),
      ),
    ),
    'view modes' => array(
      'full' => array(
        'label' => t('Full Hat'),
        'custom settings' => FALSE,
      ),
    ),
  );
  return $entity_info;
}

/**
 * Implements hook_entity_info_alter().
 */
function party_hat_entity_info_alter(&$entity_info) {

  // Set the UI controller class for parties to our own to add hats into the UI.
  $entity_info['party']['admin ui']['controller class'] = 'PartyHatPartyUIController';
}

/**
 * Entity access callback.
 *
 * @param $op
 *   The operation being performed.
 * @param $party
 *   A party to check access for.
 * @param $account
 *   (optional) The user to check access for. Omit to check for the global user.
 *
 * @return
 *   Boolean; TRUE to grant access, FALSE to deny it.
 *
 * @see party_access
 */
function party_hat_access($op, $party = NULL, $account = NULL) {
  return user_access('administer hats');
}

/**
 * URI callback for hats.
 */
function party_hat_uri($party_hat) {

  // Uses the machine name, since these are exportables.
  return array(
    'path' => 'admin/community/hats/manage/' . $party_hat->name,
  );
}

/**
 * Implements hook_permission().
 */
function party_hat_permission() {
  return array(
    'administer hats' => array(
      'title' => t('Administer party hats'),
      'description' => t('TODO write me.'),
    ),
  );
}

// -----------------------------------------------------------------------
// Party Hats API

/**
 * Save a hat.
 *
 * @param $hat a party_hat object.
 */
function party_hat_save(&$hat) {
  return entity_get_controller('party_hat')
    ->save($hat);
}

/**
 * Load a hat.
 *
 * @param $hat_name
 *   The machine name of the hat to load.
 *
 * @return
 *  A hat entity object.
 */
function party_hat_load($hat_name) {
  $party_hats = entity_load_multiple_by_name('party_hat', array(
    $hat_name,
  ));
  return reset($party_hats);
}

/**
 * Load many hats.
 */
function party_hat_load_multiple($hids = array(), $conditions = array(), $reset = FALSE) {
  return entity_load('party_hat', $hids, $conditions, $reset);
}

/**
 * Set hat data set rules into a hat.
 *
 * This should be used rather than stuffing the data set rules directly into
 * the hat, as it removes any data sets the hat doesn't grant from the array, so
 * the hat has no superflous items.
 *
 * Note the hat will still require saving.
 *
 * @todo: add a parameter to specify which data sets are being updated, if
 * needed??
 *
 * @param $hat
 *  The hat entity.
 * @param $values
 *  An array of data set rules, keyed by data set name. These are cleaned up
 *  prior to being set into the hat, to allow form values to be passed in here.
 */
function party_hat_set_data_set_rules(&$hat, $values) {
  $save_values = array();
  foreach ($values as $data_set_name => $data_set_values) {

    // Values are allowed to come in as 'key' => 'key', as saved by FormAPI
    // checkboxes. Clean these up.
    foreach ($data_set_values as $key => $value) {
      $data_set_values[$key] = (bool) $value;
    }

    // Only take data sets which are needed.
    if ($data_set_values['has']) {
      $save_values[$data_set_name] = $data_set_values;
    }
  }
  $hat->data['data_sets'] = $save_values;
}

/**
 * Get a hat data set rule.
 *
 * @param $hat
 *  The hat entity.
 * @param $data_set_name
 *  The data set name to get the rule for.
 *
 * @return
 *  @todo write me.
 */
function party_hat_get_data_set_rule($hat, $data_set_name) {
  if (isset($hat->data['data_sets'][$data_set_name])) {
    return $hat->data['data_sets'][$data_set_name];
  }
  else {

    // Return defaults.
    // @todo: needed here? These will get saved in the DB next time the hat is
    // saved anyway, so should go in the entity constructor rather than here.
    return array(
      'has' => 0,
      'multiple' => 0,
    );
  }
}

/**
 * Implements hook_party_data_sets_alter().
 */
function party_hat_party_data_sets_alter(&$party_data_sets, $party) {

  // Get the party_hats off this party.
  $party_hats = party_hat_get_hats($party);
  $hats_sets = array();

  // Collect all the data sets this party should have.
  foreach ($party_hats as $hat) {
    $hats_sets = array_merge($hats_sets, party_hat_get_hat_data_sets($hat));
  }

  // Remove data sets from party_data_sets that are not in the hats_sets array.
  foreach ($party_data_sets as $key => $data_set) {
    if (!in_array($data_set, $hats_sets)) {
      unset($party_data_sets[$key]);
    }
  }
}

/**
 * Implements hook_party_access().
 */
function party_hat_party_access($op, $party = NULL, $data_set = NULL, $account = NULL) {

  // We say nothing about access to just the party.
  if (!isset($data_set)) {
    return NULL;
  }
  switch ($op) {

    // View, attach, and add ops are the same: you can't view a party's data set
    // if you can't have it attached either.
    case 'attach':
    case 'add':
    case 'view':
      $data_set_name = $data_set['set_name'];
      $sets = party_get_party_data_sets($party);

      // If any hat has the data set, grant access.
      if (in_array($data_set_name, $sets)) {
        return TRUE;
      }
      return FALSE;
  }
}

/**
 * Get all the sets for a Hat.
 *
 * This returns an array of sets that a Party Must have or can have multiple of.
 *
 * @todo write params and return.
 */
function party_hat_get_hat_data_sets($hat) {

  // @todo: this is flawed anyway, as it will simply return ALL data sets.
  // @see http://drupal.org/node/1663370.
  return drupal_map_assoc(array_keys($hat->data['data_sets']));
}

/**
 * Get all the Hats a Party is Wearing
 *
 * @param $party
 *  The Party object or ID
 */
function party_hat_get_hats($party) {
  if (!is_object($party)) {
    $party = party_load($party);
  }
  $hats = array();
  $items = field_get_items('party', $party, 'party_hat');

  // If there are no hats set, lead the required ones.
  if (empty($items)) {
    $options = array(
      'required' => TRUE,
    );
    return party_hat_get_all_hats($options);
  }
  else {
    foreach ($items as $item) {
      $hat = party_hat_load($item['hat_name']);
      $hats[$hat->name] = $hat;
    }
  }
  return $hats;
}

/**
 * Get all available hats.
 *
 * @todo: Make filter by party type.
 */
function party_hat_get_all_hats($options = array()) {
  $query = new EntityFieldQuery();
  $query
    ->entityCondition('entity_type', 'party_hat', '=');
  if (isset($options['required']) && $options['required']) {

    // filter the search by whether they're required
    $query
      ->propertyCondition('required', 1, '=');
  }
  $result = $query
    ->execute();
  if (!isset($result['party_hat']) || !is_array($result['party_hat'])) {
    return array();
  }

  // Get the array keyed by hat name.
  $hats = array();
  foreach (entity_load('party_hat', array_keys($result['party_hat'])) as $hat) {
    $hats[$hat->name] = $hat;
  }
  return $hats;
}

/**
 * Build a hierarchical representation of hats.
 *
 * @param $parent
 *   (optional) A $hat entity. If set, the subtree of this hat is returned.
 *
 * @return
 *   An array of PartyHat entities that are children of $parent, in depth-first
 *   order. The hat objects are augmented with the following properties:
 *    - 'depth': the depth relative to the $parent
 *    - 'parents': an array of the hat's parents.
 */
function party_hat_get_tree($parent = '', $max_depth = NULL) {
  $children =& drupal_static(__FUNCTION__, array());
  $parents =& drupal_static(__FUNCTION__ . ':parents', array());
  $hats =& drupal_static(__FUNCTION__ . ':hats', array());

  // We cache trees, so it's not CPU-intensive to call get_tree() on a term
  // and its children, too.
  if (empty($children)) {
    $children = array();
    $parents = array();
    $hats = array();
    $hats = party_hat_get_all_hats();
    foreach ($hats as $hat) {

      // If the parent is empty, fill it with an empty string. This helps us
      // search from the very top of the tree and gets removed later.
      if (empty($hat->parent)) {
        $hat->parent = '';
      }
      $children[$hat->parent][] = $hat->name;
      $parents[$hat->name][] = $hat->parent;
    }
  }
  $max_depth = !isset($max_depth) ? count($children) : $max_depth;
  $tree = array();

  // Keeps track of the parents we have to process, the last entry is used
  // for the next processing step.
  $process_parents = array();
  $process_parents[] = is_object($parent) ? $parent->name : $parent;

  // Loops over the parent terms and adds its children to the tree array.
  // Uses a loop instead of a recursion, because it's more efficient.
  while (count($process_parents)) {
    $parent = array_pop($process_parents);

    // The number of parents determines the current depth.
    $depth = count($process_parents);
    if ($max_depth > $depth && !empty($children[$parent])) {
      $has_children = FALSE;
      $child = current($children[$parent]);
      do {
        if (empty($child)) {
          break;
        }
        $hat = $hats[$child];
        if (isset($parents[$hat->name])) {

          // Clone the term so that the depth attribute remains correct
          // in the event of multiple parents.
          // @todo Remove IF we're not allowing multiple parents.
          $hat = clone $hat;
        }
        $hat->depth = $depth;
        unset($hat->parent);

        // Filter out the empty string parents.
        $hat->parents = array_filter($parents[$hat->name]);
        $tree[$hat->name] = $hat;
        if (!empty($children[$hat->name])) {
          $has_children = TRUE;

          // We have to continue with this parent later.
          $process_parents[] = $parent;

          // Use the current hat as parent for the next iteration.
          $process_parents[] = $hat->name;

          // Reset pointers for child lists because we step in there more often
          // with multi parents.
          reset($children[$hat->name]);

          // Move pointer so that we get the correct hat the next time.
          next($children[$parent]);
          break;
        }
      } while ($child = next($children[$parent]));
      if (!$has_children) {

        // We processed all terms in this hierarchy-level, reset pointer
        // so that this function works the next time it gets called.
        reset($children[$parent]);
      }
    }
  }
  return $tree;
}

/**
 * Assign a number of hats to a party
 *
 * @param $party
 * @param $hats
 *   An array of hat machine names to assign.
 */
function party_hat_hats_assign($party, $hats) {

  // Get the hat items from the party object
  $hat_items = field_get_items('party', $party, 'party_hat');

  // If there are no hats, set an empty array.
  if (empty($hat_items)) {
    $hat_items = array();
  }
  foreach ($hats as $hat) {
    $has_hat = FALSE;
    foreach ($hat_items as $item) {
      if ($item['hat_name'] == $hat) {
        $has_hat = TRUE;
      }
    }

    // Don't add the hat if the Party already has it
    if ($has_hat) {
      continue;
    }
    $assigned_hats[] = $hat;
    $hat_items[]['hat_name'] = $hat;
  }
  $party->party_hat[LANGUAGE_NONE] = $hat_items;
  party_save($party);
  if (isset($assigned_hats)) {
    if (module_exists('rules')) {
      rules_invoke_all('party_hat_assign_hats', $party, $assigned_hats);
    }
    else {
      module_invoke_all('party_hat_assign_hats', $party, $assigned_hats);
    }
  }
}

/**
 * Unassign hats
 *
 * @param $party
 * @param $hats
 *   An array of hat maching names to unassign.
 */
function party_hat_hats_unassign($party, $hats) {

  // Get the hat items from the party object
  $hat_items = field_get_items('party', $party, 'party_hat');
  foreach ($hats as $hat) {
    $has_hat = FALSE;
    foreach ($hat_items as $delta => $item) {
      if ($item['hat_name'] == $hat) {
        $has_hat = TRUE;
        unset($hat_items[$delta]);
      }
    }

    // Don't remove the hat if its not assigned
    if (!$has_hat) {
      continue;
    }
    $unassigned_hats[] = $hat;
  }
  $party->party_hat[LANGUAGE_NONE] = $hat_items;
  party_save($party);
  if (isset($unassigned_hats)) {
    if (module_exists('rules')) {
      rules_invoke_all('party_hat_unassign_hats', $party, $unassigned_hats);
    }
    else {
      module_invoke_all('party_hat_unassign_hats', $party, $unassigned_hats);
    }
  }
}

/**
 * Implements hook_theme().
 */
function party_hat_theme($existing, $type, $theme, $path) {
  return array(
    'crm_hat_data_set_rules_form' => array(
      'render element' => 'form',
    ),
  );
}

// -----------------------------------------------------------------------
// Field type API

/**
 * Implements hook_field_info().
 */
function party_hat_field_info() {
  return array(
    'hat_reference' => array(
      'label' => t('Party hat reference'),
      'description' => t('Stores a reference to a party hat entity.'),
      'default_widget' => 'options_buttons',
      'default_formatter' => 'hat_reference_default',
      'module' => 'party_hat',
      'settings' => array(
        'entity_types' => array(
          'party',
        ),
      ),
      'instance_settings' => array(),
      'no_ui' => TRUE,
      'property_type' => 'party_hat',
      'property_callbacks' => array(
        'party_hat_entity_metadata_field_hat_reference_callback',
      ),
    ),
  );
}

/**
 * Implements hook_field_settings_form().
 */
function party_hat_field_settings_form($field, $instance, $has_data) {
  return array();
}

/**
 * Implements hook_field_validate().
 */
function party_hat_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
}

/**
 * Implements hook_field_widget_error().
 */
function party_hat_field_widget_error($element, $error, $form, &$form_state) {
}

/**
 * Implements hook_field_is_empty().
 */
function party_hat_field_is_empty($item, $field) {
  if (empty($item['hat_name'])) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Implements hook_field_widget_info_alter().
 */
function party_hat_widget_info_alter(&$info) {

  //Use options_select for the options
  $info['options_buttons']['field types'][] = 'hat_reference';
}

/**
 * Implements hook_options_list().
 */
function party_hat_options_list($field, $instance, $entity_type, $entity) {
  return party_hat_build_hat_options(NULL, NULL, 'edit');
}

/**
 * Build a set of hats for EntityMetadataWrapper::optionsList().
 *
 * If $op is edit, they are labeled to show heirachy with hyphens for indents.
 *
 * @return array
 *   An array of hat labels keyed by value.
 *
 * @see EntityMetadataWrapper::optionsList().
 */
function party_hat_build_hat_options($name, $info, $op) {
  $hats = party_hat_get_tree();

  // Add an indent if we're in edit mode to demonstrate heirachy.
  $indent = $op == 'edit' ? '-' : '';
  $options = array();
  foreach ($hats as $hat) {
    $options[$hat->name] = str_repeat($indent, $hat->depth) . $hat->label;
  }
  return $options;
}

/**
 * Implements hook_field_presave().
 */
function party_hat_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
}

/**
 * Implements hook_field_formatter_info().
 */
function party_hat_field_formatter_info() {
  return array(
    'hat_reference_default' => array(
      'label' => t('Default'),
      'field types' => array(
        'hat_reference',
      ),
    ),
  );
}

/**
 * Implements hook_field_formatter_view().
 */
function party_hat_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $element = array();
  switch ($display['type']) {
    case 'hat_reference_default':
      foreach ($items as $delta => $item) {
        $hat = party_hat_load($item['hat_name']);
        $element[$delta]['#markup'] = $hat->label;
      }
      break;
  }
  return $element;
}

/**
 * Get Required Hats for Field Values
 */
function party_hat_field_get_default($entity_type, $entity, $field, $instance, $langcode = NULL) {

  //only works for party at the moment.
  if ($entity_type != 'party') {
    return array();
  }

  //Get required hats.
  $options = array(
    'required' => TRUE,
  );
  $hats = party_hat_get_all_hats($options);
  $values = array();
  foreach ($hats as $hat) {
    $values[] = array(
      'hat_name' => $hat->name,
    );
  }
  return $values;
}

// ----------------------------------------------------------------------------
// Ctools Hooks

/**
 * Implements hook_ctools_plugin_directory().
 */
function party_hat_ctools_plugin_directory($owner, $plugin_type) {
  if ($owner == 'ctools' && $plugin_type == 'access') {
    return "plugins/{$plugin_type}";
  }
  if ($owner == 'ctools' && $plugin_type == 'contexts') {
    return "plugins/{$plugin_type}";
  }
}

// ----------------------------------------------------------------------------
// Form Alterations

/**
 * Additional validate handler to check party form for expected attached entities.
 *
 * This checks if all expected data sets are present and, if not, sends the user
 * back to the form with a nice message.
 */
function party_hat_party_form_hat_validate($form, $form_state) {
  $expected_data_sets = array();
  foreach (party_hat_get_hats($form_state['#party']) as $hid => $hat) {
    $expected_data_sets += party_hat_get_hat_data_sets($hat);
  }
  $attached_entities = $form_state['#attached_entities'];
  $existent_data_sets = array();
  foreach ($attached_entities as $attached_entity) {
    if (!in_array($attached_entity->data_set_name, $existent_data_sets)) {
      $existent_data_sets[] = $attached_entity->data_set_name;
    }
  }
  $missing_data_sets = array_diff($expected_data_sets, $existent_data_sets);
  foreach ($missing_data_sets as $data_set_name) {
    $data_set = party_get_data_set_info($data_set_name);
    form_set_error(NULL, t("Party %party is expected to have a %data-set which wasn't present. Please enter the data now.", array(
      '%party' => $form_state['#party']->label,
      '%data-set' => $data_set['label'],
    )));
  }
}

/**
 * Trigger Rules etc when a hat is assigned.
 */
function party_hat_party_form_hat_submit($form, &$form_state) {
  $party_unchanged = $form_state['#party_unchanged'];
  $party = $form_state['#party'];
  $old_hats = array_keys(party_hat_get_hats($party_unchanged));
  $hats = array_keys(party_hat_get_hats($party));
  $all_hats = array_merge($old_hats, $hats);
  $assigned_hats = array();
  $unassigned_hats = array();
  foreach (array_diff($old_hats, $hats) as $hat_name) {
    $unassigned_hats[] = $hat_name;
  }
  foreach (array_diff($hats, $old_hats) as $hat_name) {
    $assigned_hats[] = $hat_name;
  }
  unset($hats, $old_hats);

  //invoke hooks and rules to unassign hats
  if (!empty($unassigned_hats)) {
    if (module_exists('rules')) {
      rules_invoke_all('party_hat_unassign_hats', $party, $unassigned_hats);
    }
    else {
      module_invoke_all('party_hat_unassign_hats', $party, $unassigned_hats);
    }
  }

  // invoke hooks and rules to assign hats
  if (!empty($assigned_hats)) {
    if (module_exists('rules')) {
      rules_invoke_all('party_hat_assign_hats', $party, $assigned_hats);
    }
    else {
      module_invoke_all('party_hat_assign_hats', $party, $assigned_hats);
    }
  }
}

/**
 * Imlements hook_form_FORM_ID_alter.
 */
function party_hat_form_party_edit_form_alter(&$form, &$form_state) {
  $form['#validate'][] = 'party_hat_party_form_hat_validate';
  $form['#submit'][] = 'party_hat_party_form_hat_submit';
}

/**
 * Implements hook_facetapi_facet_info_alter().
 *
 * Change hat_reference fields to use a diferent value callback.
 *
 * @see search_api_facetapi_facetapi_facet_info_alter
 */
function party_hat_facetapi_facet_info_alter(&$facets, $search) {

  // Change the value callback for the party_hat property.
  foreach ($facets as &$facet) {

    // Only interested in party hat properties.
    if ($facet['field type'] != 'party_hat') {
      continue;
    }
    $facet['map options']['value callback'] = '_party_hat_facet_create_label';
    break;
  }
}

/**
 * Get the right mapping for party_hat facets
 */
function _party_hat_facet_create_label(array $values, array $search) {
  $entities = entity_load('party_hat', $values);
  $map = array();
  foreach ($entities as $id => $hat) {
    $label = entity_label('party_hat', $hat);
    if ($label) {
      $map[$hat->name] = $label;
    }
  }
  return $map;
}

/**
 * Callback to adapt the property info for party hat reference fields.
 *
 * @see entity_metadata_field_default_property_callback().
 * @see party_hat_field_info().
 */
function party_hat_entity_metadata_field_hat_reference_callback(&$info, $entity_type, $field, $instance, $field_type) {
  $property =& $info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
  $property['options list'] = 'party_hat_build_hat_options';
}

Functions

Namesort descending Description
party_hat_access Entity access callback.
party_hat_build_hat_options Build a set of hats for EntityMetadataWrapper::optionsList().
party_hat_ctools_plugin_directory Implements hook_ctools_plugin_directory().
party_hat_entity_info Implements hook_entity_info().
party_hat_entity_info_alter Implements hook_entity_info_alter().
party_hat_entity_metadata_field_hat_reference_callback Callback to adapt the property info for party hat reference fields.
party_hat_facetapi_facet_info_alter Implements hook_facetapi_facet_info_alter().
party_hat_field_formatter_info Implements hook_field_formatter_info().
party_hat_field_formatter_view Implements hook_field_formatter_view().
party_hat_field_get_default Get Required Hats for Field Values
party_hat_field_info Implements hook_field_info().
party_hat_field_is_empty Implements hook_field_is_empty().
party_hat_field_presave Implements hook_field_presave().
party_hat_field_settings_form Implements hook_field_settings_form().
party_hat_field_validate Implements hook_field_validate().
party_hat_field_widget_error Implements hook_field_widget_error().
party_hat_form_party_edit_form_alter Imlements hook_form_FORM_ID_alter.
party_hat_get_all_hats Get all available hats.
party_hat_get_data_set_rule Get a hat data set rule.
party_hat_get_hats Get all the Hats a Party is Wearing
party_hat_get_hat_data_sets Get all the sets for a Hat.
party_hat_get_tree Build a hierarchical representation of hats.
party_hat_hats_assign Assign a number of hats to a party
party_hat_hats_unassign Unassign hats
party_hat_load Load a hat.
party_hat_load_multiple Load many hats.
party_hat_menu Implements hook_menu().
party_hat_options_list Implements hook_options_list().
party_hat_party_access Implements hook_party_access().
party_hat_party_data_sets_alter Implements hook_party_data_sets_alter().
party_hat_party_form_hat_submit Trigger Rules etc when a hat is assigned.
party_hat_party_form_hat_validate Additional validate handler to check party form for expected attached entities.
party_hat_permission Implements hook_permission().
party_hat_save Save a hat.
party_hat_set_data_set_rules Set hat data set rules into a hat.
party_hat_theme Implements hook_theme().
party_hat_uri URI callback for hats.
party_hat_widget_info_alter Implements hook_field_widget_info_alter().
_party_hat_facet_create_label Get the right mapping for party_hat facets