Skip to content

Instantly share code, notes, and snippets.

@flocondetoile
Last active March 24, 2021 10:51
Show Gist options
  • Save flocondetoile/7a69756634d45b60110c02c30ec26947 to your computer and use it in GitHub Desktop.
Save flocondetoile/7a69756634d45b60110c02c30ec26947 to your computer and use it in GitHub Desktop.
/**
* Implements hook_entity_base_field_info().
*
* - Provides a base field that displays the current workflow state on nodes.
* This field is intended to be used to expose this information.
*/
function state_machine_workflow_entity_base_field_info(EntityTypeInterface $entity_type) {
$fields = [];
/** @var \Drupal\state_machine_workflow\WorkflowHelper $workflowHelper */
$workflowHelper = \Drupal::service('state_machine_workflow.workflow.helper');
if ($entity_type->id() === 'node') {
$fields['current_workflow_state'] = BaseFieldDefinition::create('current_workflow_state')
->setLabel(new TranslatableMarkup('Current workflow state'))
->setClass('\Drupal\state_machine_workflow\CurrentWorkflowStateFieldItemList')
->setDisplayOptions('form', ['type' => 'hidden'])
->setTranslatable(TRUE)
->setDisplayOptions('view', [
'label' => 'hidden',
'type' => 'hidden',
'weight' => 0,
])
->setDisplayConfigurable('form', TRUE)
->setDisplayConfigurable('view', TRUE)
->setComputed(TRUE);
}
return $fields;
}
/**
* Implements hook_field_formatter_info_alter().
*
* Make sure the standard formatter for string can be used for the current
* workflow state computed field.
*/
function state_machine_workflow_field_formatter_info_alter(array &$info) {
$info['string']['field_types'][] = 'current_workflow_state';
}
/**
* Implements hook_views_data_alter().
*/
function state_machine_workflow_views_data_alter(array &$data) {
if (isset($data['node'])) {
// Add the current company computed field to Views.
$data['node']['current_workflow_state'] = [
'title' => t('Current workflow state'),
'field' => [
'id' => 'state_machine_workflow_view_current_workflow_state',
],
];
}
}
================================================
<?php
namespace Drupal\state_machine_workflow;
use Drupal\Core\Field\FieldItemList;
/**
* Item list for the current workflow state.
*
* @see \Drupal\state_machine_workflow\Plugin\Field\FieldType\CurrentWorkflowStateItem
*/
class CurrentWorkflowStateFieldItemList extends FieldItemList {
/**
* {@inheritdoc}
*/
public function getIterator() {
$this->ensureLoaded();
return new \ArrayIterator($this->list);
}
/**
* {@inheritdoc}
*/
public function getValue($include_computed = FALSE) {
$this->ensureLoaded();
return parent::getValue($include_computed);
}
/**
* {@inheritdoc}
*/
public function isEmpty() {
$this->ensureLoaded();
return parent::isEmpty();
}
/**
* Makes sure that the item list is never empty.
*
* For 'normal' fields that use database storage the field item list is
* initially empty, but since this is a computed field this always has a
* value.
* Make sure the item list is always populated, so this field is not skipped
* for rendering in EntityViewDisplay and friends.
*
* This trick has been borrowed from issue #2846554 which does the same for
* the PathItem field.
*
* @see https://www.drupal.org/node/2846554
*/
protected function ensureLoaded() {
if (!isset($this->list[0])) {
$this->list[0] = $this->createItem(0);
}
}
}
================================================
<?php
namespace Drupal\state_machine_workflow\Plugin\Field\FieldType;
use Drupal\Core\Field\Plugin\Field\FieldType\StringItem;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\TypedData\DataDefinitionInterface;
use Drupal\Core\TypedData\TypedDataInterface;
/**
* Computed field that shows the current workflow state.
*
* @FieldType(
* id = "current_workflow_state",
* label = @Translation("Current workflow state"),
* description = @Translation("Computed field that shows the current workflow state."),
* no_ui = TRUE,
* default_widget = "current_workflow_state_widget",
* default_formatter = "current_workflow_state_formatter"
* )
*/
class CurrentWorkflowStateItem extends StringItem {
/**
* The workflow helper service.
*
* @var \Drupal\state_machine_workflow\WorkflowHelperInterface
*/
protected $workflowHelper;
/**
* The revision manager service.
*
* @var \Drupal\state_machine_workflow\RevisionManagerInterface
*/
protected $revisionManager;
/**
* Whether or not the value has been calculated.
*
* @var bool
*/
protected $isCalculated = FALSE;
/**
* {@inheritdoc}
*/
public function __construct(DataDefinitionInterface $definition, $name = NULL, TypedDataInterface $parent = NULL) {
parent::__construct($definition, $name, $parent);
$this->workflowHelper = \Drupal::service('state_machine_workflow.workflow.helper');
$this->revisionManager = \Drupal::service('state_machine_workflow.revision_manager');
}
/**
* {@inheritdoc}
*/
public function __get($name) {
$this->ensureCalculated();
return parent::__get($name);
}
/**
* {@inheritdoc}
*/
public function isEmpty() {
$this->ensureCalculated();
return parent::isEmpty();
}
/**
* {@inheritdoc}
*/
public function getValue() {
$this->ensureCalculated();
return parent::getValue();
}
/**
* Makes sure that the value is populated.
*
* Normal fields get their data from the database and are populated if there
* is data available. Since this is a computed field we need to make sure
* there is always data available ourselves.
*
* This trick has been borrowed from issue #2846554 which does the same for
* the PathItem field.
*
* @todo Remove this when issue #2392845 is fixed.
*
* @see https://www.drupal.org/node/2392845
* @see https://www.drupal.org/node/2846554
*/
protected function ensureCalculated() {
if (!$this->isCalculated) {
$entity = $this->getEntity();
if (!$entity->isNew()) {
$latest_revision = $this->revisionManager->loadLatestRevision($entity);
$latest_revision = \Drupal::service('entity.repository')->getTranslationFromContext($latest_revision);
if ($this->workflowHelper->hasEntityStateField($latest_revision)) {
$field = $this->workflowHelper->getEntityStateField($latest_revision);
$state_id = $field->getValue()['value'];
$value = $field->getWorkflow()->getState($state_id)->getLabel();
$this->setValue($value);
}
}
$this->isCalculated = TRUE;
}
}
/**
* Checks access to the given entity.
*
* By default, entity 'view' access is checked. However, a subclass can choose
* to exclude certain items from entity access checking by immediately
* granting access.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity to check.
*
* @return bool|\Drupal\Core\Access\AccessResult
* A cacheable access result.
*/
protected function checkAccess(EntityInterface $entity) {
return $entity->access('edit', NULL, FALSE);
}
}
================================================
<?php
namespace Drupal\state_machine_workflow\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\state_machine_workflow\WorkflowHelperInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Plugin implementation of the 'current_workflow_state_widget' widget.
*
* @FieldWidget(
* id = "current_workflow_state_widget",
* label = @Translation("Plain text"),
* field_types = {
* "current_workflow_state"
* }
* )
*/
class CurrentWorkflowStateWidget extends WidgetBase implements ContainerFactoryPluginInterface {
/**
* The workflow helper service.
*
* @var \Drupal\state_machine_workflow\WorkflowHelperInterface
*/
protected $workflowHelper;
/**
* Constructs a SearchWidget object.
*
* @param string $plugin_id
* The plugin_id for the widget.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
* The definition of the field to which the widget is associated.
* @param array $settings
* The widget settings.
* @param array $third_party_settings
* Any third party settings.
* @param \Drupal\state_machine_workflow\WorkflowHelperInterface $workflow_helper
* The workflow helper service.
*/
public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, WorkflowHelperInterface $workflow_helper) {
parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
$this->workflowHelper = $workflow_helper;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$plugin_id,
$plugin_definition,
$configuration['field_definition'],
$configuration['settings'],
$configuration['third_party_settings'],
$container->get('state_machine_workflow.workflow.helper')
);
}
/**
* {@inheritdoc}
*/
public static function defaultSettings() {
return [
'title' => 'Current workflow state',
'title_display' => 'before',
'show_for_new_entities' => FALSE,
] + parent::defaultSettings();
}
/**
* {@inheritdoc}
*/
public function settingsForm(array $form, FormStateInterface $form_state) {
$elements = parent::settingsForm($form, $form_state);
$elements['title'] = [
'#type' => 'textfield',
'#title' => $this->t('Label'),
'#default_value' => $this->getSetting('title'),
];
$elements['title_display'] = [
'#type' => 'radios',
'#title' => $this->t('Display label'),
'#default_value' => $this->getSetting('title_display'),
'#options' => [
'before' => $this->t('Label goes before the element'),
'after' => $this->t('Label goes after the element'),
'invisible' => $this->t('Label is there but is made invisible using CSS'),
'attribute' => $this->t('Make it the title attribute (hover tooltip)'),
],
];
$elements['show_for_new_entities'] = [
'#type' => 'checkbox',
'#title' => $this->t('Show when creating a new entity'),
'#description' => $this->t('If unchecked, the widget is shown only on forms where an existing entity is being edited.'),
'#default_value' => $this->getSetting('show_for_new_entities'),
];
return $elements;
}
/**
* {@inheritdoc}
*/
public function settingsSummary() {
$summary = [
$this->t('Label: @title', [
'@title' => $this->getSetting('title'),
]),
$this->t('Display label: @title_display', [
'@title_display' => $this->getSetting('title_display'),
]),
];
if ($this->getSetting('show_for_new_entities')) {
$summary[] = $this->t('Show when creating a new entity');
}
return $summary;
}
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
/** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */
$entity = $items->getEntity();
$field = $this->workflowHelper->getEntityStateField($entity);
$state_id = $field->getValue()['value'];
$element['#title'] = $this->getSetting('title');
$element['#title_display'] = $this->getSetting('title_display');
$element['#type'] = 'item';
$element['#element_validate'][] = [get_class($this), 'validateFormElement'];
$element['current_workflow_state']['#type'] = 'container';
$element['current_workflow_state']['#attributes'] = ['class' => ['current-workflow-state']];
$element['current_workflow_state']['label'] = [
'#plain_text' => $field->getWorkflow()->getState($state_id)->getLabel(),
];
// Show the widget only when the entity is not new, or when the specific
// setting is turned on.
$element['#access'] = !$entity->isNew() || $this->getSetting('show_for_new_entities');
return $element;
}
/**
* Form element validation handler.
*
* @param array $element
* The form element.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state.
*/
public static function validateFormElement(array &$element, FormStateInterface $form_state) {
// We have nothing to validate, this data is coming from a trusted source.
// Just set the value for the element directly.
$form_state->setValueForElement($element['current_workflow_state'], $element['current_workflow_state']['label']['#plain_text']);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment