You are here

class RestWSEntityResourceController in RESTful Web Services 7.2

Same name and namespace in other branches
  1. 7 restws.entity.inc \RestWSEntityResourceController

Controller for entity-bases resources.

Hierarchy

Expanded class hierarchy of RestWSEntityResourceController

1 string reference to 'RestWSEntityResourceController'
restws_restws_resource_info in ./restws.module
Implements hook_restws_resource_info().

File

./restws.entity.inc, line 138
RESTful web services module integration for entities.

View source
class RestWSEntityResourceController implements RestWSQueryResourceControllerInterface {
  protected $entityType, $entityInfo;
  public function __construct($name, $info) {
    $this->entityType = $name;
    $this->entityInfo = entity_get_info($name);
  }
  public function propertyInfo() {
    return entity_get_all_property_info($this->entityType);
  }
  public function wrapper($id) {
    return entity_metadata_wrapper($this->entityType, $id);
  }
  public function read($id) {
    return $this
      ->wrapper($id)
      ->value();
  }
  public function create(array $values) {

    // Make sure that bundle information is present on entities that have
    // bundles.
    $entity_info = entity_get_info($this->entityType);
    if (isset($entity_info['bundle keys'])) {
      foreach ($entity_info['bundle keys'] as $bundle_key) {
        if (!array_key_exists($bundle_key, $values)) {
          throw new RestWSException('Missing bundle: ' . $bundle_key, 406);
        }
      }
    }
    try {
      $wrapper = entity_property_values_create_entity($this->entityType, $values);

      // Get the ID and bundle property names.
      $entity_keys = array_intersect_key($entity_info['entity keys'], array(
        'id' => 1,
        'bundle' => 1,
      ));
      foreach (array_keys($values) as $name) {

        // Don't check access on entity keys for new entities. Otherwise,
        // property access checks will fail for, e.g., node type, which
        // requires the 'administer nodes' permission to set.
        // @see entity_metadata_node_entity_property_info().
        if (!in_array($name, $entity_keys)) {
          if (!$this
            ->checkPropertyAccess($wrapper, $name, $wrapper->{$name})) {
            throw new RestWSException(t('Not authorized to set property @p', array(
              '@p' => $name,
            )), 403);
          }
        }
      }
    } catch (EntityMetadataWrapperException $e) {
      throw new RestWSException($e
        ->getMessage(), 406);
    }
    $properties = $wrapper
      ->getPropertyInfo();
    $diff = array_diff_key($values, $properties);
    if (!empty($diff)) {
      throw new RestWSException('Unknown data properties: ' . implode(' ', array_keys($diff)) . '.', 406);
    }
    $this
      ->validateFields($wrapper);
    $wrapper
      ->save();
    return $wrapper
      ->getIdentifier();
  }
  public function update($id, array $values) {
    $wrapper = $this
      ->wrapper($id);
    $entity_info = $wrapper
      ->entityInfo();

    // Get the ID and bundle property names.
    $entity_keys = array_intersect_key($entity_info['entity keys'], array(
      'id' => 1,
      'bundle' => 1,
    ));
    try {
      foreach ($values as $name => $value) {
        if (in_array($name, $entity_keys)) {

          // We don't allow changing the entity ID or bundle.
          if ($wrapper->{$name}
            ->value() != $value) {
            throw new RestWSException('Unable to change ' . $name, 422);
          }
        }
        else {
          $wrapper->{$name}
            ->set($value);
          if (!$this
            ->checkPropertyAccess($wrapper, $name, $wrapper->{$name})) {
            throw new RestWSException(t('Not authorized to set property @p', array(
              '@p' => $name,
            )), 403);
          }
        }
      }
    } catch (EntityMetadataWrapperException $e) {
      throw new RestWSException($e
        ->getMessage(), 406);
    }
    $this
      ->validateFields($wrapper);
    $wrapper
      ->save();
  }
  public function delete($id) {
    entity_delete($this->entityType, $id);
  }

  /**
   * Implements RestWSQueryResourceControllerInterface::query().
   */
  public function query($filters = array(), $meta_controls = array()) {
    $limit = variable_get('restws_query_max_limit', 100);
    $offset = 0;
    $query = new EntityFieldQuery();
    $query
      ->entityCondition('entity_type', $this->entityType);
    foreach ($filters as $filter => $value) {
      $this
        ->propertyQueryOperation($query, 'Condition', $filter, $value);
    }
    $rest_controls = restws_meta_controls();
    foreach ($meta_controls as $control_name => $value) {
      switch ($control_name) {
        case $rest_controls['sort']:
          if (isset($meta_controls[$rest_controls['direction']]) && strtolower($meta_controls[$rest_controls['direction']]) == 'desc') {
            $direction = 'DESC';
          }
          else {
            $direction = 'ASC';
          }
          $this
            ->propertyQueryOperation($query, 'OrderBy', $value, $direction);
          break;
        case $rest_controls['limit']:
          $limit = $this
            ->limit($value);
          break;
        case $rest_controls['page']:
          $offset = $value > 0 ? $value : $offset;
          break;
      }
    }

    // Calculate the offset.
    $offset *= $limit;
    $query
      ->range($offset, $limit);
    $this
      ->nodeAccess($query);

    // Catch any errors, like wrong keywords or properties.
    try {
      $query_result = $query
        ->execute();
    } catch (PDOException $exception) {
      throw new RestWSException('Query failed.', 400);
    }
    $query_result = isset($query_result[$this->entityType]) ? $query_result[$this->entityType] : array();
    $result = array_keys($query_result);
    return $result;
  }

  /**
   * Implements RestWSQueryResourceControllerInterface::count().
   */
  public function count($filters = array()) {
    $query = new EntityFieldQuery();
    $query
      ->entityCondition('entity_type', $this->entityType);
    foreach ($filters as $filter => $value) {
      $this
        ->propertyQueryOperation($query, 'Condition', $filter, $value);
    }
    $query
      ->count();
    $this
      ->nodeAccess($query);
    return $query
      ->execute();
  }

  /**
   * Helper function to respect node permissions while querying.
   *
   * @param EntityFieldQuery $query
   *   The query object.
   */
  protected function nodeAccess(EntityFieldQuery $query) {

    // Respect node access and filter out unpublished nodes if user lacks
    // the right permission.
    if ($this
      ->resource() == 'node') {
      $query
        ->addTag('node_access');
      if (!user_access('bypass node access')) {
        $this
          ->propertyQueryOperation($query, 'Condition', 'status', 1);
      }
    }
  }

  /**
   * Implements RestWSQueryResourceControllerInterface::limit().
   */
  public function limit($client_limit = NULL) {
    $limit = variable_get('restws_query_max_limit', 100);

    // Only allow user provided limits smaller than the system hard limit.
    if (!empty($client_limit) && $client_limit < $limit) {
      return $client_limit;
    }
    else {
      return $limit;
    }
  }
  public function access($op, $id) {
    return entity_access($op, $this->entityType, isset($id) ? $this
      ->wrapper($id)
      ->value() : NULL);
  }
  public function resource() {
    return $this->entityType;
  }

  /**
   * Helper function which takes care of distinguishing between fields and
   * entity properties and executes the right EntityFieldQuery function for it.
   *
   * @param EntityFieldQuery $query
   *   The EntityFieldQuery pointer which should be used.
   *
   * @param string $operation
   *   The general function name, without the words 'property' or 'field'.
   *
   * @param string $property
   *   The property or field which should be used.
   *
   * @param string|array $value
   *   The value for the function.
   */
  protected function propertyQueryOperation(EntityFieldQuery $query, $operation, $property, $value) {
    $properties = $this
      ->propertyInfo();

    // Check property access.
    if (!empty($properties[$property]['access callback'])) {
      if (!call_user_func($properties[$property]['access callback'], 'view', $property, NULL, NULL, $this
        ->resource())) {
        throw new RestWSException(t('Not authorized to query property @p', array(
          '@p' => $property,
        )), 403);
      }
    }

    // If field is not set, then the filter is a property and we can extract
    // the schema field from the property array.
    if (empty($properties[$property]['field'])) {
      $column = $properties[$property]['schema field'];
      $operation = 'property' . $operation;
      $query
        ->{$operation}($column, $value);
    }
    else {

      // For fields we need the field info to get the right column for the
      // query.
      $field_info = field_info_field($property);
      $operation = 'field' . $operation;
      if (is_array($value)) {

        // Specific column filters are given, so add a query condition for each
        // one of them.
        foreach ($value as $column => $val) {
          $query
            ->{$operation}($field_info, $column, $val);
        }
      }
      else {

        // Just pick the first field column for the operation.
        $columns = array_keys($field_info['columns']);
        $column = $columns[0];
        $query
          ->{$operation}($field_info, $column, $value);
      }
    }
  }

  /**
   * Helper method to check access on a property.
   *
   * @todo Remove this once Entity API properly handles text format access.
   *
   * @param EntityMetadataWrapper $entity
   *   The parent entity.
   * @param string $property_name
   *   The property name on the entity.
   * @param EntityMetadataWrapper $property
   *   The property whose access is to be checked.
   *
   * @return bool
   *   TRUE if the current user has access to set the property, FALSE otherwise.
   */
  protected function checkPropertyAccess($entity, $property_name, $property) {
    global $user;

    // Special case node author: we allow access if set to the current user.
    if ($entity
      ->type() == 'node' && $property_name == 'author' && $property
      ->raw() == $GLOBALS['user']->uid) {
      return TRUE;
    }
    elseif ($property
      ->type() == 'text_formatted' && $property->format
      ->value()) {
      $format = (object) array(
        'format' => $property->format
          ->value(),
      );
      if (!filter_access($format)) {
        return FALSE;
      }
    }

    // We don't want the property wrapper to check access again on the parent
    // entity so we directly check access for the property. That way only the
    // pure property/field access is taken into account.
    $info = $property
      ->info();
    if (!empty($info['access callback'])) {
      global $user;
      $data = $entity
        ->value();
      return call_user_func($info['access callback'], 'edit', $property_name, $data, $user, $entity
        ->type());
    }
    elseif (isset($info['setter permission'])) {
      return user_access($info['setter permission']);
    }
    return TRUE;
  }

  /**
   * Validates an entity's fields before they are saved.
   *
   * @param EntityDrupalWrapper $wrapper
   *   A metadata wrapper for the entity.
   *
   * @throws RestWSException
   */
  protected function validateFields($wrapper) {
    try {
      field_attach_validate($wrapper
        ->type(), $wrapper
        ->value());
    } catch (FieldValidationException $e) {
      throw new RestWSException($e
        ->getMessage(), 422);
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
RestWSEntityResourceController::$entityType protected property
RestWSEntityResourceController::access public function Determines access for a given operation and resource. Overrides RestWSResourceControllerInterface::access
RestWSEntityResourceController::checkPropertyAccess protected function Helper method to check access on a property.
RestWSEntityResourceController::count public function Implements RestWSQueryResourceControllerInterface::count(). Overrides RestWSQueryResourceControllerInterface::count
RestWSEntityResourceController::create public function Create a new resource. Overrides RestWSResourceControllerInterface::create
RestWSEntityResourceController::delete public function Delete an existing resource. Overrides RestWSResourceControllerInterface::delete
RestWSEntityResourceController::limit public function Implements RestWSQueryResourceControllerInterface::limit(). Overrides RestWSQueryResourceControllerInterface::limit
RestWSEntityResourceController::nodeAccess protected function Helper function to respect node permissions while querying.
RestWSEntityResourceController::propertyInfo public function Returns the property info for the given resource. Overrides RestWSResourceControllerInterface::propertyInfo
RestWSEntityResourceController::propertyQueryOperation protected function Helper function which takes care of distinguishing between fields and entity properties and executes the right EntityFieldQuery function for it.
RestWSEntityResourceController::query public function Implements RestWSQueryResourceControllerInterface::query(). Overrides RestWSQueryResourceControllerInterface::query
RestWSEntityResourceController::read public function Returns an existing resource. Overrides RestWSResourceControllerInterface::read
RestWSEntityResourceController::resource public function Returns the name of the resource. Overrides RestWSResourceControllerInterface::resource
RestWSEntityResourceController::update public function Update an existing resource. Overrides RestWSResourceControllerInterface::update
RestWSEntityResourceController::validateFields protected function Validates an entity's fields before they are saved.
RestWSEntityResourceController::wrapper public function Returns a metadata wrapper for the resource with the given id. Overrides RestWSResourceControllerInterface::wrapper
RestWSEntityResourceController::__construct public function