972 lines
33 KiB
PHP
972 lines
33 KiB
PHP
<?php
|
|
/**
|
|
* @file
|
|
* Code for managing the location of assets with movement logs.
|
|
*/
|
|
|
|
/**
|
|
* Generate markup that describes an asset's current location.
|
|
*
|
|
* @param FarmAsset $asset
|
|
* The farm asset.
|
|
*
|
|
* @return string
|
|
* Returns rendered HTML.
|
|
*/
|
|
function farm_movement_asset_location_markup($asset) {
|
|
|
|
// Start an output string.
|
|
$output = '<strong>' . t('Location') . ':</strong> ';
|
|
|
|
// Get the asset's location.
|
|
$areas = farm_movement_asset_location($asset);
|
|
|
|
// If locations were found, add links to them.
|
|
if (!empty($areas)) {
|
|
$area_links = array();
|
|
foreach ($areas as $area) {
|
|
if (!empty($area->tid)) {
|
|
$area_links[] = l($area->name, 'taxonomy/term/' . $area->tid);
|
|
}
|
|
}
|
|
$output .= implode(', ', $area_links);
|
|
}
|
|
|
|
// Otherwise, none.
|
|
else {
|
|
$output .= 'N/A';
|
|
}
|
|
|
|
// Get the asset's most recent movement.
|
|
$log = farm_movement_asset_latest_movement($asset);
|
|
|
|
// Load the log's movement field, if it exists.
|
|
if (!empty($log->field_farm_movement[LANGUAGE_NONE][0]['value'])) {
|
|
$movement = field_collection_item_load($log->field_farm_movement[LANGUAGE_NONE][0]['value']);
|
|
}
|
|
|
|
// If a geofield exists on the movement, display it.
|
|
if (!empty($movement->field_farm_geofield[LANGUAGE_NONE][0]['geom'])) {
|
|
|
|
// Build the geofield map and add it to the page content.
|
|
$field_instance = field_info_instance('field_collection_item', 'field_farm_geofield', 'field_farm_movement');
|
|
$geofield = field_view_field('field_collection_item', $movement, 'field_farm_geofield', $field_instance['display']['default']);
|
|
$geofield['#title'] = t('Geometry');
|
|
$output .= drupal_render($geofield);
|
|
}
|
|
|
|
// Return the output markup.
|
|
return $output;
|
|
}
|
|
|
|
/**
|
|
* Implements hook_form_FORM_ID_alter().
|
|
*/
|
|
function farm_movement_form_farm_asset_form_alter(&$form, &$form_state, $form_id) {
|
|
|
|
// Get the farm asset entity from the form.
|
|
$asset = $form['farm_asset']['#value'];
|
|
|
|
// Get the asset's current location.
|
|
$areas = farm_movement_asset_location($asset);
|
|
$area_names = array();
|
|
if (!empty($areas)) {
|
|
foreach ($areas as $area) {
|
|
if (!empty($area->name)) {
|
|
|
|
// Get the area name.
|
|
$name = $area->name;
|
|
|
|
// If the area name contains commas, wrap it in quotes.
|
|
if (strpos($area->name, ',') !== FALSE) {
|
|
$name = '"' . $area->name . '"';
|
|
}
|
|
|
|
// Add the name to the list.
|
|
$area_names[] = $name;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Assemble the list of areas into a string.
|
|
$location = implode(', ', $area_names);
|
|
|
|
// Add a field for setting the asset's current location.
|
|
$form['location'] = array(
|
|
'#type' => 'fieldset',
|
|
'#title' => t('Location'),
|
|
'#description' => t('Set the current areas(s) that this asset is in. Separate multiple areas with commas. A movement observation log will be created automatically if you change this field.'),
|
|
'#collapsible' => TRUE,
|
|
'#collapsed' => TRUE,
|
|
'#weight' => 100,
|
|
'#tree' => TRUE,
|
|
);
|
|
$form['location']['areas'] = array(
|
|
'#type' => 'textfield',
|
|
'#title' => t('Current location'),
|
|
'#autocomplete_path' => 'taxonomy/autocomplete/field_farm_area',
|
|
'#default_value' => $location,
|
|
'#maxlength' => NULL,
|
|
);
|
|
|
|
// Add validation function to validate location input.
|
|
$form['actions']['submit']['#validate'][] = 'farm_movement_asset_location_validate';
|
|
|
|
// Add submit function to process the location.
|
|
$form['actions']['submit']['#submit'][] = 'farm_movement_asset_location_submit';
|
|
|
|
// Put the location fieldset into the "General" field group.
|
|
$form['#group_children']['location'] = 'group_farm_general';
|
|
}
|
|
|
|
/**
|
|
* Validation handler for processing the asset location field.
|
|
*
|
|
* @param array $form
|
|
* The form array.
|
|
* @param array $form_state
|
|
* The form state array.
|
|
*/
|
|
function farm_movement_asset_location_validate(array $form, array &$form_state) {
|
|
|
|
// Only proceed if current location field has a value.
|
|
if (empty($form_state['values']['location']['areas'])) {
|
|
return;
|
|
}
|
|
|
|
// Explode the value into an array and only take the first value.
|
|
// (Same behavior as taxonomy autocomplete widget.)
|
|
$values = drupal_explode_tags($form_state['values']['location']['areas']);
|
|
|
|
// Iterate over the values.
|
|
foreach ($values as $value) {
|
|
|
|
// If the area name is over 255 characters long, throw a form validation
|
|
// error.
|
|
if (strlen($value) > 255) {
|
|
$message = t('The area name "%name" is too long. It must be under 255 characters.', array('%name' => $value));
|
|
form_set_error('location][areas', $message);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Submit handler for processing the asset location field.
|
|
*
|
|
* @param array $form
|
|
* The form array.
|
|
* @param array $form_state
|
|
* The form state array.
|
|
*/
|
|
function farm_movement_asset_location_submit(array $form, array &$form_state) {
|
|
|
|
// Only proceed if current location field has a value.
|
|
if (empty($form_state['values']['location']['areas'])) {
|
|
return;
|
|
}
|
|
|
|
// Only proceed if the value is not the default value.
|
|
if ($form_state['values']['location']['areas'] == $form['location']['areas']['#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 areas.
|
|
$areas = farm_term_parse_names($form_state['values']['location']['areas'], 'farm_areas', TRUE);
|
|
|
|
// Create an observation log to record the movement.
|
|
farm_movement_create($asset, $areas, REQUEST_TIME);
|
|
}
|
|
|
|
/**
|
|
* Find the location of an asset, based on movement logs.
|
|
*
|
|
* @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 areas that the asset is in.
|
|
*/
|
|
function farm_movement_asset_location(FarmAsset $asset, $time = REQUEST_TIME, $done = TRUE) {
|
|
$areas = array();
|
|
|
|
// Load the log using our helper function.
|
|
$log = farm_movement_asset_latest_movement($asset, $time, $done);
|
|
|
|
// If a movement field doesn't exist, bail.
|
|
if (empty($log->field_farm_movement[LANGUAGE_NONE][0]['value'])) {
|
|
return $areas;
|
|
}
|
|
|
|
// Load the log's movement field
|
|
$movement = field_collection_item_load($log->field_farm_movement[LANGUAGE_NONE][0]['value']);
|
|
|
|
// Load the areas referenced in the "Move to" field.
|
|
if (!empty($movement->field_farm_move_to[LANGUAGE_NONE])) {
|
|
foreach ($movement->field_farm_move_to[LANGUAGE_NONE] as $area_reference) {
|
|
if (!empty($area_reference['tid'])) {
|
|
$term = taxonomy_term_load($area_reference['tid']);
|
|
if (!empty($term)) {
|
|
$areas[] = $term;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return $areas;
|
|
}
|
|
|
|
/**
|
|
* Find the geometry of an asset, based on movement logs.
|
|
*
|
|
* @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 string
|
|
* Returns the asset's current geometry, in WKT (well-known text).
|
|
*/
|
|
function farm_movement_asset_geometry(FarmAsset $asset, $time = REQUEST_TIME, $done = TRUE) {
|
|
$geometry = '';
|
|
|
|
// Load the log using our helper function.
|
|
$log = farm_movement_asset_latest_movement($asset, $time, $done);
|
|
|
|
// If a movement field doesn't exist, bail.
|
|
if (empty($log->field_farm_movement[LANGUAGE_NONE][0]['value'])) {
|
|
return $geometry;
|
|
}
|
|
|
|
// Load the log's movement field
|
|
$movement = field_collection_item_load($log->field_farm_movement[LANGUAGE_NONE][0]['value']);
|
|
|
|
// Load the movement geometry.
|
|
if (!empty($movement->field_farm_geofield[LANGUAGE_NONE][0]['geom'])) {
|
|
$geometry = $movement->field_farm_geofield[LANGUAGE_NONE][0]['geom'];
|
|
}
|
|
return $geometry;
|
|
}
|
|
|
|
/**
|
|
* Retrieve an area's movement history. This will provide an array of arrival
|
|
* and departure logs for each asset that has been moved to the area. Only
|
|
* movement logs that have been marked "done" will be included in the history.
|
|
*
|
|
* @param $area
|
|
* The farm area (taxonomy term object).
|
|
* @param string|array $asset_types
|
|
* Limit to only include certain asset types. This can be a single asset type
|
|
* as a string, or an array of asset types. Defaults to empty array, which
|
|
* will include all asset types.
|
|
* @param int $start_time
|
|
* How far back to look? This should be a UNIX timestamp. Defaults to NULL,
|
|
* which looks back through all movement logs in the system.
|
|
* @param int $end_time
|
|
* How far forward to look? This should be a UNIX timestamp. Defaults to the
|
|
* current time, which causes future arrival movements to be excluded.
|
|
*
|
|
* @return array
|
|
* Returns an array of movement history for each asset in the area. Array
|
|
* keys are asset IDs, and each asset will contain an array of arrays that
|
|
* contain arrival and departure logs for each movement through the area.
|
|
* If an asset moved through the area more than once, it will have multiple
|
|
* sub-arrays for each arrival+departure. If a departure log is not found
|
|
* (eg: if the asset has not left the area), the 'depart' key will be NULL.
|
|
*
|
|
* Example:
|
|
* array(
|
|
* '50' => array(
|
|
* array(
|
|
* 'arrive' => [$log],
|
|
* 'depart' => [$log],
|
|
* ),
|
|
* array(
|
|
* 'arrive' => [$log],
|
|
* 'depart' => NULL,
|
|
* ),
|
|
* ),
|
|
* '51' => array(
|
|
* array(
|
|
* 'arrive' => [$log],
|
|
* 'depart' => [$log],
|
|
* ),
|
|
* ),
|
|
* );
|
|
*/
|
|
function farm_movement_area_history($area, $asset_types = array(), $start_time = NULL, $end_time = REQUEST_TIME) {
|
|
|
|
// Start an empty history array.
|
|
$history = array();
|
|
|
|
// If the area doesn't have an id, bail.
|
|
if (empty($area->tid)) {
|
|
return $history;
|
|
}
|
|
|
|
// If $asset_types is not an array, wrap it in one.
|
|
if (!is_array($asset_types)) {
|
|
$asset_types = array($asset_types);
|
|
}
|
|
|
|
// Build a query to retrieve movement logs to this area.
|
|
$query = farm_movement_area_movement_query($area->tid, $end_time);
|
|
|
|
// Add the log ID field.
|
|
$query->addField('ss_log', 'id');
|
|
|
|
// Filter to only include logs that happened AFTER the start time.
|
|
if (!empty($start_time)) {
|
|
$query->condition('ss_log.timestamp', $start_time, '>');
|
|
}
|
|
|
|
// Join in asset references, and then the farm_asset table record for each.
|
|
$query->join('field_data_field_farm_asset', 'ss_fdffa', "ss_fdffa.entity_type = 'log' AND ss_fdffa.entity_id = ss_log.id AND ss_fdffa.deleted = 0");
|
|
$query->join('farm_asset', 'ss_fa', 'ss_fa.id = ss_fdffa.field_farm_asset_target_id');
|
|
|
|
// Filter to only include certain asset types.
|
|
if (!empty($asset_types)) {
|
|
$query->condition('ss_fa.type', $asset_types, 'IN');
|
|
}
|
|
|
|
// Group by log ID so that we don't get duplicate rows from logs that
|
|
// reference multiple assets.
|
|
$query->groupBy('ss_log.id');
|
|
|
|
// Execute the query to get a list of log IDs.
|
|
$result = $query->execute();
|
|
|
|
// Iterate through the log IDs.
|
|
foreach ($result as $row) {
|
|
|
|
// If the log ID is empty, skip it.
|
|
if (empty($row->id)) {
|
|
continue;
|
|
}
|
|
|
|
// Load the asset's arrival log.
|
|
$log_arrive = log_load($row->id);
|
|
|
|
// Create an entity metadata wrapper for the log.
|
|
$log_wrapper = entity_metadata_wrapper('log', $log_arrive);
|
|
|
|
// Iterate through the assets.
|
|
foreach ($log_wrapper->field_farm_asset as $asset_wrapper) {
|
|
|
|
// Get the asset object.
|
|
$asset = $asset_wrapper->value();
|
|
|
|
// The the asset doesn't have an ID, skip it.
|
|
if (empty($asset->id)) {
|
|
continue;
|
|
}
|
|
|
|
// If the asset is not one of the desired types, skip it.
|
|
if (!empty($asset_types) && !in_array($asset->type, $asset_types)) {
|
|
continue;
|
|
}
|
|
|
|
// Look up the asset's next movement log (departure from the area). Only
|
|
// include logs that have been marked "done".
|
|
$log_depart = farm_movement_asset_next_movement($asset, $log_arrive->timestamp, TRUE);
|
|
|
|
// Record the asset's time spent in this area.
|
|
$history[$asset->id][] = array(
|
|
'arrive' => $log_arrive,
|
|
'depart' => !empty($log_depart) ? $log_depart : NULL,
|
|
);
|
|
}
|
|
}
|
|
|
|
// Return the history.
|
|
return $history;
|
|
}
|
|
|
|
/**
|
|
* Load an asset's latest log that defines a movement.
|
|
*
|
|
* @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 $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.
|
|
*
|
|
* @return Log|bool
|
|
* Returns a log entity. FALSE if something goes wrong.
|
|
*/
|
|
function farm_movement_asset_latest_movement(FarmAsset $asset, $time = REQUEST_TIME, $done = TRUE) {
|
|
|
|
/**
|
|
* Please read the comments in farm_movement_asset_movement_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 movement log.
|
|
$query = farm_movement_asset_movement_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;
|
|
}
|
|
|
|
/**
|
|
* Load an asset's next log that defines a movement.
|
|
*
|
|
* @param FarmAsset $asset
|
|
* The farm_asset object to look for.
|
|
* @param int $time
|
|
* Unix timestamp limiter. Only logs after this time will be included.
|
|
* Defaults to the current time. Set to 0 to load the absolute first.
|
|
* @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 FALSE
|
|
* because the default $time is now, and future logs are generally not done
|
|
* yet.
|
|
*
|
|
* @return Log|bool
|
|
* Returns a log entity. FALSE if something goes wrong.
|
|
*/
|
|
function farm_movement_asset_next_movement(FarmAsset $asset, $time = REQUEST_TIME, $done = FALSE) {
|
|
|
|
/**
|
|
* Please read the comments in farm_movement_asset_movement_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 $time is valid, because it may be used directly in the query
|
|
// string. This is defensive code. See note about
|
|
// farm_movement_asset_movement_query() above.
|
|
if (!is_numeric($time) || $time < 0) {
|
|
$time = REQUEST_TIME;
|
|
}
|
|
|
|
// 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 to load all movement logs for the asset. Use a timestamp of 0
|
|
// to include future logs.
|
|
$query = farm_movement_asset_movement_query($asset->id, 0, $done, FALSE);
|
|
|
|
// Filter to only include movements after the specified timestamp.
|
|
$query->where('ss_log.timestamp > ' . $time);
|
|
|
|
// Order by timestamp and log ID ascending so we can get the first one (this
|
|
// overrides the default sort added by farm_log_query())
|
|
$query->orderBy('ss_log.timestamp', 'ASC');
|
|
$query->orderBy('ss_log.id', 'ASC');
|
|
|
|
// Limit to 1 record.
|
|
$query->range(0, 1);
|
|
|
|
// 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 movement 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_movement_handler_relationship_location::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 $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 $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 movement field_collection id
|
|
* is desired, use "movement_id".
|
|
*
|
|
* @return \SelectQuery
|
|
* Returns a SelectQuery object.
|
|
*/
|
|
function farm_movement_asset_movement_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_movement_asset_movement_query');
|
|
|
|
// Join in the Movement field collection and filter to only include logs with
|
|
// movements. Use an inner join to exclude logs that do not have any
|
|
// movement field collections attached.
|
|
$query->innerJoin('field_data_field_farm_movement', 'ss_fdffm', "ss_fdffm.entity_type = 'log' AND ss_fdffm.entity_id = ss_log.id AND ss_fdffm.deleted = 0");
|
|
|
|
// Join in the movement's "move to" field, and filter to only include logs
|
|
// that have a movement with a "move to" value. Use an inner join to exclude
|
|
// logs that do not have a "move to" area reference.
|
|
$query->innerJoin('field_data_field_farm_move_to', 'ss_fdffmt', "ss_fdffmt.entity_type = 'field_collection_item' AND ss_fdffmt.bundle = 'field_farm_movement' AND ss_fdffmt.entity_id = ss_fdffm.field_farm_movement_value AND ss_fdffmt.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 'movement_id', then add the movement ID field.
|
|
elseif ($field == 'movement_id') {
|
|
$query->addField('ss_fdffm', 'field_farm_movement_value');
|
|
}
|
|
|
|
// Return the query object.
|
|
return $query;
|
|
}
|
|
|
|
/**
|
|
* Build a query to find movement logs to a specific area.
|
|
*
|
|
* @param int $area_id
|
|
* The area 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.
|
|
*
|
|
* @return \SelectQuery
|
|
* Returns a SelectQuery object.
|
|
*/
|
|
function farm_movement_area_movement_query($area_id, $time = REQUEST_TIME, $done = TRUE) {
|
|
|
|
/**
|
|
* Please read the comments in farm_log_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 $area_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($area_id) || $area_id < 0) {
|
|
$area_id = db_escape_field($area_id);
|
|
}
|
|
|
|
// Use the farm_log_query() helper function to start a query 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 area, which may come from multiple logs.
|
|
$query = farm_log_query($time, $done, NULL, FALSE);
|
|
|
|
// Add a query tag to identify where this came from.
|
|
$query->addTag('farm_movement_area_movement_query');
|
|
|
|
// Join in the Movement field collection and filter to only include logs with
|
|
// movements. Use an inner join to exclude logs that do not have a movement
|
|
// field collection attached.
|
|
$query->innerJoin('field_data_field_farm_movement', 'ss_fdffm', "ss_fdffm.entity_type = 'log' AND ss_fdffm.entity_id = ss_log.id AND ss_fdffm.deleted = 0");
|
|
|
|
// Join in the movement's "move to" field, and filter to only include logs
|
|
// that have a movement with a "move to" the specified area. Use an inner
|
|
// join to exclude logs that do not have a "move to" area reference.
|
|
$query->innerJoin('field_data_field_farm_move_to', 'ss_fdffmt', "ss_fdffmt.entity_type = 'field_collection_item' AND ss_fdffmt.bundle = 'field_farm_movement' AND ss_fdffmt.entity_id = ss_fdffm.field_farm_movement_value AND ss_fdffmt.deleted = 0");
|
|
$query->where('ss_fdffmt.field_farm_move_to_tid = ' . $area_id);
|
|
|
|
// Return the query object.
|
|
return $query;
|
|
}
|
|
|
|
/**
|
|
* Load all assets in an area.
|
|
*
|
|
* @param $area
|
|
* The area to load assets 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 assets. Defaults to FALSE.
|
|
*
|
|
* @return array
|
|
* Returns an array of the area's assets, keyed by asset ID.
|
|
*/
|
|
function farm_movement_area_assets($area, $time = REQUEST_TIME, $done = TRUE, $archived = FALSE) {
|
|
|
|
/**
|
|
* @todo
|
|
* Merge/abstract with farm_group_members().
|
|
*/
|
|
|
|
// Start an empty array of assets.
|
|
$assets = array();
|
|
|
|
// If the area doesn't have an id, bail.
|
|
if (empty($area->tid)) {
|
|
return $assets;
|
|
}
|
|
|
|
// Build a query to find all assets in the area.
|
|
$query = farm_movement_area_assets_query($area->tid, $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, $assets)) {
|
|
continue;
|
|
}
|
|
|
|
// Load the asset.
|
|
$assets[$row->asset_id] = farm_asset_load($row->asset_id);
|
|
}
|
|
|
|
// Return the array of assets.
|
|
return $assets;
|
|
}
|
|
|
|
/**
|
|
* Build a query to find assets in a given area.
|
|
*
|
|
* @param int $area_id
|
|
* The area's taxonomy term 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 assets. Defaults to FALSE.
|
|
*
|
|
* @return \SelectQuery
|
|
* Returns a SelectQuery object.
|
|
*/
|
|
function farm_movement_area_assets_query($area_id, $time = REQUEST_TIME, $done = TRUE, $archived = FALSE) {
|
|
|
|
/**
|
|
* @todo
|
|
* Merge/abstract with farm_group_members_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 $area_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($area_id) || $area_id < 0) {
|
|
$area_id = db_escape_field($area_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 area, 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_movement_area_assets_query');
|
|
|
|
// Join in the Movement field collection. Use an inner join to exclude logs
|
|
// that do not have a movement field collection attached.
|
|
$subquery->innerJoin('field_data_field_farm_movement', '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 movement 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 Movement field collection. Use an inner join to exclude logs
|
|
// that do not have a movement field collection attached.
|
|
$query->innerJoin('field_data_field_farm_movement', '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 movement's "move to" field, and filter to only include logs
|
|
// that have a movement that references the specified area. Use an inner
|
|
// join to exclude logs that do not have an area reference.
|
|
$query->innerJoin('field_data_field_farm_move_to', 'ss_current_log_fdffmt', "ss_current_log_fdffmt.entity_type = 'field_collection_item' AND ss_current_log_fdffmt.bundle = 'field_farm_movement' AND ss_current_log_fdffmt.entity_id = ss_current_log_fdffm.field_farm_movement_value AND ss_current_log_fdffmt.deleted = 0");
|
|
$query->where('ss_current_log_fdffmt.field_farm_move_to_tid = ' . $area_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;
|
|
}
|
|
|
|
/**
|
|
* Implements hook_action_info().
|
|
*/
|
|
function farm_movement_action_info() {
|
|
return array(
|
|
'farm_movement_asset_move_action' => array(
|
|
'type' => 'farm_asset',
|
|
'label' => t('Move'),
|
|
'configurable' => TRUE,
|
|
'triggers' => array('any'),
|
|
'aggregate' => TRUE,
|
|
),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Configuration form for farm_movement_asset_move 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_movement_asset_move_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,
|
|
);
|
|
|
|
// Area reference field.
|
|
$form['areas'] = array(
|
|
'#type' => 'textfield',
|
|
'#title' => t('Location'),
|
|
'#autocomplete_path' => 'taxonomy/autocomplete/field_farm_area',
|
|
'#required' => TRUE,
|
|
);
|
|
|
|
// Done field.
|
|
$form['done'] = array(
|
|
'#type' => 'checkbox',
|
|
'#title' => t('This movement has taken place (mark the log as done)'),
|
|
'#default_value' => TRUE,
|
|
);
|
|
|
|
// Return the form.
|
|
return $form;
|
|
}
|
|
|
|
/**
|
|
* Submit handler for farm_movement_asset_move 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_movement_asset_move_action_submit(array $form, array $form_state) {
|
|
|
|
// Start to build the context array.
|
|
$context = array();
|
|
|
|
// Load the areas.
|
|
$context['areas'] = farm_term_parse_names($form_state['values']['areas'], 'farm_areas', TRUE);
|
|
|
|
// Convert the date to a timestamp.
|
|
$timestamp = strtotime($form_state['values']['date']);
|
|
|
|
// The action form only includes month, day, and year. If the movement 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 movement 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_movement_asset_move.
|
|
*
|
|
* Creates a new movement activity log for the specified assets.
|
|
*
|
|
* @param array $assets
|
|
* An array of asset entities to move.
|
|
* @param array $context
|
|
* Array with parameters for this action.
|
|
*/
|
|
function farm_movement_asset_move_action(array $assets, $context = array()) {
|
|
|
|
// If we're missing assets, areas, or a timestamp, bail.
|
|
if (empty($assets) || empty($context['areas']) || empty($context['timestamp'])) {
|
|
drupal_set_message(t('Could not perform movement because required information was missing.'), 'error');
|
|
return;
|
|
}
|
|
|
|
// Create a movement activity log.
|
|
farm_movement_create($assets, $context['areas'], $context['timestamp'], 'farm_activity', $context['done']);
|
|
}
|
|
|
|
/**
|
|
* Create a log for moving assets to areas.
|
|
*
|
|
* @param array|FarmAsset $assets
|
|
* Array of assets to include in the move.
|
|
* @param array $areas
|
|
* An array of areas to move to.
|
|
* @param int $timestamp
|
|
* The timestamp of the move. 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.
|
|
* @param string $geom
|
|
* Optionally provide a movement geometry in WKT format. If this is empty, the
|
|
* geometry will be copied from the referenced area(s).
|
|
*
|
|
* @return \Log
|
|
* Returns the log that was created.
|
|
*/
|
|
function farm_movement_create($assets, $areas = array(), $timestamp = REQUEST_TIME, $log_type = 'farm_observation', $done = TRUE, $geom = '') {
|
|
|
|
// If no areas are defined, bail.
|
|
if (empty($areas)) {
|
|
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:
|
|
// "[assets] located in [areas]".
|
|
// If the log is an activity, set the name to:
|
|
// "Move [assets] to [areas]".
|
|
$log_name = '';
|
|
$assets_summary = farm_log_entity_label_summary('farm_asset', $assets);
|
|
$areas_summary = farm_log_entity_label_summary('taxonomy_term', $areas);
|
|
$arguments = array('!assets' => $assets_summary, '!areas' => $areas_summary);
|
|
if ($log_type == 'farm_observation') {
|
|
$log_name = t('!assets located in !areas', $arguments);
|
|
}
|
|
elseif ($log_type == 'farm_activity') {
|
|
$log_name = t('Move !assets to !areas', $arguments);
|
|
}
|
|
|
|
|
|
// Create a new farm log entity.
|
|
$log = farm_log_create($log_type, $log_name, $timestamp, $done, $assets);
|
|
|
|
// Create a new movement field_collection entity attached to the log.
|
|
$movement = entity_create('field_collection_item', array('field_name' => 'field_farm_movement'));
|
|
$movement->setHostEntity('log', $log);
|
|
|
|
// Create an entity wrapper for the adjustment.
|
|
$movement_wrapper = entity_metadata_wrapper('field_collection_item', $movement);
|
|
|
|
// Iterate through the areas and add each to the "Move to" field.
|
|
foreach ($areas as $area) {
|
|
$movement_wrapper->field_farm_move_to[] = $area;
|
|
}
|
|
|
|
// Add geometry, if provided.
|
|
if (!empty($geom)) {
|
|
$movement_wrapper->field_farm_geofield->set(array(array('geom' => $geom)));
|
|
}
|
|
|
|
// Save the movement.
|
|
$movement_wrapper->save();
|
|
|
|
// Return the log.
|
|
return $log;
|
|
}
|