workflow-8.x-1.x-dev/src/Plugin/Field/FieldType/WorkflowItem.php

src/Plugin/Field/FieldType/WorkflowItem.php
<?php

namespace Drupal\workflow\Plugin\Field\FieldType;

use Drupal\Core\Entity\TypedData\EntityDataDefinition;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Form\OptGroup;
use Drupal\Core\Messenger\MessengerTrait;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\TypedData\DataReferenceDefinition;
use Drupal\Core\TypedData\OptionsProviderInterface;
use Drupal\Core\Url;
use Drupal\options\Plugin\Field\FieldType\ListStringItem;
use Drupal\workflow\Entity\Workflow;
use Drupal\workflow\Entity\WorkflowInterface;
use Drupal\workflow\Entity\WorkflowManager;
use Drupal\workflow\Entity\WorkflowState;
use Drupal\workflow\Entity\WorkflowTransition;
use Drupal\workflow\Entity\WorkflowTransitionInterface;
use Drupal\workflow\Plugin\Field\WorkflowItemInterface;
use Drupal\workflow\WorkflowTypeAttributeTrait;

/**
 * Defines the 'workflow' field, referencing a workflow_transition entity type.
 *
 * Settings depend on subclass type EntityReference or ListString.
 *
 * @FieldType(
 *   id = "workflow",
 *   label = @Translation("Workflow state"),
 *   description = @Translation("This field stores Workflow values for a certain Workflow type from a list of allowed 'value => label' pairs, i.e. 'Publishing': 1 => unpublished, 2 => draft, 3 => published."),
 *   category = "workflow",
 *   default_widget = "workflow_default",
 *   default_formatter = "list_default",
 *   list_class = "\Drupal\workflow\Plugin\Field\FieldType\WorkflowItemList",
 *   cardinality = "1",
 *   constraints = {
 *     "WorkflowField" = {}
 *   },
 * )
 *
 * @todo Remove @FieldType Annotations, replaced by #[FieldType] Attributes
 * @see https://www.drupal.org/project/workflow/issues/3522574
 * @see https://www.drupal.org/project/workflow/issues/3529071
 * @see https://www.drupal.org/project/workflow/issues/3529350
 *
 * For EntityReferenceItem, supported settings are:
 * - target_type: The entity type to reference. Required.
 */
class WorkflowItem extends ListStringItem implements OptionsProviderInterface, WorkflowItemInterface {
  // @todo Inherit from EntityReferenceItem?
  use MessengerTrait;

  /*
   * Add variables and get/set methods for Workflow property.
   */
  use WorkflowTypeAttributeTrait;

  protected const TARGET_TRANSITION = 'workflow_transition';
  protected const TARGET_STATE = 'workflow_state';

  /**
   * {@inheritdoc}
   *
   * Settings depend on subclass type EntityReference or ListString.
   *
   * @see https://www.drupal.org/project/workflow/issues/3522574
   * @see https://www.drupal.org/project/workflow/issues/3529071
   */
  public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
    // $properties = $properties
    // + ListStringItem::propertyDefinitions($field_definition)
    // + EntityReferenceItem::propertyDefinitions($field_definition);
    $properties = parent::propertyDefinitions($field_definition);

    // When Item is ListStringItem, ListItemBase.
    $properties['value'] = DataDefinition::create('string')
      ->setLabel(new TranslatableMarkup('Workflow state ID'))
      ->addConstraint('Length', ['max' => 128])
      ->setRequired(TRUE);

    // When Item is EntityReferenceItem.
    // Override parent integer ReferenceID for State key.
    $properties['target_id'] = DataDefinition::create('string')
      ->setLabel(t('Workflow state ID'))
      // @todo When Item is EntityReferenceItem, set to NOT computed (?).
      ->setComputed(TRUE)
      ->addConstraint('Length', ['max' => 128])
      ->setRequired(TRUE);
    $properties['entity'] = DataReferenceDefinition::create('entity')
      // = DataDefinition::create('WorkflowTransition')
      ->setLabel(t('State'))
      ->setDescription(new TranslatableMarkup('The Workflow state.'))
      // The entity object is computed out of the entity ID.
      ->setComputed(TRUE)
      ->setReadOnly(FALSE)
      ->setTargetDefinition(EntityDataDefinition::create(static::TARGET_STATE));

    // Custom property for storing Transition, next to State.
    $properties['workflow_transition'] = DataReferenceDefinition::create('entity')
      ->setLabel(t('Transition'))
      ->setDescription(new TranslatableMarkup('The WorkflowTransition setting the Workflow state.'))
      // The entity object is computed out of the entity ID.
      ->setComputed(TRUE)
      ->setReadOnly(FALSE)
      ->setTargetDefinition(EntityDataDefinition::create(static::TARGET_TRANSITION));

    return $properties;
  }

  /**
   * {@inheritdoc}
   */
  public static function schema(FieldStorageDefinitionInterface $field_definition) {
    // $schema = ListStringItem::schema($field_definition);
    // $schema = EntityReferenceItem::schema($field_definition);
    $schema = parent::schema($field_definition);

    $schema = [
      'columns' => [
        'value' => [
          'description' => 'The {workflow_states}.sid that this entity is currently in.',
          'type' => 'varchar',
          'length' => 128,
        ],
      ],
      'indexes' => [
        'value' => ['value'],
      ],
    ];
    return $schema;
  }

  /**
   * {@inheritdoc}
   */
  public static function defaultFieldSettings() {
    $settings = [
      // 'target_type' => '',
      // 'handler' => 'default',
      // 'handler_settings' => [],
    ] + parent::defaultFieldSettings();
    return $settings;
  }

  /**
   * {@inheritdoc}
   *
   * Note: Settings are used in static::propertyDefinitions();
   * Settings depend on subclass type EntityReference or ListString.
   *
   * @see https://www.drupal.org/project/workflow/issues/3522574
   * @see https://www.drupal.org/project/workflow/issues/3529071
   * @todo Avoid errors on admin/reports/status#error
   * @todo Avoid errors on admin/reports/config-inspector
   */
  public static function defaultStorageSettings() {
    $isEntityListStringItem = TRUE;
    $isEntityReferenceItem = FALSE;

    $settings = [
      'workflow_type' => '',
    ];
    $settings += $isEntityListStringItem
      ? [
        'allowed_values' => [],
        'allowed_values_function' => 'workflow_state_allowed_values',
      ]
      : [];
    $settings += $isEntityReferenceItem
      ? [
        'target_type' => 'workflow_state',
        'target_bundle' => '',
        'handler' => 'default',
      ]
      : [];
    $settings += parent::defaultStorageSettings();
    return $settings;
  }

  /**
   * {@inheritdoc}
   */
  public function storageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
    $element = parent::storageSettingsForm($form, $form_state, $has_data);

    $this->validateStorageSettingsForm($form, $form_state, $has_data);

    /** @var \Drupal\Core\Field\FieldStorageDefinitionInterface $field_storage */
    $field_storage = $this->getFieldDefinition()->getFieldStorageDefinition();
    $wid = $this->getWorkflowId();

    // Set required workflow_type on 'comment' field of CommentWithWorkflow.
    if (!$wid && WorkflowManager::isTargetCommentEntity($field_storage)) {
      /** @var \Drupal\Core\Config\Entity\ConfigEntityInterface $field_storage */
      $field_name = $field_storage->get('field_name');
      $workflow_options = [];
      foreach (_workflow_info_fields(NULL, '', '', $field_name) as $key => $info) {
        if (($info->getName() == $field_name)
          // && ($info->getTargetEntityTypeId() == $this->getEntity()->getEntityTypeId())
          && (!WorkflowManager::isTargetCommentEntity($info))
        ) {
          $wid = $info->getSetting('workflow_type');
          $workflow = Workflow::load($wid);
          $workflow_options[$wid] = $workflow->label();
        }
      }
    }
    else {
      // Create list of all Workflow types. Include an initial empty value.
      $workflow_options = workflow_allowed_workflow_names(FALSE);
    }

    // Let the user choose between the available workflow types.
    $url = Url::fromRoute('entity.workflow_type.collection')->toString();
    $element['workflow_type'] = [
      '#type' => 'select',
      '#title' => $this->t('Workflow type'),
      '#options' => $workflow_options,
      '#default_value' => $wid,
      '#required' => TRUE,
      '#disabled' => $has_data && $wid,
      '#description' => $this->t('Choose the Workflow type. Maintain workflows
         <a href=":url">here</a>.', [':url' => $url]),
    ];

    // Overwrite ListItemBase::storageSettingsForm().
    // First, remove 'allowed values' list, due to restructured form in D10.2.
    unset($element['allowed_values']);
    // @todo Set 'allowed_values_function' properly in storage,
    // so default parent code can be used.
    // Do not change. @see https://www.drupal.org/project/drupal/issues/2643308
    $allowed_values_function = $this->defaultStorageSettings()['allowed_values_function'];
    $element['allowed_values_function'] = [
      '#type' => 'item',
      '#title' => $this->t('Allowed values list'),
      '#markup' => $this->t('The value of this field is being determined by the %function function and may not be changed.', ['%function' => $allowed_values_function]),
      '#access' => !empty($allowed_values_function),
      '#value' => $allowed_values_function,
    ];

    return $element;
  }

  /**
   * Generate messages on ConfigFieldItemInterface::settingsForm().
   */
  protected function validateStorageSettingsForm(array &$form, FormStateInterface $form_state, $has_data) {
    // Validate each workflow, and generate a message if not complete.
    // Create list of all Workflow types. Include an initial empty value.
    $workflow_options = workflow_allowed_workflow_names(FALSE);
    // @todo D8: add this to WorkflowFieldConstraintValidator.
    // Set message, if no 'validated' workflows exist.
    if (count($workflow_options) == 1) {
      $this->messenger()->addWarning(
        $this->t('You must <a href=":create">create at least one workflow</a>
          before content can be assigned to a workflow.',
          [':create' => Url::fromRoute('entity.workflow_type.collection')->toString()]
        ));
    }

    // Validate via WorkflowFieldConstraint annotation for CommentWithWorkflow.
    // Show a message for each error.
    /** @var \Symfony\Component\Validator\ConstraintViolationList $violation_list */
    $violation_list = $this->validate();
    foreach ($violation_list->getIterator() as $violation) {
      switch ($violation->getPropertyPath()) {
        case 'fieldnameOnComment':
          // @todo CommentWithWorkflow & storageSettingsForm() constraints.
          // A 'comment' field name MUST be equal to content field name.
          // @todo Fix fields on a non-relevant entity_type.
          $this->messenger()->addError($violation->getMessage());
          $workflow_options = [];
          break;

        default:
          break;
      }
    }
  }

  /**
   * {@inheritdoc}
   */
  public function getEntity() {
    $entity = parent::getEntity();
    return $entity;
  }

  /**
   * {@inheritdoc}
   */
  public function isEmpty() {
    // @todo $is_empty = parent::isEmpty();
    $is_empty = empty($this->value);
    return $is_empty;
  }

  /**
   * {@inheritdoc}
   *
   * Set both the Transition property AND the to_sid value.
   */
  public function setValue($values, $notify = TRUE) {
    $transition = NULL;
    $entity = $this->getEntity();
    $field_name = $this->getFieldName();

    switch (TRUE) {
      case $values instanceof WorkflowTransitionInterface:
        /** @var \Drupal\workflow\Entity\WorkflowTransitionInterface $transition */
        $transition = $values;
        $sid = $transition->isScheduled()
          ? $transition->getFromSid()
          : $transition->getToSid();
        break;

      case is_array($values) && isset($values['value']) && (count($values) == 1):
        // @todo Add code when changing to EntityReferenceItem.
        $sid = $values['value'];
        break;

      case is_array($values) && isset($values['value']):
        // @todo Add code when changing to EntityReferenceItem.
        $sid = $values['value'];
        break;

      case is_array($values):
        $sid = $values['value'] ?? NULL;
        $sid ??= $values['to_sid'][0]['value'] ?? '';
        break;

      default:
        // Add provisions for ReferencedEntity.
        $sid = $values;
        break;
    }

    // $items may be empty on initial FieldItemList::setValue($values).
    // $items may be empty on node with core options widget.
    $transition ??= WorkflowTransition::create([
      'from_sid' => $sid,
      'entity' => $entity,
      'field_name' => $field_name,
      'wid' => $this->getWorkflowId(),
      'to_sid' => $sid,
    ]);

    $values = [
      'value' => $sid,
      'target_id' => $sid,
      'workflow_transition' => $transition,
    ];
    parent::setValue($values, $notify);
  }

  /**
   * {@inheritdoc}
   */
  public function getFieldName(): string {
    $field_name = $this->getParent()->getName();
    return $field_name;
  }

  /**
   * {@inheritdoc}
   */
  public function getState(): ?WorkflowState {
    $sid = $this->getStateId();
    $state = WorkflowState::load($sid)
      ?? WorkflowState::create([
        'id' => $sid,
        'wid' => $this->getWorkflowId(),
      ]);
    return $state;
  }

  /**
   * {@inheritdoc}
   */
  public function getStateId(): string {
    $sid = $this->value ?? '';
    return $sid;
  }

  /**
   * {@inheritdoc}
   */
  public function getTransition(): ?WorkflowTransitionInterface {
    $transition = NULL;

    $property = $this->get(static::TARGET_TRANSITION);
    /** @var \Drupal\workflow\Entity\WorkflowTransitionInterface $transition */
    $transition = $property->getValue();
    // Create a transition, to pass to the form.
    $transition ??= WorkflowTransition::create([
      'entity' => $this->getEntity(),
      'field_name' => $this->getParent()->getName(),
      'wid' => $this->getWorkflowId(),
    ]);
    return $transition;
  }

  /**
   * {@inheritdoc}
   */
  public function getWorkflowId(): ?string {
    // Get the Workflow ID, accommodating WorkflowTypeAttributeTrait.
    if (!empty($this->wid)) {
      return $this->wid;
    }

    // workflow_type may not set yet upon field creation.
    $wid = $this->getSetting('workflow_type');
    $this->setWorkflowId($wid);
    return $wid;
  }

  /**
   * {@inheritdoc}
   */
  protected function allowedValuesDescription() {
    return '';
  }

  /**
   * Generates a string representation of an array of 'allowed values'.
   *
   * This string format is suitable for edition in a textarea.
   *
   * @param \Drupal\Core\Entity\EntityInterface[] $states
   *   An array of WorkflowStates, with [key =>label] pairs.
   *
   * @return string
   *   The string representation of the $states array:
   *    - Values are separated by a carriage return.
   *    - Each value is in the format "value|label" or "value".
   */
  protected function allowedValuesString($states) {
    $lines = [];

    $wid = $this->getWorkflowId();

    $previous_wid = -1;
    /** @var \Drupal\workflow\Entity\WorkflowState $state */
    foreach ($states as $key => $state) {
      // Only show enabled states.
      if ($state->isActive()) {
        // Show a Workflow name between Workflows, if more then 1 in the list.
        if ((!$wid) && ($previous_wid !== $state->getWorkflowId())) {
          $previous_wid = $state->getWorkflowId();
          $workflow_label = $state->getWorkflow()->label();
          $lines[] = "$workflow_label's states: ";
        }
        $label = $this->t('@label', ['@label' => $state->label()]);

        $lines[] = "   $key|$label";
      }
    }
    return implode("\n", $lines);
  }

  /**
   * Implementation of TypedDataInterface.
   *
   * @see folder \workflow\src\Plugin\Validation\Constraint
   */

  /**
   * Implementation of OptionsProviderInterface.
   *
   *   An array of settable options for the object that may be used in an
   *   Options widget, usually when new data should be entered. It may either be
   *   a flat array of option labels keyed by values, or a two-dimensional array
   *   of option groups (array of flat option arrays, keyed by option group
   *   label). Note that labels should NOT be sanitized.
   */

  /**
   * {@inheritdoc}
   */
  public function getPossibleValues(?AccountInterface $account = NULL) {
    // Flatten options first, because SettableOptions may have 'group' arrays.
    $flatten_options = OptGroup::flattenOptions($this->getPossibleOptions($account));
    return array_keys($flatten_options);
  }

  /**
   * {@inheritdoc}
   */
  public function getPossibleOptions(?AccountInterface $account = NULL) {
    $allowed_options = [];

    // When we are initially on the Storage settings form, no wid is set, yet.
    if ($workflow = $this->getWorkflow()) {
      $allowed_options = $workflow->getStates(WorkflowInterface::ALL_STATES);
    }

    return $allowed_options;
  }

  /**
   * {@inheritdoc}
   */
  public function getSettableValues(?AccountInterface $account = NULL) {
    // Flatten options first, because SettableOptions may have 'group' arrays.
    $flatten_options = OptGroup::flattenOptions($this->getSettableOptions($account));
    return array_keys($flatten_options);
  }

  /**
   * {@inheritdoc}
   */
  public function getSettableOptions(?AccountInterface $account = NULL) {
    $allowed_options = [];

    // When we are initially on the Storage settings form, no wid is set, yet.
    if (!$wid = $this->getWorkflowId()) {
      return $allowed_options;
    }

    // On Field settings page, no entity is set.
    if (!$entity = $this->getEntity()) {
      return $allowed_options;
    }

    // Get the allowed new states for the entity's current state.
    $transition = $this->getTransition();
    $allowed_options = $transition?->getSettableOptions($account, 'to_sid');
    // $field_name = $this->getFieldDefinition()->getName();
    // $state = $this->getState();
    // @done use workflow_state_allowed_values, for executed transition.
    // $allowed_options = $state?->getOptions($entity, $field_name, NULL, FALSE);

    return $allowed_options ?? [];
  }

}

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

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