party_hat.module in Party 7
Same filename and directory in other branches
party_hat.module Provides an extensible access system for parties.
File
modules/party_hat/party_hat.moduleView 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.'),
),
);
}
/**
* Implements hook_hook_info().
*
* @see party_hook_info().
*/
function party_hat_hook_info() {
// General hooks that are called frequently on operations or page builds.
$hooks['party_hat_assign_hats'] = array(
'group' => 'party',
);
$hooks['party_hat_unassign_hats'] = array(
'group' => 'party',
);
}
// -----------------------------------------------------------------------
// 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,
);
}
}
/**
* 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]);
}
}
}
/**
* 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']));
}
/**
* Determine whether a party has a given hat.
*
* @param Party $party
* The party to check.
* @param PartyHat|string $hat
* Party Hat or hat name.
*
* @return bool
* Returns True if the party has the hat and false otherwise.
*/
function party_hat_has_hat($party, $hat) {
if (is_object($hat)) {
$hat = $hat->name;
}
if (empty($party->party_hat[LANGUAGE_NONE])) {
return FALSE;
}
foreach ($party->party_hat[LANGUAGE_NONE] as $item) {
if ($item['hat_name'] == $hat) {
return TRUE;
}
}
return FALSE;
}
/**
* 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) {
if ($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.
* @param boolean $save
* (default: True) If true, save the party and trigger rules after assigning
* hats.
*/
function party_hat_hats_assign($party, $hats, $save = TRUE) {
// 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;
// Stop here if we're not saving the party.
if (!$save) {
return;
}
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.
* @param boolean $save
* (default: True) If true, save the party and trigger rules after
* unassigning hats.
*/
function party_hat_hats_unassign($party, $hats, $save = TRUE) {
// 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;
// Stop here if we're not saving the party.
if (!$save) {
return;
}
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';
}