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_constraint/farm_constraint.module

368 lines
10 KiB
Text

<?php
/**
* @file
* Farm constraint module.
*/
/**
* Implements hook_hook_info().
*/
function farm_constraint_hook_info() {
$hooks['farm_constraint'] = array(
'group' => 'farm_constraint',
);
return $hooks;
}
/**
* Function for checking if a constraint exists (if a record is referenced by
* other records).
*
* @param $type
* The entity type.
* @param $bundle
* The entity bundle.
* @param $id
* The entity id.
*
* @return bool
* Returns TRUE if the record is referenced elsewhere, FALSE otherwise.
*/
function farm_constraint_exists($type, $bundle, $id) {
// Get a list of constraints.
$constraints = farm_constraint_list($type, $bundle, $id);
// Iterate through the constraints and return TRUE if any were detected.
foreach ($constraints as $constraint) {
if (!empty($constraint)) {
return TRUE;
}
}
// If we haven't already returned, no constraints were detected.
return FALSE;
}
/**
* Function for generating a list of constraints for a given entity.
*
* @param $type
* The entity type.
* @param $bundle
* The entity bundle.
* @param $id
* The entity id.
*
* @return array
* Returns an array of constraints preventing an entity from being deleted.
*/
function farm_constraint_list($type, $bundle, $id) {
// Ask other modules if they are aware of any constraints.
$constraints = module_invoke_all('farm_constraint', $type, $bundle, $id);
// Return the constraints.
return $constraints;
}
/**
* Helper function for checking to see if an entity reference exists in a
* database table.
*
* @param $references
* See farm_constraint_table_references() below.
* @param $type
* The entity type.
* @param $bundle
* The entity bundle.
* @param $id
* The entity ID.
*
* @return bool
* Returns TRUE if a constraint was found, FALSE otherwise.
*/
function farm_constraint_table_references_exist($references, $type, $bundle, $id) {
// Get a list of matching records.
$records = farm_constraint_table_references($references, $type, $bundle, $id);
// If records were found, return TRUE. Otherwise, return FALSE.
if (!empty($records)) {
return TRUE;
}
return FALSE;
}
/**
* Helper function for finding references to entities in tables.
*
* @param $references
* An array of information about entity references. For example:
* $references = array(
* 'herd' => array(
* 'type' => 'farm_asset',
* 'bundle' => 'group',
* 'tables' => array(
* 'farm_grazing_rotations' => 'herd_id',
* 'farm_grazing_herds' => 'herd_id',
* ),
* ),
* );
* @param $type
* The entity type.
* @param $bundle
* The entity bundle.
* @param $id
* The entity ID.
*
* @return array
* Returns an array of matching records.
*/
function farm_constraint_table_references($references, $type, $bundle, $id) {
// Start an empty records array.
$records = array();
// Iterate through the references.
foreach ($references as $info) {
// If the entity type doesn't match, skip it.
if ($type != $info['type']) {
continue;
}
// If a the referenced bundle matters, check it.
if (!empty($info['bundle']) && $bundle != $info['bundle']) {
continue;
}
// If there is no table information defined, skip it.
if (empty($info['tables'])) {
continue;
}
// Iterate through the tables.
foreach ($info['tables'] as $table => $column) {
// Query for a matching entity reference in the table.
$exists = db_query('SELECT COUNT(*) FROM {' . $table . '} WHERE ' . $column . ' = :id', array(':id' => $id))->fetchField();
// If one exists, a constraint exists. Return TRUE.
if (!empty($exists)) {
$records[] = array(
'constraint' => 'table_reference',
'table' => $table,
'column' => $column,
'entity_type' => $type,
'entity_id' => $id,
);
}
}
}
// Return matching records.
return $records;
}
/**
* Implements hook_form_alter().
*/
function farm_constraint_form_alter(&$form, &$form_state, $form_id) {
// This alters forms for deleting individual entities.
// Extract the entity type, bundle, and ID from the form.
$type = NULL;
$bundle = NULL;
$id = NULL;
switch ($form_id) {
// Farm asset delete form.
case 'farm_asset_delete_form':
$type = 'farm_asset';
if (!empty($form['farm_asset']['#value'])) {
$bundle = $form['farm_asset']['#value']->type;
$id = $form['farm_asset']['#value']->id;
}
break;
// Log delete form.
case 'log_delete_form':
$type = 'log';
if (!empty($form['log']['#value'])) {
$bundle = $form['log']['#value']->type;
$id = $form['log']['#value']->id;
}
break;
// User cancel form.
case 'user_cancel_confirm_form':
$type = 'user';
if (!empty($form['_account']['#value'])) {
$bundle = '';
$id = $form['_account']['#value']->uid;
}
break;
// Taxonomy term edit form.
// The taxonomy module uses the same form for editing and deleting,
// differentiated by the presence of $form_state['confirm_delete'].
case 'taxonomy_form_term':
$type = 'taxonomy_term';
if (!empty($form_state['confirm_delete']) && !empty($form['#term'])) {
$bundle = $form['#term']->vocabulary_machine_name;
$id = $form['#term']->tid;
}
break;
}
// If the entity type and ID were not found, bail (bundle will be blank on
// user entities).
if (empty($type) || empty($id)) {
return;
}
// Check to see if any constraints exist for this entity. If not, bail.
if (!farm_constraint_exists($type, $bundle, $id)) {
return;
}
// From here on, we know that constraints exist, so we want to prevent
// deletion of the entity.
// If this is a user, remove additional options.
if ($type == 'user') {
unset($form['user_cancel_method']);
unset($form['user_cancel_confirm']);
unset($form['user_cancel_notify']);
}
// Override the page title.
drupal_set_title('Unable to delete this record');
// Modify the form description.
$form['description']['#markup'] = t('This record cannot be deleted because it is referenced by other records.') . ' ' . t('You must remove all references to this record before you can delete it.');
// Remove submit handlers and buttons.
unset($form['#submit']);
unset($form['actions']);
}
/**
* Implements hook_views_bulk_operations_form_alter().
*/
function farm_constraint_views_bulk_operations_form_alter(&$form, &$form_state, $vbo) {
// Add the entity type as a form value, for use in validation.
if (!empty($vbo->entity_type)) {
$form['farm_constraint_entity_type'] = array(
'#type' => 'value',
'#value' => $vbo->entity_type,
);
}
// Add a validation function to the submit button which checks for
// constraints before deleting entities.
if ($vbo->get_vbo_option('display_type') == 0) {
$form['select']['submit']['#validate'][] = 'farm_constraint_views_bulk_operations_form_validate';
}
else {
if (!empty($form['select']['action::views_bulk_operations_delete_item'])) {
$form['select']['action::views_bulk_operations_delete_item']['#validate'][] = 'farm_constraint_views_bulk_operations_form_validate';
}
}
}
/**
* Validation function for Views Bulk Operations form.
*/
function farm_constraint_views_bulk_operations_form_validate(&$form, &$form_state) {
// Only validate if entities are being deleted.
if (empty($form_state['values']['operation']) || $form_state['values']['operation'] != 'action::views_bulk_operations_delete_item') {
return;
}
// If the entity type was not saved, bail.
if (empty($form_state['values']['farm_constraint_entity_type'])) {
return;
}
// Get the entity type.
$type = $form_state['values']['farm_constraint_entity_type'];
// Iterate through the selected entities.
foreach ($form_state['values']['views_bulk_operations'] as $key => $id) {
if (!empty($id)) {
// Load the entity.
$entities = entity_load($type, array($id));
$entity = reset($entities);
// Get the entity bundle.
list(, , $bundle) = entity_extract_ids($type, $entity);
// If a constraint exists on the entity, set a form error.
if (farm_constraint_exists($type, $bundle, $id)) {
$entity_label = entity_label($type, $entity);
$entity_uri = entity_uri($type, $entity);
$message = t('The record <a href="@entity_path">@entity_label</a> cannot be deleted because it is referenced by other records.', array('@entity_path' => url($entity_uri['path']), '@entity_label' => $entity_label)) . ' ' . t('You must remove all references to this record before you can delete it.');
form_set_error('views_bulk_operations][' . $key, $message);
}
}
}
}
/**
* Implements hook_restws_request_alter().
*/
function farm_constraint_restws_request_alter(array &$request) {
// Prevent deletion of entities that are referenced by other entities.
// This works by altering the "operation" in the request object that is built
// in restws_handle_request() before the access check is called. We replace
// the "delete" operation with "noop" so that the access check fails.
// This is a heavy-handed approach, and doesn't provide more nuanced options
// for dealing with referenced data, but it works to stop the deletion from
// occurring.
/**
* @todo
* This is a very hacky way to do this, and doesn't provide any feedback to
* the API user as to why their request fails.
*/
// If an operation, resource, and ID are not available, bail.
if (empty($request['op']) || empty($request['resource']) || empty($request['id'])) {
return;
}
// We only care about delete operations.
if ($request['op'] != 'delete') {
return;
}
// Get the entity type.
$type = $request['resource']->resource();
// Get the entity ID.
$id = $request['id'];
// Get the entity.
$entity = $request['resource']->read($id);
// If the entity doesn't exist it cannot have constraints.
if (empty($entity)) {
return;
}
// Get the entity bundle.
list($entity_id, $revision_id, $bundle) = entity_extract_ids($type, $entity);
// If constraints exist on the entity, change the operation to 'noop' so the
// access check in restws_handle_request() fails.
if (farm_constraint_exists($type, $bundle, $id)) {
$request['op'] = 'noop';
}
}