View source
<?php
namespace Drupal\workflow\Entity;
use Drupal\Core\Entity\ContentEntityBase;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\RevisionableInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Language\Language;
use Drupal\Core\Messenger\MessengerTrait;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\user\Entity\User;
use Drupal\user\UserInterface;
use Drupal\workflow\WorkflowTypeAttributeTrait;
class WorkflowTransition extends ContentEntityBase implements WorkflowTransitionInterface {
use MessengerTrait;
use StringTranslationTrait;
use WorkflowTypeAttributeTrait;
protected $entity = NULL;
protected $user = NULL;
protected $isScheduled = FALSE;
protected $isExecuted = FALSE;
protected $isForced = FALSE;
public function __construct(array $values = [], $entityType = 'workflow_transition', $bundle = FALSE, array $translations = []) {
parent::__construct($values, $entityType, $bundle, $translations);
$this->isScheduled = FALSE;
$this->isExecuted = $this
->id() > 0;
}
public static function create(array $values = []) {
if (is_array($values) && isset($values[0])) {
$value = $values[0];
$state = NULL;
if (is_string($value)) {
$state = WorkflowState::load($value);
}
elseif (is_object($value) && $value instanceof WorkflowState) {
$state = $value;
}
$values['wid'] = $state ? $state
->getWorkflowId() : '';
$values['from_sid'] = $state ? $state
->id() : '';
}
$values += [
'timestamp' => \Drupal::time()
->getRequestTime(),
'uid' => \Drupal::currentUser()
->id(),
];
return parent::create($values);
}
public function setValues($to_sid, $uid = NULL, $timestamp = NULL, $comment = '', $force_create = FALSE) {
$uid = $uid === NULL ? workflow_current_user()
->id() : $uid;
$from_sid = $this
->getFromSid();
$this
->set('to_sid', $to_sid);
$this
->setOwnerId($uid);
$this
->setTimestamp($timestamp == NULL ? \Drupal::time()
->getRequestTime() : $timestamp);
$this
->setComment($comment);
if (!$from_sid && !$to_sid && !$this
->getTargetEntity()) {
}
elseif ($from_sid && $this
->getTargetEntity()) {
}
elseif (!$from_sid) {
if (!$force_create) {
$this
->messenger()
->addError($this
->t('Wrong call to constructor Workflow*Transition(%from_sid to %to_sid)', [
'%from_sid' => $from_sid,
'%to_sid' => $to_sid,
]));
}
}
return $this;
}
public function save() {
$reference = $this
->get('entity_id')
->first();
if ($reference) {
$reference
->set('entity', $this
->getTargetEntity());
}
if ($this
->isScheduled()) {
return parent::save();
}
if ($this
->getEntityTypeId() != 'workflow_transition') {
return parent::save();
}
$transition = $this;
$entity_type = $transition
->getTargetEntityTypeId();
$entity_id = $transition
->getTargetEntityId();
$field_name = $transition
->getFieldName();
foreach (WorkflowScheduledTransition::loadMultipleByProperties($entity_type, [
$entity_id,
], [], $field_name) as $scheduled_transition) {
$scheduled_transition
->delete();
}
if ($this
->isEmpty()) {
return SAVED_UPDATED;
}
if ($this
->id()) {
return parent::save();
}
$found_transition = self::loadByProperties($entity_type, $entity_id, [], $field_name);
if ($found_transition && $found_transition
->getTimestamp() == \Drupal::time()
->getRequestTime() && $found_transition
->getToSid() == $this
->getToSid()) {
return SAVED_UPDATED;
}
return parent::save();
}
public static function loadByProperties($entity_type, $entity_id, array $revision_ids = [], $field_name = '', $langcode = '', $sort = 'ASC', $transition_type = 'workflow_transition') {
$limit = 1;
$transitions = self::loadMultipleByProperties($entity_type, [
$entity_id,
], $revision_ids, $field_name, $langcode, $limit, $sort, $transition_type);
if ($transitions) {
$transition = reset($transitions);
return $transition;
}
return NULL;
}
public static function loadMultipleByProperties($entity_type, array $entity_ids, array $revision_ids = [], $field_name = '', $langcode = '', $limit = NULL, $sort = 'ASC', $transition_type = 'workflow_transition') {
$query = \Drupal::entityQuery($transition_type)
->condition('entity_type', $entity_type)
->sort('timestamp', $sort)
->addTag($transition_type);
if (!empty($entity_ids)) {
$query
->condition('entity_id', $entity_ids, 'IN');
}
if (!empty($revision_ids)) {
$query
->condition('revision_id', $entity_ids, 'IN');
}
if ($field_name != '') {
$query
->condition('field_name', $field_name, '=');
}
if ($langcode != '') {
$query
->condition('langcode', $langcode, '=');
}
if ($limit) {
$query
->range(0, $limit);
}
if ($transition_type == 'workflow_transition') {
$query
->sort('hid', 'DESC');
}
$ids = $query
->execute();
$transitions = self::loadMultiple($ids);
return $transitions;
}
public function isValid() {
$entity = $this
->getTargetEntity();
if (!$entity) {
$message = 'User tried to execute a Transition without an entity.';
$this
->logError($message);
return FALSE;
}
if (!$this
->getFromState()) {
$message = $this
->t('You tried to set a Workflow State, but
the entity is not relevant. Please contact your system administrator.');
$this
->messenger()
->addError($message);
$message = 'Setting a non-relevant Entity from state %sid1 to %sid2';
$this
->logError($message);
return FALSE;
}
return TRUE;
}
protected function isEmpty() {
if ($this
->getToSid() != $this
->getFromSid()) {
return FALSE;
}
if ($this
->getComment()) {
return FALSE;
}
$fields = WorkflowManager::getAttachedFields('workflow_transition', $this
->bundle());
foreach ($fields as $field_name => $field) {
if (!$this->{$field_name}
->isEmpty()) {
return FALSE;
}
}
return TRUE;
}
public function isAllowed(UserInterface $user, $force = FALSE) {
if ($force) {
return TRUE;
}
$type_id = $this
->getWorkflowId();
if ($user
->hasPermission("bypass {$type_id} workflow_transition access")) {
return TRUE;
}
$is_owner = WorkflowManager::isOwner($user, $this
->getTargetEntity());
if ($is_owner) {
$user
->addRole(WORKFLOW_ROLE_AUTHOR_RID);
}
$config_transitions = $this
->getWorkflow()
->getTransitionsByStateId($this
->getFromSid(), $this
->getToSid());
$result = FALSE;
foreach ($config_transitions as $config_transition) {
$result = $result || $config_transition
->isAllowed($user, $force);
}
if ($result == FALSE) {
$message = $this
->t('Attempt to go to nonexistent transition (from %sid1 to %sid2)');
$this
->logError($message);
}
return $result;
}
public function hasStateChange() {
if ($this->from_sid->target_id == $this->to_sid->target_id) {
return FALSE;
}
return TRUE;
}
public function execute($force = FALSE) {
$entity = $this
->getTargetEntity();
$user = $this
->getOwner();
$from_sid = $this
->getFromSid();
$to_sid = $this
->getToSid();
$field_name = $this
->getFieldName();
$comment = $this
->getComment();
$label = $from_sid . '-' . $to_sid;
static $static_info = NULL;
$entity_id = $entity
->id();
if ($entity instanceof RevisionableInterface && !$entity
->isDefaultRevision()) {
$entity_id = $entity_id . $entity
->getRevisionId();
}
if (isset($static_info[$entity_id][$field_name][$label]) && !$this
->isEmpty()) {
$message = 'Transition is executed twice in a call. The second call for
@entity_type %entity_id is not executed.';
$this
->logError($message);
return $static_info[$entity_id][$field_name][$label];
}
$static_info[$entity_id][$field_name][$label] = $from_sid;
if ($force) {
$this
->force($force);
}
$force = $this
->isForced();
$entity->workflow_transitions[$field_name] = $this;
if (!$this
->isValid()) {
return $from_sid;
}
if ($this
->hasStateChange()) {
if (!$force) {
$user
->addRole(WORKFLOW_ROLE_AUTHOR_RID);
}
if (!$this
->isAllowed($user, $force)) {
$message = 'User %user not allowed to go from state %sid1 to %sid2';
$this
->logError($message);
return FALSE;
}
$permitted = \Drupal::moduleHandler()
->invokeAll('workflow', [
'transition pre',
$this,
$user,
]);
if (in_array(FALSE, $permitted, TRUE)) {
$message = 'Transition vetoed by module.';
$this
->logError($message, 'notice');
return FALSE;
}
}
if ($this
->isScheduled()) {
$this
->save();
}
else {
$context = [
'transition' => $this,
];
\Drupal::moduleHandler()
->alter('workflow_comment', $comment, $context);
$this
->setComment($comment);
$this->isExecuted = TRUE;
if (!$this
->isEmpty()) {
$this
->save();
if ($this
->hasStateChange() && !empty($this
->getWorkflow()
->getSetting('watchdog_log'))) {
if ($this
->getEntityTypeId() == 'workflow_scheduled_transition') {
$message = 'Scheduled state change of @entity_type_label %entity_label to %sid2 executed';
}
else {
$message = 'State of @entity_type_label %entity_label set to %sid2';
}
$this
->logError($message, 'notice');
}
}
}
$static_info[$entity_id][$field_name][$label] = $to_sid;
return $to_sid;
}
public function executeAndUpdateEntity($force = FALSE) {
$to_sid = $this
->getToSid();
if (!$to_sid) {
$t_args = [
'%sid2' => $this
->getToState()
->label(),
'%entity_label' => $this
->getTargetEntity()
->label(),
];
$message = "Transition is not executed for %entity_label, since 'To' state %sid2 is invalid.";
$this
->logError($message);
$this
->messenger()
->addError($this
->t($message, $t_args));
return $this
->getFromSid();
}
$do_update_entity = !$this
->isScheduled() && !$this
->isExecuted();
if ($do_update_entity) {
$this
->_updateEntity();
}
else {
$to_sid = $this
->execute($force);
}
return $to_sid;
}
private function _updateEntity() {
$field_name = $this
->getFieldName();
$entity = $this
->getTargetEntity();
$entity->{$field_name}->workflow_transition = $this;
$entity->{$field_name}->value = $this
->getToSid();
if ($this
->getWorkflow()
->getSetting('always_update_entity')) {
$entity->changed = $this
->getTimestamp();
}
$entity
->save();
}
public function post_execute($force = FALSE) {
workflow_debug(__FILE__, __FUNCTION__, __LINE__);
if (!$this
->isEmpty()) {
$user = $this
->getOwner();
\Drupal::moduleHandler()
->invokeAll('workflow', [
'transition post',
$this,
$user,
]);
}
}
public function setTargetEntity(EntityInterface $entity) {
if (!$entity) {
$this->entity_type = '';
$this->entity_id = '';
$this->revision_id = '';
$this->delta = 0;
$this->langcode = Language::LANGCODE_NOT_SPECIFIED;
return $this;
}
if ($entity
->getEntityTypeId() == 'comment') {
$entity = $entity
->getCommentedEntity();
}
$this->entity = $entity;
$this->entity_type = $entity
->getEntityTypeId();
$this->entity_id = $entity
->id();
$this->revision_id = $entity
->getRevisionId();
$this->delta = 0;
$this->langcode = $entity
->language()
->getId();
return $this;
}
public function getTargetEntity() {
if (isset($this->entity)) {
return $this->entity;
}
$entity_type = $this
->getTargetEntityTypeId();
if ($id = $this
->getTargetEntityId()) {
$this->entity = \Drupal::entityTypeManager()
->getStorage($entity_type)
->load($id);
}
return $this->entity;
}
public function getTargetEntityId() {
return $this
->get('entity_id')->target_id;
}
public function getTargetEntityTypeId() {
return $this
->get('entity_type')->value;
}
public function getFieldName() {
return $this
->get('field_name')->value;
}
public function getLangcode() {
return $this
->getTargetEntity()
->language()
->getId();
}
public function getFromState() {
$sid = $this
->getFromSid();
return $sid ? WorkflowState::load($sid) : NULL;
}
public function getToState() {
$sid = $this
->getToSid();
return $sid ? WorkflowState::load($sid) : NULL;
}
public function getFromSid() {
$sid = $this->{'from_sid'}->target_id;
return $sid;
}
public function getToSid() {
$sid = $this->{'to_sid'}->target_id;
return $sid;
}
public function getComment() {
return $this
->get('comment')->value;
}
public function setComment($value) {
$this
->set('comment', $value);
return $this;
}
public function getTimestamp() {
return $this
->get('timestamp')->value;
}
public function getTimestampFormatted() {
$timestamp = $this
->getTimestamp();
return \Drupal::service('date.formatter')
->format($timestamp);
}
public function setTimestamp($value) {
$this
->set('timestamp', $value);
return $this;
}
public function isScheduled() {
return $this->isScheduled;
}
public function isRevertable() {
if ($this
->getFromSid() == $this
->getToSid()) {
return FALSE;
}
$from_state = $this
->getFromState();
if (!$from_state || !$from_state
->isActive() || $from_state
->isCreationState()) {
return FALSE;
}
return TRUE;
}
public function schedule($schedule = TRUE) {
$this->isScheduled = $schedule;
return $this;
}
public function setExecuted($isExecuted = TRUE) {
$this->isExecuted = $isExecuted;
return $this;
}
public function isExecuted() {
return (bool) $this->isExecuted;
}
public function isForced() {
return (bool) $this->isForced;
}
public function force($force = TRUE) {
$this->isForced = $force;
return $this;
}
public function getOwner() {
$user = $this
->get('uid')->entity;
if (!$user || $user
->isAnonymous()) {
$user = User::getAnonymousUser();
$user
->setUsername(\Drupal::config('user.settings')
->get('anonymous'));
}
return $user;
}
public function getOwnerId() {
return $this
->get('uid')->target_id;
}
public function setOwnerId($uid) {
$this
->set('uid', $uid);
return $this;
}
public function setOwner(UserInterface $account) {
$this
->set('uid', $account
->id());
return $this;
}
public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
$fields = [];
$fields['hid'] = BaseFieldDefinition::create('integer')
->setLabel(t('Transition ID'))
->setDescription(t('The transition ID.'))
->setReadOnly(TRUE)
->setSetting('unsigned', TRUE);
$fields['wid'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Workflow Type'))
->setDescription(t('The workflow type the transition relates to.'))
->setSetting('target_type', 'workflow_type')
->setRequired(TRUE)
->setTranslatable(FALSE)
->setRevisionable(FALSE);
$fields['entity_type'] = BaseFieldDefinition::create('string')
->setLabel(t('Entity type'))
->setDescription(t('The Entity type this transition belongs to.'))
->setSetting('is_ascii', TRUE)
->setSetting('max_length', EntityTypeInterface::ID_MAX_LENGTH)
->setReadOnly(TRUE);
$fields['entity_id'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Entity ID'))
->setDescription(t('The Entity ID this record is for.'))
->setRequired(TRUE)
->setReadOnly(TRUE)
->setSetting('unsigned', TRUE);
$fields['revision_id'] = BaseFieldDefinition::create('integer')
->setLabel(t('Revision ID'))
->setDescription(t('The current version identifier.'))
->setReadOnly(TRUE)
->setSetting('unsigned', TRUE);
$fields['field_name'] = BaseFieldDefinition::create('string')
->setLabel(t('Field name'))
->setDescription(t('The name of the field the transition relates to.'))
->setRequired(TRUE)
->setTranslatable(FALSE)
->setRevisionable(FALSE)
->setSetting('max_length', 32);
$fields['langcode'] = BaseFieldDefinition::create('language')
->setLabel(t('Language'))
->setDescription(t('The entity language code.'))
->setTranslatable(TRUE)
->setDisplayOptions('view', [
'region' => 'hidden',
])
->setDisplayOptions('form', [
'type' => 'language_select',
'weight' => 2,
]);
$fields['delta'] = BaseFieldDefinition::create('integer')
->setLabel(t('Delta'))
->setDescription(t('The sequence number for this data item, used for multi-value fields.'))
->setReadOnly(TRUE)
->setSetting('unsigned', TRUE);
$fields['from_sid'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('From state'))
->setDescription(t('The {workflow_states}.sid the entity started as.'))
->setSetting('target_type', 'workflow_state')
->setReadOnly(TRUE);
$fields['to_sid'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('To state'))
->setDescription(t('The {workflow_states}.sid the entity transitioned to.'))
->setSetting('target_type', 'workflow_state')
->setReadOnly(TRUE);
$fields['uid'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('User ID'))
->setDescription(t('The user ID of the transition author.'))
->setTranslatable(TRUE)
->setSetting('target_type', 'user')
->setDefaultValue(0)
->setRevisionable(TRUE);
$fields['timestamp'] = BaseFieldDefinition::create('created')
->setLabel(t('Timestamp'))
->setDescription(t('The time that the current transition was executed.'))
->setRevisionable(TRUE);
$fields['comment'] = BaseFieldDefinition::create('string_long')
->setLabel(t('Log message'))
->setDescription(t('The comment explaining this transition.'))
->setRevisionable(TRUE)
->setTranslatable(TRUE);
return $fields;
}
public function logError($message, $type = 'error', $from_sid = '', $to_sid = '') {
$entity = $this
->getTargetEntity();
$t_args = [
'%user' => ($user = $this
->getOwner()) ? $user
->getDisplayName() : '',
'%sid1' => $from_sid || !$this
->getFromState() ? $from_sid : $this
->getFromState()
->label(),
'%sid2' => $to_sid || !$this
->getToState() ? $to_sid : $this
->getToState()
->label(),
'%entity_id' => $this
->getTargetEntityId(),
'%entity_label' => $entity ? $entity
->label() : '',
'@entity_type' => $entity ? $entity
->getEntityTypeId() : '',
'@entity_type_label' => $entity ? $entity
->getEntityType()
->getLabel() : '',
'link' => $this
->getTargetEntityId() && $this
->getTargetEntity()
->hasLinkTemplate('canonical') ? $this
->getTargetEntity()
->toLink($this
->t('View'))
->toString() : '',
];
$type == 'error' ? \Drupal::logger('workflow')
->error($message, $t_args) : \Drupal::logger('workflow')
->notice($message, $t_args);
}
public function dpm($function = '') {
$transition = $this;
$entity = $transition
->getTargetEntity();
$time = \Drupal::service('date.formatter')
->format($transition
->getTimestamp());
$user = $transition
->getOwner();
$user_name = $user ? $user
->getAccountName() : 'unknown username';
$t_string = $this
->getEntityTypeId() . ' ' . $this
->id() . ' for workflow_type <i>' . $this
->getWorkflowId() . '</i> ' . ($function ? "in function '{$function}'" : '');
$output[] = 'Entity = ' . $this
->getTargetEntityTypeId() . '/' . ($entity ? $entity
->bundle() . '/' . $entity
->id() : '___/0');
$output[] = 'Field = ' . $transition
->getFieldName();
$output[] = 'From/To = ' . $transition
->getFromSid() . ' > ' . $transition
->getToSid() . ' @ ' . $time;
$output[] = 'Comment = ' . $user_name . ' says: ' . $transition
->getComment();
$output[] = 'Forced = ' . ($transition
->isForced() ? 'yes' : 'no') . '; ' . 'Scheduled = ' . ($transition
->isScheduled() ? 'yes' : 'no');
if (function_exists('dpm')) {
dpm($output, $t_string);
}
}
}