You are here

changed_fields.core.inc in Changed Fields API 7.2

Same filename and directory in other branches
  1. 7 includes/changed_fields.core.inc

File contains core classes and interfaces.

File

includes/changed_fields.core.inc
View source
<?php

/**
 * @file
 * File contains core classes and interfaces.
 */

/**
 * Interface CFSubjectInterface.
 */
interface CFSubjectInterface {

  /**
   * Add observer to list.
   *
   * @param CFObserverInterface $observer
   *   Observer object to add.
   */
  public function addObserver(CFObserverInterface $observer);

  /**
   * Remove observer from list.
   *
   * @param CFObserverInterface $observer
   *   Observer object to remove.
   */
  public function removeObserver(CFObserverInterface $observer);

  /**
   * Notify all registered observers if needed.
   */
  public function checkNodeFields();

  /**
   * Returns node object.
   */
  public function getNode();

  /**
   * Returns changed fields.
   */
  public function getChangedFields();

}

/**
 * Interface CFObserverInterface.
 */
interface CFObserverInterface {

  /**
   * Update method with info about event.
   *
   * It is called by CFNodeSubject when node fields have been changed.
   *
   * @param CFSubjectInterface $nodeSubject
   *   Node subject object.
   */
  public function update(CFSubjectInterface $nodeSubject);

  /**
   * Method to get observer id.
   */
  public function getId();

}

/**
 * Class CFObserver.
 */
abstract class CFObserver implements CFObserverInterface {

  /**
   * Observer unique identifier.
   *
   * @var string
   */
  private $id;

  /**
   * Observer constructor.
   *
   * @param string $id
   *   Observer unique identifier.
   */
  public function __construct($id) {
    $this->id = $id;
  }

  /**
   * {@inheritdoc}
   */
  public abstract function update(CFSubjectInterface $nodeSubject);

  /**
   * {@inheritdoc}
   */
  public function getId() {
    return $this->id;
  }

}

/**
 * Class CFNodeSubject.
 */
class CFNodeSubject implements CFSubjectInterface {

  /**
   * Node object.
   *
   * @var \stdClass
   */
  private $node;

  /**
   * Info array.
   *
   * Array contains mapping for content types and fields to watch.
   *
   * @var array
   */
  private $info;

  /**
   * Field comparator object.
   *
   * @var CFDefaultFieldComparator
   */
  private $fieldComparator;

  /**
   * Array contains all registered observers.
   *
   * @var array
   */
  private $observers;

  /**
   * Node subject constructor.
   *
   * @param \stdClass $node
   *   Node object.
   * @param array $info
   *   Array contains mapping for content types and fields to watch.
   * @param CFDefaultFieldComparator $fieldComparator
   *   Field comparator object.
   */
  public function __construct(\stdClass $node, array $info, CFDefaultFieldComparator $fieldComparator) {
    $this->node = $node;
    $this->info = $info;
    $this->fieldComparator = $fieldComparator;
  }

  /**
   * {@inheritdoc}
   */
  public function addObserver(CFObserverInterface $observer) {
    $this->observers[$observer
      ->getId()] = $observer;
  }

  /**
   * {@inheritdoc}
   */
  public function removeObserver(CFObserverInterface $observer) {
    unset($this->observers[$observer
      ->getId()]);
  }

  /**
   * {@inheritdoc}
   */
  public function checkNodeFields() {
    foreach ($this->info as $nodeType => $fields) {
      if (isset($this->node->original) && $this->node->type == $nodeType) {
        $this->node->changed_fields = array();
        foreach ($fields as $fieldName) {
          if ($fieldName == 'title') {
            $oldValue = $this->node->original->{$fieldName};
            $newValue = $this->node->{$fieldName};
            $fieldInfo['field_base'] = array(
              'type' => 'title',
            );
          }
          else {
            $oldValue = field_get_items('node', $this->node->original, $fieldName);
            $newValue = field_get_items('node', $this->node, $fieldName);
            $fieldInfo['field_base'] = field_info_field($fieldName);
          }
          $fieldInfo['field_instance'] = field_info_instance('node', $fieldName, $nodeType);
          $result = $this->fieldComparator
            ->runFieldComparison($fieldInfo, $oldValue, $newValue);
          if (is_array($result)) {
            $this->node->changed_fields[$fieldName] = $result;
          }
        }
        if (!empty($this->node->changed_fields)) {
          foreach ($this->observers as $observer) {
            $observer
              ->update($this);
          }
        }
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getNode() {
    return $this->node;
  }

  /**
   * {@inheritdoc}
   */
  public function getChangedFields() {
    return $this->node->changed_fields;
  }

}

/**
 * Class CFDefaultFieldComparator.
 */
class CFDefaultFieldComparator {

  /**
   * Method that runs comparison of field values.
   *
   * @param array $fieldInfo
   *   Array contains field instance and field base information.
   * @param mixed $oldValue
   *   Old field value to compare.
   * @param mixed $newValue
   *   Old field value to compare.
   *
   * @return array|bool
   *   TRUE if fields are identical or array with differences if fields are
   *   different.
   */
  public function runFieldComparison(array $fieldInfo, $oldValue, $newValue) {
    $similarFields = TRUE;
    if ($fieldInfo['field_base']['type'] == 'field_collection') {

      // If collection was added or removed then we have already
      // different collections.
      if (!$oldValue && $newValue || $oldValue && !$newValue) {
        $similarFields = $this
          ->makeResultArray($fieldInfo['field_base']['type'], $oldValue, $newValue);
      }
      else {
        if ($oldValue && $newValue) {

          // If value was added|removed to|from multi-value field then we have
          // already different values.
          if (count($newValue) != count($oldValue)) {
            $similarFields = $this
              ->makeResultArray($fieldInfo['field_base']['type'], $oldValue, $newValue);
          }
          else {
            foreach ($oldValue as $key => $fc) {
              if (is_array($similarFields)) {
                break;
              }
              $oldFc = entity_load('field_collection_item', array(
                $fc['value'],
              ));
              $oldFc = reset($oldFc);
              $newFc = $newValue[$key]['entity'];
              $fcFields = field_info_instances('field_collection_item', $fieldInfo['field_base']['field_name']);
              foreach ($fcFields as $fcFieldName => $fcFieldData) {
                $fcFieldData = field_info_field($fcFieldName);
                $oldFcFieldValue = field_get_items('field_collection_item', $oldFc, $fcFieldName);
                $newFcFieldValue = field_get_items('field_collection_item', $newFc, $fcFieldName);
                $similarFields = $this
                  ->runFieldComparison($fcFieldData, $oldFcFieldValue, $newFcFieldValue);

                // If changes have been detected.
                if (is_array($similarFields)) {

                  // Make result array with old and new
                  // field collection entities.
                  $similarFields = $this
                    ->makeResultArray($fieldInfo['field_base']['type'], $oldValue, $newValue);
                  break;
                }
              }
            }
          }
        }
      }
    }
    else {
      $similarFields = $this
        ->compareFieldValues($fieldInfo, $oldValue, $newValue);
    }
    return $similarFields;
  }

  /**
   * Method that returns comparable properties for existing field type.
   *
   * @param array $fieldInfo
   *   Array contains field instance and field base information.
   *
   * @return array
   *    Array with properties that we need to use to compare two field values.
   */
  private function getComparableProperties(array $fieldInfo) {
    switch ($fieldInfo['field_base']['type']) {
      case 'text_with_summary':
        $properties = array(
          'value',
          'summary',
          'format',
        );
        break;
      case 'text':
      case 'text_long':
      case 'number_decimal':
      case 'number_float':
      case 'number_integer':
      case 'list_float':
      case 'list_integer':
      case 'list_boolean':
      case 'list_text':
      case 'phone':
        $properties = array(
          'value',
        );
        break;
      case 'taxonomy_term_reference':
        $properties = array(
          'tid',
        );
        break;
      case 'entityreference':
        $properties = array(
          'target_id',
        );
        break;
      case 'image':
        $properties = array(
          'fid',
          'width',
          'height',
        );
        if (!empty($fieldInfo['field_instance']['settings']['alt_field'])) {
          $properties[] = 'alt';
        }
        if (!empty($fieldInfo['field_instance']['settings']['title_field'])) {
          $properties[] = 'title';
        }
        break;
      case 'file':
        $properties = array(
          'fid',
        );
        if (!empty($fieldInfo['field_instance']['settings']['description_field'])) {
          $properties[] = 'description';
        }
        if (!empty($fieldInfo['field_instance']['settings']['display_field'])) {
          $properties[] = 'display';
        }
        break;
      case 'date':
      case 'datetime':
      case 'datestamp':
        $properties = array(
          'value',
          'timezone',
        );
        break;
      case 'email':
        $properties = array(
          'email',
        );
        break;
      case 'link_field':
        $properties = array(
          'url',
          'title',
        );
        break;
      default:
        $properties = $this
          ->getDefaultComparableProperties($fieldInfo);
        break;
    }
    return $this
      ->extendComparableProperties($fieldInfo, $properties);
  }

  /**
   * Method that returns comparable properties for extra or custom field type.
   *
   * Use it if you want to add comparison support
   * for extra or custom field types.
   *
   * @param array $fieldInfo
   *   Array contains field instance and field base information.
   *
   * @return array
   *   Array with properties that system needs to use to compare two field
   *   values depends on custom or extra field type.
   */
  protected function getDefaultComparableProperties(array $fieldInfo) {
    return array();
  }

  /**
   * Method that returns extended comparable properties for field type.
   *
   * Use it if you want to extend comparable properties for a given field type.
   *
   * @param array $fieldInfo
   *   Array contains field instance and field base information.
   * @param array $properties
   *   Array with properties that we need to use to compare two field values.
   *
   * @return array
   *   Array with extended properties that system needs to use to compare two
   *   field values depends on core field type.
   */
  protected function extendComparableProperties(array $fieldInfo, array $properties) {
    return $properties;
  }

  /**
   * Method that compares old and new field values.
   *
   * @param array $fieldInfo
   *   Array contains field instance and field base information.
   * @param mixed $oldValue
   *   Old field value to compare.
   * @param mixed $newValue
   *   New field value to compare.
   *
   * @return array|bool
   *   TRUE if fields are identical or array with differences if fields are
   *   different.
   */
  private function compareFieldValues(array $fieldInfo, $oldValue, $newValue) {
    $result = TRUE;
    $properties = $this
      ->getComparableProperties($fieldInfo);

    // If value was added or removed then we have already different values.
    if (!$oldValue && $newValue || $oldValue && !$newValue) {
      $result = $this
        ->makeResultArray($fieldInfo['field_base']['type'], $oldValue, $newValue);
    }
    else {
      if ($oldValue && $newValue) {

        // Simple comparison (for title).
        if (empty($properties) && $fieldInfo['field_base']['type'] == 'title') {
          if ($newValue != $oldValue) {
            $result = $this
              ->makeResultArray($fieldInfo['field_base']['type'], $oldValue, $newValue);
          }
        }
        else {

          // If value was added|removed to|from multi-value field then we have
          // already different values.
          if (count($newValue) != count($oldValue)) {
            $result = $this
              ->makeResultArray($fieldInfo['field_base']['type'], $oldValue, $newValue);
          }
          else {

            // Walk through each field value and compare it's properties.
            foreach ($newValue as $key => $value) {
              if (is_array($result)) {
                break;
              }
              foreach ($properties as $property) {
                if (array_key_exists($property, $newValue[$key]) && array_key_exists($property, $oldValue[$key]) && $newValue[$key][$property] != $oldValue[$key][$property]) {
                  $result = $this
                    ->makeResultArray($fieldInfo['field_base']['type'], $oldValue, $newValue);
                  break;
                }
              }
            }
          }
        }
      }
    }
    return $result;
  }

  /**
   * Method that generates result array for CFDefaultFieldComparator::compareFieldValues().
   *
   * @param string $fieldType
   *   Field type.
   * @param mixed $oldValue
   *   Old field value to compare.
   * @param mixed $newValue
   *   New field value to compare.
   *
   * @return array
   *   Array with old and new field values for compareFieldValues() method.
   */
  private function makeResultArray($fieldType, $oldValue, $newValue) {

    // Return field collection item entities like field values for
    // 'field_collection' field type.
    if ($fieldType == 'field_collection') {
      $resultOldValue = FALSE;
      $resultNewValue = FALSE;
      if ($oldValue) {
        foreach ($oldValue as $key => $fc) {
          $oldFc = entity_load('field_collection_item', array(
            $fc['value'],
          ));
          $oldFc = reset($oldFc);
          $resultOldValue[] = $oldFc;
        }
      }
      if ($newValue) {
        foreach ($newValue as $key => $fc) {
          $resultNewValue[] = $fc['entity'];
        }
      }
    }
    else {
      $resultOldValue = $oldValue;
      $resultNewValue = $newValue;
    }
    return array(
      'old_value' => $resultOldValue,
      'new_value' => $resultNewValue,
    );
  }

}

Classes

Namesort descending Description
CFDefaultFieldComparator Class CFDefaultFieldComparator.
CFNodeSubject Class CFNodeSubject.
CFObserver Class CFObserver.

Interfaces

Namesort descending Description
CFObserverInterface Interface CFObserverInterface.
CFSubjectInterface Interface CFSubjectInterface.