You are here

class InstallableRequirement in Markdown 8.2

Markdown Requirement Annotation.

@Target("ANNOTATION")

@property \Drupal\markdown\Annotation\Identifier $id Optional. Note: if this contains a colon (:), it will be treated as a type based identifier, where everything prior to the colon is considered the type and everything following the colon is considered the identifier, relative in context with the type. Available types are:

  • parser:<parserPluginId>
  • extension<extensionPluginId>
  • filter:<filterPluginId>
  • service:<service.id>

@todo Move upstream to https://www.drupal.org/project/installable_plugins.

Hierarchy

Expanded class hierarchy of InstallableRequirement

5 files declare their use of InstallableRequirement
AllowedHtmlManager.php in src/PluginManager/AllowedHtmlManager.php
ExtensionManager.php in src/PluginManager/ExtensionManager.php
FilterAlign.php in src/Plugin/Markdown/AllowedHtml/FilterAlign.php
InstallablePluginManager.php in src/PluginManager/InstallablePluginManager.php
TableOfContentsExtension.php in src/Plugin/Markdown/CommonMark/Extension/TableOfContentsExtension.php

File

src/Annotation/InstallableRequirement.php, line 39

Namespace

Drupal\markdown\Annotation
View source
class InstallableRequirement extends AnnotationObject {

  /**
   * An array of arguments to be passed to the callback.
   *
   * @var array
   */
  public $arguments = [];

  /**
   * A callback invoked to retrieve the value used to validate the requirement.
   *
   * Note: this is only used if there have been constraints set to validate.
   *
   * If a value is not supplied, then the requirement will simply check whether
   * the provider is installed.
   *
   * If the value is supplied and it isn't callable, it will check whether
   * the provider type is a filter, service, markdown-extension,
   * markdown-library, or markdown-parser and then see if it is a publicly
   * accessible method on the provider's object. Optionally, a method name
   * can be prefixed with a double-colon (::) to ensure no matching static
   * callable function with a similar name is used.
   *
   * @var string
   */
  public $callback;

  /**
   * An array of validation constraints used to validate the callable value.
   *
   * @var array
   *
   * @see \Drupal\Core\TypedData\TypedData::getConstraints().
   */
  public $constraints = [];

  /**
   * The name of the constraint, if any.
   *
   * Note: this will automatically be determined if using a typed identifier
   * and not already provided.
   *
   * @var string
   */
  public $name;

  /**
   * The value used for constraints.
   *
   * Note: If this value is explicitly provided, then any callback set will
   * be ignored.
   *
   * @var mixed
   */
  public $value;

  /**
   * Retrieves the object defined by id/type.
   *
   * @return mixed|void
   *   The object defined by id/type.
   *
   * @noinspection PhpDocMissingThrowsInspection
   */
  public function getObject() {
    if ($this
      ->isTyped()) {
      $container = \Drupal::getContainer();
      list($type, $id) = $this
        ->listTypeId();
      switch ($type) {
        case 'parser':
          if (($parserManager = ParserManager::create($container)) && $parserManager
            ->hasDefinition($id)) {
            if (!isset($this->name)) {
              $definition = $parserManager
                ->getDefinition($id);
              if ($library = $definition
                ->getInstalledLibrary() ?: $definition
                ->getPreferredLibrary()) {
                $this->name = $library
                  ->getLink();
              }
            }
            return $parserManager
              ->createInstance($id);
          }
          break;
        case 'extension':
          if (($extensionManager = ExtensionManager::create($container)) && $extensionManager
            ->hasDefinition($id)) {
            if (!isset($this->name)) {
              $definition = $extensionManager
                ->getDefinition($id);
              if ($library = $definition
                ->getInstalledLibrary() ?: $definition
                ->getPreferredLibrary()) {
                $this->name = $library
                  ->getLink();
              }
            }
            return $extensionManager
              ->createInstance($id);
          }
          break;
        case 'filter':

          /** @var \Drupal\filter\FilterPluginManager $filterManager */
          if (($filterManager = $container
            ->get('plugin.manager.filter')) && $filterManager
            ->hasDefinition($id)) {
            if (!isset($this->name)) {
              $this->name = t('Filter "@id"', [
                '@id' => $id,
              ]);
            }

            /* @noinspection PhpUnhandledExceptionInspection */
            return $filterManager
              ->createInstance($id);
          }
          break;
        case 'service':
          if ($container
            ->has($id)) {
            if (!isset($this->name)) {
              $this->name = t('Service "@id"', [
                '@id' => $id,
              ]);
            }
            return $container
              ->get($id);
          }
          break;
      }
    }
  }

  /**
   * Retrieves the identifier type, if any.
   *
   * @return string|void
   *   The identifier type, if any.
   */
  public function getType() {
    if ($this
      ->isTyped() && ($type = $this
      ->listTypeId()[0])) {
      return $type;
    }
  }

  /**
   * Retrieves the typed identifier, if any.
   *
   * @return string|void
   *   The typed identifier, if any.
   */
  public function getTypeId() {
    if ($this
      ->isTyped() && ($type = $this
      ->listTypeId()[1])) {
      return $type;
    }
  }

  /**
   * Indicates whether the plugin has a typed identifier.
   *
   * @return bool
   *   TRUE or FALSE
   */
  public function isTyped() {
    return $this->id && strpos($this->id, ':') !== FALSE;
  }

  /**
   * Retrieves the split typed identifier.
   *
   * @return string[]|void
   *   An indexed array with type values: type, id; intended to be
   *   used with list().
   */
  public function listTypeId() {
    if ($this
      ->isTyped()) {
      return explode(':', $this->id, 2);
    }
  }

  /**
   * Validates the requirement.
   *
   * @return \Symfony\Component\Validator\ConstraintViolationListInterface
   *   A list of constraint violations. If the list is empty, validation
   *   succeeded.
   */
  public function validate() {
    $object = $this
      ->getObject();
    if (isset($this->name)) {
      foreach ($this->constraints as $name => &$constraint) {
        if ($name !== 'Version') {
          continue;
        }
        if (!is_array($constraint)) {
          $constraint = [
            'value' => $constraint,
          ];
        }
        if (!isset($constraint['name'])) {
          $constraint['name'] = $this->name;
        }
      }
    }
    if (!isset($this->value)) {
      if ($this->callback) {
        if ($object) {
          $callback = [
            $object,
            ltrim($this->callback, ':'),
          ];
          $this->value = call_user_func_array($callback, $this->arguments);
        }
        else {
          $this->value = call_user_func_array($this->callback, $this->arguments);
        }
        if (!$this->constraints) {
          $this->constraints = [
            'NotNull' => [],
          ];
        }
      }
      else {
        $this->value = $object ? [
          $this
            ->getId(),
        ] : [];
        if (!$this->constraints) {
          $this->constraints = [
            'Count' => [
              'min' => 1,
              'max' => 1,
            ],
          ];
        }
      }
    }

    // If returned value is typed data, add the conditions to its definition.
    if (($value = $this->value) instanceof TypedDataInterface) {
      $typed = $this->value;
      $definition = $typed
        ->getDataDefinition();
      foreach ($this->constraints as $name => $options) {
        $definition
          ->addConstraint($name, $options);
      }
    }
    elseif (is_array($value) || $value instanceof \Traversable) {

      // Don't use config based types. Bug in earlier versions of core.
      // @see https://www.drupal.org/project/drupal/issues/1928868.
      $valueType = is_scalar(current($value)) ? gettype(current($value)) : 'string';
      $itemDefinition = \Drupal::typedDataManager()
        ->createDataDefinition($valueType);
      $definition = new ListDataDefinition([], $itemDefinition);
      $definition
        ->setConstraints($this->constraints);
      $typed = ItemList::createInstance($definition);
      $typed
        ->setValue($value);
    }
    else {
      $valueType = is_scalar($value) ? gettype($value) : 'string';
      $definition = DataDefinition::create($valueType)
        ->setConstraints($this->constraints);
      switch ($valueType) {
        case 'boolean':
          $typed = BooleanData::createInstance($definition);
          break;
        case 'float':
          $typed = FloatData::createInstance($definition);
          break;
        case 'integer':
          $typed = IntegerData::createInstance($definition);
          break;
        case 'string':
        default:
          $typed = StringData::createInstance($definition);
          $value = (string) $value;
          break;
      }
      $typed
        ->setValue($value);
    }

    // Attempt to validate the requirement constraints.
    try {
      return $typed
        ->validate();
    } catch (PluginNotFoundException $exception) {

      // See if a global was set in markdown_requirements().
      // @todo This is currently only needed because its bundled with the
      //   markdown module; remove when moved to a standalone upstream project
      //   https://www.drupal.org/project/installable_plugins
      global $_markdown_requirements;
      $message = $exception
        ->getMessage();
      $violationList = new ConstraintViolationList();

      // The exception to this exception is when it is attempting to find
      // constraints provided by this module, which may not yet be installed.
      // In this case, the constraints must be validated manually.
      // @see markdown_requirements()
      // @todo This is currently only needed because its bundled with the
      //   markdown module; remove when moved to a standalone upstream project
      //   https://www.drupal.org/project/installable_plugins
      $markdownConstraints = [
        'Installed',
        'Exists',
        'Version',
      ];
      if ($_markdown_requirements === 'install' && preg_match('/(?:Plugin ID |The )\\s*[\'"]([^\'"]+)[\'"]\\s*(?:was not found|plugin does not exist)/i', $message, $matches) && in_array($matches[1], $markdownConstraints)) {
        $pluginId = $matches[1];
        if (($class = '\\Drupal\\markdown\\Plugin\\Validation\\Constraint\\' . $pluginId) && class_exists($class)) {
          $value = $typed
            ->getValue();
          $context = new ExecutionContext($typed
            ->getTypedDataManager()
            ->getValidator(), $value, new DrupalTranslator());
          foreach ($this->constraints as $name => $options) {
            if ($name === $pluginId) {

              /** @var \Symfony\Component\Validator\Constraint $constraint */
              $constraint = new $class($options);
              if (($validatorClass = $constraint
                ->validatedBy()) && class_exists($validatorClass)) {

                /** @var \Symfony\Component\Validator\ConstraintValidatorInterface $constraintValidator */
                $constraintValidator = new $validatorClass();
                $constraintValidator
                  ->initialize($context);
                $constraintValidator
                  ->validate($value, $constraint);
              }
            }
          }
          $violationList
            ->addAll($context
            ->getViolations());
          return $violationList;
        }
      }

      // Add the exception message to the violations list.
      $violationList
        ->add(new ConstraintViolation($message, '', [], '', '', ''));
      return $violationList;
    }
  }

}

Members

Namesort descending Modifiers Type Description Overrides
AnnotationBase::$class protected property The class used for this annotated class.
AnnotationBase::$id public property The annotated class ID. 1
AnnotationBase::$provider protected property The provider of the annotated class.
AnnotationBase::getClass public function Gets the class of the annotated class. Overrides AnnotationInterface::getClass
AnnotationBase::getProvider public function Gets the name of the provider of the annotated class. Overrides AnnotationInterface::getProvider
AnnotationBase::setClass public function Sets the class of the annotated class. Overrides AnnotationInterface::setClass
AnnotationBase::setProvider public function Sets the name of the provider of the annotated class. Overrides AnnotationInterface::setProvider
AnnotationObject::$description public property The description of the plugin.
AnnotationObject::$label public property A human-readable label.
AnnotationObject::$weight public property The weight of the plugin.
AnnotationObject::$_deprecated protected property Stores deprecated values.
AnnotationObject::$_deprecatedProperties protected property A list of deprecation messages, keyed by the deprecated property name.
AnnotationObject::$_triggeredDeprecations private property A list of triggered deprecations.
AnnotationObject::create public static function Allows the creation of new objects statically, for easier chainability.
AnnotationObject::DEPRECATED_REGEX constant
AnnotationObject::doMerge protected function Merges values with this plugin.
AnnotationObject::get public function Gets the value of an annotation. Overrides AnnotationInterface::get
AnnotationObject::getId public function Gets the unique ID for this annotated class. Overrides AnnotationBase::getId
AnnotationObject::getIterator public function
AnnotationObject::id public function Gets the unique identifier of the plugin. Overrides PluginDefinitionInterface::id
AnnotationObject::merge public function Merges values with this plugin.
AnnotationObject::normalizeValue protected function Normalizes a value to ensure its ready to be merged with the definition.
AnnotationObject::offsetExists public function
AnnotationObject::offsetGet public function
AnnotationObject::offsetSet public function
AnnotationObject::offsetUnset public function
AnnotationObject::protectedProperties protected function Indicates properties that should never be overridden after instantiation. 1
AnnotationObject::triggerDeprecation private function Triggers a deprecation notice for a given property.
AnnotationObject::validateIdentifier protected function Helper method for validating the definition identifier. 2
AnnotationObject::__construct public function AnnotationObject constructor. 1
AnnotationObject::__get public function
AnnotationObject::__isset public function
AnnotationObject::__set public function
AnnotationObject::__sleep public function
AnnotationObject::__unset public function
AnnotationObject::__wakeup public function
DependencySerializationTrait::$_entityStorages protected property An array of entity type IDs keyed by the property name of their storages.
DependencySerializationTrait::$_serviceIds protected property An array of service IDs keyed by property name used for serialization.
DependencySerializationTrait::__sleep public function Aliased as: __sleepTrait 1
DependencySerializationTrait::__wakeup public function Aliased as: __wakeupTrait 2
InstallableRequirement::$arguments public property An array of arguments to be passed to the callback.
InstallableRequirement::$callback public property A callback invoked to retrieve the value used to validate the requirement.
InstallableRequirement::$constraints public property An array of validation constraints used to validate the callable value.
InstallableRequirement::$name public property The name of the constraint, if any.
InstallableRequirement::$value public property The value used for constraints.
InstallableRequirement::getObject public function Retrieves the object defined by id/type.
InstallableRequirement::getType public function Retrieves the identifier type, if any.
InstallableRequirement::getTypeId public function Retrieves the typed identifier, if any.
InstallableRequirement::isTyped public function Indicates whether the plugin has a typed identifier.
InstallableRequirement::listTypeId public function Retrieves the split typed identifier.
InstallableRequirement::validate public function Validates the requirement.