3
0
Fork 0
mirror of https://github.com/farmOS/farmOS.git synced 2024-02-23 11:37:38 +01:00
farmOS/modules/farm/farm_group/farm_group.module

976 lines
32 KiB
Text

<?php
/**
* @file
* Code for the Farm Group feature.
*/
include_once 'farm_group.features.inc';
/**
* Implements hook_help().
*/
function farm_group_help($path, $arg) {
if ($path == 'farm/assets/groups') {
return t('Groups are a special asset type that can be used to organize other assets. For more information, see the <a href="@groups_doc_url">Groups</a> documentation.', array('@groups_doc_url' => url('https://farmOS.org/guide/assets/groups')));
}
}
/**
* Implements hook_farm_ui_entities().
*/
function farm_group_farm_ui_entities() {
return array(
'farm_asset' => array(
'group' => array(
'label' => t('Group'),
'label_plural' => t('Groups'),
'view' => 'farm_groups',
),
),
);
}
/**
* Implements hook_restws_field_collection_info().
*/
function farm_group_restws_field_collection_info() {
return array(
'field_farm_membership' => array(
'alias' => 'membership',
'label' => t('Group membership'),
'fields' => array(
'group' => array(
'field_name' => 'field_farm_group',
'field_label' => t('Group'),
'field_type' => 'farm_asset',
'field_value' => 'target_id',
'multiple' => TRUE,
),
),
),
);
}
/**
* Implements hook_farm_ui_entity_views().
*/
function farm_group_farm_ui_entity_views($entity_type, $bundle, $entity) {
$views = array();
// Add Views to assets.
if ($entity_type == 'farm_asset') {
// Add group membership log View at the bottom of assets.
$views[] = array(
'name' => 'farm_group_log',
'group' => 'logs_special',
'weight' => 100,
);
// If the asset is a group, add View of group members.
if ($bundle == 'group') {
$views[] = array(
'name' => 'farm_group_members',
'display' => 'page',
'title' => t('Group members'),
'group' => 'assets',
'weight' => -100,
'always' => TRUE,
);
}
}
return $views;
}
/**
* Implements hook_entity_view_alter().
*/
function farm_group_entity_view_alter(&$build, $type) {
// If it's not a farm_asset, or if the entity object is not available, bail.
if ($type != 'farm_asset' || empty($build['#entity'])) {
return;
}
// Get the asset's group membership.
$membership = farm_group_asset_membership($build['#entity']);
// If no group membership information was found, bail.
if (empty($membership)) {
return;
}
// Start an output string.
$output = '<strong>' . t('Group membership') . ':</strong> ';
// Iterate through the group memberships and add links to them.
$group_links = array();
foreach ($membership as $group) {
$uri = entity_uri('farm_asset', $group);
if (!empty($uri['path'])) {
$group_links[] = l(entity_label('farm_asset', $group), $uri['path']);
}
}
$output .= implode(', ', $group_links);
// Add it to the build array.
$build['group'] = array(
'#markup' => $output,
'#prefix' => '<div class="group-membership">',
'#suffix' => '</div>',
'#weight' => -110,
);
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function farm_group_form_log_form_alter(&$form, &$form_state, $form_id) {
// If this log form contains field_farm_membership, add validation.
if (!empty($form['field_farm_membership'])) {
$form['#validate'][] = 'farm_group_field_farm_membership_validate';
}
}
/**
* Validation callback for the field_farm_group field in logs.
*/
function farm_group_field_farm_membership_validate($form, &$form_state) {
// If there are no groups referenced in the log, bail.
if (empty($form_state['values']['field_farm_membership'][LANGUAGE_NONE][0]['field_farm_group'][LANGUAGE_NONE])) {
return;
}
// If there are no assets referenced in the log, warn the user.
if (empty($form_state['values']['field_farm_asset'][LANGUAGE_NONE])) {
form_set_error('field_farm_asset', t('No asset(s) have been selected to become members of the group(s). Please select asset(s) or remove the group(s).'));
}
// Build an array of asset IDs.
$asset_ids = array();
foreach ($form_state['values']['field_farm_asset'][LANGUAGE_NONE] as $reference) {
if (!empty($reference['target_id'])) {
$asset_ids[] = $reference['target_id'];
}
}
// Build an array of group IDs.
$group_ids = array();
foreach ($form_state['values']['field_farm_membership'][LANGUAGE_NONE][0]['field_farm_group'][LANGUAGE_NONE] as $reference) {
if (!empty($reference['target_id'])) {
$group_ids[] = $reference['target_id'];
}
}
// Validate the asset IDs and group IDs to prevent circular memberships.
// If an issue is found, flag the 'field_farm_asset' element in the form.
farm_group_circular_membership_validate($asset_ids, $group_ids, 'field_farm_asset');
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function farm_group_form_farm_asset_form_alter(&$form, &$form_state, $form_id) {
// Get the farm asset entity from the form.
$asset = $form['farm_asset']['#value'];
// Get a list of active groups.
$active_groups = farm_group_options();
// Get the asset's current group membership.
$membership = farm_group_asset_membership($asset);
// Build a list of default options for the group select list below.
$current_groups = array();
if (!empty($membership)) {
foreach ($membership as $group) {
if (!empty($group->id)) {
$current_groups[$group->id] = $group->id;
}
}
}
// Add a field for assigning group membership.
$form['group'] = array(
'#type' => 'fieldset',
'#title' => t('Group membership'),
'#description' => t('Set the current group membership for this asset. An observation log will be created automatically that assigns the new membership.'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#weight' => 100,
'#tree' => TRUE,
);
// If there are active groups available, show a multi-select field.
if (!empty($active_groups)) {
$form['group']['group'] = array(
'#type' => 'select',
'#title' => t('Select group(s)'),
'#multiple' => TRUE,
'#options' => $active_groups,
'#default_value' => $current_groups,
);
}
// Add a field for creating a new group.
$form['group']['create'] = array(
'#type' => 'textfield',
'#title' => t('Create a new group'),
'#description' => t('Optionally enter the name of a new group to be created. The assets will become members of this group.'),
);
// Add validate and submit functions and put the fieldset into the general
// field group.
$form['actions']['submit']['#validate'][] = 'farm_group_asset_form_validate';
$form['actions']['submit']['#submit'][] = 'farm_group_asset_form_submit';
$form['#group_children']['group'] = 'group_farm_general';
}
/**
* Validation handler for processing the asset group field.
*
* @param array $form
* The form array.
* @param array $form_state
* The form state array.
*/
function farm_group_asset_form_validate(array $form, array &$form_state) {
// Only proceed if the group multiselect has a value, and there is an asset.
if (empty($form_state['values']['group']['group']) || empty($form_state['values']['farm_asset'])) {
return;
}
// Grab the asset ID.
$asset = $form_state['values']['farm_asset'];
$asset_ids[] = $asset->id;
// Get selected group IDs.
$group_ids = $form_state['values']['group']['group'];
// Validate the asset IDs and group IDs to prevent circular memberships.
// If an issue is found, flag the 'field_farm_asset' element in the form.
farm_group_circular_membership_validate($asset_ids, $group_ids, 'group][group');
}
/**
* Submit handler for processing the asset group field.
*
* @param array $form
* The form array.
* @param array $form_state
* The form state array.
*/
function farm_group_asset_form_submit(array $form, array &$form_state) {
// Only proceed if the group multiselect has a value, or a new group is
// being created.
if (empty($form_state['values']['group']['group']) && empty($form_state['values']['group']['create'])) {
return;
}
// If no groups are being created, and the selected groups match the
// default value (meaning nothing is changing), bail.
if (empty($form_state['values']['group']['create']) && ($form_state['values']['group']['group'] == $form['group']['group']['#default_value'])) {
return;
}
// If an asset doesn't exist, bail.
if (empty($form_state['values']['farm_asset'])) {
return;
}
// Grab the asset.
$asset = $form_state['values']['farm_asset'];
// Load the selected groups.
$groups = array();
if (!empty($form_state['values']['group']['group'])) {
$groups = farm_asset_load_multiple($form_state['values']['group']['group']);
}
// If a new group needs to be created, create it and add it to the list.
if (!empty($form_state['values']['group']['create'])) {
// Build the new group.
$values = array(
'name' => check_plain($form_state['values']['group']['create']),
'type' => 'group',
);
$new_group = entity_create('farm_asset', $values);
// Save the group and print a message.
farm_asset_save($new_group);
// Print a message.
$label = entity_label('farm_asset', $new_group);
$uri = entity_uri('farm_asset', $new_group);
drupal_set_message(t('Group created:') . ' ' . l($label, $uri['path']));
// Add the group to the array.
$groups[] = $new_group;
}
// Create an observation log to set the group membership.
farm_group_membership_set($asset, $groups);
}
/**
* Implements hook_action_info().
*/
function farm_group_action_info() {
return array(
'farm_group_asset_membership_action' => array(
'type' => 'farm_asset',
'label' => t('Group'),
'configurable' => TRUE,
'triggers' => array('any'),
'aggregate' => TRUE,
),
);
}
/**
* Configuration form for farm_group_asset_membership action.
*
* @param array $context
* The context passed into the action form function.
* @param array $form_state
* The form state passed into the action form function.
*
* @return array
* Returns a form array.
*/
function farm_group_asset_membership_action_form(array $context, array $form_state) {
// Date field.
$form['date'] = array(
'#type' => 'date_select',
'#title' => t('Date'),
'#date_format' => 'M j Y',
'#date_type' => DATE_FORMAT_UNIX,
'#date_year_range' => '-10:+3',
'#default_value' => date('Y-m-d H:i', REQUEST_TIME),
'#required' => TRUE,
);
// Group reference field.
$form['groups'] = array(
'#type' => 'select',
'#title' => t('Group'),
'#options' => farm_group_options(),
'#required' => TRUE,
'#multiple' => TRUE,
);
// Done field.
$form['done'] = array(
'#type' => 'checkbox',
'#title' => t('This membership change has taken place (mark the log as done)'),
'#default_value' => TRUE,
);
// Return the form.
return $form;
}
/**
* Validation handler for farm_group_asset_membership action configuration form.
*
* @param array $form
* The form array.
* @param array $form_state
* The form state array.
*/
function farm_group_asset_membership_action_validate(array $form, array $form_state) {
// Get the asset IDs.
$asset_ids = array_values($form_state['selection']);
// Get the group IDs.
$group_ids = $form_state['values']['groups'];
// Validate the asset IDs and group IDs to prevent circular memberships.
// If an issue is found, flag the 'groups' element in the form.
farm_group_circular_membership_validate($asset_ids, $group_ids, 'groups');
}
/**
* Submit handler for farm_group_asset_membership action configuration form.
*
* @param array $form
* The form array.
* @param array $form_state
* The form state array.
*
* @return array
* Returns an array that will end up in the action's context.
*/
function farm_group_asset_membership_action_submit(array $form, array $form_state) {
// Start to build the context array.
$context = array();
// Load the groups.
$context['groups'] = farm_asset_load_multiple($form_state['values']['groups']);
// Convert the date to a timestamp.
$timestamp = strtotime($form_state['values']['date']);
// The action form only includes month, day, and year. If the event is today,
// then we assume that the current time should also be included.
if (date('Ymd', $timestamp) == date('Ymd', REQUEST_TIME)) {
$context['timestamp'] = REQUEST_TIME;
}
// Otherwise, the event is in the past/future, so don't include a time.
else {
$context['timestamp'] = $timestamp;
}
// Copy the "done" value as a boolean.
$context['done'] = !empty($form_state['values']['done']) ? TRUE : FALSE;
// Return the context array.
return $context;
}
/**
* Action function for farm_group_asset_membership.
*
* Creates a new group membership observation log for the specified assets.
*
* @param array $assets
* An array of asset entities to change membership of.
* @param array $context
* Array with parameters for this action.
*/
function farm_group_asset_membership_action(array $assets, $context = array()) {
// If we're missing assets, areas, or a timestamp, bail.
if (empty($assets) || empty($context['groups']) || empty($context['timestamp'])) {
drupal_set_message('Could not perform membership change because required information was missing.', 'error');
return;
}
// Create a group membership observation log.
farm_group_membership_set($assets, $context['groups'], $context['timestamp'], 'farm_observation', $context['done']);
}
/**
* Build a list of group options for use in form select fields.
*
* @param bool $archived
* Whether or not to include archived groups. Defaults to FALSE. If TRUE,
* both active and archived groups will be included in the list.
*
* @return array
* Returns an array of groups for use in a form.
*/
function farm_group_options($archived = FALSE) {
// Start an empty options array.
$options = array();
// Build an entity field query of group assets.
$query = new EntityFieldQuery();
$query->entityCondition('entity_type', 'farm_asset');
$query->entityCondition('bundle', 'group');
$query->propertyOrderBy('name', 'ASC');
// Limit to non-archived groups.
if (empty($archived)) {
$query->propertyCondition('archived', 0);
}
// Execute the query and build a list of options.
$result = $query->execute();
if (isset($result['farm_asset'])) {
$group_ids = array_keys($result['farm_asset']);
$groups = farm_asset_load_multiple($group_ids);
if (!empty($groups)) {
foreach ($groups as $group) {
if (!empty($group->id)) {
$options[$group->id] = entity_label('farm_asset', $group);
}
}
}
}
// Return the options array.
return $options;
}
/**
* Load groups that an asset is a member of.
*
* @param FarmAsset $asset
* The farm_asset object to look for.
* @param int $time
* Unix timestamp limiter. Only logs before this time will be included.
* Defaults to the current time. Set to 0 to load the absolute last.
* @param bool|null $done
* Whether or not to only show logs that are marked as "done". TRUE will limit
* to logs that are done, and FALSE will limit to logs that are not done. If
* this is set to NULL, no filtering will be applied. Defaults to TRUE.
*
* @return array
* Returns an array of groups that the asset is a member of.
*/
function farm_group_asset_membership(FarmAsset $asset, $time = REQUEST_TIME, $done = TRUE) {
$groups = array();
// Load the log using our helper function.
$log = farm_group_asset_latest_membership($asset, $time, $done);
// Load the log's membership field, if it exists.
if (!empty($log->field_farm_membership[LANGUAGE_NONE][0]['value'])) {
$membership = field_collection_item_load($log->field_farm_membership[LANGUAGE_NONE][0]['value']);
}
// Create an entity metadata wrapper so we can get the membership info.
// If no groups are specified, bail.
if (empty($membership->field_farm_group[LANGUAGE_NONE])) {
return $groups;
}
// Iterate through the referenced groups and load them.
foreach ($membership->field_farm_group[LANGUAGE_NONE] as $group_reference) {
if (!empty($group_reference['target_id'])) {
$group = farm_asset_load($group_reference['target_id']);
if (!empty($group)) {
$groups[] = $group;
}
}
}
return $groups;
}
/**
* Load an asset's latest log that defines a group membership.
*
* @param FarmAsset $asset
* The farm_asset object to look for.
* @param int $time
* Unix timestamp limiter. Only logs before this time will be included.
* Defaults to the current time. Set to 0 to load the absolute last.
* @param bool|null $done
* Whether or not to only show logs that are marked as "done". TRUE will limit
* to logs that are done, and FALSE will limit to logs that are not done. If
* this is set to NULL, no filtering will be applied. Defaults to TRUE.
*
* @return Log|bool
* Returns a log entity. FALSE if something goes wrong.
*/
function farm_group_asset_latest_membership(FarmAsset $asset, $time = REQUEST_TIME, $done = TRUE) {
/**
* Please read the comments in farm_group_asset_membership_query() to
* understand how this works, and to be aware of the limitations and
* responsibilities we have in this function with regard to sanitizing query
* inputs.
*/
// If the asset doesn't have an ID (for instance if it is new and hasn't been
// saved yet), bail.
if (empty($asset->id)) {
return FALSE;
}
// Make a query for loading the latest group membership log.
$query = farm_group_asset_membership_query($asset->id, $time, $done);
// Execute the query and gather the log id.
$result = $query->execute();
$log_id = $result->fetchField();
// If a log id exists, load and return it.
if (!empty($log_id)) {
return log_load($log_id);
}
return FALSE;
}
/**
* Build a query to find group membership logs of a specific asset.
*
* @param int|string $asset_id
* The asset id to search for. This can either be a specific id, or a field
* alias string from another query (ie: 'mytable.assetid'). For an example
* of field alias string usage, see the Views relationship handler code in
* farm_group_handler_relationship_membership::query().
* @param int $time
* Unix timestamp limiter. Only logs before this time will be included.
* Defaults to the current time. Set to 0 to load the absolute last.
* @param bool|null $done
* Whether or not to only show logs that are marked as "done". TRUE will limit
* to logs that are done, and FALSE will limit to logs that are not done. If
* this is set to NULL, no filtering will be applied. Defaults to TRUE.
* @param bool $single
* Whether or not to limit the query to a single result. Defaults to TRUE.
* @param string $field
* If the log id is desired, use "log_id. If the membership field_collection
* id is desired, use "membership_id".
*
* @return \SelectQuery
* Returns a SelectQuery object.
*/
function farm_group_asset_membership_query($asset_id, $time = REQUEST_TIME, $done = TRUE, $single = TRUE, $field = 'log_id') {
/**
* Please read the comments in farm_log_asset_query() to understand how this
* works, and to be aware of the limitations and responsibilities we have in
* this function with regard to sanitizing query inputs.
*/
// Use the farm_log_asset_query() helper function to start a query object.
$query = farm_log_asset_query($asset_id, $time, $done, NULL, $single);
// Add a query tag to identify where this came from.
$query->addTag('farm_group_asset_membership_query');
// Join in the Membership field collection. Use an inner join to exclude logs
// that do not a membership field collection attached.
$query->innerJoin('field_data_field_farm_membership', 'ss_fdffm', "ss_fdffm.entity_type = 'log' AND ss_fdffm.entity_id = ss_log.id AND ss_fdffm.deleted = 0");
// Join in the membership's "group" field. Use an inner join to exclude logs
// that do not have a group reference.
$query->innerJoin('field_data_field_farm_group', 'ss_fdffg', "ss_fdffg.entity_type = 'field_collection_item' AND ss_fdffg.bundle = 'field_farm_membership' AND ss_fdffg.entity_id = ss_fdffm.field_farm_membership_value AND ss_fdffg.deleted = 0");
// If $field is 'log_id', then add the log ID field.
if ($field == 'log_id') {
$query->addField('ss_log', 'id');
}
// Or, if $field is 'membership_id', then add the membership ID field.
elseif ($field == 'membership_id') {
$query->addField('ss_fdffm', 'field_farm_membership_value');
}
// Return the query object.
return $query;
}
/**
* Load all members of a group.
*
* @param FarmAsset $group
* The group to load members from.
* @param int $time
* Unix timestamp limiter. Only logs before this time will be included.
* Defaults to the current time. Set to 0 to load the absolute last.
* @param bool|null $done
* Whether or not to only show logs that are marked as "done". TRUE will limit
* to logs that are done, and FALSE will limit to logs that are not done. If
* this is set to NULL, no filtering will be applied. Defaults to TRUE.
* @param bool $archived
* Whether or not to include archived member assets. Defaults to FALSE.
*
* @return array
* Returns an array of the group's member assets, keyed by asset ID.
*/
function farm_group_members(FarmAsset $group, $time = REQUEST_TIME, $done = TRUE, $archived = FALSE) {
/**
* @todo
* Merge/abstract with farm_movement_area_assets().
*/
// Start an empty array of members.
$members = array();
// If the group doesn't have an id, bail.
if (empty($group->id)) {
return $members;
}
// Build a query to find all members of the group.
$query = farm_group_members_query($group->id, $time, $done, $archived);
// Execute the query to get a list of asset IDs.
$result = $query->execute();
// Iterate through the results.
foreach ($result as $row) {
// If the asset ID is empty, skip it.
if (empty($row->asset_id)) {
continue;
}
// If the asset has already been loaded, skip it.
if (array_key_exists($row->asset_id, $members)) {
continue;
}
// Load the asset.
$members[$row->asset_id] = farm_asset_load($row->asset_id);
}
// Return the array of members.
return $members;
}
/**
* Build a query to find members of a specific group.
*
* @param int $group_id
* The group's asset id to search for.
* @param int $time
* Unix timestamp limiter. Only logs before this time will be included.
* Defaults to the current time. Set to 0 to load the absolute last.
* @param $done
* Whether or not to only show logs that are marked as "done". TRUE will limit
* to logs that are done, and FALSE will limit to logs that are not done. If
* any other value is used, no filtering will be applied. Defaults to TRUE.
* @param bool $archived
* Whether or not to include archived member assets. Defaults to FALSE.
*
* @return \SelectQuery
* Returns a SelectQuery object.
*/
function farm_group_members_query($group_id, $time = REQUEST_TIME, $done = TRUE, $archived = TRUE) {
/**
* @todo
* Merge/abstract with farm_movement_area_assets_query().
*/
/**
* Please read the comments in farm_log_asset_query() to understand how this
* works, and to be aware of the limitations and responsibilities we have in
* this function with regard to sanitizing query inputs.
*/
// Ensure $group_id is valid, because it will be used directly in the query
// string. This is defensive code. See note about farm_log_query() above.
if (!is_numeric($group_id) || $group_id < 0) {
$group_id = db_escape_field($group_id);
}
// Use the farm_log_asset_query() helper function to start a subquery object.
// Do not limit the results to a single row because by the very nature of
// this we want to find all assets in the group, which may come from multiple
// logs.
$subquery = farm_log_asset_query(NULL, $time, $done, NULL, FALSE);
// Add a query tag to identify where this came from.
$subquery->addTag('farm_group_members_query');
// Join in the Membership field collection. Use an inner join to exclude logs
// that do not have a membership field collection attached.
$subquery->innerJoin('field_data_field_farm_membership', 'ss_fdffm', "ss_fdffm.entity_type = 'log' AND ss_fdffm.entity_id = ss_log.id AND ss_fdffm.deleted = 0");
// Add the asset ID field.
$subquery->addField('ss_fdffa', 'field_farm_asset_target_id');
// Add an expression to extract the assets most recent membership log ID.
$subquery->addExpression("SUBSTRING_INDEX(GROUP_CONCAT(ss_log.id ORDER BY ss_log.timestamp DESC, ss_log.id DESC SEPARATOR ','), ',', 1)", 'ss_current_log_id');
// Group by asset ID.
$subquery->groupBy('ss_fdffa.field_farm_asset_target_id');
// Create a query that selects from the subquery.
$query = db_select($subquery, 'ss_asset_current_log');
// Join in the asset's current log.
$query->join('log', 'ss_current_log', 'ss_current_log.id = ss_asset_current_log.ss_current_log_id');
// Join in the Membership field collection. Use an inner join to exclude logs
// that do not have a membership field collection attached.
$query->innerJoin('field_data_field_farm_membership', 'ss_current_log_fdffm', "ss_current_log_fdffm.entity_type = 'log' AND ss_current_log_fdffm.entity_id = ss_current_log.id AND ss_current_log_fdffm.deleted = 0");
// Join in the membership's "group" field, and filter to only include logs
// that have a membership that references the specified group. Use an inner
// join to exclude logs that do not have a group reference.
$query->innerJoin('field_data_field_farm_group', 'ss_current_log_fdffg', "ss_current_log_fdffg.entity_type = 'field_collection_item' AND ss_current_log_fdffg.bundle = 'field_farm_membership' AND ss_current_log_fdffg.entity_id = ss_current_log_fdffm.field_farm_membership_value AND ss_current_log_fdffg.deleted = 0");
$query->where('ss_current_log_fdffg.field_farm_group_target_id = ' . $group_id);
// Exclude archived assets, if requested.
if (empty($archived)) {
$query->join('farm_asset', 'ss_current_log_fa', "ss_asset_current_log.field_farm_asset_target_id = ss_current_log_fa.id");
$query->where('ss_current_log_fa.archived = 0');
}
// Add the asset ID field.
$query->addField('ss_asset_current_log', 'field_farm_asset_target_id', 'asset_id');
// Return the query object.
return $query;
}
/**
* Recursively check for circular group membership.
*
* @param FarmAsset $group
* The group that the asset will be added to.
* @params FarmAsset $asset
* The asset being considered for membership in the group.
*
* @return bool
* Returns TRUE if a circular dependency would exist if the asset became a
* member of the group, FALSE otherwise.
*/
function farm_group_circular_membership(FarmAsset $group, FarmAsset $asset) {
// A group can't be inside itself. This is primarily how we will check for
// circular membership, along with recursively checking parent groups below.
if ($group->id == $asset->id) {
return TRUE;
}
// Check to see if the group is a member of other groups.
$parent_groups = farm_group_asset_membership($group);
// If no parent groups were found, no circular membership can exist.
if (empty($parent_groups)) {
return FALSE;
}
// Iterate through the parent groups and recurse into them to check if the
// new asset will create a circular membership anywhere down the line.
foreach ($parent_groups as $parent_group) {
if (farm_group_circular_membership($parent_group, $asset)) {
return TRUE;
}
}
// Ok we're good! No circular memberships detected!
return FALSE;
}
/**
* Form helper function for validating against circular membership assignment.
*
* @param array $asset_ids
* An array of asset IDs that are being assigned to group(s).
* @param array $group_ids
* An array of group IDs that the assets are being assigned to.
* @param string $element_name
* The form element name to flag in form_set_error() if circular membership
* is detected.
*/
function farm_group_circular_membership_validate($asset_ids, $group_ids, $element_name) {
// Iterate through the selected groups and assets to check for possible
// circular membership.
foreach ($group_ids as $group_id) {
// If the group ID is empty, skip it.
if (empty($group_id)) {
continue;
}
// Load the group.
$group = farm_asset_load($group_id);
// If the group did not load, skip it.
if (empty($group)) {
continue;
}
// Iterate through the assets being assigned to the group.
foreach ($asset_ids as $asset_id) {
// If the asset ID is empty, skip it.
if (empty($asset_id)) {
continue;
}
// Load the asset.
$asset = farm_asset_load($asset_id);
// If the group did not load, skip it.
if (empty($asset)) {
continue;
}
// Check for a circular membership.
$circular = farm_group_circular_membership($group, $asset);
// If a circular membership is detected, warn the user.
if ($circular) {
// Get the URI information for the group and asset.
$group_uri = entity_uri('farm_asset', $group);
$asset_uri = entity_uri('farm_asset', $asset);
// Create links to the asset and group.
$group_link = l(entity_label('farm_asset', $group), $group_uri['path']);
$asset_link = l(entity_label('farm_asset', $asset), $asset_uri['path']);
// Set an error on the asset field and describe which asset and group
// would create the circular membership.
form_set_error($element_name, t('The asset "!asset_link" cannot be added to the group "!group_link" because it would create a circular membership.', array('!asset_link' => $asset_link, '!group_link' => $group_link)));
}
}
}
}
/**
* Create a log for assigning assets to group(s).
*
* @param array|FarmAsset $assets
* Array of assets to assign to the groups.
* @param array $groups
* An array of groups to move to.
* @param int $timestamp
* The timestamp of the assignment. Defaults to the current time.
* @param string $log_type
* The type of log to create. Defaults to "farm_observation".
* @param bool $done
* Boolean indicating whether or not the log should be marked "done". Defaults
* to TRUE.
*
* @return \Log
* Returns the log that was created.
*/
function farm_group_membership_set($assets, $groups = array(), $timestamp = REQUEST_TIME, $log_type = 'farm_observation', $done = TRUE) {
// If there are no groups specified, bail.
if (empty($groups)) {
return;
}
// If $assets isn't an array, wrap it.
if (!is_array($assets)) {
$assets = array($assets);
}
// If the log is an observation, set the name to:
// "Group [assets] into [groups]".
$log_name = '';
if ($log_type == 'farm_observation') {
$assets_summary = farm_log_entity_label_summary('farm_asset', $assets);
$groups_summary = farm_log_entity_label_summary('farm_asset', $groups);
$log_name = t('Group !assets into !groups', array('!assets' => $assets_summary, '!groups' => $groups_summary));
}
// Create a new farm log entity.
$log = farm_log_create($log_type, $log_name, $timestamp, $done, $assets);
// Create a new membership field_collection entity attached to the log.
$membership = entity_create('field_collection_item', array('field_name' => 'field_farm_membership'));
$membership->setHostEntity('log', $log);
// Create an entity wrapper for the membership.
$membership_wrapper = entity_metadata_wrapper('field_collection_item', $membership);
// Iterate through the areas and add each to the "Move to" field.
// If they are not group assets, ignore them.
foreach ($groups as $group) {
if ($group->type == 'group') {
$membership_wrapper->field_farm_group[] = $group;
}
}
// Save the membership.
$membership_wrapper->save();
// Return the log.
return $log;
}