You are here

relation.rules.inc in Relation 8

Same filename and directory in other branches
  1. 8.2 relation.rules.inc
  2. 7 relation.rules.inc

Implements the Rules module API for Relation.

File

relation.rules.inc
View source
<?php

/**
 * @file
 * Implements the Rules module API for Relation.
 */
use Drupal\Core\Language\Language;
use Drupal\Core\Render\Element;
use Drupal\relation\Entity\Relation;
use Drupal\relation\Entity\RelationType;

/**
 * Implements hook_rules_event_info().
 */
function relation_rules_event_info() {
  return array(
    'relation_update' => array(
      'label' => t('After updating a relation'),
      'group' => t('Relation'),
      'variables' => array(
        'relation' => array(
          'type' => 'relation',
          'label' => t('relation'),
          'description' => t('The relation.'),
        ),
        'relation_unchanged' => array(
          'type' => 'relation',
          'label' => t('unchanged relation'),
          'description' => t('The unchanged relation.'),
          'handler' => 'rules_events_entity_unchanged',
        ),
      ),
    ),
    'relation_delete' => array(
      'label' => t('After deleting a relation'),
      'group' => t('Relation'),
      'variables' => array(
        'relation' => array(
          'type' => 'relation',
          'label' => t('relation'),
        ),
      ),
    ),
  );
}

/**
 * Implements hook_rules_action_info().
 */
function relation_rules_action_info() {
  $items = array(
    'relation_rules_load_related' => array(
      'label' => t('Loads related entities'),
      'group' => t('Relation'),
      'parameter' => array(
        'left' => array(
          'type' => 'entity',
          'label' => t('Entity'),
        ),
        'relation_type' => array(
          'type' => 'text',
          'label' => t('Relation type'),
          'options list' => 'relation_get_relation_types_options',
        ),
      ),
      'provides' => array(
        'endpoints' => array(
          'type' => 'list<entity>',
          'label' => t('List of related entities'),
        ),
      ),
    ),
    'relation_rules_fetch_endpoint' => array(
      'label' => t('Fetch relation endpoints'),
      'group' => t('Relation'),
      'description' => 'Fetch relation endpoint(s) of at a particular entity type.',
      'parameter' => array(
        'relation' => array(
          'type' => 'relation',
          'label' => t('Relation'),
          'restriction' => 'selector',
        ),
      ),
      'provides' => array(
        'endpoint_fetched' => array(
          'type' => 'entity',
          'label' => t('Fetched Endpoint'),
        ),
      ),
    ),
  );
  return $items;
}

/**
 * Callback for creating a relation, in the form Rules wants it.
 */
function relation_rules_create($values = array()) {
  return relation_create($values['relation_type'], array());
}

/**
 * Access callback for creating a relation.
 *
 * For now, everyone has permission to trigger the creation of a relation.
 */
function relation_rules_access($op, $entity = NULL, $account = NULL) {
  return TRUE;
}

/**
 * Endpoint property getter callback.
 */
function relation_rules_get_endpoints($relation, array $options, $property_name, $entity_type) {
  $array = array();
  foreach (relation_get_endpoints($relation) as $entity_type => $entities) {
    foreach ($entities as $entity) {
      $array[] = entity_metadata_wrapper($entity_type, $entity);
    }
  }
  return $array;
}

/**
 * Entity-type specific property getter callback.
 */
function relation_rules_get_specific_endpoints($relation, array $options, $property_name, $entity_type, $info) {
  if ($info['endpoint_type'] == 'source') {
    if ($info['relation_directional']) {
      $endpoints = array(
        $relation->endpoints[Language::LANGCODE_NOT_SPECIFIED][0],
      );
    }
    else {
      $endpoints = $relation->endpoints[Language::LANGCODE_NOT_SPECIFIED];
    }
  }
  else {
    $endpoints = array_slice($relation->endpoints[Language::LANGCODE_NOT_SPECIFIED], 1);
  }
  $array = array();
  foreach ($endpoints as $endpoint) {
    $storage_handler = \Drupal::entityTypeManager()
      ->getStorage($endpoint['entity_type']);
    $entity = $storage_handler
      ->load($endpoint['entity_id']);
    $array[] = $entity
      ->id();
  }
  return $array;
}

/**
 * Endpoint property setter callback.
 *
 * @param $data
 *   The relation object that we are going to modify.
 * @param $name
 *   Name of the provided Rules variable.
 * @param $endpoint_wrappers
 *   Array of entity wrappers that we are going to add to the relation object.
 */
function relation_rules_set_endpoints(&$relation = NULL, $name = NULL, $entity_wrappers = NULL) {

  // Check that we are creating a new relation. Updating existing relations
  // aren't supported.
  $relation_id = $relation
    ->id();
  if (isset($relation_id) || empty($entity_wrappers)) {
    return;
  }
  foreach ($entity_wrappers as $i => $entity_wrapper) {
    $entity = $entity_wrapper
      ->value();
    $entity_type = $entity_wrapper
      ->type();
    $id_key = $entity_wrapper
      ->entityKey('id');
    $bundle_key = $entity_wrapper
      ->entityKey('bundle');
    if (isset($entity->{$id_key})) {
      $relation->endpoints[Language::LANGCODE_NOT_SPECIFIED][$i] = array(
        'entity_type' => $entity_wrapper
          ->type(),
        'entity_id' => $entity->{$id_key},
        'entity_bundle' => isset($entity->{$bundle_key}) ? $entity->{$bundle_key} : $entity_type,
        'r_index' => $i,
      );
    }
  }
}

/**
 * Related entities getter callback.
 */
function relation_rules_get_related_entities($entity, array $options, $name, $type, $info) {
  $source_entity = entity_metadata_wrapper($type, $entity);
  $source_entity_type = $source_entity
    ->type();
  $source_entity_id = $source_entity
    ->getIdentifier();
  $results = relation_query($source_entity_type, $source_entity_id)
    ->entityCondition('bundle', $info['relation_type'])
    ->range(0, 50)
    ->execute();
  $relation_ids = array_keys($results);
  $entities_ids = array();
  if (!$relation_ids) {
    return $entities_ids;
  }
  foreach (Relation::loadMultiple($relation_ids) as $relation) {
    foreach ($relation->endpoints[Language::LANGCODE_NOT_SPECIFIED] as $endpoint) {
      if ($endpoint['entity_type'] == $info['target_type']) {
        $entities_ids[] = $endpoint['entity_id'];
      }
    }
  }
  return $entities_ids;
}

/**
 * Info alter callback for the load_related action.
 */
function relation_rules_load_related_info_alter(&$element_info, $element) {
  if (!empty($element->settings['relation_type'])) {

    // We only make this parameter available after we've selected the relation type. This way we
    // can limit the entity type list to only those relative to the selected relation.
    $element_info['parameter']['entity_type_op'] = array(
      'type' => 'text',
      'label' => t('Entity type'),
      'options list' => 'relation_rules_fetch_endpoint_type_options',
      'optional' => TRUE,
      'default value' => '',
      'description' => t('Optional: Select the specific type of entities to return. This will allow you to access their field/property data.'),
    );
    if (!empty($element->settings['entity_type_op'])) {

      // Set the returned entity type so we can access all the data.
      $element_info['provides']['endpoints']['type'] = 'list<' . $element->settings['entity_type_op'] . '>';

      // More then one.
    }
  }
}

/**
 * Form alter callback for the load_related action.
 */
function relation_rules_load_related_form_alter(&$form, &$form_state, $options, RulesAbstractPlugin $element) {
  $first_step = empty($element->settings['relation_type']);
  $form['reload'] = array(
    '#weight' => 5,
    '#type' => 'submit',
    '#name' => 'reload',
    '#value' => $first_step ? t('Continue') : t('Reload form'),
    '#submit' => array(
      'rules_action_type_form_submit_rebuild',
    ),
    '#ajax' => rules_ui_form_default_ajax(),
    '#description' => $first_step ? '' : t('Reload the form to change the entity types list.'),
  );
  if ($first_step) {

    // In the first step only show relevant parameters.
    foreach (Element::children($form['parameter']) as $key) {
      if ($key != 'relation_type' && $key != 'left') {
        unset($form['parameter'][$key]);
      }
    }
    unset($form['submit']);
    unset($form['provides']);
  }

  // Add #ajax to the relation_type selection dropdown to reload the form.
  if (isset($form['parameter']['relation_type'])) {
    $form['parameter']['relation_type']['#ajax'] = rules_ui_form_default_ajax() + array(
      'event' => 'change',
      'trigger_as' => array(
        'name' => 'reload',
      ),
    );
  }
}

/**
 * Action callback loading all related entities.
 */
function relation_rules_load_related($source_entity, $relation_type, $entity_type_op = NULL) {
  $endpoints = array();
  $source_entity_type = $source_entity
    ->type();
  $source_entity_id = $source_entity
    ->getIdentifier();
  foreach ($source_entity
    ->getPropertyInfo() as $property_name => $property) {
    if (isset($property['relation_type']) && $property['relation_type'] == $relation_type && isset($property['target_type'])) {
      $related_entities = $source_entity->{$property_name}
        ->value();
      if (!empty($related_entities)) {
        foreach ($related_entities as $related_entity) {
          if (empty($entity_type_op) || $entity_type_op == $property['target_type']) {
            $endpoint_wrapper = entity_metadata_wrapper($property['target_type'], $related_entity);
            if ($endpoint_wrapper
              ->type() != $source_entity_type || $endpoint_wrapper
              ->getIdentifier() != $source_entity_id) {
              $endpoints[] = $related_entity;
            }
          }
        }
      }
    }
  }
  return array(
    'endpoints' => $endpoints,
  );
}

/**
 * Returns the options list of available endpoint entity types for the chosen relation type.
 */
function relation_rules_fetch_endpoint_type_options(RulesAbstractPlugin $element, $param_name = NULL) {
  $options = $types = array();

  // The parameter is optional.
  if ($param_name = 'entity_type_op') {
    $options[''] = t('--All types--');
  }
  $all_entity_types = rules_entity_type_options();
  if (!empty($element->settings['relation_type'])) {
    $types[] = $element->settings['relation_type'];
  }
  elseif ($wrapper = $element
    ->applyDataSelector($element->settings['relation:select'])) {

    // If we can: limit the list of entity types to those relative to the selected relation type.
    if (($info = $wrapper
      ->info()) && !empty($info['bundle'])) {
      $types[] = $info['bundle'];
    }
  }
  foreach (RelationType::loadMultiple() as $relation_type) {

    // Add the allowed source entity types to the list.
    if (!empty($relation_type->source_bundles)) {
      foreach ($relation_type->source_bundles as $source_bundle) {
        list($entity_type, ) = explode(':', $source_bundle, 2);
        $options[$entity_type] = $all_entity_types[$entity_type];
      }
    }

    // Add the allowed target entity types to the list.
    if (!empty($relation_type->target_bundles)) {
      foreach ($relation_type->target_bundles as $target_bundle) {
        list($entity_type, ) = explode(':', $target_bundle, 2);
        $options[$entity_type] = $all_entity_types[$entity_type];
      }
    }
  }
  return $options;
}

/**
 * Info alter callback for the fetch_endpoint action.
 */
function relation_rules_fetch_endpoint_info_alter(&$element_info, $element) {
  $element->settings += array(
    'relation:select' => NULL,
  );
  if ($wrapper = $element
    ->applyDataSelector($element->settings['relation:select'])) {

    // We only make this parameter available after we've selected the relation. This way we
    // can limit the entity type list to only those relative to the selected relation.
    $element_info['parameter']['entity_type'] = array(
      'type' => 'text',
      'label' => t('Entity type'),
      'options list' => 'relation_rules_fetch_endpoint_type_options',
      'description' => t('Select the specific entity type to return.'),
    );
    if (!empty($element->settings['entity_type'])) {

      // Having a number parameter allows us to be flexible between returning a list or a single entity.
      $element_info['parameter']['number'] = array(
        'type' => 'integer',
        'label' => t('How many endpoints to return'),
        'default value' => 1,
        'description' => t('The number of enitites to return that match the above entity type criteria and in what form (single entity or a list). !zero returns a list containing every entity found; The default !one will return a single entity; !gt1 returns a list with maximum the specified number of entities.', array(
          '!zero' => '<strong>0</strong>',
          '!one' => '<strong>1</strong>',
          '!gt1' => '<strong># &gt; 1</strong>',
        )),
      );

      // Set the returned entity type so we can access all the data.
      if (!empty($element->settings['number']) && 1 == $element->settings['number']) {
        $element_info['provides']['endpoint_fetched']['type'] = $element->settings['entity_type'];

        // Only one.
      }
      else {
        $element_info['provides']['endpoint_fetched']['type'] = 'list<' . $element->settings['entity_type'] . '>';

        // More then one.
      }
    }
  }
}

/**
 * Form alter callback for the fetch_endpoint action.
 */
function relation_rules_fetch_endpoint_form_alter(&$form, &$form_state, $options, RulesAbstractPlugin $element) {
  $first_step = empty($element->settings['relation:select']);
  $second_step = !$first_step && empty($element->settings['entity_type']);
  $form['reload'] = array(
    '#weight' => 5,
    '#type' => 'submit',
    '#name' => 'reload',
    '#value' => $first_step ? t('Continue') : t('Reload form'),
    '#limit_validation_errors' => array(
      array(
        'parameter',
        'relation',
      ),
    ),
    '#submit' => array(
      'rules_action_type_form_submit_rebuild',
    ),
    '#ajax' => rules_ui_form_default_ajax(),
    '#description' => $first_step ? '' : t('Reload the form to change the entity/bundle types list.'),
  );

  // Use ajax and trigger as the reload button.
  $form['parameter']['relation']['settings']['relation:select']['#ajax'] = $form['reload']['#ajax'] + array(
    'event' => 'blur',
    'trigger_as' => array(
      'name' => 'reload',
    ),
  );
  if ($first_step || $second_step) {

    // In the first step and second step only show relevant parameters.
    foreach (Element::children($form['parameter']) as $key) {
      if ($key != 'relation' && !($second_step && $key == 'entity_type')) {
        unset($form['parameter'][$key]);
      }
    }
    unset($form['submit']);
    unset($form['provides']);
  }
  else {

    // Change the entity parameter to be not editable.
    $form['parameter']['relation']['settings']['#access'] = FALSE;
    $form['parameter']['relation']['info'] = array(
      '#prefix' => '<p>',
      '#markup' => t('<strong>Selected relation:</strong> %selector', array(
        '%selector' => $element->settings['relation:select'],
      )),
      '#suffix' => '</p>',
    );

    // Hide the reload button in case js is enabled and it's not the first step.
    $form['reload']['#attributes'] = array(
      'class' => array(
        'rules-hide-js',
      ),
    );
  }

  // Add #ajax to the entity_type selection dropdown to reload the form.
  if (isset($form['parameter']['entity_type'])) {
    $form['parameter']['entity_type']['#ajax'] = rules_ui_form_default_ajax() + array(
      'event' => 'change',
      'trigger_as' => array(
        'name' => 'reload',
      ),
    );
  }

  // Add #ajax to the number parameter to allow us to change the type of the provided variable.
  if (isset($form['parameter']['number'])) {
    $form['parameter']['number']['#ajax'] = rules_ui_form_default_ajax() + array(
      'event' => 'change',
      'trigger_as' => array(
        'name' => 'reload',
      ),
    );
  }

  // Disable #ajax for the 'relation:select' as it has troubles with lazy-loaded JS.
  // @TODO: Re-enable once JS lazy-loading is fixed in core.
  unset($form['parameter']['relation']['settings']['relation:select']['#ajax']);
}

/**
 * Action callback fetching a given number of endpoint entities for a particular relation.
 */
function relation_rules_fetch_endpoint($relation, $entity_type, $number = 1) {

  // Make sure we have the fully loaded relation entity.
  $loaded_relation = Relation::load($relation
    ->id());

  // Load the endpoints.
  $endpoints = field_get_items($loaded_relation, 'endpoints');
  $entity_ids = array();
  foreach ($endpoints as $endpoint) {

    // We only want to return entities of the selected type.
    if (!empty($endpoint['entity_type']) && $entity_type == $endpoint['entity_type']) {
      $entity_ids[] = $endpoint['entity_id'];
      if ($number == count($entity_ids)) {
        break;
      }
    }
  }
  if ($entity_ids) {
    $storage_handler = \Drupal::entityTypeManager()
      ->getStorage($entity_type);
    $return = $storage_handler
      ->loadMultiple($entity_ids);

    // Return a list unless we are only supposed to return a single entity.
    if (1 == $number) {
      $return = reset($return);
      if (!$return) {
        throw new RulesEvaluationException('Unable to load relation endpoint of type "@type" for @entity with id "@id".', array(
          '@type' => $entity_type,
          '@entity' => $relation->relation_type,
          '@id' => $relation
            ->id(),
        ));
      }
    }
    return array(
      'endpoint_fetched' => $return,
    );
  }

  // We didn't find any entities in the relation that matched the provided conditions.
  return NULL;
}

Functions

Namesort descending Description
relation_rules_access Access callback for creating a relation.
relation_rules_action_info Implements hook_rules_action_info().
relation_rules_create Callback for creating a relation, in the form Rules wants it.
relation_rules_event_info Implements hook_rules_event_info().
relation_rules_fetch_endpoint Action callback fetching a given number of endpoint entities for a particular relation.
relation_rules_fetch_endpoint_form_alter Form alter callback for the fetch_endpoint action.
relation_rules_fetch_endpoint_info_alter Info alter callback for the fetch_endpoint action.
relation_rules_fetch_endpoint_type_options Returns the options list of available endpoint entity types for the chosen relation type.
relation_rules_get_endpoints Endpoint property getter callback.
relation_rules_get_related_entities Related entities getter callback.
relation_rules_get_specific_endpoints Entity-type specific property getter callback.
relation_rules_load_related Action callback loading all related entities.
relation_rules_load_related_form_alter Form alter callback for the load_related action.
relation_rules_load_related_info_alter Info alter callback for the load_related action.
relation_rules_set_endpoints Endpoint property setter callback.