sparql_entity_storage-8.x-1.0-alpha8/src/Entity/Query/Sparql/SparqlCondition.php

src/Entity/Query/Sparql/SparqlCondition.php
<?php

declare(strict_types=1);

namespace Drupal\sparql_entity_storage\Entity\Query\Sparql;

use Drupal\Component\Utility\Crypt;
use Drupal\Core\Entity\Query\ConditionFundamentals;
use Drupal\Core\Entity\Query\ConditionInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\sparql_entity_storage\SparqlEntityStorageFieldHandlerInterface;
use EasyRdf\Serialiser\Ntriples;

/**
 * Defines the condition class for the entity query.
 */
class SparqlCondition extends ConditionFundamentals implements SparqlConditionInterface {

  /**
   * The SPARQL field mapping handler service.
   */
  protected SparqlEntityStorageFieldHandlerInterface $fieldHandler;

  /**
   * The language manager service.
   */
  protected LanguageManagerInterface $languageManager;

  /**
   * A list of allowed operators for the ID and bundle key.
   *
   * @var string[]
   */
  protected array $allowedOperatorsForIdAndBundle = [
    '=',
    '!=',
    '<',
    '>',
    '<=',
    '>=',
    '<>',
    'IN',
    'NOT IN',
  ];

  /**
   * Provides a map of filter operators to operator options.
   *
   * @var string[][]
   */
  protected static array $filterOperatorMap = [
    'IN' => ['delimiter' => ' ', 'prefix' => '', 'suffix' => ''],
    'NOT IN' => ['delimiter' => ', ', 'prefix' => '', 'suffix' => ''],
    'IS NULL' => ['use_value' => FALSE],
    'IS NOT NULL' => ['use_value' => FALSE],
    'CONTAINS' => ['prefix' => 'FILTER(CONTAINS(', 'suffix' => '))'],
    'STARTS WITH' => ['prefix' => 'FILTER(STRSTARTS(', 'suffix' => '))'],
    'ENDS WITH' => ['prefix' => 'FILTER(STRENDS(', 'suffix' => '))'],
    'LIKE' => ['prefix' => 'FILTER(CONTAINS(', 'suffix' => '))'],
    'NOT LIKE' => ['prefix' => 'FILTER(!CONTAINS(', 'suffix' => '))'],
    'EXISTS' => ['prefix' => '', 'suffix' => ''],
    'NOT EXISTS' => ['prefix' => 'FILTER NOT EXISTS {', 'suffix' => '}'],
    '<' => ['prefix' => '', 'suffix' => ''],
    '>' => ['prefix' => '', 'suffix' => ''],
    '>=' => ['prefix' => '', 'suffix' => ''],
    '<=' => ['prefix' => '', 'suffix' => ''],
  ];

  /**
   * The operators that require a default triple pattern to be added.
   *
   * In SPARQL, some of the conditions might need a combination of a pattern and
   * a condition. Below are the operators that need a secondary condition.
   *
   * @var string[]
   */
  protected array $requiresDefaultPatternOperators = [
    'IN',
    'NOT IN',
    'IS NULL',
    'IS NOT NULL',
    'CONTAINS',
    'LIKE',
    'NOT LIKE',
    'STARTS WITH',
    'ENDS WITH',
    '<',
    '>',
    '<=',
    '>=',
    '!=',
    '<>',
  ];

  /**
   * Whether the default triple pattern is required in the query.
   *
   * This will be turned to false if there is at least one condition that does
   * not involve the ID.
   */
  protected bool $requiresDefaultPattern = TRUE;

  /**
   * The default bundle predicates.
   */
  protected array $typePredicates = ['<http://www.w3.org/1999/02/22-rdf-syntax-ns#type>'];

  /**
   * An array of triples in their string version.
   *
   * These are mainly fragments of the '=' operator because they can create
   * triples that will greatly reduce the results.
   *
   * @var string[]
   */
  protected array $tripleFragments = [];

  /**
   * An array of conditions in their string version.
   *
   * These are formed during the compilation phase.
   *
   * @var string[]
   */
  protected array $conditionFragments = [];

  /**
   * An array of field names and their corresponding mapping.
   *
   * @var string[]
   */
  protected array $fieldMappings;

  /**
   * An array of conditions regarding fields with multiple possible mappings.
   *
   * @var array
   */
  protected array $fieldMappingConditions = [];

  /**
   * The entity type ID key.
   */
  protected string $idKey;

  /**
   * The entity type bundle key.
   */
  protected string $bundleKey;

  /**
   * The entity type label key.
   */
  protected string $labelKey;

  /**
   * A list of properties regarding the query conjunction.
   *
   * @var array
   */
  protected static array $conjunctionMap = [
    'AND' => ['delimiter' => " .\n", 'prefix' => '', 'suffix' => ''],
    'OR' => ['delimiter' => " UNION\n", 'prefix' => '{ ', 'suffix' => ' }'],
  ];

  /**
   * Constructs a Condition object.
   *
   * @param string $conjunction
   *   The operator to use to combine conditions: 'AND' or 'OR'.
   * @param \Drupal\sparql_entity_storage\Entity\Query\Sparql\SparqlQueryInterface $query
   *   The entity query this condition belongs to.
   * @param array $namespaces
   *   List of potential namespaces of the classes belonging to this condition.
   * @param \Drupal\sparql_entity_storage\SparqlEntityStorageFieldHandlerInterface $sparql_field_handler
   *   The SPARQL field mapping handler service.
   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
   *   The language manager service.
   */
  public function __construct($conjunction, SparqlQueryInterface $query, array $namespaces, SparqlEntityStorageFieldHandlerInterface $sparql_field_handler, LanguageManagerInterface $language_manager) {
    $conjunction = strtoupper($conjunction);
    parent::__construct($conjunction, $query, $namespaces);
    $this->fieldHandler = $sparql_field_handler;
    $this->languageManager = $language_manager;
    $this->typePredicates = $query->getEntityStorage()->getBundlePredicates();
    $this->bundleKey = $query->getEntityType()->getKey('bundle') ?: $query->getEntityTypeId();
    $this->idKey = $query->getEntityType()->getKey('id');
    $this->labelKey = $query->getEntityType()->getKey('label');
    $this->fieldMappings = [
      $this->idKey => self::ID_KEY,
      $this->bundleKey => count($this->typePredicates) === 1 ? reset($this->typePredicates) : SparqlArg::toVar($this->bundleKey . '_predicate'),
    ];
  }

  /**
   * {@inheritdoc}
   *
   * @todo handle the lang.
   */
  public function condition($field = NULL, $value = NULL, $operator = NULL, $lang = NULL): self {
    if ($this->conjunction == 'OR') {
      $sub_condition = $this->query->andConditionGroup();
      $sub_condition->condition($field, $value, $operator, $lang);
      $this->conditions[] = ['field' => $sub_condition];
      return $this;
    }

    // If the field is a nested condition, simply add it to the list of
    // conditions.
    if ($field instanceof ConditionInterface) {
      $this->conditions[] = ['field' => $field];
      return $this;
    }

    if ($operator === NULL) {
      $operator = '=';
    }

    switch ($field) {
      case $this->bundleKey:
        // If a bundle filter is passed, then there is no need for a default
        // condition.
        $this->requiresDefaultPattern = FALSE;

      case $this->idKey:
        $this->keyCondition($field, $value, $operator);
        break;

      default:
        // In case the field name includes the column, explode it.
        // @see \Drupal\og\MembershipManager::getGroupContentIds
        $field_name_parts = explode('.', $field);
        $field = $field_name_parts[0];
        // Unmapped fields, such as fields without storage cannot be added as
        // entity query conditions.
        if (!$this->fieldHandler->fieldIsMapped($this->query->getEntityTypeId(), $field)) {
          return $this;
        }

        if (isset($field_name_parts[1])) {
          // The 'entity' column designates an entity reference.
          // @see \Drupal\Core\Entity\Query\QueryInterface::condition()
          // @todo Handle more complex column representations, such as
          //   'tags.entity.name' or tags.entity:taxonomy_term.name'.
          $column = $field_name_parts[1] === 'entity' ? 'target_id' : $field_name_parts[1];
        }
        else {
          $column = $this->fieldHandler->getFieldMainProperty($this->query->getEntityTypeId(), $field);
        }

        $this->conditions[] = [
          'field' => $field,
          'value' => $value,
          'operator' => $operator,
          'lang' => $lang,
          'column' => $column,
        ];

        if ($operator !== 'NOT EXISTS') {
          $this->requiresDefaultPattern = FALSE;
        }
    }

    return $this;
  }

  /**
   * Handle the ID and bundle keys.
   *
   * @param string $field
   *   The field name. Should be either the ID or the bundle key.
   * @param string|array $value
   *   A string or an array of strings.
   * @param string $operator
   *   The operator.
   *
   * @return $this
   *
   * @throws \Exception
   *   Thrown if the value is NULL or the operator is not allowed.
   */
  protected function keyCondition(string $field, $value, string $operator): self {
    // @todo Add support for loadMultiple with empty Id (load all).
    if ($value == NULL) {
      throw new \Exception('The value cannot be NULL for conditions related to the Id and bundle keys.');
    }
    if (!in_array($operator, $this->allowedOperatorsForIdAndBundle, TRUE)) {
      throw new \Exception("Only '=', '!=', '<>', 'IN', 'NOT IN' operators are allowed for the Id and bundle keys.");
    }

    switch ($operator) {
      case '=':
        $value = [$value];
        $operator = 'IN';

      case 'IN':
        $this->conditions[] = [
          'field' => $field,
          'value' => $value,
          'operator' => $operator,
          'column' => NULL,
          'lang' => NULL,
        ];

        break;

      // Re-write '!=' and '<>' as 'NOT IN' operators as it can be handled
      // in a generic way.
      case '!=':
      case '<>':
        $value = [$value];
        $operator = 'NOT IN';

      case '>':
      case '<':
      case '>=':
      case '<=':
      case 'NOT IN':
        $this->conditions[] = [
          'field' => $field,
          'value' => $value,
          'operator' => $operator,
          'column' => NULL,
          'lang' => NULL,
        ];
    }
    return $this;
  }

  /**
   * {@inheritdoc}
   *
   * Map the field names with the corresponding resource IDs.
   * The predicate mapping can not added as a direct filter. It is being
   * loaded from the database. There is no way that in a single request, the
   * same predicate is found with a single and multiple mappings.
   * There is no filter per bundle in the query. That makes it safe to not check
   * on the predicate mappings that are already in the query.
   */
  public function compile($query): void {
    $entity_type = $query->getEntityType();
    $condition_stack = array_merge($this->conditions, $this->fieldMappingConditions);
    // The ID and bundle keys do not need to be compiled as they were already
    // handled in the keyCondition.
    $condition_stack = array_filter($condition_stack, function ($condition) {
      return !in_array($condition['field'], [
        $this->idKey,
        $this->bundleKey,
      ], TRUE);
    });

    foreach ($condition_stack as $condition) {
      if ($condition['field'] instanceof ConditionInterface) {
        $condition['field']->compile($query);
      }
      else {
        $this->addFieldMappingRequirement($entity_type->id(), $condition['field'], $condition['column']);
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function addFieldMappingRequirement(string $entity_type_id, string $field, ?string $column = NULL): void {
    if (empty($column)) {
      $column = $this->fieldHandler->getFieldMainProperty($entity_type_id, $field);
    }

    $mappings = $this->fieldHandler->getFieldPredicates($entity_type_id, $field, $column);
    $mappings = array_values(array_unique($mappings));
    $field_name = $field . '__' . $column;
    if (count($mappings) === 1) {
      $this->fieldMappings[$field_name] = reset($mappings);
    }
    else {
      if (!isset($this->fieldMappings[$field_name])) {
        $this->fieldMappings[$field_name] = $field_name . '_predicate';
      }
      // The predicate mapping is not added as a direct filter. It is being
      // loaded by the database. There is no way that in a single request,
      // the same predicate is found with a single and multiple mappings.
      // There is no filter per bundle in the query.
      $this->fieldMappingConditions[$field_name] = [
        'field' => $field,
        'column' => $column,
        'value' => $mappings,
        'operator' => 'IN',
      ];
    }
  }

  /**
   * Returns the string version of the conditions.
   *
   * @return string
   *   The string version of the conditions.
   */
  public function __toString(): string {
    // In case of re-compiling, remove previous fragments. This will ensure that
    // not previous duplicates or leftovers remain.
    $this->conditionFragments = [];

    // There are cases where not a single triple is formed. In these cases, a
    // default condition has to be added in order to retrieve distinct Ids.
    // Also, in case the query is an OR, it should not get the default condition
    // because it will be added in every subquery which has an 'AND'
    // conjunction.
    if (($this->requiresDefaultPattern && $this->conjunction === 'AND') || empty($this->conditions())) {
      $this->addDefaultTriplePattern();
    }

    // The fieldMappingConditions are added first because they are converted
    // into a 'VALUES' clause which increases performance.
    $this->fieldMappingConditionsToString();

    // Convert the conditions.
    $this->conditionsToString();

    // Put together everything.
    $condition_fragments = array_merge($this->tripleFragments, $this->conditionFragments);

    return implode(self::$conjunctionMap[$this->conjunction]['delimiter'], array_unique($condition_fragments));
  }

  /**
   * Converts the field mapping conditions into string versions.
   */
  protected function fieldMappingConditionsToString(): void {
    foreach ($this->fieldMappingConditions as $condition) {
      $field_name = $condition['field'] . '__' . $condition['column'];
      $field_predicate = $this->fieldMappings[$field_name];
      $condition_string = self::ID_KEY . ' ' . $this->escapePredicate($field_predicate) . ' ' . SparqlArg::toVar($field_name);
      $this->addConditionFragment($condition_string);

      $condition['value'] = SparqlArg::toResourceUris($condition['value']);
      $condition['field'] = $field_predicate;
      $condition_string = $this->compileValuesFilter($condition);
      $this->addConditionFragment($condition_string);
    }
  }

  /**
   * Converts the conditions into string versions.
   */
  protected function conditionsToString(): void {
    $filter_fragments = [];

    foreach ($this->conditions as $condition) {
      if ($condition['field'] instanceof ConditionInterface) {
        // Get the string version of a nested condition.
        $this->addConditionFragment((string) $condition['field']);
        continue;
      }

      // The ID key only needs a conversion on the field to its corresponding
      // variable version.
      if ($condition['field'] === $this->idKey) {
        $field_name = $this->fieldMappings[$condition['field']];
        $predicate = $this->fieldMappings[$condition['field']];
      }
      // The bundle needs special handle as it might get a filter both in the
      // predicate and the value.
      elseif ($condition['field'] === $this->bundleKey) {
        $this->compileBundleCondition($condition);
        // This will be '{$bundle_key}' as it is always an 'IN' or a 'NOT IN'
        // clause.
        $field_name = $condition['field'];
        $predicate = $this->fieldMappings[$condition['field']];
      }
      // Implement the appropriate conversions. If the field has a single
      // mapping, convert it into a triple. If field predicate is having more
      // than one values, get the predicate variable and set it in the triple.
      else {
        $field_name = $condition['field'] . '__' . $condition['column'];
        $predicate = $this->fieldMappings[$field_name];
        // In case the operator is not '=', add a support triple pattern.
        if (in_array($condition['operator'], $this->requiresDefaultPatternOperators) && isset($this->fieldMappings[$field_name])) {
          $this->addConditionFragment(self::ID_KEY . ' ' . $this->escapePredicate($this->fieldMappings[$field_name]) . ' ' . SparqlArg::toVar($field_name));
        }
      }

      $condition['value'] = $this->escapeValue($condition['field'], $condition['value'], $condition['column'], $condition['lang']);
      $condition['field'] = $field_name;
      switch ($condition['operator']) {
        case '=':
          // The ID is not going to end with an '=' operator so it is safe to
          // use the $predicate variable.
          $this->tripleFragments[] = self::ID_KEY . ' ' . $this->escapePredicate($predicate) . ' ' . $condition['value'];
          break;

        case 'EXISTS':
          $this->compileExists($condition);
          break;

        case 'NOT EXISTS':
          $this->compileNotExists($condition);
          break;

        case 'CONTAINS':
        case 'LIKE':
        case 'NOT LIKE':
        case 'STARTS WITH':
        case 'ENDS WITH':
          $this->addConditionFragment($this->compileLike($condition));
          break;

        case 'IN':
          $this->addConditionFragment($this->compileValuesFilter($condition));
          break;

        default:
          $filter_fragments[] = $this->compileFilter($condition);

      }
    }

    // Finally, bring the filters together.
    if (!empty($filter_fragments)) {
      $this->addConditionFragment($this->compileFilters($filter_fragments));
    }
  }

  /**
   * Adds a condition string to the condition fragments.
   *
   * The encapsulation of the condition according to the conjunction is taking
   * place here.
   *
   * @param string $condition_string
   *   A string version of the condition.
   */
  protected function addConditionFragment(string $condition_string): void {
    $prefix = self::$conjunctionMap[$this->conjunction]['prefix'];
    $suffix = self::$conjunctionMap[$this->conjunction]['suffix'];
    $this->conditionFragments[] = $prefix . $condition_string . $suffix;
  }

  /**
   * Adds a default condition to the condition class.
   */
  protected function addDefaultTriplePattern(): void {
    $this->compileBundleCondition(['field' => $this->bundleKey]);
  }

  /**
   * Adds a default bundle condition to the condition class.
   *
   * If the type predicates are more than one, this will result in
   * { ?entity ?{$bundle_key}_predicate ?{$bundle_key} .
   * VALUES ?{$bundle_key}_predicate { ... }}.
   *
   * If there is only one type predicate then it is added directly like
   * { ?entity ?<bundle_predicate> ?{$bundle_key} }
   *
   * This method simply adds the mapping condition for the bundle if needed
   * otherwise, it simply adds a needed triple.
   *
   * @param array $condition
   *   The query condition.
   *
   * @todo This could work generically but currently the term storage has
   * two possible predicates. This can be removed when we will be left with one
   * predicate for the term storage.
   */
  protected function compileBundleCondition(array $condition): void {
    if (count($this->typePredicates) > 1) {
      $this->addConditionFragment(self::ID_KEY . ' ' . $this->escapePredicate($this->fieldMappings[$condition['field']]) . ' ' . SparqlArg::toVar($condition['field']));
      $this->addConditionFragment($this->compileValuesFilter([
        'field' => $this->escapePredicate($this->fieldMappings[$condition['field']]),
        'value' => SparqlArg::toResourceUris($this->typePredicates),
        'operator' => 'IN',
      ]));
    }
    else {
      $this->addConditionFragment(self::ID_KEY . ' ' . $this->escapePredicate($this->fieldMappings[$condition['field']]) . ' ' . SparqlArg::toVar($condition['field']));
      $this->fieldMappings[$this->bundleKey] = reset($this->typePredicates);
    }
  }

  /**
   * Compiles a filter exists condition.
   *
   * Since a triple in SPARQL works just like EXISTS does, for EXISTS we add
   * any condition missing from the field mapping fragments.
   *
   * @param array $condition
   *   An array that contains the 'field', 'value', 'operator' values.
   */
  protected function compileExists(array $condition): void {
    $field_predicate = $this->fieldMappings[$condition['field']];
    $condition_strings = [];
    $condition_strings[] = self::ID_KEY . ' ' . $this->escapePredicate($field_predicate) . ' ' . SparqlArg::toVar($condition['field']);

    if (isset($this->fieldMappingConditions[$condition['field']])) {
      $mapping_condition = $this->fieldMappingConditions[$condition['field']];
      $mapping_condition['value'] = SparqlArg::toResourceUris($mapping_condition['value']);
      $mapping_condition['field'] = $field_predicate;
      $condition_strings[] = $this->compileValuesFilter($mapping_condition);
    }

    foreach ($condition_strings as $condition_string) {
      if (array_search($condition_string, $this->conditionFragments) === FALSE) {
        $this->addConditionFragment($condition_string);
      }
    }
  }

  /**
   * Compiles a filter not exists condition.
   *
   * @param array $condition
   *   An array that contains the 'field', 'value', 'operator' values.
   */
  protected function compileNotExists(array $condition): void {
    $prefix = self::$filterOperatorMap[$condition['operator']]['prefix'] ?? '';
    $suffix = self::$filterOperatorMap[$condition['operator']]['suffix'] ?? '';

    $field_predicate = $this->fieldMappings[$condition['field']];
    $condition_strings = [];
    $condition_strings[] = self::ID_KEY . ' ' . $this->escapePredicate($field_predicate) . ' ' . SparqlArg::toVar($condition['field']);

    if (isset($this->fieldMappingConditions[$condition['field']])) {
      $mapping_condition = $this->fieldMappingConditions[$condition['field']];
      $mapping_condition['value'] = SparqlArg::toResourceUris($mapping_condition['value']);
      $mapping_condition['field'] = $field_predicate;
      $condition_strings[] = $this->compileValuesFilter($mapping_condition);
    }

    foreach ($condition_strings as $condition_string) {
      $key = array_search($condition_string, $this->conditionFragments);
      // Since field mapping conditions act also as EXISTS (the triple patterns
      // MUST exist), remove any pattern added in the mapping conditions so that
      // only the negative condition below exists.
      if ($key !== FALSE) {
        unset($this->conditionFragments[$key]);
      }
    }

    $this->addConditionFragment($prefix . implode(' . ', $condition_strings) . $suffix);

  }

  /**
   * Compiles a filter 'LIKE' condition using a regex.
   *
   * @param array $condition
   *   An array that contains the 'field', 'value', 'operator' values.
   *
   * @return string
   *   A condition fragment string.
   */
  protected function compileLike(array $condition): string {
    $prefix = self::$filterOperatorMap[$condition['operator']]['prefix'] ?? '';
    $suffix = self::$filterOperatorMap[$condition['operator']]['suffix'] ?? '';
    $value = 'lcase(str(' . SparqlArg::toVar($condition['field']) . ')), lcase(str(' . $condition['value'] . '))';
    return $prefix . $value . $suffix;
  }

  /**
   * Compiles a filter condition.
   *
   * @param array $condition
   *   An array that contains the 'field', 'value', 'operator' values.
   *
   * @return string
   *   A condition fragment string.
   *
   * @throws \Exception
   *    Thrown when a value is an array but a string is expected.
   */
  protected function compileFilter(array $condition): string {
    $prefix = self::$filterOperatorMap[$condition['operator']]['prefix'] ?? '';
    $suffix = self::$filterOperatorMap[$condition['operator']]['suffix'] ?? '';
    $condition['field'] = SparqlArg::toVar($condition['field']);
    $operators = ['<', '>', '<=', '>='];
    // If the ID is compared as a string, we need to convert it to one.
    if ($condition['field'] === $this->fieldMappings[$this->idKey] && in_array($condition['operator'], $operators)) {
      $condition['field'] = 'STR(' . $condition['field'] . ')';
    }
    if (is_array($condition['value'])) {
      if (!isset(self::$filterOperatorMap[$condition['operator']]['delimiter'])) {
        throw new \Exception("An array value is not supported for this operator.");
      }
      $condition['value'] = '(' . implode(self::$filterOperatorMap[$condition['operator']]['delimiter'], $condition['value']) . ')';
    }
    return $prefix . $condition['field'] . ' ' . $condition['operator'] . ' ' . $condition['value'] . $suffix;
  }

  /**
   * Compiles an 'IN' condition as a SPARQL 'VALUES'.
   *
   * 'VALUES' is preferred over 'FILTER IN' for performance.
   * This should only be called for subject and predicate filter as it considers
   * values to be resources.
   *
   * @param array $condition
   *   The condition array.
   *
   * @return string
   *   The string version of the condition.
   */
  protected function compileValuesFilter(array $condition): string {
    if (is_string($condition['value'])) {
      $value = [$condition['value']];
    }
    else {
      $value = $condition['value'];
    }
    return 'VALUES ' . SparqlArg::toVar($condition['field']) . ' {' . implode(' ', $value) . '}';
  }

  /**
   * Compiles a filter condition.
   *
   * @param array $filter_fragments
   *   An array of filter strings.
   *
   * @return string
   *   A condition fragment string.
   */
  protected function compileFilters(array $filter_fragments): string {
    // The delimiter is always a '&&' because otherwise it would be a separate
    // condition class.
    $delimiter = '&&';
    if (count($filter_fragments) > 1) {
      $compiled_filter = '(' . implode(') ' . $delimiter . '(', $filter_fragments) . ')';
    }
    else {
      $compiled_filter = reset($filter_fragments);
    }

    return 'FILTER (' . $compiled_filter . ')';
  }

  /**
   * Calculates the langcode of the field if one exists.
   *
   * @param string $field
   *   The field name.
   * @param string|null $column
   *   The column name.
   * @param string|null $default_lang
   *   A default langcode to return.
   *
   * @return string|null
   *   The langcode to be used.
   *
   * @throws \Exception
   *   Thrown when a non existing field is passed.
   */
  protected function getLangCode(string $field, ?string $column = NULL, ?string $default_lang = NULL): ?string {
    $format = $this->fieldHandler->getFieldFormat($this->query->getEntityTypeId(), $field, $column);
    $format = reset($format);
    if ($format !== SparqlEntityStorageFieldHandlerInterface::TRANSLATABLE_LITERAL) {
      return NULL;
    }

    $non_languages = [
      LanguageInterface::LANGCODE_NOT_SPECIFIED,
      LanguageInterface::LANGCODE_DEFAULT,
      LanguageInterface::LANGCODE_NOT_APPLICABLE,
      LanguageInterface::LANGCODE_SITE_DEFAULT,
      LanguageInterface::LANGCODE_SYSTEM,
    ];

    if (empty($default_lang) || in_array($default_lang, $non_languages)) {
      return $this->languageManager->getCurrentLanguage()->getId();
    }

    return $default_lang;
  }

  /**
   * {@inheritdoc}
   */
  public function exists($field, $lang = NULL): self {
    return $this->condition($field, NULL, 'EXISTS');
  }

  /**
   * {@inheritdoc}
   */
  public function notExists($field, $lang = NULL): self {
    return $this->condition($field, NULL, 'NOT EXISTS');
  }

  /**
   * Escapes the predicate.
   *
   * If the value is a URI, convert it to a resource. If it is not a URI,
   * convert it to a variable.
   *
   * @param string $field_name
   *   The field machine name.
   *
   * @return string
   *   The altered $value.
   */
  protected function escapePredicate(string $field_name): string {
    if (SparqlArg::isValidResource($field_name)) {
      return SparqlArg::uri($field_name);
    }
    return SparqlArg::toVar($field_name);
  }

  /**
   * Handles the value according to their type.
   *
   * @param string $field
   *   The field name.
   * @param mixed $value
   *   The value.
   * @param string|null $column
   *   The column name.
   * @param string|null $lang
   *   The langcode of the value.
   *
   * @return string|array
   *   The altered $value.
   *
   * @throws \EasyRdf\Exception
   *   Thrown when the bundle does not have a mapping.
   */
  protected function escapeValue(string $field, $value, ?string $column = NULL, ?string $lang = NULL) {
    if (empty($value)) {
      return $value;
    }

    if ($field === $this->idKey) {
      // If the field name is the ID, and the operators are not among the '>',
      // '<', '>=', '<=', then the value has already been converted in an array
      // so escape it.
      if (is_array($value)) {
        return SparqlArg::toResourceUris($value);
      }
      // Convert the value to a string in order to perform the remaining
      // comparisons.
      elseif (is_string($value)) {
        return 'STR("' . $value . '")';
      }
    }

    // If the field name is the bundle key, convert the values to their
    // corresponding mappings. The value is also an array.
    elseif ($field === $this->bundleKey) {
      $is_str = is_string($value);
      $values = is_array($value) ? $value : [$value];

      foreach ($values as &$value) {
        if (!$this->fieldHandler->isBundleMapped($this->query->getEntityTypeId(), $value)) {
          $value = 'http://' . Crypt::randomBytesBase64(20);
          $value = SparqlArg::toResourceUris([$value])[0];
        }
        else {
          $converted = $this->fieldHandler->bundlesToUris($this->query->getEntityTypeId(), [$value], TRUE);
          $value = reset($converted);
        }
      }

      return $is_str ? reset($values) : $values;
    }

    $serializer = new Ntriples();
    $lang = $this->getLangCode($field, $column, $lang);
    if (is_array($value)) {
      foreach ($value as $i => $v) {
        $outbound_value = $this->fieldHandler->getOutboundValue($this->query->getEntityTypeId(), $field, $v, $lang, $column);
        $value[$i] = $serializer->serialiseValue($outbound_value);
      }
    }
    else {
      $outbound_value = $this->fieldHandler->getOutboundValue($this->query->getEntityTypeId(), $field, $value, $lang, $column);
      $value = $serializer->serialiseValue($outbound_value);
    }
    return $value;
  }

  /**
   * {@inheritdoc}
   */
  public function __clone() {}

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc