mirror of
https://github.com/farmOS/farmOS.git
synced 2024-02-23 11:37:38 +01:00
976 lines
32 KiB
Text
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;
|
|
}
|