class WorkflowState in Workflow 7.2
Same name and namespace in other branches
- 7 includes/Entity/WorkflowState.php \WorkflowState
Class WorkflowState
Hierarchy
- class \Entity implements EntityInterface
- class \WorkflowState
Expanded class hierarchy of WorkflowState
8 string references to 'WorkflowState'
- Workflow::createState in includes/
Entity/ Workflow.php - Create a new state for this workflow.
- WorkflowScheduledTransition::save in includes/
Entity/ WorkflowScheduledTransition.php - Save a scheduled transition. If the transition is executed, save in history.
- WorkflowState::getStates in includes/
Entity/ WorkflowState.php - Get all states in the system, with options to filter, only where a workflow exists.
- workflow_entity_info in ./
workflow.entity.inc - Implements hook_entity_info().
- workflow_entity_property_info_alter in ./
workflow.entity.inc - Implements hook_entity_property_info_alter().
File
- includes/
Entity/ WorkflowState.php, line 12 - Contains workflow\includes\Entity\WorkflowState. Contains workflow\includes\Entity\WorkflowStateController.
View source
class WorkflowState extends Entity {
// Since workflows do not change, it is implemented as a singleton.
protected static $states = array();
public $sid = 0;
public $wid = 0;
public $weight = 0;
public $sysid = 0;
public $state = '';
// @todo D8: remove $state, use $label/$name. (requires conversion of Views displays.)
public $status = 1;
/**
* CRUD functions.
*/
/**
* Constructor.
*
* @param array $values
* @param string $entityType
*/
public function __construct(array $values = array(), $entityType = 'WorkflowState') {
// Please be aware that $entity_type and $entityType are different things!
// Keep official name and external name equal. Both are required.
// @todo: still needed? test import, manual creation, programmatic creation, etc.
if (!isset($values['state']) && isset($values['name'])) {
$values['state'] = $values['name'];
}
// Set default values for '(creation)' state.
if (!empty($values['is_new']) && $values['name'] == WORKFLOW_CREATION_STATE_NAME) {
$values['sysid'] = WORKFLOW_CREATION;
$values['weight'] = WORKFLOW_CREATION_DEFAULT_WEIGHT;
$values['name'] = '(creation)';
// machine_name;
}
parent::__construct($values, $entityType);
if (empty($values)) {
// Automatic constructor when casting an array or object.
// Add pre-existing states to cache (not new/temp ones).
if (!isset(self::$states[$this->sid])) {
self::$states[$this->sid] = $this;
}
}
}
/*
// Implementing clone needs a list of tid-less transitions, and a conversion
// of sids for both States and ConfigTransitions.
// public function __clone() {}
*/
/**
* Alternative constructor, loading objects from table {workflow_states}.
*
* @param int $sid
* The requested State ID
* @param int $wid
* An optional Workflow ID, to check if the requested State is valid for the Workflow.
*
* @return WorkflowState|NULL|FALSE $state
* WorkflowState if state is successfully loaded,
* NULL if not loaded,
* FALSE if state does not belong to requested Workflow.
*/
public static function load($sid, $wid = 0) {
$states = self::getStates();
$state = isset($states[$sid]) ? $states[$sid] : NULL;
if ($wid && $state && $wid != $state->wid) {
return FALSE;
}
return $state;
}
/**
* Get all states in the system, with options to filter, only where a workflow exists.
*
* @param $wid
* The requested Workflow ID.
* @param bool $reset
* An option to refresh all caches.
*
* @return array $states
* An array of cached states.
*
* D7.x-2.x: deprecated workflow_get_workflow_states --> workflow_state_load_multiple
* D7.x-2.x: deprecated workflow_get_workflow_states_all --> workflow_state_load_multiple
* D7.x-2.x: deprecated workflow_get_other_states_by_sid --> workflow_state_load_multiple
*/
public static function getStates($wid = 0, $reset = FALSE) {
if ($reset) {
self::$states = array();
}
if (empty(self::$states)) {
// Build the query, and get ALL states.
// Note: self::states[] is populated in respective constructors.
$query = db_select('workflow_states', 'ws');
$query
->fields('ws');
$query
->orderBy('ws.weight');
$query
->orderBy('ws.wid');
// Just for grins, add a tag that might result in modifications.
$query
->addTag('workflow_states');
// @see #2285983 for using SQLite.
// $query->execute()->fetchAll(PDO::FETCH_CLASS, 'WorkflowState');
/* @var $tmp DatabaseStatementBase */
$statement = $query
->execute();
$statement
->setFetchMode(PDO::FETCH_CLASS, 'WorkflowState');
foreach ($statement
->fetchAll() as $state) {
self::$states[$state->sid] = $state;
}
}
if (!$wid) {
// All states are requested and cached: return them.
return self::$states;
}
else {
// All states of only 1 Workflow is requested: return this one.
$result = array();
foreach (self::$states as $state) {
if ($state->wid == $wid) {
$result[$state->sid] = $state;
}
}
return $result;
}
}
/**
* Get all states in the system, with options to filter, only where a workflow exists.
*
* May return more then one State, since a name is not (yet) an UUID.
*
* @param $name
* @param int $wid
*
* @return WorkflowState
*/
public static function loadByName($name, $wid = 0) {
/* @var $state WorkflowState */
foreach ($states = self::getStates($wid) as $state) {
if ($name == $state->name) {
return $state;
}
}
return NULL;
}
/**
* Deactivate a Workflow State, moving existing nodes to a given State.
*
* @param int $new_sid
* The state ID, to which all affected entities must be moved.
*
* D7.x-2.x: deprecated workflow_delete_workflow_states_by_sid() --> WorkflowState->deactivate() + delete()
*/
public function deactivate($new_sid) {
$current_sid = $this->sid;
$force = TRUE;
// Notify interested modules. We notify first to allow access to data before we zap it.
// E.g., Node API implements this.
// - re-parents any nodes that we don't want to orphan, whilst deactivating a State.
// - delete any lingering node to state values.
module_invoke_all('workflow', 'state delete', $current_sid, $new_sid, NULL, $force);
// Re-parent any nodes that we don't want to orphan, whilst deactivating a State.
if ($new_sid) {
// A candidate for the batch API.
// @TODO: Future updates should seriously consider setting this with batch.
global $user;
// We can use global, since deactivate() is a UI-only function.
$comment = t('Previous state deleted');
// Re-assign workflow_node nodes.
foreach (workflow_get_workflow_node_by_sid($current_sid) as $workflow_node) {
// @todo: add Field support in 'state delete', by using workflow_node_history or reading current field.
$entity_type = 'node';
$entity = entity_load_single('node', $workflow_node->nid);
$field_name = '';
$transition = new WorkflowTransition();
$transition
->setValues($entity_type, $entity, $field_name, $current_sid, $new_sid, $user->uid, REQUEST_TIME, $comment);
$transition
->force($force);
// Execute Transition, invoke 'pre' and 'post' events, save new state in workflow_node, save also in workflow_node_history.
// For Workflow Node, only {workflow_node} and {workflow_node_history} are updated. For Field, also the Entity itself.
$new_sid = workflow_execute_transition($entity_type, $entity, $field_name, $transition, $force);
}
// Re-assign workflow_field_entities.
foreach (_workflow_info_fields() as $field_name => $field_info) {
$query = new EntityFieldQuery();
$query
->fieldCondition($field_name, 'value', $current_sid, '=');
$result = $query
->execute();
foreach ($result as $entity_type => $entities) {
if ($entity_type == 'comment') {
// Do not reset comments.
continue;
}
foreach ($entities as $entity_id => $entity) {
$entity = entity_load_single($entity_type, $entity_id);
/* @var $transition WorkflowTransition */
$transition = new WorkflowTransition();
$transition
->setValues($entity_type, $entity, $field_name, $current_sid, $new_sid, $user->uid, REQUEST_TIME, $comment, TRUE);
$transition
->force($force);
// Execute Transition, invoke 'pre' and 'post' events, save new state in Field-table, save also in workflow_transition_history.
// For Workflow Node, only {workflow_node} and {workflow_transition_history} are updated. For Field, also the Entity itself.
$new_sid = workflow_execute_transition($entity_type, $entity, $field_name, $transition, $force);
}
}
}
}
// Delete any lingering node to state values.
workflow_delete_workflow_node_by_sid($current_sid);
// Delete the config transitions this state is involved in.
$workflow = workflow_load_single($this->wid);
/* @var $transition WorkflowTransition */
foreach ($workflow
->getTransitionsBySid($current_sid, 'ALL') as $transition) {
$transition
->delete();
}
foreach ($workflow
->getTransitionsByTargetSid($current_sid, 'ALL') as $transition) {
$transition
->delete();
}
// Delete the state. -- We don't actually delete, just deactivate.
// This is a matter up for some debate, to delete or not to delete, since this
// causes name conflicts for states. In the meantime, we just stick with what we know.
// If you really want to delete the states, use workflow_cleanup module, or delete().
$this->status = FALSE;
$this
->save();
// Clear the cache.
self::getStates(0, TRUE);
}
/**
* Property functions.
*/
/**
* Returns the Workflow object of this State.
*
* @return Workflow
* Workflow object.
*/
public function getWorkflow() {
if (isset($this->workflow)) {
return $this->workflow;
}
return workflow_load_single($this->wid);
}
public function setWorkflow($workflow) {
$this->wid = $workflow->wid;
$this->workflow = $workflow;
}
/**
* Returns the Workflow object of this State.
*
* @return bool
* TRUE if state is active, else FALSE.
*/
public function isActive() {
return (bool) $this->status;
}
public function isCreationState() {
return $this->sysid == WORKFLOW_CREATION;
}
/**
* Determines if the Workflow Form must be shown.
*
* If not, a formatter must be shown, since there are no valid options.
*
* @param $entity_type
* @param $entity
* @param $field_name
* @param $user
* @param $force
*
* @return bool $show_widget
* TRUE = a form (a.k.a. widget) must be shown; FALSE = no form, a formatter must be shown instead.
*/
public function showWidget($entity_type, $entity, $field_name, $user, $force) {
$options = $this
->getOptions($entity_type, $entity, $field_name, $user, $force);
$count = count($options);
// The easiest case first: more then one option: always show form.
if ($count > 1) {
return TRUE;
}
// #2226451: Even in Creation state, we must have 2 visible states to show the widget.
// // Only when in creation phase, one option is sufficient,
// // since the '(creation)' option is not included in $options.
// // When in creation state,
// if ($this->isCreationState()) {
// return TRUE;
// }
return FALSE;
}
/**
* Returns the allowed transitions for the current state.
*
* @param string $entity_type
* The type of the entity at hand.
* @param object $entity
* The entity at hand. May be NULL (E.g., on a Field settings page).
* @param string $field_name
* @param null $user
* @param bool $force
*
* @return array
* An array of tid=>transition pairs with allowed transitions for State.
*/
public function getTransitions($entity_type = '', $entity = NULL, $field_name = '', $user = NULL, $force = FALSE) {
$transitions = array();
$current_sid = $this->sid;
$current_state = $this;
if (!($workflow = $this
->getWorkflow())) {
// No workflow, no options ;-)
return $transitions;
}
// Get the role IDs of the user, to get the proper permissions.
$roles = $user ? array_keys($user->roles) : array();
// Some entities (e.g., taxonomy_term) do not have a uid.
$entity_uid = isset($entity->uid) ? $entity->uid : 0;
// Fetch entity_id from entity for _newness_ check
$entity_id = $entity ? entity_id($entity_type, $entity) : '';
if ($force || $user && $user->uid == 1) {
// Superuser is special. And $force allows Rules to cause transition.
$roles = 'ALL';
}
elseif ($entity && (!empty($entity->is_new) || empty($entity_id))) {
// Add 'author' role to user, if this is a new entity.
// - $entity can be NULL (E.g., on a Field settings page).
// - on display of new entity, $entity_id and $is_new are not set.
// - on submit of new entity, $entity_id and $is_new are both set.
$roles = array_merge(array(
WORKFLOW_ROLE_AUTHOR_RID,
), $roles);
}
elseif ($entity_uid > 0 && $user->uid > 0 && $entity_uid == $user->uid) {
// Add 'author' role to user, if user is author of this entity.
// - Some entities (e.g, taxonomy_term) do not have a uid.
// - If 'anonymous' is the author, don't allow access to History Tab,
// since anyone can access it, and it will be published in Search engines.
$roles = array_merge(array(
WORKFLOW_ROLE_AUTHOR_RID,
), $roles);
}
// Set up an array with states - they are already properly sorted.
// Unfortunately, the config_transitions are not sorted.
// Also, $transitions does not contain the 'stay on current state' transition.
// The allowed objects will be replaced with names.
$transitions = $workflow
->getTransitionsBySid($current_sid, $roles);
// Let custom code add/remove/alter the available transitions.
// Using the new drupal_alter.
// Modules may veto a choice by removing a transition from the list.
$context = array(
'entity_type' => $entity_type,
'entity' => $entity,
'field_name' => $field_name,
'force' => $force,
'workflow' => $workflow,
'state' => $current_state,
'user' => $user,
'user_roles' => $roles,
);
// @todo D8: rename to 'workflow_permitted_transitions'.
drupal_alter('workflow_permitted_state_transitions', $transitions, $context);
// Let custom code change the options, using old_style hook.
// @todo D8: delete below foreach/hook for better performance and flexibility.
// Above drupal_alter() calls hook_workflow_permitted_state_transitions_alter() only once.
foreach ($transitions as $transition) {
$new_sid = $transition->target_sid;
$permitted = array();
// We now have a list of config_transitions. Check each against the Entity.
// Invoke a callback indicating that we are collecting state choices.
// Modules may veto a choice by returning FALSE.
// In this case, the choice is never presented to the user.
if ($roles != 'ALL') {
$permitted = module_invoke_all('workflow', 'transition permitted', $current_sid, $new_sid, $entity, $force, $entity_type, $field_name, $transition, $user);
}
// If vetoed by a module, remove from list.
if (in_array(FALSE, $permitted, TRUE)) {
unset($transitions[$transition->tid]);
}
}
return $transitions;
}
/**
* Returns the allowed values for the current state.
*
* @param string $entity_type
* The type of the entity at hand.
* @param object $entity
* The entity at hand. May be NULL (E.g., on a Field settings page).
* @param $field_name
* @param $user
* @param bool $force
*
* @return array
* An array of sid=>label pairs.
* If $this->sid is set, returns the allowed transitions from this state.
* If $this->sid is 0 or FALSE, then labels of ALL states of the State's
* Workflow are returned.
*
* D7.x-2.x: deprecated workflow_field_choices() --> WorkflowState->getOptions()
*/
public function getOptions($entity_type, $entity, $field_name, $user, $force = FALSE) {
// Define an Entity-specific cache per page load.
static $cache = array();
$options = array();
$entity_id = $entity ? entity_id($entity_type, $entity) : '';
$current_sid = $this->sid;
// Get options from page cache, using a non-empty index (just to be sure).
$entity_index = !$entity ? 'x' : $entity_id;
if (isset($cache[$entity_type][$entity_index][$force][$current_sid])) {
$options = $cache[$entity_type][$entity_index][$force][$current_sid];
return $options;
}
$workflow = $this
->getWorkflow();
if (!$workflow) {
// No workflow, no options ;-)
}
elseif (!$current_sid) {
// If no State ID is given, we return all states.
// We cannot use getTransitions, since there are no ConfigTransitions
// from State with ID 0, and we do not want to repeat States.
foreach ($workflow
->getStates() as $state) {
$options[$state
->value()] = $state
->label();
// Translation is done as part of defaultLabel().
}
}
else {
/* @var $transition WorkflowTransition */
$transitions = $this
->getTransitions($entity_type, $entity, $field_name, $user, $force);
foreach ($transitions as $transition) {
// Get the label of the transition, and if empty of the target state.
// Beware: the target state may not exist, since it can be invented
// by custom code in the above drupal_alter() hook.
if (!($label = $transition
->label())) {
$target_state = $transition
->getNewState();
$label = $target_state ? $target_state
->label() : '';
}
$new_sid = $transition->target_sid;
$options[$new_sid] = $label;
// Translation is done as part of defaultLabel().
}
// Include current state for same-state transitions, except when $sid = 0.
// Caveat: this unnecessary since 7.x-2.3 (where stay-on-state transitions are saved, too.)
// but only if the transitions have been saved at least one time.
if ($current_sid && $current_sid != $workflow
->getCreationSid()) {
if (!isset($options[$current_sid])) {
$options[$current_sid] = $this
->label();
// Translation is done as part of defaultLabel().
}
}
// Save to entity-specific cache.
$cache[$entity_type][$entity_index][$force][$current_sid] = $options;
}
return $options;
}
/**
* Returns the number of entities with this state.
*
* @return int
* Counted number.
*
* @todo: add $options to select on entity type, etc.
*/
public function count() {
$sid = $this->sid;
// Get the numbers for Workflow Node.
$result = db_select('workflow_node', 'wn')
->fields('wn')
->condition('sid', $sid, '=')
->execute();
$count = count($result
->fetchAll());
// @see #2285983 for using SQLite.
// Get the numbers for Workflow Field.
$fields = _workflow_info_fields();
foreach ($fields as $field_name => $field_map) {
if ($field_map['type'] == 'workflow') {
$query = new EntityFieldQuery();
$query
->fieldCondition($field_name, 'value', $sid, '=')
->count();
// We only need the count.
$result = $query
->execute();
$count += $result;
}
}
return $count;
}
/**
* Mimics Entity API functions.
*/
protected function defaultLabel() {
return isset($this->state) ? t('@state', array(
'@state' => $this->state,
)) : '';
}
public function getName() {
return isset($this->name) ? $this->name : '';
}
public function setName($name) {
return $this->name = $name;
}
public function value() {
return $this->sid;
}
public function save() {
parent::save();
// Ensure Workflow is marked overridden.
$workflow = $this
->getWorkflow();
if ($workflow->status == ENTITY_IN_CODE) {
$workflow->status = ENTITY_OVERRIDDEN;
$workflow
->save();
}
}
}
Members
Name | Modifiers | Type | Description | Overrides |
---|---|---|---|---|
Entity:: |
protected | property | 1 | |
Entity:: |
protected | property | ||
Entity:: |
protected | property | ||
Entity:: |
protected | property | ||
Entity:: |
protected | property | ||
Entity:: |
public | function |
Builds a structured array representing the entity's content. Overrides EntityInterface:: |
1 |
Entity:: |
public | function |
Returns the bundle of the entity. Overrides EntityInterface:: |
|
Entity:: |
protected | function | Override this in order to implement a custom default URI and specify 'entity_class_uri' as 'uri callback' hook_entity_info(). | |
Entity:: |
public | function |
Permanently deletes the entity. Overrides EntityInterface:: |
|
Entity:: |
public | function |
Returns the info of the type of the entity. Overrides EntityInterface:: |
|
Entity:: |
public | function |
Returns the type of the entity. Overrides EntityInterface:: |
|
Entity:: |
public | function |
Exports the entity. Overrides EntityInterface:: |
|
Entity:: |
public | function |
Gets the raw, translated value of a property or field. Overrides EntityInterface:: |
|
Entity:: |
public | function |
Checks if the entity has a certain exportable status. Overrides EntityInterface:: |
|
Entity:: |
public | function |
Returns the entity identifier, i.e. the entities name or numeric id. Overrides EntityInterface:: |
|
Entity:: |
public | function |
Returns the internal, numeric identifier. Overrides EntityInterface:: |
|
Entity:: |
public | function |
Checks whether the entity is the default revision. Overrides EntityInterface:: |
|
Entity:: |
public | function |
Returns the label of the entity. Overrides EntityInterface:: |
|
Entity:: |
protected | function | Set up the object instance on construction or unserializiation. | |
Entity:: |
public | function |
Returns the uri of the entity just as entity_uri(). Overrides EntityInterface:: |
|
Entity:: |
public | function |
Generate an array for rendering the entity. Overrides EntityInterface:: |
|
Entity:: |
public | function |
Returns the EntityMetadataWrapper of the entity. Overrides EntityInterface:: |
|
Entity:: |
public | function | Magic method to only serialize what's necessary. | |
Entity:: |
public | function | Magic method to invoke setUp() on unserialization. | |
WorkflowState:: |
public | property | ||
WorkflowState:: |
public | property | ||
WorkflowState:: |
protected static | property | ||
WorkflowState:: |
public | property | ||
WorkflowState:: |
public | property | ||
WorkflowState:: |
public | property | ||
WorkflowState:: |
public | property | ||
WorkflowState:: |
public | function | Returns the number of entities with this state. | |
WorkflowState:: |
public | function | Deactivate a Workflow State, moving existing nodes to a given State. | |
WorkflowState:: |
protected | function |
Mimics Entity API functions. Overrides Entity:: |
|
WorkflowState:: |
public | function | ||
WorkflowState:: |
public | function | Returns the allowed values for the current state. | |
WorkflowState:: |
public static | function | Get all states in the system, with options to filter, only where a workflow exists. | |
WorkflowState:: |
public | function | Returns the allowed transitions for the current state. | |
WorkflowState:: |
public | function | Returns the Workflow object of this State. | |
WorkflowState:: |
public | function | Returns the Workflow object of this State. | |
WorkflowState:: |
public | function | ||
WorkflowState:: |
public static | function | Alternative constructor, loading objects from table {workflow_states}. | |
WorkflowState:: |
public static | function | Get all states in the system, with options to filter, only where a workflow exists. | |
WorkflowState:: |
public | function |
Permanently saves the entity. Overrides Entity:: |
|
WorkflowState:: |
public | function | ||
WorkflowState:: |
public | function | ||
WorkflowState:: |
public | function | Determines if the Workflow Form must be shown. | |
WorkflowState:: |
public | function | ||
WorkflowState:: |
public | function |
Constructor. Overrides Entity:: |