You are here

farm_constraint.module in farmOS 7

Farm constraint module.

File

modules/farm/farm_constraint/farm_constraint.module
View source
<?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';
  }
}

Functions

Namesort descending Description
farm_constraint_exists Function for checking if a constraint exists (if a record is referenced by other records).
farm_constraint_form_alter Implements hook_form_alter().
farm_constraint_hook_info Implements hook_hook_info().
farm_constraint_list Function for generating a list of constraints for a given entity.
farm_constraint_restws_request_alter Implements hook_restws_request_alter().
farm_constraint_table_references Helper function for finding references to entities in tables.
farm_constraint_table_references_exist Helper function for checking to see if an entity reference exists in a database table.
farm_constraint_views_bulk_operations_form_alter Implements hook_views_bulk_operations_form_alter().
farm_constraint_views_bulk_operations_form_validate Validation function for Views Bulk Operations form.