You are here

class WorkflowState in Workflow 7.2

Same name and namespace in other branches
  1. 7 includes/Entity/WorkflowState.php \WorkflowState

Class WorkflowState

Hierarchy

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().

... See full list

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

Namesort descending Modifiers Type Description Overrides
Entity::$defaultLabel protected property 1
Entity::$entityInfo protected property
Entity::$entityType protected property
Entity::$idKey protected property
Entity::$wrapper protected property
Entity::buildContent public function Builds a structured array representing the entity's content. Overrides EntityInterface::buildContent 1
Entity::bundle public function Returns the bundle of the entity. Overrides EntityInterface::bundle
Entity::defaultUri protected function Override this in order to implement a custom default URI and specify 'entity_class_uri' as 'uri callback' hook_entity_info().
Entity::delete public function Permanently deletes the entity. Overrides EntityInterface::delete
Entity::entityInfo public function Returns the info of the type of the entity. Overrides EntityInterface::entityInfo
Entity::entityType public function Returns the type of the entity. Overrides EntityInterface::entityType
Entity::export public function Exports the entity. Overrides EntityInterface::export
Entity::getTranslation public function Gets the raw, translated value of a property or field. Overrides EntityInterface::getTranslation
Entity::hasStatus public function Checks if the entity has a certain exportable status. Overrides EntityInterface::hasStatus
Entity::identifier public function Returns the entity identifier, i.e. the entities name or numeric id. Overrides EntityInterface::identifier
Entity::internalIdentifier public function Returns the internal, numeric identifier. Overrides EntityInterface::internalIdentifier
Entity::isDefaultRevision public function Checks whether the entity is the default revision. Overrides EntityInterface::isDefaultRevision
Entity::label public function Returns the label of the entity. Overrides EntityInterface::label
Entity::setUp protected function Set up the object instance on construction or unserializiation.
Entity::uri public function Returns the uri of the entity just as entity_uri(). Overrides EntityInterface::uri
Entity::view public function Generate an array for rendering the entity. Overrides EntityInterface::view
Entity::wrapper public function Returns the EntityMetadataWrapper of the entity. Overrides EntityInterface::wrapper
Entity::__sleep public function Magic method to only serialize what's necessary.
Entity::__wakeup public function Magic method to invoke setUp() on unserialization.
WorkflowState::$sid public property
WorkflowState::$state public property
WorkflowState::$states protected static property
WorkflowState::$status public property
WorkflowState::$sysid public property
WorkflowState::$weight public property
WorkflowState::$wid public property
WorkflowState::count public function Returns the number of entities with this state.
WorkflowState::deactivate public function Deactivate a Workflow State, moving existing nodes to a given State.
WorkflowState::defaultLabel protected function Mimics Entity API functions. Overrides Entity::defaultLabel
WorkflowState::getName public function
WorkflowState::getOptions public function Returns the allowed values for the current state.
WorkflowState::getStates public static function Get all states in the system, with options to filter, only where a workflow exists.
WorkflowState::getTransitions public function Returns the allowed transitions for the current state.
WorkflowState::getWorkflow public function Returns the Workflow object of this State.
WorkflowState::isActive public function Returns the Workflow object of this State.
WorkflowState::isCreationState public function
WorkflowState::load public static function Alternative constructor, loading objects from table {workflow_states}.
WorkflowState::loadByName public static function Get all states in the system, with options to filter, only where a workflow exists.
WorkflowState::save public function Permanently saves the entity. Overrides Entity::save
WorkflowState::setName public function
WorkflowState::setWorkflow public function
WorkflowState::showWidget public function Determines if the Workflow Form must be shown.
WorkflowState::value public function
WorkflowState::__construct public function Constructor. Overrides Entity::__construct