updated workflow module to last dev
This commit is contained in:
@@ -0,0 +1,811 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains workflow\includes\Entity\Workflow.
|
||||
* Contains workflow\includes\Entity\WorkflowController.
|
||||
*/
|
||||
|
||||
// Include file to avoid drush upgrade errors.
|
||||
include_once('WorkflowInterface.php');
|
||||
|
||||
class Workflow extends Entity implements WorkflowInterface {
|
||||
public $wid = 0;
|
||||
public $name = '';
|
||||
public $tab_roles = array();
|
||||
public $options = array();
|
||||
protected $creation_sid = 0;
|
||||
|
||||
// Attached States.
|
||||
public $states = NULL;
|
||||
public $transitions = NULL;
|
||||
|
||||
/**
|
||||
* CRUD functions.
|
||||
*/
|
||||
|
||||
// public function __construct(array $values = array(), $entityType = NULL) {
|
||||
// return parent::__construct($values, $entityType);
|
||||
// }
|
||||
|
||||
public function __clone() {
|
||||
// Clone the arrays of States and Transitions.
|
||||
foreach ($this->states as &$state) {
|
||||
$state = clone $state;
|
||||
}
|
||||
foreach ($this->transitions as &$transition) {
|
||||
$transition = clone $transition;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given information, update or insert a new workflow.
|
||||
*
|
||||
* This also handles importing, rebuilding, reverting from Features,
|
||||
* as defined in workflow.features.inc.
|
||||
*
|
||||
* When changing this function, test with the following situations:
|
||||
* - maintain Workflow in Admin UI;
|
||||
* - clone Workflow in Admin UI;
|
||||
* - create/revert/rebuild Workflow with Features; @see workflow.features.inc
|
||||
* - save Workflow programmatically;
|
||||
*/
|
||||
public function save($create_creation_state = TRUE) {
|
||||
// Are we saving a new Workflow?
|
||||
$is_new = !empty($this->is_new);
|
||||
// Are we rebuilding, reverting a new Workflow? @see workflow.features.inc
|
||||
$is_rebuild = !empty($this->is_rebuild) || !empty($this->is_reverted);
|
||||
|
||||
if ($is_rebuild) {
|
||||
$this->is_rebuild = TRUE;
|
||||
$this->preRebuild();
|
||||
}
|
||||
|
||||
$return = parent::save();
|
||||
|
||||
// On either clone or rebuild from features.
|
||||
if ($is_new || $is_rebuild) {
|
||||
$this->rebuildInternals();
|
||||
if ($is_rebuild) {
|
||||
// The above may have marked us overridden!
|
||||
$this->status = ENTITY_IN_CODE;
|
||||
parent::save();
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure a Creation state exists.
|
||||
if ($is_new) {
|
||||
$state = $this->getCreationState();
|
||||
}
|
||||
|
||||
workflow_reset_cache($this->wid);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild things that get saved with this entity.
|
||||
*/
|
||||
protected function preRebuild() {
|
||||
// Remap roles. They can come from another system with shifted role IDs.
|
||||
// See also https://drupal.org/node/1702626 .
|
||||
$this->rebuildRoles($this->tab_roles);
|
||||
|
||||
// After update.php or import feature, label might be empty. @todo: remove in D8.
|
||||
if (empty($this->label)) {
|
||||
$this->label = $this->name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuild internals that get saved separately.
|
||||
*/
|
||||
protected function rebuildInternals() {
|
||||
// Insert the type_map when building from Features.
|
||||
if (isset($this->typeMap)) {
|
||||
foreach ($this->typeMap as $node_type) {
|
||||
workflow_insert_workflow_type_map($node_type, $this->wid);
|
||||
}
|
||||
}
|
||||
|
||||
// Index the existing states and transitions by name.
|
||||
$db_name_map = WorkflowState::getStates($this->wid, TRUE); // sid -> state.
|
||||
$db_states = array(); // name -> state.
|
||||
foreach ($db_name_map as $state) {
|
||||
$db_states[$state->getName()] = $state;
|
||||
}
|
||||
$db_transitions = array();
|
||||
foreach (entity_load('WorkflowConfigTransition') as $transition) {
|
||||
if ($transition->wid == $this->wid) {
|
||||
$start_name = $db_name_map[$transition->sid]->getName();
|
||||
$end_name = $db_name_map[$transition->target_sid]->getName();
|
||||
$name = WorkflowConfigTransition::machineName($start_name, $end_name);
|
||||
$db_transitions[$name] = $transition;
|
||||
}
|
||||
}
|
||||
|
||||
// Update/create states.
|
||||
$states = isset($this->states) ? $this->states : array();
|
||||
$saved_states = array(); // Saved states: key -> sid.
|
||||
$saved_state_names = array();
|
||||
foreach ($states as $key => $data) {
|
||||
$data = (array)$data;
|
||||
|
||||
$name = $data['name'];
|
||||
if (isset($db_states[$name])) {
|
||||
$state = $db_states[$name];
|
||||
}
|
||||
else {
|
||||
$state = $this->createState($name, FALSE);
|
||||
}
|
||||
|
||||
$state->wid = $this->wid;
|
||||
$state->state = $data['state'];
|
||||
$state->weight = $data['weight'];
|
||||
$state->sysid = $data['sysid'];
|
||||
if (!$data['status']) {
|
||||
$this->rebuildStateInactive($state);
|
||||
}
|
||||
$state->status = $data['status'];
|
||||
$state->save();
|
||||
|
||||
unset($db_states[$name]);
|
||||
$saved_states[$key] = $state;
|
||||
$saved_state_names[$state->sid] = $key;
|
||||
}
|
||||
|
||||
// Update/create transitions.
|
||||
$transitions = isset($this->transitions) ? $this->transitions : array();
|
||||
foreach ($transitions as $name => $data) {
|
||||
$data = (array)$data;
|
||||
|
||||
if (is_numeric($name)) {
|
||||
$start_state = $saved_states[$saved_state_names[$data['sid']]];
|
||||
$end_state = $saved_states[$saved_state_names[$data['target_sid']]];
|
||||
$name = WorkflowConfigTransition::machineName($start_state->getName(),
|
||||
$end_state->getName());
|
||||
}
|
||||
else {
|
||||
$start_state = $saved_states[$data['start_state']];
|
||||
$end_state = $saved_states[$data['end_state']];
|
||||
}
|
||||
|
||||
if (isset($db_transitions[$name])) {
|
||||
$transition = $db_transitions[$name];
|
||||
}
|
||||
else {
|
||||
$transition = $this->createTransition($start_state->sid,
|
||||
$end_state->sid);
|
||||
}
|
||||
|
||||
$transition->wid = $this->wid;
|
||||
$transition->sid = $start_state->sid;
|
||||
$transition->target_sid = $end_state->sid;
|
||||
$transition->label = $data['label'];
|
||||
$transition->roles = $data['roles'];
|
||||
$this->rebuildRoles($transition->roles);
|
||||
$transition->save();
|
||||
|
||||
unset($db_transitions[$name]);
|
||||
}
|
||||
|
||||
// Any states/transitions left in $db_states/transitions need deletion.
|
||||
foreach ($db_states as $state) {
|
||||
$this->rebuildStateInactive($state);
|
||||
$state->delete();
|
||||
}
|
||||
foreach ($db_transitions as $transition) {
|
||||
$transition->delete();
|
||||
}
|
||||
|
||||
// Clear the caches, and set $this->states and $this->transitions.
|
||||
$this->states = $this->transitions = NULL;
|
||||
$this->getStates(TRUE, TRUE);
|
||||
$this->getTransitions(FALSE, array(), TRUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a state becoming inactive during a rebuild.
|
||||
*/
|
||||
protected function rebuildStateInactive($state) {
|
||||
if (!$state->isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: What should we do in this case? Is this safe?
|
||||
$state->deactivate(NULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a wid, delete the workflow and its data.
|
||||
*
|
||||
* @deprecated: workflow_delete_workflows_by_wid() --> Workflow::delete().
|
||||
*/
|
||||
public function delete() {
|
||||
$wid = $this->wid;
|
||||
|
||||
// Notify any interested modules before we delete the workflow.
|
||||
// E.g., Workflow Node deletes the {workflow_type_map} record.
|
||||
module_invoke_all('workflow', 'workflow delete', $wid, NULL, NULL, FALSE);
|
||||
|
||||
// Delete associated state (also deletes any associated transitions).
|
||||
foreach ($this->getStates($all = TRUE) as $state) {
|
||||
$state->deactivate(0);
|
||||
$state->delete();
|
||||
}
|
||||
|
||||
// Delete the workflow.
|
||||
db_delete('workflows')->condition('wid', $wid)->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the workflow. Generate a message if not correct.
|
||||
*
|
||||
* This function is used on the settings page of:
|
||||
* - Workflow node: workflow_admin_ui_type_map_form()
|
||||
* - Workflow field: WorkflowItem->settingsForm()
|
||||
*
|
||||
* @return bool
|
||||
* $is_valid
|
||||
*/
|
||||
public function isValid() {
|
||||
$is_valid = TRUE;
|
||||
|
||||
// Don't allow workflows with no states. There should always be a creation state.
|
||||
$states = $this->getStates($all = FALSE);
|
||||
if (count($states) < 1) {
|
||||
// That's all, so let's remind them to create some states.
|
||||
$message = t('Workflow %workflow has no states defined, so it cannot be assigned to content yet.',
|
||||
array('%workflow' => $this->getName()));
|
||||
drupal_set_message($message, 'warning');
|
||||
|
||||
// Skip allowing this workflow.
|
||||
$is_valid = FALSE;
|
||||
}
|
||||
|
||||
// Also check for transitions, at least out of the creation state. Use 'ALL' role.
|
||||
$transitions = $this->getTransitionsBySid($this->getCreationSid(), $roles = 'ALL');
|
||||
if (count($transitions) < 1) {
|
||||
// That's all, so let's remind them to create some transitions.
|
||||
$message = t('Workflow %workflow has no transitions defined, so it cannot be assigned to content yet.',
|
||||
array('%workflow' => $this->getName()));
|
||||
drupal_set_message($message, 'warning');
|
||||
|
||||
// Skip allowing this workflow.
|
||||
$is_valid = FALSE;
|
||||
}
|
||||
|
||||
// If the Workflow is mapped to a node type, check if workflow->options is set.
|
||||
if ($this->getTypeMap() && !count($this->options)) {
|
||||
// That's all, so let's remind them to create some transitions.
|
||||
$message = t('Please maintain Workflow %workflow on its <a href="@url">settings</a> page.',
|
||||
array(
|
||||
'%workflow' => $this->getName(),
|
||||
'@url' => url('admin/config/workflow/workflow/manage/' . $this->wid),
|
||||
)
|
||||
);
|
||||
drupal_set_message($message, 'warning');
|
||||
|
||||
// Skip allowing this workflow.
|
||||
// $is_valid = FALSE;
|
||||
}
|
||||
|
||||
return $is_valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the Workflow may be deleted.
|
||||
*
|
||||
* @return bool $is_deletable
|
||||
* TRUE if a Workflow may safely be deleted.
|
||||
*/
|
||||
public function isDeletable() {
|
||||
$is_deletable = FALSE;
|
||||
|
||||
// May not be deleted if a TypeMap exists.
|
||||
if ($this->getTypeMap()) {
|
||||
return $is_deletable;
|
||||
}
|
||||
|
||||
// May not be deleted if assigned to a Field.
|
||||
foreach (_workflow_info_fields() as $field) {
|
||||
if ($field['settings']['wid'] == $this->wid) {
|
||||
return $is_deletable;
|
||||
}
|
||||
}
|
||||
|
||||
// May not be deleted if a State is assigned to a state.
|
||||
foreach ($this->getStates(TRUE) as $state) {
|
||||
if ($state->count()) {
|
||||
return $is_deletable;
|
||||
}
|
||||
}
|
||||
$is_deletable = TRUE;
|
||||
return $is_deletable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Property functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the workflow id.
|
||||
*
|
||||
* @return int
|
||||
* $wid
|
||||
*/
|
||||
public function getWorkflowId() {
|
||||
return $this->wid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new state for this workflow.
|
||||
*
|
||||
* @param string $name
|
||||
* The untranslated human readable label of the state.
|
||||
* @param bool $save
|
||||
* Indicator if the new state must be saved. Normally, the new State is
|
||||
* saved directly in the database. This is because you can use States only
|
||||
* with Transitions, and they rely on State IDs which are generated
|
||||
* magically when saving the State. But you may need a temporary state.
|
||||
*
|
||||
* @return WorkflowState
|
||||
*/
|
||||
public function createState($name, $save = TRUE) {
|
||||
$wid = $this->wid;
|
||||
$state = workflow_state_load_by_name($name, $wid);
|
||||
if (!$state) {
|
||||
$state = entity_create('WorkflowState', array('name' => $name, 'state' => $name, 'wid' => $wid));
|
||||
if ($save) {
|
||||
$state->save();
|
||||
}
|
||||
}
|
||||
$state->setWorkflow($this);
|
||||
// Maintain the new object in the workflow.
|
||||
$this->states[$state->sid] = $state;
|
||||
|
||||
return $state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the initial state for a newly created entity.
|
||||
*/
|
||||
public function getCreationState() {
|
||||
$sid = $this->getCreationSid();
|
||||
return ($sid) ? $this->getState($sid) : $this->createState(WORKFLOW_CREATION_STATE_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ID of the initial state for a newly created entity.
|
||||
*/
|
||||
public function getCreationSid() {
|
||||
if (!$this->creation_sid) {
|
||||
foreach ($this->getStates($all = TRUE) as $state) {
|
||||
if ($state->isCreationState()) {
|
||||
$this->creation_sid = $state->sid;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->creation_sid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first valid state ID, after the creation state.
|
||||
*
|
||||
* Uses WorkflowState::getOptions(), because this does a access check.
|
||||
* The first State ID is user-dependent!
|
||||
*/
|
||||
public function getFirstSid($entity_type, $entity, $field_name, $user, $force) {
|
||||
$creation_state = $this->getCreationState();
|
||||
$options = $creation_state->getOptions($entity_type, $entity, $field_name, $user, $force);
|
||||
if ($options) {
|
||||
$keys = array_keys($options);
|
||||
$sid = $keys[0];
|
||||
}
|
||||
else {
|
||||
// This should never happen, but it did during testing.
|
||||
drupal_set_message(t('There are no workflow states available. Please notify your site administrator.'), 'error');
|
||||
$sid = 0;
|
||||
}
|
||||
return $sid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next state 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 int $sid
|
||||
* A state ID.
|
||||
*/
|
||||
public function getNextSid($entity_type, $entity, $field_name, $user, $force = FALSE) {
|
||||
$new_sid = workflow_node_current_state($entity, $entity_type, $field_name);
|
||||
|
||||
if ($new_sid && $new_state = workflow_state_load_single($new_sid)) {
|
||||
/* @var $current_state WorkflowState */
|
||||
$options = $new_state->getOptions($entity_type, $entity, $field_name, $user, $force);
|
||||
// Loop over every option. To find the next one.
|
||||
$flag = $new_state->isCreationState();
|
||||
foreach ($options as $sid => $name) {
|
||||
if ($flag) {
|
||||
$new_sid = $sid;
|
||||
break;
|
||||
}
|
||||
if ($sid == $new_state->sid) {
|
||||
$flag = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $new_sid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all states for a given workflow.
|
||||
*
|
||||
* @param mixed $all
|
||||
* Indicates to which states to return.
|
||||
* - TRUE = all, including Creation and Inactive;
|
||||
* - FALSE = only Active states, not Creation;
|
||||
* - 'CREATION' = only Active states, including Creation.
|
||||
*
|
||||
* @return array
|
||||
* An array of WorkflowState objects.
|
||||
*/
|
||||
public function getStates($all = FALSE, $reset = FALSE) {
|
||||
if ($this->states === NULL || $reset) {
|
||||
$this->states = $this->wid ? WorkflowState::getStates($this->wid, $reset) : array();
|
||||
}
|
||||
// Do not unset, but add to array - you'll remove global objects otherwise.
|
||||
$states = array();
|
||||
foreach ($this->states as $state) {
|
||||
if ($all === TRUE) {
|
||||
$states[$state->sid] = $state;
|
||||
}
|
||||
elseif (($all === FALSE) && ($state->isActive() && !$state->isCreationState())) {
|
||||
$states[$state->sid] = $state;
|
||||
}
|
||||
elseif (($all == 'CREATION') && ($state->isActive() || $state->isCreationState())) {
|
||||
$states[$state->sid] = $state;
|
||||
}
|
||||
}
|
||||
return $states;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a state for a given workflow.
|
||||
*
|
||||
* @param mixed $key
|
||||
* A state ID or state Name.
|
||||
*
|
||||
* @return WorkflowState
|
||||
* A WorkflowState object.
|
||||
*/
|
||||
public function getState($key) {
|
||||
if (is_numeric($key)) {
|
||||
return workflow_state_load_single($key, $this->wid);
|
||||
}
|
||||
else {
|
||||
return workflow_state_load_by_name($key, $this->wid);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a Transition for this workflow.
|
||||
*/
|
||||
public function createTransition($sid, $target_sid, $values = array()) {
|
||||
$workflow = $this;
|
||||
if (is_numeric($sid) && is_numeric($target_sid)) {
|
||||
$values['sid'] = $sid;
|
||||
$values['target_sid'] = $target_sid;
|
||||
}
|
||||
else {
|
||||
$state = $workflow->getState($sid);
|
||||
$target_state = $workflow->getState($target_sid);
|
||||
$values['sid'] = $state->sid;
|
||||
$values['target_sid'] = $target_state->sid;
|
||||
}
|
||||
|
||||
// First check if this transition already exists.
|
||||
if ($transitions = entity_load('WorkflowConfigTransition', FALSE, $values)) {
|
||||
$transition = reset($transitions);
|
||||
}
|
||||
else {
|
||||
$values['wid'] = $workflow->wid;
|
||||
$transition = entity_create('WorkflowConfigTransition', $values);
|
||||
$transition->save();
|
||||
}
|
||||
$transition->setWorkflow($this);
|
||||
// Maintain the new object in the workflow.
|
||||
$this->transitions[$transition->tid] = $transition;
|
||||
|
||||
return $transition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts all Transitions for this workflow, according to State weight.
|
||||
*
|
||||
* This is only needed for the Admin UI.
|
||||
*/
|
||||
public function sortTransitions() {
|
||||
// Sort the transitions on state weight.
|
||||
usort($this->transitions, '_workflow_transitions_sort_by_weight');
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads all allowed ConfigTransitions for this workflow.
|
||||
*
|
||||
* @param mixed $tids
|
||||
* Array of Transitions IDs. If FALSE, show all transitions.
|
||||
* @param array $conditions
|
||||
* $conditions['sid'] : if provided, a 'from' State ID.
|
||||
* $conditions['target_sid'] : if provided, a 'to' state ID.
|
||||
* $conditions['roles'] : if provided, an array of roles, or 'ALL'.
|
||||
* @param bool $reset
|
||||
* Indicator to reset the cache.
|
||||
*
|
||||
* @return array
|
||||
* An array of keyed transitions.
|
||||
*/
|
||||
public function getTransitions($tids = FALSE, array $conditions = array(), $reset = FALSE) {
|
||||
$config_transitions = array();
|
||||
|
||||
// Get valid + creation states.
|
||||
$states = $this->getStates('CREATION');
|
||||
|
||||
// Get filters on 'from' states, 'to' states, roles.
|
||||
$sid = isset($conditions['sid']) ? $conditions['sid'] : FALSE;
|
||||
$target_sid = isset($conditions['target_sid']) ? $conditions['target_sid'] : FALSE;
|
||||
$roles = isset($conditions['roles']) ? $conditions['roles'] : 'ALL';
|
||||
|
||||
// Cache all transitions in the workflow.
|
||||
// We may have 0 transitions....
|
||||
if ($this->transitions === NULL) {
|
||||
$this->transitions = array();
|
||||
// Get all transitions. (Even from other workflows. :-( )
|
||||
$config_transitions = entity_load('WorkflowConfigTransition', $tids, array(), $reset);
|
||||
foreach ($config_transitions as &$config_transition) {
|
||||
if (isset($states[$config_transition->sid])) {
|
||||
$config_transition->setWorkflow($this);
|
||||
$this->transitions[$config_transition->tid] = $config_transition;
|
||||
}
|
||||
}
|
||||
$this->sortTransitions();
|
||||
}
|
||||
|
||||
$config_transitions = array();
|
||||
foreach ($this->transitions as &$config_transition) {
|
||||
if (!isset($states[$config_transition->sid])) {
|
||||
// Not a valid transition for this workflow.
|
||||
}
|
||||
elseif ($sid && $sid != $config_transition->sid) {
|
||||
// Not the requested 'from' state.
|
||||
}
|
||||
elseif ($target_sid && $target_sid != $config_transition->target_sid) {
|
||||
// Not the requested 'to' state.
|
||||
}
|
||||
elseif ($roles == 'ALL' || $config_transition->isAllowed($roles)) {
|
||||
// Transition is allowed, permitted. Add to list.
|
||||
$config_transition->setWorkflow($this);
|
||||
$config_transitions[$config_transition->tid] = $config_transition;
|
||||
}
|
||||
else {
|
||||
// Transition is otherwise not allowed.
|
||||
}
|
||||
}
|
||||
|
||||
return $config_transitions;
|
||||
}
|
||||
|
||||
public function getTransitionsByTid($tid, $roles = '', $reset = FALSE) {
|
||||
$conditions = array(
|
||||
'roles' => $roles,
|
||||
);
|
||||
return $this->getTransitions(array($tid), $conditions, $reset);
|
||||
}
|
||||
|
||||
public function getTransitionsBySid($sid, $roles = '', $reset = FALSE) {
|
||||
$conditions = array(
|
||||
'sid' => $sid,
|
||||
'roles' => $roles,
|
||||
);
|
||||
return $this->getTransitions(FALSE, $conditions, $reset);
|
||||
}
|
||||
|
||||
public function getTransitionsByTargetSid($target_sid, $roles = '', $reset = FALSE) {
|
||||
$conditions = array(
|
||||
'target_sid' => $target_sid,
|
||||
'roles' => $roles,
|
||||
);
|
||||
return $this->getTransitions(FALSE, $conditions, $reset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific transition. Therefore, use $roles = 'ALL'.
|
||||
*/
|
||||
public function getTransitionsBySidTargetSid($sid, $target_sid, $roles = 'ALL', $reset = FALSE) {
|
||||
$conditions = array(
|
||||
'sid' => $sid,
|
||||
'target_sid' => $target_sid,
|
||||
'roles' => $roles,
|
||||
);
|
||||
return $this->getTransitions(FALSE, $conditions, $reset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a the type map for a given workflow.
|
||||
*
|
||||
* @param int $sid
|
||||
* A state ID.
|
||||
*
|
||||
* @return array
|
||||
* An array of typemaps.
|
||||
*/
|
||||
public function getTypeMap() {
|
||||
$result = array();
|
||||
|
||||
$type_maps = module_exists('workflownode') ? workflow_get_workflow_type_map_by_wid($this->wid) : array();
|
||||
foreach ($type_maps as $map) {
|
||||
$result[] = $map->type;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a setting from the state object.
|
||||
*/
|
||||
public function getSetting($key, array $field = array()) {
|
||||
switch ($key) {
|
||||
case 'watchdog_log':
|
||||
if (isset($this->options['watchdog_log'])) {
|
||||
// This is set via Node API.
|
||||
return $this->options['watchdog_log'];
|
||||
}
|
||||
elseif ($field) {
|
||||
if (isset($field['settings']['watchdog_log'])) {
|
||||
// This is set via Field API.
|
||||
return $field['settings']['watchdog_log'];
|
||||
}
|
||||
}
|
||||
drupal_set_message('Setting Workflow::getSetting(' . $key . ') does not exist', 'error');
|
||||
break;
|
||||
|
||||
default:
|
||||
drupal_set_message('Setting Workflow::getSetting(' . $key . ') does not exist', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mimics Entity API functions.
|
||||
*/
|
||||
public function getName() {
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
protected function defaultLabel() {
|
||||
return isset($this->label) ? $this->label : '';
|
||||
}
|
||||
|
||||
protected function defaultUri() {
|
||||
return array('path' => 'admin/config/workflow/workflow/manage/' . $this->wid);
|
||||
}
|
||||
|
||||
protected function rebuildRoles(array &$roles) {
|
||||
$role_map = isset($this->system_roles) ? $this->system_roles : array();
|
||||
if (!$role_map) {
|
||||
return;
|
||||
}
|
||||
|
||||
// See also https://drupal.org/node/1702626 .
|
||||
$new_roles = array();
|
||||
foreach ($roles as $key => $rid) {
|
||||
if ($rid == WORKFLOW_ROLE_AUTHOR_RID) {
|
||||
$new_roles[$rid] = $rid;
|
||||
}
|
||||
else {
|
||||
if ($role = user_role_load_by_name($role_map[$rid])) {
|
||||
$new_roles[$role->rid] = (int)($role->rid);
|
||||
}
|
||||
}
|
||||
}
|
||||
$roles = $new_roles;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to sort the transitions.
|
||||
*
|
||||
* @param WorkflowConfigTransition $a
|
||||
* @param WorkflowConfigTransition $b
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
function _workflow_transitions_sort_by_weight($a, $b) {
|
||||
// First sort on From-State.
|
||||
$old_state_a = $a->getOldState();
|
||||
$old_state_b = $b->getOldState();
|
||||
if ($old_state_a->weight < $old_state_b->weight) return -1;
|
||||
if ($old_state_a->weight > $old_state_b->weight) return +1;
|
||||
|
||||
// Then sort on To-State.
|
||||
$new_state_a = $a->getNewState();
|
||||
$new_state_b = $b->getNewState();
|
||||
if ($new_state_a->weight < $new_state_b->weight) return -1;
|
||||
if ($new_state_a->weight > $new_state_b->weight) return +1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Implements a controller class for Workflow.
|
||||
*/
|
||||
class WorkflowController extends EntityAPIControllerExportable {
|
||||
|
||||
// public function create(array $values = array()) { return parent::create($values); }
|
||||
// public function load($ids = array(), $conditions = array()) { }
|
||||
|
||||
public function delete($ids, DatabaseTransaction $transaction = NULL) {
|
||||
// @todo: replace WorkflowController::delete() with parent.
|
||||
// @todo: throw error if not workflow->isDeletable().
|
||||
foreach ($ids as $wid) {
|
||||
if ($workflow = workflow_load($wid)) {
|
||||
$workflow->delete();
|
||||
}
|
||||
}
|
||||
$this->resetCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides DrupalDefaultEntityController::cacheGet().
|
||||
*
|
||||
* Override default function, due to Core issue #1572466.
|
||||
*/
|
||||
protected function cacheGet($ids, $conditions = array()) {
|
||||
// Load any available entities from the internal cache.
|
||||
if ($ids === FALSE && !$conditions) {
|
||||
return $this->entityCache;
|
||||
}
|
||||
return parent::cacheGet($ids, $conditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides DrupalDefaultEntityController::cacheSet().
|
||||
*/
|
||||
/*
|
||||
// protected function cacheSet($entities) { }
|
||||
// return parent::cacheSet($entities);
|
||||
// }
|
||||
*/
|
||||
|
||||
/**
|
||||
* Overrides DrupalDefaultEntityController::resetCache().
|
||||
*
|
||||
* Called by workflow_reset_cache, to
|
||||
* Reset the Workflow when States, Transitions have been changed.
|
||||
*/
|
||||
// public function resetCache(array $ids = NULL) {
|
||||
// parent::resetCache($ids);
|
||||
// }
|
||||
|
||||
/**
|
||||
* Overrides DrupalDefaultEntityController::attachLoad().
|
||||
*/
|
||||
protected function attachLoad(&$queried_entities, $revision_id = FALSE) {
|
||||
foreach ($queried_entities as $entity) {
|
||||
// Load the states, so they are already present on the next (cached) load.
|
||||
$entity->states = $entity->getStates($all = TRUE);
|
||||
$entity->transitions = $entity->getTransitions(FALSE);
|
||||
$entity->typeMap = $entity->getTypeMap();
|
||||
}
|
||||
|
||||
parent::attachLoad($queried_entities, $revision_id);
|
||||
}
|
||||
}
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains workflow\includes\Entity\WorkflowConfigTransition.
|
||||
* Contains workflow\includes\Entity\WorkflowConfigTransitionController.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements a configurated Transition.
|
||||
*/
|
||||
class WorkflowConfigTransition extends Entity {
|
||||
|
||||
// Transition data.
|
||||
public $tid = 0;
|
||||
// public $old_sid = 0;
|
||||
// public $new_sid = 0;
|
||||
public $sid = 0; // @todo D8: remove $sid, use $new_sid. (requires conversion of Views displays.)
|
||||
public $target_sid = 0;
|
||||
public $roles = array();
|
||||
|
||||
// Extra fields.
|
||||
public $wid = 0;
|
||||
// The following must explicitely defined, and not be public, to avoid errors when exporting with json_encode().
|
||||
protected $workflow = NULL;
|
||||
|
||||
/**
|
||||
* Entity class functions.
|
||||
*/
|
||||
|
||||
/*
|
||||
// Implementing clone needs a list of tid-less transitions, and a conversion
|
||||
// of sids for both States and ConfigTransitions.
|
||||
// public function __clone() {}
|
||||
*/
|
||||
|
||||
public function __construct(array $values = array(), $entityType = NULL) {
|
||||
// Please be aware that $entity_type and $entityType are different things!
|
||||
return parent::__construct($values, $entityType = 'WorkflowConfigTransition');
|
||||
}
|
||||
|
||||
/**
|
||||
* Permanently deletes the entity.
|
||||
*/
|
||||
public function delete() {
|
||||
// Notify any interested modules before we delete, in case there's data needed.
|
||||
// @todo D8: this can be replaced by a hook_entity_delete(?)
|
||||
module_invoke_all('workflow', 'transition delete', $this->tid, NULL, NULL, FALSE);
|
||||
|
||||
return parent::delete();
|
||||
}
|
||||
|
||||
protected function defaultLabel() {
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
protected function defaultUri() {
|
||||
return array('path' => 'admin/config/workflow/workflow/manage/' . $this->wid . '/transitions/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Property functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns the Workflow object of this State.
|
||||
*
|
||||
* @param Workflow $workflow
|
||||
* An optional workflow object. Can be used as a setter.
|
||||
*
|
||||
* @return Workflow
|
||||
* Workflow object.
|
||||
*/
|
||||
public function setWorkflow($workflow) {
|
||||
$this->wid = $workflow->wid;
|
||||
$this->workflow = $workflow;
|
||||
}
|
||||
|
||||
public function getWorkflow() {
|
||||
if (isset($this->workflow)) {
|
||||
return $this->workflow;
|
||||
}
|
||||
return workflow_load_single($this->wid);
|
||||
}
|
||||
public function getOldState() {
|
||||
return workflow_state_load_single($this->sid);
|
||||
}
|
||||
public function getNewState() {
|
||||
return workflow_state_load_single($this->target_sid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies if the given transition is allowed.
|
||||
*
|
||||
* - In settings;
|
||||
* - In permissions;
|
||||
* - By permission hooks, implemented by other modules.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if OK, else FALSE.
|
||||
*/
|
||||
public function isAllowed($user_roles) {
|
||||
if ($user_roles == 'ALL') {
|
||||
// Superuser.
|
||||
return TRUE;
|
||||
}
|
||||
elseif ($user_roles) {
|
||||
return array_intersect($user_roles, $this->roles) == TRUE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a machine name for a transition.
|
||||
*/
|
||||
public static function machineName($start_name, $end_name) {
|
||||
$new_name = sprintf("%s_to_%s", $start_name, $end_name);
|
||||
|
||||
// Special case: replace parens in creation state transition names.
|
||||
$new_name = str_replace("(creation)", "_creation", $new_name);
|
||||
|
||||
return $new_name;
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a controller class for WorkflowConfigTransition.
|
||||
*
|
||||
* The 'true' controller class is 'Workflow'.
|
||||
*/
|
||||
class WorkflowConfigTransitionController extends EntityAPIController {
|
||||
|
||||
/**
|
||||
* Overrides DrupalDefaultEntityController::cacheGet().
|
||||
*
|
||||
* Override default function, due to core issue #1572466.
|
||||
*/
|
||||
protected function cacheGet($ids, $conditions = array()) {
|
||||
// Load any available entities from the internal cache.
|
||||
if ($ids === FALSE && !$conditions) {
|
||||
return $this->entityCache;
|
||||
}
|
||||
return parent::cacheGet($ids, $conditions);
|
||||
}
|
||||
|
||||
public function save($entity, DatabaseTransaction $transaction = NULL) {
|
||||
$workflow = $entity->getWorkflow();
|
||||
|
||||
// To avoid double posting, check if this transition already exist.
|
||||
if (empty($entity->tid)) {
|
||||
if ($workflow) {
|
||||
$config_transitions = $workflow->getTransitionsBySidTargetSid($entity->sid, $entity->target_sid);
|
||||
$config_transition = reset($config_transitions);
|
||||
if ($config_transition) {
|
||||
$entity->tid = $config_transition->tid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the machine_name. This can be used to rebuild/revert the Feature in a target system.
|
||||
if (empty($entity->name)) {
|
||||
$entity->name = $entity->sid . '_' . $entity->target_sid;
|
||||
}
|
||||
|
||||
$return = parent::save($entity, $transaction);
|
||||
if ($return) {
|
||||
// Save in current workflow for the remainder of this page request.
|
||||
// Keep in sync with Workflow::getTransitions() !
|
||||
$workflow = $entity->getWorkflow();
|
||||
if ($workflow) {
|
||||
$workflow->transitions[$entity->tid] = $entity;
|
||||
// $workflow->sortTransitions();
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the cache for the affected workflow, to force reload upon next page_load.
|
||||
workflow_reset_cache($entity->wid);
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains Drupal\workflow\Entity\WorkflowInterface.
|
||||
*/
|
||||
|
||||
// D8: namespace Drupal\workflow\Entity;
|
||||
|
||||
// D8: use Drupal\Core\Config\Entity\ConfigEntityBase;
|
||||
// D8: use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Defines a common interface for Workflow*Transition* objects.
|
||||
*
|
||||
* @see \Drupal\workflow\Entity\WorkflowConfigTransition
|
||||
* @see \Drupal\workflow\Entity\WorkflowTransition
|
||||
* @see \Drupal\workflow\Entity\WorkflowScheduledTransition
|
||||
*/
|
||||
interface WorkflowInterface {
|
||||
|
||||
/**
|
||||
* Retrieves the entity manager service.
|
||||
*
|
||||
* @return \Drupal\workflow\Entity\WorkflowManagerInterface
|
||||
* The entity manager service.
|
||||
*/
|
||||
//D8: public static function workflowManager();
|
||||
|
||||
/**
|
||||
* Returns the workflow id.
|
||||
*
|
||||
* @return string
|
||||
* $wid
|
||||
*/
|
||||
public function getWorkflowId();
|
||||
|
||||
/**
|
||||
* Validate the workflow. Generate a message if not correct.
|
||||
*
|
||||
* This function is used on the settings page of:
|
||||
* - Workflow field: WorkflowItem->settingsForm()
|
||||
*
|
||||
* @return bool
|
||||
* $is_valid
|
||||
*/
|
||||
public function isValid();
|
||||
|
||||
/**
|
||||
* Returns if the Workflow may be deleted.
|
||||
*
|
||||
* @return bool $is_deletable
|
||||
* TRUE if a Workflow may safely be deleted.
|
||||
*/
|
||||
public function isDeletable();
|
||||
|
||||
/**
|
||||
* Create a new state for this workflow.
|
||||
*
|
||||
* @param string $name
|
||||
* The untranslated human readable label of the state.
|
||||
* @param bool $save
|
||||
* Indicator if the new state must be saved. Normally, the new State is
|
||||
* saved directly in the database. This is because you can use States only
|
||||
* with Transitions, and they rely on State IDs which are generated
|
||||
* magically when saving the State. But you may need a temporary state.
|
||||
* @return \Drupal\workflow\Entity\WorkflowState
|
||||
* The new state.
|
||||
*/
|
||||
public function createState($sid, $save = TRUE);
|
||||
|
||||
/**
|
||||
* Gets the initial state for a newly created entity.
|
||||
*/
|
||||
public function getCreationState();
|
||||
|
||||
/**
|
||||
* Gets the ID of the initial state for a newly created entity.
|
||||
*/
|
||||
public function getCreationSid();
|
||||
|
||||
/**
|
||||
* Gets the first valid state ID, after the creation state.
|
||||
*
|
||||
* Uses WorkflowState::getOptions(), because this does an access check.
|
||||
* The first State ID is user-dependent!
|
||||
*/
|
||||
public function getFirstSid($entity_type, $entity, $field_name, $user, $force);
|
||||
|
||||
/**
|
||||
* Returns the next state for the current state.
|
||||
* Is used in VBO Bulk actions.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
*/
|
||||
public function getNextSid($entity_type, $entity, $field_name, $user, $force = FALSE);
|
||||
|
||||
/**
|
||||
* Gets all states for a given workflow.
|
||||
*
|
||||
* @param mixed $all
|
||||
* Indicates to which states to return.
|
||||
* - TRUE = all, including Creation and Inactive;
|
||||
* - FALSE = only Active states, not Creation;
|
||||
* - 'CREATION' = only Active states, including Creation.
|
||||
*
|
||||
* @return WorkflowState[]
|
||||
* An array of WorkflowState objects.
|
||||
*/
|
||||
public function getStates($all = FALSE, $reset = FALSE);
|
||||
|
||||
/**
|
||||
* Gets a state for a given workflow.
|
||||
*
|
||||
* @param mixed $key
|
||||
* A state ID or state Name.
|
||||
*
|
||||
* @return WorkflowState
|
||||
* A WorkflowState object.
|
||||
*/
|
||||
public function getState($sid);
|
||||
|
||||
/**
|
||||
* Creates a Transition for this workflow.
|
||||
*
|
||||
* @param string $from_sid
|
||||
* @param string $to_sid
|
||||
* @param array $values
|
||||
*
|
||||
* @return mixed|null|static
|
||||
*/
|
||||
public function createTransition($from_sid, $to_sid, $values = array());
|
||||
|
||||
/**
|
||||
* Sorts all Transitions for this workflow, according to State weight.
|
||||
*
|
||||
* This is only needed for the Admin UI.
|
||||
*/
|
||||
public function sortTransitions();
|
||||
|
||||
/**
|
||||
* Loads all allowed ConfigTransitions for this workflow.
|
||||
*
|
||||
* @param array|NULL $ids
|
||||
* Array of Transitions IDs. If NULL, show all transitions.
|
||||
* @param array $conditions
|
||||
* $conditions['from_sid'] : if provided, a 'from' State ID.
|
||||
* $conditions['to_sid'] : if provided, a 'to' state ID.
|
||||
*
|
||||
* @return \Drupal\workflow\Entity\WorkflowConfigTransition[]
|
||||
*/
|
||||
public function getTransitions($tids = FALSE, array $conditions = array(), $reset = FALSE);
|
||||
|
||||
public function getTransitionsByTid($tid);
|
||||
|
||||
/**
|
||||
*
|
||||
* Get a specific transition.
|
||||
*
|
||||
* @param string $from_sid
|
||||
* @param string $to_sid
|
||||
*
|
||||
* @return WorkflowConfigTransition[]
|
||||
*/
|
||||
//D8: public function getTransitionsByStateId($from_sid, $to_sid);
|
||||
|
||||
}
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains workflow\includes\Entity\WorkflowScheduledTransition.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements a scheduled transition, as shown on Workflow form.
|
||||
*/
|
||||
class WorkflowScheduledTransition extends WorkflowTransition {
|
||||
// Scheduled timestamp of state change.
|
||||
public $scheduled;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct(array $values = array(), $entityType = 'WorkflowScheduledTransition') {
|
||||
// Please be aware that $entity_type and $entityType are different things!
|
||||
parent::__construct($values, $entityType);
|
||||
|
||||
$this->is_scheduled = TRUE;
|
||||
$this->is_executed = FALSE;
|
||||
}
|
||||
|
||||
public function setValues($entity_type, $entity, $field_name, $old_sid, $new_sid, $uid = NULL, $scheduled = REQUEST_TIME, $comment = '') {
|
||||
// A scheduled transition does not have a timestamp, yet.
|
||||
$stamp = 0;
|
||||
parent::setValues($entity_type, $entity, $field_name, $old_sid, $new_sid, $uid, $stamp, $comment);
|
||||
|
||||
// Set the scheduled timestamp of state change.
|
||||
$this->scheduled = $scheduled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a node, get all scheduled transitions for it.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* @param int $entity_id
|
||||
* @param string $field_name
|
||||
* Optional.
|
||||
*
|
||||
* @return array
|
||||
* An array of WorkflowScheduledTransitions.
|
||||
*
|
||||
* deprecated: workflow_get_workflow_scheduled_transition_by_nid() --> WorkflowScheduledTransition::load()
|
||||
*/
|
||||
public static function load($entity_type, $entity_id, $field_name = '', $limit = NULL) {
|
||||
if (!$entity_id) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$query = db_select('workflow_scheduled_transition', 'wst');
|
||||
$query->fields('wst');
|
||||
$query->condition('entity_type', $entity_type, '=');
|
||||
$query->condition('nid', $entity_id, '=');
|
||||
if ($field_name !== NULL) {
|
||||
$query->condition('field_name', $field_name, '=');
|
||||
}
|
||||
$query->orderBy('scheduled', 'ASC');
|
||||
$query->addTag('workflow_scheduled_transition');
|
||||
if ($limit) {
|
||||
$query->range(0, $limit);
|
||||
}
|
||||
$result = $query->execute()->fetchAll(PDO::FETCH_CLASS, 'WorkflowScheduledTransition');
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a timeframe, get all scheduled transitions.
|
||||
*
|
||||
* deprecated: workflow_get_workflow_scheduled_transition_by_between() --> WorkflowScheduledTransition::loadBetween()
|
||||
*/
|
||||
public static function loadBetween($start = 0, $end = 0) {
|
||||
$query = db_select('workflow_scheduled_transition', 'wst');
|
||||
$query->fields('wst');
|
||||
$query->orderBy('scheduled', 'ASC');
|
||||
$query->addTag('workflow_scheduled_transition');
|
||||
|
||||
if ($start) {
|
||||
$query->condition('scheduled', $start, '>');
|
||||
}
|
||||
if ($end) {
|
||||
$query->condition('scheduled', $end, '<');
|
||||
}
|
||||
|
||||
$result = $query->execute()->fetchAll(PDO::FETCH_CLASS, 'WorkflowScheduledTransition');
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a scheduled transition. If the transition is executed, save in history.
|
||||
*/
|
||||
public function save() {
|
||||
// If executed, save in history.
|
||||
if ($this->is_executed) {
|
||||
// Be careful, we are not a WorkflowScheduleTransition anymore!
|
||||
$this->entityType = 'WorkflowTransition';
|
||||
$this->setUp();
|
||||
|
||||
return parent::save(); // <--- exit !!
|
||||
}
|
||||
|
||||
// Since we do not have an entity_id here, we cannot use entity_delete.
|
||||
// @todo: Add an 'entity id' to WorkflowScheduledTransition entity class.
|
||||
// $result = parent::save();
|
||||
|
||||
// Avoid duplicate entries.
|
||||
$clone = clone $this;
|
||||
$clone->delete();
|
||||
// Save (insert or update) a record to the database based upon the schema.
|
||||
drupal_write_record('workflow_scheduled_transition', $this);
|
||||
|
||||
// Create user message.
|
||||
if ($state = $this->getNewState()) {
|
||||
$entity_type = $this->entity_type;
|
||||
$entity = $this->getEntity();
|
||||
$message = '%entity_title scheduled for state change to %state_name on %scheduled_date';
|
||||
$args = array(
|
||||
'@entity_type' => $entity_type,
|
||||
'%entity_title' => entity_label($entity_type, $entity),
|
||||
'%state_name' => entity_label('WorkflowState', $state),
|
||||
'%scheduled_date' => format_date($this->scheduled),
|
||||
);
|
||||
$uri = entity_uri($entity_type, $entity);
|
||||
watchdog('workflow', $message, $args, WATCHDOG_NOTICE, l('view', $uri['path'] . '/workflow'));
|
||||
drupal_set_message(t($message, $args));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a node, delete transitions for it.
|
||||
*
|
||||
* deprecated: workflow_delete_workflow_scheduled_transition_by_nid() --> WorkflowScheduledTransition::delete()
|
||||
*/
|
||||
public function delete() {
|
||||
// Support translated Workflow Field workflows by including the language.
|
||||
db_delete($this->entityInfo['base table'])
|
||||
->condition('entity_type', $this->entity_type)
|
||||
->condition('nid', $this->entity_id)
|
||||
->condition('field_name', $this->field_name)
|
||||
->condition('language', $this->language)
|
||||
->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* Property functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* If a scheduled transition has no comment, a default comment is added before executing it.
|
||||
*/
|
||||
public function addDefaultComment() {
|
||||
$this->comment = t('Scheduled by user @uid.', array('@uid' => $this->uid));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTimestamp() {
|
||||
return $this->scheduled;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,607 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains workflow\includes\Entity\WorkflowState.
|
||||
* Contains workflow\includes\Entity\WorkflowStateController.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class WorkflowState
|
||||
*/
|
||||
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->getName()) {
|
||||
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: can be removed in D8, since $user is in.
|
||||
);
|
||||
// @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 later.
|
||||
}
|
||||
}
|
||||
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 later.
|
||||
}
|
||||
|
||||
// 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 later.
|
||||
}
|
||||
}
|
||||
|
||||
// Properly fix the labels.
|
||||
// Translate, convert '&', make secure.
|
||||
foreach($options as $key => $label) {
|
||||
$options[$key] = html_entity_decode(check_plain(t($label)));
|
||||
}
|
||||
|
||||
// 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, '=')
|
||||
// ->entityCondition('bundle', 'article')
|
||||
// ->addMetaData('account', user_load(1)) // Run the query as user 1.
|
||||
->count(); // We only need the count.
|
||||
|
||||
$result = $query->execute();
|
||||
$count += $result;
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mimics Entity API functions.
|
||||
*/
|
||||
protected function defaultLabel() {
|
||||
return $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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class WorkflowStateController extends EntityAPIController {
|
||||
|
||||
public function save($entity, DatabaseTransaction $transaction = NULL) {
|
||||
// Create the machine_name.
|
||||
if (empty($entity->name)) {
|
||||
if ($label = $entity->state) {
|
||||
$entity->name = str_replace(' ', '_', strtolower($label));
|
||||
}
|
||||
else {
|
||||
$entity->name = 'state_' . $entity->sid;
|
||||
}
|
||||
}
|
||||
|
||||
$return = parent::save($entity, $transaction);
|
||||
if ($return) {
|
||||
$workflow = $entity->getWorkflow();
|
||||
// Maintain the new object in the workflow.
|
||||
$workflow->states[$entity->sid] = $entity;
|
||||
}
|
||||
|
||||
// Reset the cache for the affected workflow.
|
||||
workflow_reset_cache($entity->wid);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
public function delete($ids, DatabaseTransaction $transaction = NULL) {
|
||||
// @todo: replace with parent.
|
||||
foreach ($ids as $id) {
|
||||
if ($state = workflow_state_load($id)) {
|
||||
$wid = $state->wid;
|
||||
db_delete('workflow_states')
|
||||
->condition('sid', $state->sid)
|
||||
->execute();
|
||||
|
||||
// Reset the cache for the affected workflow.
|
||||
workflow_reset_cache($wid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,737 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains workflow\includes\Entity\WorkflowTransition.
|
||||
* Contains workflow\includes\Entity\WorkflowTransitionController.
|
||||
*
|
||||
* Implements (scheduled/executed) state transitions on entities.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements an actual Transition.
|
||||
*
|
||||
* If a transition is executed, the new state is saved in the Field or {workflow_node}.
|
||||
* If a transition is saved, it is saved in table {workflow_history_node}
|
||||
*/
|
||||
class WorkflowTransition extends Entity {
|
||||
// Field data.
|
||||
public $entity_type;
|
||||
public $field_name = '';
|
||||
public $language = LANGUAGE_NONE;
|
||||
public $delta = 0;
|
||||
// Entity data.
|
||||
public $revision_id;
|
||||
public $entity_id; // Use WorkflowTransition->getEntity() to fetch this.
|
||||
public $nid; // @todo D8: remove $nid, use $entity_id. (requires conversion of Views displays.)
|
||||
// Transition data.
|
||||
// public $hid = 0;
|
||||
public $wid = 0;
|
||||
public $old_sid = 0;
|
||||
public $new_sid = 0;
|
||||
public $sid = 0; // @todo D8: remove $sid, use $new_sid. (requires conversion of Views displays.)
|
||||
public $uid = 0; // Use WorkflowTransition->getUser() to fetch this.
|
||||
public $stamp;
|
||||
public $comment = '';
|
||||
// Cached data, from $this->entity_id and $this->uid.
|
||||
protected $entity = NULL; // Use WorkflowTransition->getEntity() to fetch this.
|
||||
protected $user = NULL; // Use WorkflowTransition->getUser() to fetch this.
|
||||
// Extra data.
|
||||
protected $is_scheduled = NULL;
|
||||
protected $is_executed = NULL;
|
||||
protected $force = NULL;
|
||||
|
||||
/**
|
||||
* Entity class functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates a new entity.
|
||||
*
|
||||
* @param array $values
|
||||
* The initial values.
|
||||
* @param string $entityType
|
||||
* The entity type of this Entity subclass.
|
||||
*
|
||||
* @see entity_create()
|
||||
*
|
||||
* No arguments passed, when loading from DB.
|
||||
* All arguments must be passed, when creating an object programmatically.
|
||||
* One argument $entity may be passed, only to directly call delete() afterwards.
|
||||
*/
|
||||
public function __construct(array $values = array(), $entityType = 'WorkflowTransition') {
|
||||
// Please be aware that $entity_type and $entityType are different things!
|
||||
parent::__construct($values, $entityType);
|
||||
|
||||
$this->hid = isset($this->hid) ? $this->hid : 0;
|
||||
// This transition is not scheduled
|
||||
$this->is_scheduled = FALSE;
|
||||
// This transition is not executed, if it has no hid, yet, upon load.
|
||||
$this->is_executed = ($this->hid > 0);
|
||||
|
||||
// Fill the 'new' fields correctly. @todo D8: rename these fields in db table.
|
||||
$this->entity_id = $this->nid;
|
||||
$this->new_sid = $this->sid;
|
||||
// Initialize wid, if not set.
|
||||
if ($this->old_sid && !$this->wid) {
|
||||
$this->getWorkflow();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for __construct. Used for all children of WorkflowTransition (aka WorkflowScheduledTransition)
|
||||
*
|
||||
* @param $entity_type
|
||||
* @param $entity
|
||||
* @param $field_name
|
||||
* @param $old_sid
|
||||
* @param $new_sid
|
||||
* @param null $uid
|
||||
* @param int $stamp
|
||||
* @param string $comment
|
||||
*/
|
||||
public function setValues($entity_type, $entity, $field_name, $old_sid, $new_sid, $uid = NULL, $stamp = REQUEST_TIME, $comment = '') {
|
||||
// Normally, the values are passed in an array, and set in parent::__construct, but we do it ourselves.
|
||||
// (But there is no objection to do it there.)
|
||||
|
||||
global $user;
|
||||
|
||||
$this->entity_type = (!$entity_type) ? $this->entity_type : $entity_type;
|
||||
$this->field_name = (!$field_name) ? $this->field_name : $field_name;
|
||||
$uid = ($uid === NULL) ? $user->uid : $uid;
|
||||
|
||||
// If constructor is called with new() and arguments.
|
||||
// Load the supplied entity.
|
||||
if ($entity && !$entity_type) {
|
||||
// Not all parameters are passed programmatically.
|
||||
drupal_set_message(t('Wrong call to new Workflow*Transition()'), 'error');
|
||||
}
|
||||
elseif ($entity) {
|
||||
$this->setEntity($entity_type, $entity);
|
||||
}
|
||||
|
||||
if (!$entity && !$old_sid && !$new_sid) {
|
||||
// If constructor is called without arguments, e.g., loading from db.
|
||||
}
|
||||
elseif ($entity && $old_sid) {
|
||||
// Caveat: upon entity_delete, $new_sid is '0'.
|
||||
// If constructor is called with new() and arguments.
|
||||
$this->old_sid = $old_sid;
|
||||
$this->sid = $new_sid;
|
||||
|
||||
$this->uid = $uid;
|
||||
$this->stamp = $stamp;
|
||||
$this->comment = $comment;
|
||||
|
||||
// Set language. Multi-language is not supported for Workflow Node.
|
||||
$this->language = _workflow_metadata_workflow_get_properties($entity, array(), 'langcode', $entity_type, $field_name);
|
||||
}
|
||||
elseif (!$old_sid) {
|
||||
// Not all parameters are passed programmatically.
|
||||
drupal_set_message(
|
||||
t('Wrong call to constructor Workflow*Transition(@old_sid to @new_sid)', array('@old_sid' => $old_sid, '@new_sid' => $new_sid)),
|
||||
'error');
|
||||
}
|
||||
|
||||
// Fill the 'new' fields correctly. @todo D8: rename these fields in db table.
|
||||
$this->entity_id = $this->nid;
|
||||
$this->new_sid = $this->sid;
|
||||
// Initialize wid, if not set.
|
||||
if ($this->old_sid && !$this->wid) {
|
||||
$this->getWorkflow();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function defaultLabel() {
|
||||
// @todo; Should return title of WorkflowConfigTransition. Make it a superclass??
|
||||
return t('Workflow transition !hid', array('!hid' =>3));
|
||||
}
|
||||
|
||||
// protected function defaultUri() {
|
||||
// return array('path' => 'workflow_transition/' . $this->hid);
|
||||
// }
|
||||
|
||||
/**
|
||||
* CRUD functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Given a node, get all transitions for it.
|
||||
*
|
||||
* Since this may return a lot of data, a limit is included to allow for only one result.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* @param array $entity_ids
|
||||
* @param string $field_name
|
||||
* Optional. Can be NULL, if you want to load any field.
|
||||
* @param null $limit
|
||||
* @param string $langcode
|
||||
*
|
||||
* @return array
|
||||
* An array of WorkflowTransitions.
|
||||
*/
|
||||
public static function loadMultiple($entity_type, array $entity_ids, $field_name = '', $limit = NULL, $langcode = '') {
|
||||
$query = db_select('workflow_node_history', 'h');
|
||||
$query->condition('h.entity_type', $entity_type);
|
||||
if ($entity_ids) {
|
||||
$query->condition('h.nid', $entity_ids);
|
||||
}
|
||||
if ($field_name !== NULL) {
|
||||
// If we do not know/care for the field_name, fetch all history.
|
||||
// E.g., in workflow.tokens.
|
||||
$query->condition('h.field_name', $field_name);
|
||||
}
|
||||
// Add selection on language.
|
||||
// Workflow Node: only has 'und'.
|
||||
// Workflow Field: untranslated field have 'und'.
|
||||
// Workflow Field: translated fields may be specified.
|
||||
if ($langcode) {
|
||||
$query->condition('h.language', $langcode);
|
||||
}
|
||||
|
||||
$query->fields('h');
|
||||
// The timestamp is only granular to the second; on a busy site, we need the id.
|
||||
// $query->orderBy('h.stamp', 'DESC');
|
||||
$query->orderBy('h.hid', 'DESC');
|
||||
if ($limit) {
|
||||
$query->range(0, $limit);
|
||||
}
|
||||
$result = $query->execute()->fetchAll(PDO::FETCH_CLASS, 'WorkflowTransition');
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Property functions.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Verifies if the given transition is allowed.
|
||||
*
|
||||
* - In settings;
|
||||
* - In permissions;
|
||||
* - By permission hooks, implemented by other modules.
|
||||
*
|
||||
* @param $roles
|
||||
* @param $user
|
||||
* @param $force
|
||||
*
|
||||
* @return bool TRUE if OK, else FALSE.
|
||||
* TRUE if OK, else FALSE.
|
||||
*
|
||||
* Having both $roles AND $user seems redundant, but $roles have been
|
||||
* tampered with, even though they belong to the $user.
|
||||
* @see WorkflowConfigTransition::isAllowed()
|
||||
*/
|
||||
protected function isAllowed($roles, $user, $force) {
|
||||
if ($force || ($user->uid == 1)) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Check allow-ability of state change if user is not superuser (might be cron).
|
||||
// Get the WorkflowConfigTransition.
|
||||
// @todo: some day, WorkflowConfigTransition can be a parent of WorkflowTransition.
|
||||
$workflow = $this->getWorkflow();
|
||||
$config_transitions = $workflow->getTransitionsBySidTargetSid($this->old_sid, $this->new_sid);
|
||||
$config_transition = reset($config_transitions);
|
||||
if (!$config_transition || !$config_transition->isAllowed($roles)) {
|
||||
$t_args = array(
|
||||
'%old_sid' => $this->old_sid,
|
||||
'%new_sid' => $this->new_sid,
|
||||
);
|
||||
watchdog('workflow', 'Attempt to go to nonexistent transition (from %old_sid to %new_sid)', $t_args, WATCHDOG_ERROR);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a transition (change state of a node).
|
||||
*
|
||||
* @param bool $force
|
||||
* If set to TRUE, workflow permissions will be ignored.
|
||||
*
|
||||
* @return int
|
||||
* New state ID. If execution failed, old state ID is returned,
|
||||
*
|
||||
* deprecated workflow_execute_transition() --> WorkflowTransition::execute().
|
||||
*/
|
||||
public function execute($force = FALSE) {
|
||||
$user = $this->getUser();
|
||||
$old_sid = $this->old_sid;
|
||||
$new_sid = $this->new_sid;
|
||||
|
||||
// Load the entity, if not already loaded.
|
||||
// This also sets the (empty) $revision_id in Scheduled Transitions.
|
||||
$entity = $this->getEntity();
|
||||
// Only after getEntity(), the following are surely set.
|
||||
$entity_type = $this->entity_type;
|
||||
$entity_id = $this->entity_id;
|
||||
$field_name = $this->field_name;
|
||||
|
||||
// Make sure $force is set in the transition, too.
|
||||
if ($force) {
|
||||
$this->force($force);
|
||||
}
|
||||
$force = $this->isForced();
|
||||
|
||||
// Prepare an array of arguments for error messages.
|
||||
$args = array(
|
||||
'%user' => isset($user->name) ? $user->name : '',
|
||||
'%old' => $old_sid,
|
||||
'%new' => $new_sid,
|
||||
);
|
||||
|
||||
if (!$this->getOldState()) {
|
||||
drupal_set_message($message = t('You tried to set a Workflow State, but
|
||||
the entity is not relevant. Please contact your system administrator.'),
|
||||
'error');
|
||||
$message = 'Setting a non-relevant Entity from state %old to %new';
|
||||
$uri = entity_uri($entity_type, $entity);
|
||||
watchdog('workflow', $message, $args, WATCHDOG_ERROR, l('view', $uri['path']));
|
||||
return $old_sid;
|
||||
}
|
||||
|
||||
// Check if the state has changed.
|
||||
$state_changed = ($old_sid != $new_sid);
|
||||
|
||||
// If so, check the permissions.
|
||||
if ($state_changed) {
|
||||
// State has changed. Do some checks upfront.
|
||||
|
||||
if (!$force) {
|
||||
// Make sure this transition is allowed by workflow module Admin UI.
|
||||
$roles = array_keys($user->roles);
|
||||
$roles = array_merge(array(WORKFLOW_ROLE_AUTHOR_RID), $roles);
|
||||
if (!$this->isAllowed($roles, $user, $force)) {
|
||||
watchdog('workflow', 'User %user not allowed to go from state %old to %new', $args, WATCHDOG_NOTICE);
|
||||
// If incorrect, quit.
|
||||
return $old_sid;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$force) {
|
||||
// Make sure this transition is allowed by custom module.
|
||||
// @todo D8: remove, or replace by 'transition pre'. See WorkflowState::getOptions().
|
||||
// @todo D8: replace all parameters that are included in $transition.
|
||||
$permitted = module_invoke_all('workflow', 'transition permitted', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name, $this, $user);
|
||||
// Stop if a module says so.
|
||||
if (in_array(FALSE, $permitted, TRUE)) {
|
||||
watchdog('workflow', 'Transition vetoed by module.');
|
||||
return $old_sid;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure this transition is valid and allowed for the current user.
|
||||
// Invoke a callback indicating a transition is about to occur.
|
||||
// Modules may veto the transition by returning FALSE.
|
||||
// (Even if $force is TRUE, but they shouldn't do that.)
|
||||
$permitted = module_invoke_all('workflow', 'transition pre', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name, $this);
|
||||
// Stop if a module says so.
|
||||
if (in_array(FALSE, $permitted, TRUE)) {
|
||||
watchdog('workflow', 'Transition vetoed by module.');
|
||||
return $old_sid;
|
||||
}
|
||||
|
||||
}
|
||||
elseif ($this->comment) {
|
||||
// No need to ask permission for adding comments.
|
||||
// Since you should not add actions to a 'transition pre' event, there is
|
||||
// no need to invoke the event.
|
||||
}
|
||||
else {
|
||||
// There is no state change, and no comment.
|
||||
// We may need to clean up something.
|
||||
}
|
||||
|
||||
if ($state_changed || $this->comment) {
|
||||
// Store the transition, so it can be easily fetched later on.
|
||||
// Store in an array, to prepare for multiple workflow_fields per entity.
|
||||
// This is a.o. used in hook_entity_update to trigger 'transition post'.
|
||||
// Only add the Transition once! or you will encounter endless loops in
|
||||
// hook_entity_update() in workflow_actions_entity_update et all.
|
||||
if (!isset($entity->workflow_transitions[$field_name])) {
|
||||
$entity->workflow_transitions[$field_name] = &$this;
|
||||
}
|
||||
|
||||
// The transition is allowed. Let other modules modify the comment.
|
||||
// @todo D8: remove all but last items from $context.
|
||||
$context = array(
|
||||
'node' => $entity,
|
||||
'sid' => $new_sid,
|
||||
'old_sid' => $old_sid,
|
||||
'uid' => $user->uid,
|
||||
'transition' => $this,
|
||||
);
|
||||
drupal_alter('workflow_comment', $this->comment, $context);
|
||||
}
|
||||
|
||||
// Now, change the database.
|
||||
|
||||
// Log the new state in {workflow_node}.
|
||||
if (!$field_name) {
|
||||
if ($state_changed || $this->comment) {
|
||||
// If the node does not have an existing 'workflow' property,
|
||||
// save the $old_sid there, so it can be logged.
|
||||
if (!isset($entity->workflow)) { // This is a workflow_node sid.
|
||||
$entity->workflow = $old_sid; // This is a workflow_node sid.
|
||||
}
|
||||
|
||||
// Change the state for {workflow_node}.
|
||||
// The equivalent for Field API is in WorkflowDefaultWidget::submit.
|
||||
$data = array(
|
||||
'nid' => $entity_id,
|
||||
'sid' => $new_sid,
|
||||
'uid' => (isset($entity->workflow_uid) ? $entity->workflow_uid : $user->uid),
|
||||
'stamp' => REQUEST_TIME,
|
||||
);
|
||||
workflow_update_workflow_node($data);
|
||||
|
||||
$entity->workflow = $new_sid; // This is a workflow_node sid.
|
||||
}
|
||||
}
|
||||
else {
|
||||
// This is a Workflow Field.
|
||||
// Until now, adding code here (instead of in workflow_execute_transition() )
|
||||
// doesn't work, creating an endless loop.
|
||||
// Update 10-dec-2016: the following line, added above, may have resolved that.
|
||||
// if (!isset($entity->workflow_transitions[$field_name]))
|
||||
/*
|
||||
if ($state_changed || $this->comment) {
|
||||
// Do a separate update to update the field (Workflow Field API)
|
||||
// This will call hook_field_update() and WorkflowFieldDefaultWidget::submit().
|
||||
// $entity->{$field_name}[$this->language] = array();
|
||||
// $entity->{$field_name}[$this->language][0]['workflow']['workflow_sid'] = $new_sid;
|
||||
// $entity->{$field_name}[$this->language][0]['workflow']['workflow_comment'] = $this->comment;
|
||||
$entity->{$field_name}[$this->language][0]['transition'] = $this;
|
||||
|
||||
// Save the entity, but not through entity_save(),
|
||||
// since this will check permissions again and trigger rules.
|
||||
// @TODO: replace below by a workflow_field setter callback.
|
||||
// The transition was successfully executed, or else a message was raised.
|
||||
// entity_save($entity_type, $entity);
|
||||
// or
|
||||
// field_attach_update($entity_type, $entity);
|
||||
|
||||
// Reset the entity cache after update.
|
||||
entity_get_controller($entity_type)->resetCache(array($entity_id));
|
||||
|
||||
$new_sid = workflow_node_current_state($entity, $entity_type, $field_name);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
$this->is_executed = TRUE;
|
||||
|
||||
if ($state_changed || $this->comment) {
|
||||
|
||||
// Log the transition in {workflow_node_history}.
|
||||
$this->save();
|
||||
|
||||
// Register state change with watchdog.
|
||||
if ($state_changed) {
|
||||
$workflow = $this->getWorkflow();
|
||||
// Get the workflow_settings, unified for workflow_node and workflow_field.
|
||||
// @todo D8: move settings back to Workflow (like workflownode currently is).
|
||||
// @todo D8: to move settings back, grep for "workflow->options" and "field['settings']".
|
||||
$field = _workflow_info_field($field_name, $workflow);
|
||||
|
||||
if (($new_state = $this->getNewState()) && !empty($field['settings']['watchdog_log'])) {
|
||||
$entity_type_info = entity_get_info($entity_type);
|
||||
$message = ($this->isScheduled()) ? 'Scheduled state change of @type %label to %state_name executed' : 'State of @type %label set to %state_name';
|
||||
$args = array(
|
||||
'@type' => $entity_type_info['label'],
|
||||
'%label' => entity_label($entity_type, $entity),
|
||||
'%state_name' => check_plain(t($new_state->label())),
|
||||
);
|
||||
$uri = entity_uri($entity_type, $entity);
|
||||
watchdog('workflow', $message, $args, WATCHDOG_NOTICE, l('view', $uri['path']));
|
||||
}
|
||||
}
|
||||
|
||||
// Remove any scheduled state transitions.
|
||||
foreach (WorkflowScheduledTransition::load($entity_type, $entity_id, $field_name) as $scheduled_transition) {
|
||||
/* @var $scheduled_transition WorkflowScheduledTransition */
|
||||
$scheduled_transition->delete();
|
||||
}
|
||||
|
||||
// Notify modules that transition has occurred.
|
||||
// Action triggers should take place in response to this callback, not the 'transaction pre'.
|
||||
if (!$field_name) {
|
||||
// Now that workflow data is saved, reset stuff to avoid problems
|
||||
// when Rules etc want to resave the data.
|
||||
// Remember, this is only for nodes, and node_save() is not necessarily performed.
|
||||
unset($entity->workflow_comment);
|
||||
module_invoke_all('workflow', 'transition post', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name, $this);
|
||||
entity_get_controller('node')->resetCache(array($entity->nid)); // from entity_load(), node_save();
|
||||
}
|
||||
else {
|
||||
// module_invoke_all('workflow', 'transition post', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name, $this);
|
||||
// We have a problem here with Rules, Trigger, etc. when invoking
|
||||
// 'transition post': the entity has not been saved, yet. we are still
|
||||
// IN the transition, not AFTER. Alternatives:
|
||||
// 1. Save the field here explicitly, using field_attach_save;
|
||||
// 2. Move the invoke to another place: hook_entity_insert(), hook_entity_update();
|
||||
// 3. Rely on the entity hooks. This works for Rules, not for Trigger.
|
||||
// --> We choose option 2:
|
||||
// - First, $entity->workflow_transitions[] is set for easy re-fetching.
|
||||
// - Then, post_execute() is invoked via workflowfield_entity_insert(), _update().
|
||||
}
|
||||
}
|
||||
|
||||
return $new_sid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes 'transition post'.
|
||||
*
|
||||
* Add the possibility to invoke the hook from elsewhere.
|
||||
*/
|
||||
public function post_execute($force = FALSE) {
|
||||
$old_sid = $this->old_sid;
|
||||
$new_sid = $this->new_sid;
|
||||
$entity = $this->getEntity(); // Entity may not be loaded, yet.
|
||||
$entity_type = $this->entity_type;
|
||||
// $entity_id = $this->entity_id;
|
||||
$field_name = $this->field_name;
|
||||
|
||||
$state_changed = ($old_sid != $new_sid);
|
||||
if ($state_changed || $this->comment) {
|
||||
module_invoke_all('workflow', 'transition post', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name, $this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the Transitions $workflow.
|
||||
*
|
||||
* @return Workflow|NULL
|
||||
* The workflow for this Transition.
|
||||
*/
|
||||
public function getWorkflow() {
|
||||
$workflow = NULL;
|
||||
if (!$this->wid) {
|
||||
$state = workflow_state_load_single($this->new_sid ? $this->new_sid : $this->old_sid);
|
||||
$this->wid = (int) $state->wid;
|
||||
}
|
||||
if ($this->wid) {
|
||||
$workflow = workflow_load($this->wid);
|
||||
}
|
||||
return $workflow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Transitions $entity.
|
||||
*
|
||||
* @return object
|
||||
* The entity, that is added to the Transition.
|
||||
*/
|
||||
public function getEntity() {
|
||||
if (empty($this->entity) && $this->entity_type) {
|
||||
$entity_type = $this->entity_type;
|
||||
$entity_id = $this->entity_id;
|
||||
$entity = entity_load_single($entity_type, $entity_id);
|
||||
|
||||
// Set the entity cache.
|
||||
$this->entity = $entity;
|
||||
|
||||
// Make sure the vid of Entity and Transition are equal.
|
||||
// Especially for Scheduled Transition, that do not have this set, yet,
|
||||
// or may have an outdated revision ID.
|
||||
$info = entity_get_info($entity_type);
|
||||
$revision_key = $info['entity keys']['revision'];
|
||||
$this->revision_id = (isset($entity->{$revision_key})) ? $entity->{$revision_key} : NULL;
|
||||
}
|
||||
|
||||
return $this->entity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Transitions $entity.
|
||||
*
|
||||
* @param string $entity_type
|
||||
* The entity type of the entity.
|
||||
* @param mixed $entity
|
||||
* The Entity ID or the Entity object, to add to the Transition.
|
||||
*
|
||||
* @return object $entity
|
||||
* The Entity, that is added to the Transition.
|
||||
*/
|
||||
public function setEntity($entity_type, $entity) {
|
||||
if (!is_object($entity)) {
|
||||
$entity_id = $entity;
|
||||
// Use node API or Entity API to load the object first.
|
||||
$entity = entity_load_single($entity_type, $entity_id);
|
||||
}
|
||||
$this->entity = $entity;
|
||||
$this->entity_type = $entity_type;
|
||||
list($this->entity_id, $this->revision_id,) = entity_extract_ids($entity_type, $entity);
|
||||
|
||||
// For backwards compatibility, set nid.
|
||||
$this->nid = $this->entity_id;
|
||||
|
||||
return $this->entity;
|
||||
}
|
||||
|
||||
public function getUser() {
|
||||
if (!isset($this->user) || ($this->user->uid != $this->uid)) {
|
||||
$this->user = user_load($this->uid);
|
||||
}
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFieldName() {
|
||||
return $this->field_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Functions, common to the WorkflowTransitions.
|
||||
*/
|
||||
public function getOldState() {
|
||||
return workflow_state_load_single($this->old_sid);
|
||||
}
|
||||
public function getNewState() {
|
||||
return workflow_state_load_single($this->new_sid);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getComment() {
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the time on which the transitions was or will be executed.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getTimestamp() {
|
||||
return $this->stamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getTimestampFormatted() {
|
||||
$timestamp = $this->stamp;
|
||||
return format_date($timestamp);;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setTimestamp($value) {
|
||||
$this->stamp = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this is a Scheduled Transition.
|
||||
*/
|
||||
public function isScheduled() {
|
||||
return $this->is_scheduled;
|
||||
}
|
||||
public function schedule($schedule = TRUE) {
|
||||
return $this->is_scheduled = $schedule;
|
||||
}
|
||||
|
||||
public function isExecuted() {
|
||||
return $this->is_executed;
|
||||
}
|
||||
|
||||
/**
|
||||
* A transition may be forced skipping checks.
|
||||
*/
|
||||
public function isForced() {
|
||||
return (bool) $this->force;
|
||||
}
|
||||
public function force($force = TRUE) {
|
||||
return $this->force = $force;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper debugging function to easily show the contents fo a transition.
|
||||
*/
|
||||
public function dpm($function = '') {
|
||||
$transition = $this;
|
||||
$entity = $transition->getEntity();
|
||||
$entity_type = $transition->entity_type;
|
||||
list($entity_id, , $entity_bundle) = ($entity) ? entity_extract_ids($entity_type, $entity) : array('', '', '');
|
||||
$time = $transition->getTimestampFormatted();
|
||||
// Do this extensive $user_name lines, for some troubles with Action.
|
||||
$user = $transition->getUser();
|
||||
$user_name = ($user) ? $user->name : 'unknown username';
|
||||
$t_string = get_class($this) . ' ' . (isset($this->hid) ? $this->hid : '') . ' ' . ($function ? ("in function '$function'") : '');
|
||||
$output[] = 'Entity = ' . ((!$entity) ? 'NULL' : ($entity_type . '/' . $entity_bundle . '/' . $entity_id));
|
||||
$output[] = 'Field = ' . $transition->getFieldName();
|
||||
$output[] = 'From/To = ' . $transition->old_sid . ' > ' . $transition->new_sid . ' @ ' . $time;
|
||||
$output[] = 'Comment = ' . $user_name . ' says: ' . $transition->getComment();
|
||||
$output[] = 'Forced = ' . ($transition->isForced() ? 'yes' : 'no');
|
||||
if (function_exists('dpm')) { dpm($output, $t_string); }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements a controller class for WorkflowTransition.
|
||||
*
|
||||
* The 'true' controller class is 'Workflow'.
|
||||
*/
|
||||
class WorkflowTransitionController extends EntityAPIController {
|
||||
|
||||
/**
|
||||
* Overrides DrupalDefaultEntityController::cacheGet().
|
||||
*
|
||||
* Override default function, due to core issue #1572466.
|
||||
*/
|
||||
protected function cacheGet($ids, $conditions = array()) {
|
||||
// Load any available entities from the internal cache.
|
||||
if ($ids === FALSE && !$conditions) {
|
||||
return $this->entityCache;
|
||||
}
|
||||
return parent::cacheGet($ids, $conditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert (no update) a transition.
|
||||
*
|
||||
* deprecated workflow_insert_workflow_node_history() --> WorkflowTransition::save()
|
||||
*/
|
||||
public function save($entity, DatabaseTransaction $transaction = NULL) {
|
||||
// Check for no transition.
|
||||
if ($entity->old_sid == $entity->new_sid) {
|
||||
if (!$entity->comment) {
|
||||
// Write comment into history though.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($entity->hid)) {
|
||||
// Insert the transition. Make sure it hasn't already been inserted.
|
||||
$last_history = workflow_transition_load_single($entity->entity_type, $entity->entity_id, $entity->field_name, $entity->language);
|
||||
if ($last_history &&
|
||||
$last_history->stamp == REQUEST_TIME &&
|
||||
$last_history->new_sid == $entity->new_sid) {
|
||||
return;
|
||||
}
|
||||
else {
|
||||
unset($entity->hid);
|
||||
$entity->stamp = isset($entity->stamp) ? $entity->stamp : REQUEST_TIME;
|
||||
|
||||
return parent::save($entity, $transaction);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Update the transition.
|
||||
return parent::save($entity, $transaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains a stub class.
|
||||
* The functionality is removed but apparently caches aren't refreshed
|
||||
* properly during upgrade.
|
||||
* @see https://www.drupal.org/node/2620530
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
class WorkflowTransitionController {
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains workflow\includes\Field\WorkflowD7Base.
|
||||
*/
|
||||
|
||||
/*
|
||||
* A Retrofit/Stub class, that contains the most basic functions of the D8 WidgetBase class.
|
||||
* It serves as a superclass to containe the $field and $instance array structures for the Field Type and the Widget.
|
||||
* @todo D8: Remove this class.
|
||||
*/
|
||||
abstract class WorkflowD7Base {
|
||||
// Properties for Field and Widget.
|
||||
protected $field = array();
|
||||
protected $instance = array();
|
||||
// Properties for Field.
|
||||
protected $entity = NULL;
|
||||
protected $entity_type = '';
|
||||
|
||||
/**
|
||||
* Constructor, stub for D8 WidgetBase.
|
||||
*/
|
||||
public function __construct(array $field, array $instance, $entity_type = '', $entity = NULL) {
|
||||
if (!empty($entity) && !is_object($entity)) {
|
||||
throw new Exception('Entity should be an object.');
|
||||
}
|
||||
|
||||
// Properties for Widget and Field.
|
||||
$this->field = $field;
|
||||
$this->instance = $instance;
|
||||
// Properties for FieldItem.
|
||||
$this->entity = $entity;
|
||||
$this->entity_type = $entity_type;
|
||||
}
|
||||
|
||||
public function getField() {
|
||||
return $this->field;
|
||||
}
|
||||
|
||||
public function getInstance() {
|
||||
return $this->instance;
|
||||
}
|
||||
|
||||
public function delete() {
|
||||
}
|
||||
|
||||
protected function getSettings() {
|
||||
$settings = isset($this->instance['widget']['settings']) ? $this->instance['widget']['settings'] : array();
|
||||
$field_info = self::settings();
|
||||
return $settings += $field_info['workflow']['settings'];
|
||||
}
|
||||
|
||||
protected function getSetting($key) {
|
||||
if (isset($this->instance['widget']['settings'][$key])) {
|
||||
return $this->instance['widget']['settings'][$key];
|
||||
}
|
||||
else {
|
||||
$field_info = $this->settings();
|
||||
return $field_info['workflow']['settings'][$key];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains workflow\includes\Field\WorkflowDefaultWidget.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'workflow_default' widget.
|
||||
*/
|
||||
class WorkflowDefaultWidget extends WorkflowD7Base { // D8: extends WidgetBase {
|
||||
|
||||
/**
|
||||
* Returns the settings.
|
||||
*
|
||||
* @todo d8: Replace by the 'annotations' in D8 (See comments above this class).
|
||||
*/
|
||||
public static function settings() {
|
||||
return array(
|
||||
'workflow_default' => array(
|
||||
'label' => t('Workflow'),
|
||||
'field types' => array('workflow'),
|
||||
'settings' => array(
|
||||
'name_as_title' => 1,
|
||||
'fieldset' => 0,
|
||||
'comment' => 1,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_widget_settings_form() --> WidgetInterface::settingsForm().
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* The Widget Instance has no settings. To have a uniform UX, all settings are done on the Field level.
|
||||
*/
|
||||
public function settingsForm(array $form, array &$form_state, $has_data) {
|
||||
$element = array();
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_widget_form --> WidgetInterface::formElement().
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* Be careful: Widget may be shown in very different places. Test carefully!!
|
||||
* - On a entity add/edit page
|
||||
* - On a entity preview page
|
||||
* - On a entity view page
|
||||
* - On a entity 'workflow history' tab
|
||||
* - On a comment display, in the comment history
|
||||
* - On a comment form, below the comment history
|
||||
*
|
||||
* @todo D8: change "array $items" to "FieldInterface $items"
|
||||
*/
|
||||
public function formElement(array $items, $delta, array $element, array &$form, array &$form_state) {
|
||||
$field = $this->field;
|
||||
$instance = $this->instance;
|
||||
$entity = $this->entity;
|
||||
$entity_type = $this->entity_type;
|
||||
|
||||
// Add the element. Do not use drupal_get_form, or you will have a form in a form.
|
||||
workflow_transition_form($form, $form_state, $field, $instance, $entity_type, $entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements workflow_transition() -> WorkflowDefaultWidget::submit().
|
||||
*
|
||||
* Overrides submit(array $form, array &$form_state).
|
||||
* Contains 2 extra parameters for D7
|
||||
*
|
||||
* @param array $form
|
||||
* @param array $form_state
|
||||
* @param array $items
|
||||
* The value of the field.
|
||||
* @param bool $force
|
||||
* TRUE if all access must be overridden, e.g., for Rules.
|
||||
*
|
||||
* @return int
|
||||
* If update succeeded, the new State Id. Else, the old Id is returned.
|
||||
*
|
||||
* This is called from function _workflowfield_form_submit($form, &$form_state)
|
||||
* It is a replacement of function workflow_transition($node, $new_sid, $force, $field)
|
||||
* It performs the following actions;
|
||||
* - save a scheduled action
|
||||
* - update history
|
||||
* - restore the normal $items for the field.
|
||||
* @todo: remove update of {node_form} table. (separate task, because it has features, too)
|
||||
*/
|
||||
public function submit(array $form, array &$form_state, array &$items, $force = FALSE) {
|
||||
return workflow_transition_form_submit($form, $form_state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_widget_error --> WidgetInterface::errorElement().
|
||||
*/
|
||||
// public function errorElement(array $element, ConstraintViolationInterface $violation, array $form, array &$form_state) {
|
||||
// }
|
||||
// public function settingsSummary() {
|
||||
// }
|
||||
// public function massageFormValues(array $values, array $form, array &$form_state) {
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,329 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains workflow\includes\Field\WorkflowItem.
|
||||
* @see https://drupal.org/node/2064123 for 'Field Type Plugin' change record D7->D8.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Plugin implementation of the 'workflow' field type.
|
||||
*
|
||||
* @FieldType(
|
||||
* id = "workflow",
|
||||
* label = @Translation("Workflow"),
|
||||
* 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."),
|
||||
* default_widget = "options_select",
|
||||
* default_formatter = "list_formatter",
|
||||
* property_type' = WORKFLOWFIELD_PROPERTY_TYPE,
|
||||
* )
|
||||
*/
|
||||
class WorkflowItem extends WorkflowD7Base {// D8: extends ConfigFieldItemBase implements PrepareCacheInterface {
|
||||
/**
|
||||
* Function, that gets replaced by the 'annotations' in D8. (@see comments above this class)
|
||||
*/
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'workflow' => array(
|
||||
'label' => t('Workflow'),
|
||||
'description' => t("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."),
|
||||
'settings' => array(
|
||||
'allowed_values_function' => 'workflowfield_allowed_values', // For the list.module formatter
|
||||
// 'allowed_values_function' => 'WorkflowItem::getAllowedValues', // For the list.module formatter.
|
||||
'wid' => '',
|
||||
// 'history' => 1,
|
||||
// 'schedule' => 0,
|
||||
// 'comment' => 0,
|
||||
'widget' => array(
|
||||
'options' => 'select',
|
||||
'name_as_title' => 1,
|
||||
'fieldset' => 0,
|
||||
'hide' => 0,
|
||||
'schedule' => 1,
|
||||
'schedule_timezone' => 1,
|
||||
'comment' => 1,
|
||||
),
|
||||
'watchdog_log' => 1,
|
||||
'history' => array(
|
||||
'history_tab_show' => 1,
|
||||
'roles' => array(),
|
||||
),
|
||||
),
|
||||
'instance_settings' => array(),
|
||||
'default_widget' => 'workflow',
|
||||
'default_formatter' => 'list_default',
|
||||
// Properties are introduced in Entity API and used for Rules integration.
|
||||
'property_type' => WORKFLOWFIELD_PROPERTY_TYPE,
|
||||
'property_callbacks' => array('workflowfield_property_info_callback'),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_settings_form() -> ConfigFieldItemInterface::settingsForm().
|
||||
*
|
||||
* @param array $form
|
||||
* @param array $form_state
|
||||
* @param $has_data
|
||||
*
|
||||
* @return array $element
|
||||
* The newly constructed element.
|
||||
*/
|
||||
public function settingsForm(array $form, array &$form_state, $has_data) {
|
||||
$field_info = self::getInfo();
|
||||
$settings = $this->field['settings'];
|
||||
$settings += $field_info['workflow']['settings'];
|
||||
$settings['widget'] += $field_info['workflow']['settings']['widget'];
|
||||
|
||||
// Create list of all Workflow types. Include an initial empty value.
|
||||
// Validate each workflow, and generate a message if not complete.
|
||||
/* @var $workflow Workflow */
|
||||
$workflows = array();
|
||||
$workflows[''] = t('- Select a value -');
|
||||
foreach ($workflows += workflow_get_workflow_names() as $wid => $label) {
|
||||
$workflow = workflow_load_single($wid);
|
||||
if ($wid && !$workflow->isValid()) {
|
||||
unset($workflows[$wid]);
|
||||
}
|
||||
}
|
||||
|
||||
// Set message, if no 'validated' workflows exist.
|
||||
if (count($workflows) == 1) {
|
||||
drupal_set_message(
|
||||
t('You must create at least one workflow before content can be
|
||||
assigned to a workflow.')
|
||||
);
|
||||
}
|
||||
|
||||
// The allowed_values_functions is used in the formatter from list.module.
|
||||
$element['allowed_values_function'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $settings['allowed_values_function'], // = 'workflowfield_allowed_values',
|
||||
);
|
||||
|
||||
// $field['settings']['wid'] can be numeric or named, or empty.
|
||||
$wid = isset($settings['wid']) ? $settings['wid'] : '';
|
||||
// Let the user choose between the available workflow types.
|
||||
$element['wid'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Workflow type'),
|
||||
'#options' => $workflows,
|
||||
'#default_value' => $wid,
|
||||
'#required' => TRUE,
|
||||
'#disabled' => $has_data,
|
||||
'#description' => t('Choose the Workflow type. Maintain workflows !url.', array('!url' => l(t('here'), 'admin/config/workflow/workflow'))),
|
||||
);
|
||||
|
||||
// Inform the user of possible states.
|
||||
// If no Workflow type is selected yet, do not show anything.
|
||||
if ($wid) {
|
||||
// Get a string representation to show all options.
|
||||
$allowed_values = workflow_state_load_multiple($wid);
|
||||
$allowed_values_string = $this->_allowed_values_string($wid);
|
||||
|
||||
$element['allowed_values_string'] = array(
|
||||
'#type' => 'textarea',
|
||||
'#title' => t('Allowed values for the selected Workflow type'),
|
||||
'#default_value' => $allowed_values_string,
|
||||
'#rows' => count($allowed_values),
|
||||
'#access' => TRUE, // User can see the data,
|
||||
'#disabled' => TRUE, // .. but cannot change them.
|
||||
);
|
||||
}
|
||||
|
||||
$element['widget'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Workflow widget'),
|
||||
'#description' => t('Set some global properties of the widgets for this
|
||||
workflow. Some can be altered per widget instance.'
|
||||
),
|
||||
);
|
||||
$fieldset_options = array(0 => t('No fieldset'), 1 => t('Collapsible fieldset'), 2 => t('Collapsed fieldset'));
|
||||
$element['widget']['fieldset'] = array(
|
||||
'#type' => 'select',
|
||||
'#options' => $fieldset_options,
|
||||
'#title' => t('Show the form in a fieldset?'),
|
||||
'#default_value' => $settings['widget']['fieldset'],
|
||||
'#description' => t("The Widget can be wrapped in a visible fieldset. You'd
|
||||
do this when you use the widget on a Node Edit page."
|
||||
),
|
||||
);
|
||||
$element['widget']['options'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('How to show the available states'),
|
||||
'#required' => FALSE,
|
||||
'#default_value' => $settings['widget']['options'],
|
||||
// '#multiple' => TRUE / FALSE,
|
||||
'#options' => array(
|
||||
// These options are taken from options.module
|
||||
'select' => 'Select list',
|
||||
'radios' => 'Radio buttons',
|
||||
// This option does not work properly on Comment Add form.
|
||||
'buttons' => 'Action buttons',
|
||||
),
|
||||
'#description' => t("The Widget shows all available states. Decide which
|
||||
is the best way to show them. ('Action buttons' do not work on Comment form.)"
|
||||
),
|
||||
);
|
||||
$element['widget']['hide'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#attributes' => array('class' => array('container-inline')),
|
||||
'#title' => t('Hide the widget on Entity form.'),
|
||||
'#default_value' => $settings['widget']['hide'],
|
||||
'#description' => t(
|
||||
'Using Workflow Field, the widget is always shown when editing an
|
||||
Entity. Set this checkbox in case you only want to change the status
|
||||
on the Workflow History tab or on the Node View. (This checkbox is
|
||||
only needed because Drupal core does not have a "hidden" widget.)'
|
||||
),
|
||||
);
|
||||
$element['widget']['name_as_title'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#attributes' => array('class' => array('container-inline')),
|
||||
'#title' => t('Use the workflow name as the title of the workflow form'),
|
||||
'#default_value' => $settings['widget']['name_as_title'],
|
||||
'#description' => t(
|
||||
'The workflow section of the editing form is in its own fieldset.
|
||||
Checking the box will add the workflow name as the title of workflow
|
||||
section of the editing form.'
|
||||
),
|
||||
);
|
||||
$element['widget']['schedule'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Allow scheduling of workflow transitions.'),
|
||||
'#required' => FALSE,
|
||||
'#default_value' => $settings['widget']['schedule'],
|
||||
'#description' => t(
|
||||
'Workflow transitions may be scheduled to a moment in the future.
|
||||
Soon after the desired moment, the transition is executed by Cron.
|
||||
This may be hidden by settings in widgets, formatters or permissions.'
|
||||
),
|
||||
);
|
||||
$element['widget']['schedule_timezone'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Show a timezone when scheduling a transition.'),
|
||||
'#required' => FALSE,
|
||||
'#default_value' => $settings['widget']['schedule_timezone'],
|
||||
);
|
||||
$element['widget']['comment'] = array(
|
||||
'#type' => 'select',
|
||||
'#title' => t('Allow adding a comment to workflow transitions'),
|
||||
'#required' => FALSE,
|
||||
'#options' => array(
|
||||
// Use 0/1/2 to stay compatible with previous checkbox.
|
||||
0 => t('hidden'),
|
||||
1 => t('optional'),
|
||||
2 => t('required'),
|
||||
),
|
||||
'#default_value' => $settings['widget']['comment'],
|
||||
'#description' => t('On the Workflow form, a Comment form can be included
|
||||
so that the person making the state change can record reasons for doing
|
||||
so. The comment is then included in the node\'s workflow history. This
|
||||
may be altered by settings in widgets, formatters or permissions.'
|
||||
),
|
||||
);
|
||||
|
||||
$element['watchdog_log'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#attributes' => array('class' => array('container-inline')),
|
||||
'#title' => t('Log informational watchdog messages when a transition is
|
||||
executed (a state value is changed)'),
|
||||
'#default_value' => $settings['watchdog_log'],
|
||||
'#description' => t('Optionally log transition state changes to watchdog.'),
|
||||
);
|
||||
|
||||
$element['history'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('Workflow history'),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => FALSE,
|
||||
);
|
||||
$element['history']['history_tab_show'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Use the workflow history, and show it on a separate tab.'),
|
||||
'#required' => FALSE,
|
||||
'#default_value' => $settings['history']['history_tab_show'],
|
||||
'#description' => t("Every state change is recorded in table
|
||||
{workflow_node_history}. If checked and user has proper permission, a
|
||||
tab 'Workflow' is shown on the entity view page, which gives access to
|
||||
the History of the workflow. If you have multiple workflows per bundle,
|
||||
better disable this feature, and use, clone & adapt the Views display
|
||||
'Workflow history per Entity'."),
|
||||
);
|
||||
$element['history']['roles'] = array(
|
||||
'#type' => 'checkboxes',
|
||||
'#options' => workflow_get_roles(),
|
||||
'#title' => t('Workflow history permissions'),
|
||||
'#default_value' => $settings['history']['roles'],
|
||||
'#description' => t('Select any roles that should have access to the workflow tab on nodes that have a workflow.'),
|
||||
);
|
||||
|
||||
return $element;
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements hook_field_insert() -> FieldItemInterface::insert().
|
||||
*/
|
||||
public function insert() {
|
||||
return $this->update();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper functions for the Field Settings page.
|
||||
*
|
||||
* Generates a string representation of an array of 'allowed values'.
|
||||
* This is a copy from list.module's list_allowed_values_string().
|
||||
* The string format is suitable for edition in a textarea.
|
||||
*
|
||||
* @param int $wid
|
||||
* The Workflow Id.
|
||||
*
|
||||
* @return string
|
||||
* The string representation of the $values array:
|
||||
* - Values are separated by a carriage return.
|
||||
* - Each value is in the format "value|label" or "value".
|
||||
*/
|
||||
protected function _allowed_values_string($wid = 0) {
|
||||
$lines = array();
|
||||
$states = workflow_state_load_multiple($wid);
|
||||
$previous_wid = -1;
|
||||
|
||||
/* @var $state WorkflowState */
|
||||
foreach ($states as $state) {
|
||||
// Only show enabled states.
|
||||
if ($state->isActive()) {
|
||||
// Show a Workflow name between Workflows, if more then 1 in the list.
|
||||
if (($wid == 0) && ($previous_wid <> $state->wid)) {
|
||||
$previous_wid = $state->wid;
|
||||
$lines[] = $state->name . "'s states: ";
|
||||
}
|
||||
$label = check_plain(t($state->label()));
|
||||
$states[$state->sid] = $label;
|
||||
$lines[] = $state->sid . ' | ' . $label;
|
||||
}
|
||||
}
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for list.module formatter.
|
||||
*
|
||||
* Callback function for the list module formatter.
|
||||
*
|
||||
* @see list_allowed_values
|
||||
* "The strings are not safe for output. Keys and values of the array should
|
||||
* "be sanitized through field_filter_xss() before being displayed.
|
||||
*
|
||||
* @return array
|
||||
* The array of allowed values. Keys of the array are the raw stored values
|
||||
* (number or text), values of the array are the display labels.
|
||||
* It contains all possible values, beause the result is cached,
|
||||
* and used for all nodes on a page.
|
||||
*/
|
||||
public function getAllowedValues() {
|
||||
// Get all state names, including inactive states.
|
||||
$options = workflow_get_workflow_state_names(0, $grouped = FALSE, $all = TRUE);
|
||||
return $options;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,752 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \workflow\Form\WorkflowTransitionForm.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Provides a Transition Form to be used in the Workflow Widget.
|
||||
*/
|
||||
class WorkflowTransitionForm { // extends FormBase {
|
||||
|
||||
/**
|
||||
* The Workflow Transition storage.
|
||||
*/
|
||||
protected $field;
|
||||
protected $instance;
|
||||
protected $entity;
|
||||
|
||||
/**
|
||||
* Constructs a WorkflowTransitionForm object.
|
||||
* @param array $field
|
||||
* @param array $instance
|
||||
* @param $entity_type
|
||||
* @param $entity
|
||||
*/
|
||||
public function __construct(array $field, array $instance, $entity_type, $entity) {
|
||||
$this->field = $field; // TODO : needed?
|
||||
$this->instance = $instance; // TODO : needed?
|
||||
$this->entity = $entity; // TODO : needed?
|
||||
$this->entity_type = $entity_type; // TODO : needed?
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function getFormId() {
|
||||
$field = $this->field;
|
||||
// No entity may be set on VBO form.
|
||||
$entity_id = ($this->entity) ? entity_id($this->entity_type, $this->entity) : '';
|
||||
// The field is not set when editing a stand alone Transition.
|
||||
$field_id = isset($field['id']) ? $field['id'] : '';
|
||||
|
||||
return implode('_', array('workflow_transition_form', $this->entity_type, $entity_id, $field_id));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param array $form
|
||||
* @param array $form_state
|
||||
* @param WorkflowTransition
|
||||
* The Transition to be edited, created.
|
||||
*
|
||||
* @return
|
||||
* The enhanced form structure.
|
||||
*/
|
||||
public function buildForm(array $form, array &$form_state) {
|
||||
global $user;
|
||||
|
||||
/* @var $transition WorkflowTransition */
|
||||
$transition = NULL;
|
||||
if (isset($form_state['WorkflowTransition'])) {
|
||||
// If provided, get data from WorkflowTransition.
|
||||
// This happens when calling entity_ui_get_form(), like in the
|
||||
// WorkflowTransition Comment Edit form.
|
||||
$transition = $form_state['WorkflowTransition'];
|
||||
|
||||
$field_name = $transition->field_name;
|
||||
$workflow = $transition->getWorkflow();
|
||||
$wid = $transition->wid;
|
||||
|
||||
$entity = $this->entity = $transition->getEntity();
|
||||
$entity_type = $this->entity_type = $transition->entity_type;
|
||||
// Figure out the $entity's bundle and id.
|
||||
list(, , $entity_bundle) = entity_extract_ids($entity_type, $entity);
|
||||
$entity_id = entity_id($entity_type, $entity);
|
||||
|
||||
// Show the current state and the Workflow form to allow state changing.
|
||||
// N.B. This part is replicated in hook_node_view, workflow_tab_page, workflow_vbo, transition_edit.
|
||||
// @todo: support multiple workflows per entity.
|
||||
// For workflow_tab_page with multiple workflows, use a separate view. See [#2217291].
|
||||
$field = _workflow_info_field($field_name, $workflow);
|
||||
$instance = $this->instance + field_info_instance($entity_type, $field_name, $entity_bundle);
|
||||
}
|
||||
else {
|
||||
// Get data from normal parameters.
|
||||
$entity = $this->entity;
|
||||
$entity_type = $this->entity_type;
|
||||
$entity_id = ($entity) ? entity_id($entity_type, $entity) : 0;
|
||||
|
||||
$field = $this->field;
|
||||
$field_name = $field['field_name'];
|
||||
$instance = $this->instance;
|
||||
|
||||
// $field['settings']['wid'] can be numeric or named.
|
||||
// $wid may not be specified.
|
||||
$wid = $field['settings']['wid'];
|
||||
$workflow = workflow_load_single($wid);
|
||||
}
|
||||
|
||||
$force = FALSE;
|
||||
|
||||
// Get values.
|
||||
// Current sid and default value may differ in a scheduled transition.
|
||||
// Set 'grouped' option. Only valid for select list and undefined/multiple workflows.
|
||||
$settings_options_type = $field['settings']['widget']['options'];
|
||||
$grouped = ($settings_options_type == 'select');
|
||||
if ($transition) {
|
||||
// If a Transition is passed as parameter, use this.
|
||||
if ($transition->isExecuted()) {
|
||||
// We are editing an existing/executed/not-scheduled transition.
|
||||
// Only the comments may be changed!
|
||||
// Fetch the old state for the formatter on top of form.
|
||||
$current_state = $transition->getOldState();
|
||||
$current_sid = $current_state->sid;
|
||||
|
||||
// The states may not be changed anymore.
|
||||
$new_state = $transition->getNewState();
|
||||
$options = array($new_state->sid => $new_state->label());
|
||||
// We need the widget to edit the comment.
|
||||
$show_widget = TRUE;
|
||||
}
|
||||
else {
|
||||
$current_state = $transition->getOldState();
|
||||
$current_sid = $current_state->sid;
|
||||
$options = $current_state->getOptions($entity_type, $entity, $field_name, $user, $force);
|
||||
$show_widget = $current_state->showWidget($entity_type, $entity, $field_name, $user, $force);
|
||||
}
|
||||
$default_value = $transition->new_sid;
|
||||
}
|
||||
elseif (!$entity) {
|
||||
// Sometimes, no entity is given. We encountered the following cases:
|
||||
// - the Field settings page,
|
||||
// - the VBO action form;
|
||||
// - the Advance Action form on admin/config/system/actions;
|
||||
// If so, show all options for the given workflow(s).
|
||||
$options = workflow_get_workflow_state_names($wid, $grouped, $all = FALSE);
|
||||
$show_widget = TRUE;
|
||||
$default_value = $current_sid = isset($items[0]['value']) ? $items[0]['value'] : '0';
|
||||
}
|
||||
else {
|
||||
$current_sid = workflow_node_current_state($entity, $entity_type, $field_name);
|
||||
if ($current_state = workflow_state_load_single($current_sid)) {
|
||||
/* @var $current_state WorkflowTransition */
|
||||
$options = $current_state->getOptions($entity_type, $entity, $field_name, $user, $force);
|
||||
$show_widget = $current_state->showWidget($entity_type, $entity, $field_name, $user, $force);
|
||||
$default_value = !$current_state->isCreationState() ? $current_sid : $workflow->getFirstSid($entity_type, $entity, $field_name, $user, FALSE);
|
||||
}
|
||||
else {
|
||||
// We are in trouble! A message is already set in workflow_node_current_state().
|
||||
$options = array();
|
||||
$show_widget = FALSE;
|
||||
$default_value = $current_sid;
|
||||
}
|
||||
|
||||
// Get the scheduling info. This may change the $default_value on the Form.
|
||||
// Read scheduled information, only if an entity exists.
|
||||
// Technically you could have more than one scheduled, but this will only add the soonest one.
|
||||
foreach (WorkflowScheduledTransition::load($entity_type, $entity_id, $field_name, 1) as $transition) {
|
||||
$default_value = $transition->new_sid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Prepare a new transition, if still not provided.
|
||||
if (!$transition) {
|
||||
$transition = new WorkflowTransition(array(
|
||||
'old_sid' => $default_value,
|
||||
'stamp' => REQUEST_TIME,
|
||||
));
|
||||
}
|
||||
|
||||
// Fetch the form ID. This is unique for each entity, to allow multiple form per page (Views, etc.).
|
||||
// Make it uniquer by adding the field name, or else the scheduling of
|
||||
// multiple workflow_fields is not independent of each other.
|
||||
// IF we are truly on a Transition form (so, not a Node Form with widget)
|
||||
// then change the form id, too.
|
||||
$form_id = $this->getFormId();
|
||||
if (!isset($form_state['build_info']['base_form_id'])) {
|
||||
// Strange: on node form, the base_form_id is node_form,
|
||||
// but on term form, it is not set.
|
||||
// In both cases, it is OK.
|
||||
}
|
||||
else {
|
||||
if ($form_state['build_info']['base_form_id'] == 'workflow_transition_wrapper_form') {
|
||||
$form_state['build_info']['base_form_id'] = 'workflow_transition_form';
|
||||
}
|
||||
if ($form_state['build_info']['base_form_id'] == 'workflow_transition_form') {
|
||||
$form_state['build_info']['form_id'] = $form_id;
|
||||
}
|
||||
}
|
||||
|
||||
$workflow_label = $workflow ? check_plain(t($workflow->label())) : '';
|
||||
|
||||
// Change settings locally.
|
||||
if (!$field_name) {
|
||||
// This is a Workflow Node workflow. Set widget options as in v7.x-1.2
|
||||
if ($form_state['build_info']['base_form_id'] == 'node_form') {
|
||||
$field['settings']['widget']['comment'] = isset($workflow->options['comment_log_node']) ? $workflow->options['comment_log_node'] : 1; // vs. ['comment_log_tab'];
|
||||
$field['settings']['widget']['current_status'] = TRUE;
|
||||
}
|
||||
else {
|
||||
$field['settings']['widget']['comment'] = isset($workflow->options['comment_log_tab']) ? $workflow->options['comment_log_tab'] : 1; // vs. ['comment_log_node'];
|
||||
$field['settings']['widget']['current_status'] = TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
// Capture settings to format the form/widget.
|
||||
$settings_title_as_name = !empty($field['settings']['widget']['name_as_title']);
|
||||
$settings_fieldset = isset($field['settings']['widget']['fieldset']) ? $field['settings']['widget']['fieldset'] : 0;
|
||||
$settings_options_type = $field['settings']['widget']['options'];
|
||||
// The scheduling info can be hidden via field settings, ...
|
||||
// You may not schedule an existing Transition.
|
||||
// You must have the correct permission.
|
||||
$settings_schedule = !empty($field['settings']['widget']['schedule']) && !$transition->isExecuted() && user_access('schedule workflow transitions');
|
||||
if ($settings_schedule) {
|
||||
if (isset($form_state['step']) && ($form_state['step'] == 'views_bulk_operations_config_form')) {
|
||||
// On VBO 'modify entity values' form, leave field settings.
|
||||
$settings_schedule = TRUE;
|
||||
}
|
||||
else {
|
||||
// ... and cannot be shown on a Content add page (no $entity_id),
|
||||
// ...but can be shown on a VBO 'set workflow state to..'page (no entity).
|
||||
$settings_schedule = !($entity && !$entity_id);
|
||||
}
|
||||
}
|
||||
$settings_schedule_timezone = !empty($field['settings']['widget']['schedule_timezone']);
|
||||
// Show comment, when both Field and Instance allow this.
|
||||
$settings_comment = $field['settings']['widget']['comment'];
|
||||
|
||||
// Save the current value of the node in the form, for later Workflow-module specific references.
|
||||
// We add prefix, since #tree == FALSE.
|
||||
$element['workflow']['workflow_entity'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $this->entity,
|
||||
);
|
||||
$element['workflow']['workflow_entity_type'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $this->entity_type,
|
||||
);
|
||||
$element['workflow']['workflow_field'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $field,
|
||||
);
|
||||
$element['workflow']['workflow_instance'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $instance,
|
||||
);
|
||||
|
||||
// Save the form_id, so the form values can be retrieved in submit function.
|
||||
$element['workflow']['form_id'] = array(
|
||||
'#type' => 'value',
|
||||
'#value' => $form_id,
|
||||
);
|
||||
|
||||
// Save the hid, when editing an existing transition.
|
||||
$element['workflow']['workflow_hid'] = array(
|
||||
'#type' => 'hidden',
|
||||
'#value' => $transition->hid,
|
||||
);
|
||||
|
||||
// Add the default value in the place where normal fields
|
||||
// have it. This is to cater for 'preview' of the entity.
|
||||
$element['#default_value'] = $default_value;
|
||||
|
||||
// Decide if we show a widget or a formatter.
|
||||
// There is no need for a widget when the only option is the current sid.
|
||||
|
||||
// Show state formatter before the rest of the form,
|
||||
// when transition is scheduled or widget is hidden.
|
||||
if ( (!$show_widget) || $transition->isScheduled() || $transition->isExecuted()) {
|
||||
$form['workflow_current_state'] = workflow_state_formatter($entity_type, $entity, $field, $instance, $current_sid);
|
||||
// Set a proper weight, which works for Workflow Options in select list AND action buttons.
|
||||
$form['workflow_current_state']['#weight'] = -0.005;
|
||||
}
|
||||
|
||||
// Add class following node-form pattern (both on form and container).
|
||||
$workflow_type_id = ($workflow) ? $workflow->getName() : 'none'; // No workflow on New Action form.
|
||||
$element['workflow']['#attributes']['class'][] = 'workflow-transition-container';
|
||||
$element['workflow']['#attributes']['class'][] = 'workflow-transition-' . $workflow_type_id . '-container';
|
||||
// Add class for D7-backwards compatibility (only on container).
|
||||
$element['workflow']['#attributes']['class'][] = 'workflow-form-container';
|
||||
|
||||
if (!$show_widget) {
|
||||
// Show no widget.
|
||||
$element['workflow']['workflow_sid']['#type'] = 'value';
|
||||
$element['workflow']['workflow_sid']['#value'] = $default_value;
|
||||
$element['workflow']['workflow_sid']['#options'] = $options; // In case action buttons need them.
|
||||
|
||||
$form += $element;
|
||||
return $form; // <-- exit.
|
||||
}
|
||||
else {
|
||||
// Prepare a UI wrapper. This might be a fieldset or a container.
|
||||
if ($settings_fieldset == 0) { // Use 'container'.
|
||||
$element['workflow'] += array(
|
||||
'#type' => 'container',
|
||||
);
|
||||
}
|
||||
else {
|
||||
$element['workflow'] += array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t($workflow_label),
|
||||
'#collapsible' => TRUE,
|
||||
'#collapsed' => ($settings_fieldset == 1) ? FALSE : TRUE,
|
||||
);
|
||||
}
|
||||
|
||||
// The 'options' widget. May be removed later if 'Action buttons' are chosen.
|
||||
// The help text is not available for container. Let's add it to the
|
||||
// State box.
|
||||
$help_text = isset($instance['description']) ? $instance['description'] : '';
|
||||
$element['workflow']['workflow_sid'] = array(
|
||||
'#type' => $settings_options_type,
|
||||
'#title' => $settings_title_as_name ? t('Change !name state', array('!name' => $workflow_label)) : t('Target state'),
|
||||
'#access' => TRUE,
|
||||
'#options' => $options,
|
||||
// '#name' => $workflow_label,
|
||||
// '#parents' => array('workflow'),
|
||||
'#default_value' => $default_value,
|
||||
'#description' => $help_text,
|
||||
);
|
||||
}
|
||||
|
||||
// Display scheduling form, but only if entity is being edited and user has
|
||||
// permission. State change cannot be scheduled at entity creation because
|
||||
// that leaves the entity in the (creation) state.
|
||||
if ($settings_schedule == TRUE) {
|
||||
if (variable_get('configurable_timezones', 1) && $user->uid && drupal_strlen($user->timezone)) {
|
||||
$timezone = $user->timezone;
|
||||
}
|
||||
else {
|
||||
$timezone = variable_get('date_default_timezone', 0);
|
||||
}
|
||||
$timezones = drupal_map_assoc(timezone_identifiers_list());
|
||||
$timestamp = $transition->getTimestamp();
|
||||
$hours = (!$transition->isScheduled()) ? '00:00' : format_date($timestamp, 'custom', 'H:i', $timezone);
|
||||
// Add a container, so checkbox and time stay together in extra fields.
|
||||
$element['workflow']['workflow_scheduling'] = array(
|
||||
'#type' => 'container',
|
||||
'#tree' => TRUE,
|
||||
);
|
||||
$element['workflow']['workflow_scheduling']['scheduled'] = array(
|
||||
'#type' => 'radios',
|
||||
'#title' => t('Schedule'),
|
||||
'#options' => array(
|
||||
'0' => t('Immediately'),
|
||||
'1' => t('Schedule for state change'),
|
||||
),
|
||||
'#default_value' => $transition->isScheduled() ? '1' : '0',
|
||||
'#attributes' => array(
|
||||
// 'id' => 'scheduled_' . $form_id,
|
||||
'class' => array(drupal_html_class('scheduled_' . $form_id)),
|
||||
),
|
||||
);
|
||||
$element['workflow']['workflow_scheduling']['date_time'] = array(
|
||||
'#type' => 'fieldset',
|
||||
'#title' => t('At'),
|
||||
'#attributes' => array('class' => array('container-inline')),
|
||||
'#prefix' => '<div style="margin-left: 1em;">',
|
||||
'#suffix' => '</div>',
|
||||
'#states' => array(
|
||||
//'visible' => array(':input[id="' . 'scheduled_' . $form_id . '"]' => array('value' => '1')),
|
||||
'visible' => array('input.' . drupal_html_class('scheduled_' . $form_id) => array('value' => '1')),
|
||||
),
|
||||
);
|
||||
$element['workflow']['workflow_scheduling']['date_time']['workflow_scheduled_date'] = array(
|
||||
'#type' => 'date',
|
||||
'#default_value' => array(
|
||||
'day' => date('j', $timestamp),
|
||||
'month' => date('n', $timestamp),
|
||||
'year' => date('Y', $timestamp),
|
||||
),
|
||||
);
|
||||
$element['workflow']['workflow_scheduling']['date_time']['workflow_scheduled_hour'] = array(
|
||||
'#type' => 'textfield',
|
||||
'#title' => t('Time'),
|
||||
'#maxlength' => 7,
|
||||
'#size' => 6,
|
||||
'#default_value' => $hours,
|
||||
'#element_validate' => array('_workflow_transition_form_element_validate_time'),
|
||||
);
|
||||
$element['workflow']['workflow_scheduling']['date_time']['workflow_scheduled_timezone'] = array(
|
||||
'#type' => $settings_schedule_timezone ? 'select' : 'hidden',
|
||||
'#title' => t('Time zone'),
|
||||
'#options' => $timezones,
|
||||
'#default_value' => array($timezone => $timezone),
|
||||
);
|
||||
$element['workflow']['workflow_scheduling']['date_time']['workflow_scheduled_help'] = array(
|
||||
'#type' => 'item',
|
||||
'#prefix' => '<br />',
|
||||
'#description' => t('Please enter a time.
|
||||
If no time is included, the default will be midnight on the specified date.
|
||||
The current time is: @time.', array('@time' => format_date(REQUEST_TIME, 'custom', 'H:i', $timezone))
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
$element['workflow']['workflow_comment'] = array(
|
||||
'#type' => 'textarea',
|
||||
'#required' => $settings_comment == '2',
|
||||
'#access' => $settings_comment !='0', // Align with action buttons.
|
||||
'#title' => t('Workflow comment'),
|
||||
'#description' => t('A comment to put in the workflow log.'),
|
||||
'#default_value' => $transition->comment,
|
||||
'#rows' => 2,
|
||||
);
|
||||
|
||||
// Add the fields and extra_fields from the WorkflowTransition.
|
||||
// Because we have a 'workflow' wrapper, it doesn't work flawlessly.
|
||||
field_attach_form('WorkflowTransition', $transition, $element['workflow'], $form_state);
|
||||
// Undo the following elements from field_attach_from. They mess up $this->getTransition().
|
||||
// - '#parents' corrupts the Defaultwidget.
|
||||
unset($element['workflow']['#parents']);
|
||||
// - '#pre_render' adds the exra_fields from workflow_field_extra_fields().
|
||||
// That doesn't work, since 'workflow' is not of #type 'form', but
|
||||
// 'container' or 'fieldset', and must be executed separately,.
|
||||
$element['workflow']['#pre_render'] = array_diff( $element['workflow']['#pre_render'], array('_field_extra_fields_pre_render') );
|
||||
// Add extra fields.
|
||||
$rescue_value = $element['workflow']['#type'];
|
||||
$element['workflow']['#type'] = 'form';
|
||||
$element['workflow'] = _field_extra_fields_pre_render($element['workflow']);
|
||||
$element['workflow']['#type'] = $rescue_value;
|
||||
|
||||
// Finally, add Submit buttons/Action buttons.
|
||||
// Either a default 'Submit' button is added, or a button per permitted state.
|
||||
if ($settings_options_type == 'buttons') {
|
||||
// How do action buttons work? See also d.o. issue #2187151.
|
||||
// Create 'action buttons' per state option. Set $sid property on each button.
|
||||
// 1. Admin sets ['widget']['options']['#type'] = 'buttons'.
|
||||
// 2. This function formElement() creates 'action buttons' per state option;
|
||||
// sets $sid property on each button.
|
||||
// 3. User clicks button.
|
||||
// 4. Callback _workflow_transition_form_validate_buttons() sets proper State.
|
||||
// 5. Callback _workflow_transition_form_validate_buttons() sets Submit function.
|
||||
// @todo: this does not work yet for the Add Comment form.
|
||||
|
||||
// Performance: inform workflow_form_alter() to do its job.
|
||||
_workflow_use_action_buttons(TRUE);
|
||||
|
||||
// Hide the options box. It will be replaced by action buttons.
|
||||
$element['workflow']['workflow_sid']['#type'] = 'select';
|
||||
$element['workflow']['workflow_sid']['#access'] = FALSE;
|
||||
}
|
||||
|
||||
// Some forms (Term) do not have 'base_form_id' set.
|
||||
if (isset($form_state['build_info']['base_form_id']) && $form_state['build_info']['base_form_id'] == 'workflow_transition_form') {
|
||||
// Add action buttons on WorkflowTransitionForm (history tab, formatter)
|
||||
// but not on Entity form, and not if action_buttons is selected.
|
||||
|
||||
// you can explicitly NOT add a submit button, e.g., on VBO page.
|
||||
if ($instance['widget']['settings']['submit_function'] !== '') {
|
||||
// @todo D8: put buttons outside of 'workflow' element, in the standard location.
|
||||
$element['workflow']['actions']['#type'] = 'actions';
|
||||
$element['workflow']['actions']['submit'] = array(
|
||||
'#type' => 'submit',
|
||||
// '#access' => TRUE,
|
||||
'#value' => t('Update workflow'),
|
||||
'#weight' => -5,
|
||||
// '#submit' => array( isset($instance['widget']['settings']['submit_function']) ? $instance['widget']['settings']['submit_function'] : NULL),
|
||||
// '#executes_submit_callback' => TRUE,
|
||||
'#attributes' => array('class' => array('form-save-default-button')),
|
||||
);
|
||||
// The 'add submit' can explicitly set by workflowfield_field_formatter_view(),
|
||||
// to add the submit button on the Content view page and the Workflow history tab.
|
||||
// Add a submit button, but only on Entity View and History page.
|
||||
// Add the submit function only if one provided. Set the submit_callback accordingly.
|
||||
if (!empty($instance['widget']['settings']['submit_function'])) {
|
||||
$element['workflow']['actions']['submit']['#submit'] = array($instance['widget']['settings']['submit_function']);
|
||||
}
|
||||
else {
|
||||
// '#submit' Must be empty, or else the submit function is not called.
|
||||
// $element['workflow']['actions']['submit']['#submit'] = array();
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
$submit_functions = empty($instance['widget']['settings']['submit_function']) ? array() : array($instance['widget']['settings']['submit_function']);
|
||||
if ($settings_options_type == 'buttons' || $submit_functions) {
|
||||
}
|
||||
else {
|
||||
// In some cases, no submit callback function is specified. This is
|
||||
// explicitly done on e.g., the node edit form, because the workflow form
|
||||
// is 'just a field'.
|
||||
// So, no Submit button is to be shown.
|
||||
}
|
||||
*/
|
||||
|
||||
$form += $element;
|
||||
|
||||
// Add class following node-form pattern (both on form and container).
|
||||
$workflow_type_id = ($workflow) ? $workflow->getName() : 'none'; // No workflow on New Action form.
|
||||
$form['#attributes']['class'][] = 'workflow-transition-form';
|
||||
$form['#attributes']['class'][] = 'workflow-transition-' . $workflow_type_id . '-form';
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function validateForm(array &$form, array $form_state) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function submitForm(array &$form, array &$form_state, array &$items) {
|
||||
// $items is a D7 parameter.
|
||||
// @todo: clean this code up. It is the result of gluing code together.
|
||||
global $user; // @todo #2287057: verify if submit() really is only used for UI. If not, $user must be passed.
|
||||
|
||||
$entity = $this->entity;
|
||||
$entity_type = $this->entity_type;
|
||||
|
||||
$field = $this->field;
|
||||
$field_name = $field['field_name'];
|
||||
|
||||
// Retrieve the data from the form.
|
||||
if (isset($form_state['values']['workflow_field'])) {
|
||||
// If $entity filled: We are on a Entity View page or Workflow History Tab page.
|
||||
// If $entity empty: We are on an Advanced Action page.
|
||||
// $field = $form_state['values']['workflow_field'];
|
||||
// $instance = $form_state['values']['workflow_instance'];
|
||||
// $entity_type = $form_state['values']['workflow_entity_type'];
|
||||
// $entity = $form_state['values']['workflow_entity'];
|
||||
// $field_name = $field['field_name'];
|
||||
}
|
||||
elseif (isset($form_state['triggering_element'])) {
|
||||
// We are on an Entity/Node/Comment Form page (add/edit).
|
||||
$field_name = $form_state['triggering_element']['#workflow_field_name'];
|
||||
}
|
||||
else {
|
||||
// We are on an Entity/Comment Form page (add/edit).
|
||||
}
|
||||
|
||||
// Determine if the transition is forced.
|
||||
// This can be set by a 'workflow_vbo action' in an additional form element.
|
||||
$force = isset($form_state['input']['workflow_force']) ? $form_state['input']['workflow_force'] : FALSE;
|
||||
|
||||
// Set language. Multi-language is not supported for Workflow Node.
|
||||
$langcode = _workflow_metadata_workflow_get_properties($entity, array(), 'langcode', $entity_type, $field_name);
|
||||
|
||||
if (!$entity) {
|
||||
// E.g., on VBO form.
|
||||
}
|
||||
elseif ($field_name) {
|
||||
// Save the entity, but only if we were not in edit mode.
|
||||
// Perhaps there is a better way, but for now we use 'changed' property.
|
||||
// Also test for 'is_new'. When Migrating content, the 'changed' property may be set externally.
|
||||
// Caveat: Some entities do not have 'changed' property set.
|
||||
if ((!empty($entity->is_new)) || (isset($entity->changed) && $entity->changed == REQUEST_TIME)) {
|
||||
// N.B. ONLY for Nodes!
|
||||
// We are in add/edit mode. No need to save the entity explicitly.
|
||||
|
||||
// // Add the $form_state to the $items, so we can do a getTransition() later on.
|
||||
// $items[0]['workflow'] = $form_state['input'];
|
||||
// // Create a Transition. The Widget knows if it is scheduled.
|
||||
// $widget = new WorkflowDefaultWidget($field, $instance, $entity_type, $entity);
|
||||
// $new_sid = $widget->submit($form, $form_state, $items, $force);
|
||||
}
|
||||
elseif (isset($form_state['input'])) {
|
||||
// Save $entity, but only if sid has changed.
|
||||
// Use field_attach_update for this? Save always?
|
||||
$entity->{$field_name}[$langcode][0]['workflow'] = $form_state['input'];
|
||||
// @todo & totest: Save ony the field, not the complete entity.
|
||||
// workflow_entity_field_save($entity_type, $entity, $field_name, $langcode, FALSE);
|
||||
entity_save($entity_type, $entity);
|
||||
|
||||
return; // <-- exit!
|
||||
}
|
||||
elseif ($entity_type == 'node') {
|
||||
// N.B. ONLY for Nodes!
|
||||
// We are saving a node from a comment.
|
||||
$entity->{$field_name}[$langcode] = $items;
|
||||
// @todo & totest: Save ony the field, not the complete entity.
|
||||
// workflow_entity_field_save($entity_type, $entity, $field_name, $langcode, FALSE);
|
||||
entity_save($entity_type, $entity);
|
||||
|
||||
return; // <-- exit!
|
||||
}
|
||||
else {
|
||||
// We are saving a non-node from an entity form.
|
||||
$entity->{$field_name}[$langcode] = $items;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// For a Node API form, only contrib fields need to be filled.
|
||||
// No updating of the node itself.
|
||||
// (Unless we need to record the timestamp.)
|
||||
|
||||
// Add the $form_state to the $items, so we can do a getTransition() later on.
|
||||
$items[0]['workflow'] = $form_state['input'];
|
||||
// // Create a Transition. The Widget knows if it is scheduled.
|
||||
// $widget = new WorkflowDefaultWidget($field, $instance, $entity_type, $entity);
|
||||
// $new_sid = $widget->submit($form, $form_state, $items, $force);
|
||||
}
|
||||
|
||||
// Extract the data from $items, depending on the type of widget.
|
||||
// @todo D8: use MassageFormValues($values, $form, $form_state).
|
||||
$old_sid = workflow_node_previous_state($entity, $entity_type, $field_name);
|
||||
if (!$old_sid) {
|
||||
// At this moment, $old_sid should have a value. If the content does not
|
||||
// have a state yet, old_sid contains '(creation)' state. But if the
|
||||
// content is not associated to a workflow, old_sid is now 0. This may
|
||||
// happen in workflow_vbo, if you assign a state to non-relevant nodes.
|
||||
$entity_id = entity_id($entity_type, $entity);
|
||||
drupal_set_message(t('Error: content !id has no workflow attached. The data is not saved.', array('!id' => $entity_id)), 'error');
|
||||
// The new state is still the previous state.
|
||||
$new_sid = $old_sid;
|
||||
return $new_sid;
|
||||
}
|
||||
|
||||
// Now, save/execute the transition.
|
||||
$transition = $this->getTransition($old_sid, $items, $field_name, $user, $form, $form_state);
|
||||
|
||||
// Try to execute the transition. Return $old_sid when error.
|
||||
if (!$transition) {
|
||||
// This should only happen when testing/developing.
|
||||
drupal_set_message(t('Error: the transition from %old_sid to %new_sid could not be generated.'), 'error');
|
||||
// The current value is still the previous state.
|
||||
$new_sid = $old_sid;
|
||||
}
|
||||
elseif ($transition->isScheduled() || $transition->isExecuted()) {
|
||||
// A scheduled or executed transition must only be saved to the database.
|
||||
// The entity is not changed.
|
||||
$force = $force || $transition->isForced();
|
||||
$transition->save();
|
||||
|
||||
// The current value is still the previous state.
|
||||
$new_sid = $old_sid;
|
||||
}
|
||||
elseif (!$transition->isScheduled()) {
|
||||
// Now the data is captured in the Transition, and before calling the
|
||||
// Execution, restore the default values for Workflow Field.
|
||||
// For instance, workflow_rules evaluates this.
|
||||
if ($field_name) {
|
||||
// $items = array();
|
||||
// $items[0]['value'] = $old_sid;
|
||||
// $entity->{$field_name}[$transition->language] = $items;
|
||||
}
|
||||
|
||||
// It's an immediate change. Do the transition.
|
||||
// - validate option; add hook to let other modules change comment.
|
||||
// - add to history; add to watchdog
|
||||
// Return the new State ID. (Execution may fail and return the old Sid.)
|
||||
$force = $force || $transition->isForced();
|
||||
$new_sid = $transition->execute($force);
|
||||
}
|
||||
|
||||
// The entity is still to be saved, so set to a 'normal' value.
|
||||
if ($field_name) {
|
||||
$items = array();
|
||||
$items[0]['value'] = $new_sid;
|
||||
$entity->{$field_name}[$transition->language] = $items;
|
||||
}
|
||||
|
||||
return $new_sid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract WorkflowTransition or WorkflowScheduledTransition from the form.
|
||||
*
|
||||
* This merely extracts the transition from the form/widget. No validation.
|
||||
*
|
||||
* @param $old_sid
|
||||
* @param array $items
|
||||
* @param $field_name
|
||||
* @param \stdClass $user
|
||||
*
|
||||
* @return \WorkflowScheduledTransition|\WorkflowTransition|null
|
||||
*/
|
||||
public function getTransition($old_sid, array $items, $field_name, stdClass $user, array &$form = array(), array &$form_state = array()) {
|
||||
$entity_type = $this->entity_type;
|
||||
$entity = $this->entity;
|
||||
// $entity_id = entity_id($entity_type, $entity);
|
||||
$field_name = !empty($this->field) ? $this->field['field_name'] : '';
|
||||
|
||||
if (isset($items[0]['transition'])) {
|
||||
// a complete transition was already passed on.
|
||||
$transition = $items[0]['transition'];
|
||||
}
|
||||
else {
|
||||
// Get the new Transition properties. First the new State ID.
|
||||
if (isset($items[0]['workflow']['workflow_sid'])) {
|
||||
// We have shown a workflow form.
|
||||
$new_sid = $items[0]['workflow']['workflow_sid'];
|
||||
}
|
||||
elseif (isset($items[0]['value'])) {
|
||||
// We have shown a core options widget (radios, select).
|
||||
$new_sid = $items[0]['value'];
|
||||
}
|
||||
else {
|
||||
// This may happen if only 1 option is left, and a formatter is shown.
|
||||
$state = workflow_state_load_single($old_sid);
|
||||
if (!$state->isCreationState()) {
|
||||
$new_sid = $old_sid;
|
||||
}
|
||||
else {
|
||||
// This only happens on workflows, when only one transition from
|
||||
// '(creation)' to another state is allowed.
|
||||
/* @var $workflow Workflow */
|
||||
$workflow = $state->getWorkflow();
|
||||
$new_sid = $workflow->getFirstSid($this->entity_type, $this->entity, $field_name, $user, FALSE);
|
||||
}
|
||||
}
|
||||
// If an existing Transition has been edited, $hid is set.
|
||||
$hid = isset($items[0]['workflow']['workflow_hid']) ? $items[0]['workflow']['workflow_hid'] : '';
|
||||
// Get the comment.
|
||||
$comment = isset($items[0]['workflow']['workflow_comment']) ? $items[0]['workflow']['workflow_comment'] : '';
|
||||
// Remember, the workflow_scheduled element is not set on 'add' page.
|
||||
$scheduled = !empty($items[0]['workflow']['workflow_scheduling']['scheduled']);
|
||||
if ($hid) {
|
||||
// We are editing an existing transition. Only comment may be changed.
|
||||
$transition = workflow_transition_load($hid);
|
||||
$transition->comment = $comment;
|
||||
}
|
||||
elseif (!$scheduled) {
|
||||
$transition = new WorkflowTransition();
|
||||
$transition->setValues($entity_type, $entity, $field_name, $old_sid, $new_sid, $user->uid, REQUEST_TIME, $comment);
|
||||
}
|
||||
else {
|
||||
// Schedule the time to change the state.
|
||||
// If Field Form is used, use plain values;
|
||||
// If Node Form is used, use fieldset 'date_time'.
|
||||
$schedule = isset($items[0]['workflow']['workflow_scheduling']['date_time']) ? $items[0]['workflow']['workflow_scheduling']['date_time'] : $items[0]['workflow'];
|
||||
if (!isset($schedule['workflow_scheduled_hour'])) {
|
||||
$schedule['workflow_scheduled_hour'] = '00:00';
|
||||
}
|
||||
|
||||
$scheduled_date_time
|
||||
= $schedule['workflow_scheduled_date']['year']
|
||||
. substr('0' . $schedule['workflow_scheduled_date']['month'], -2, 2)
|
||||
. substr('0' . $schedule['workflow_scheduled_date']['day'], -2, 2)
|
||||
. ' '
|
||||
. $schedule['workflow_scheduled_hour']
|
||||
. ' '
|
||||
. $schedule['workflow_scheduled_timezone'];
|
||||
|
||||
if ($timestamp = strtotime($scheduled_date_time)) {
|
||||
$transition = new WorkflowScheduledTransition();
|
||||
$transition->setValues($entity_type, $entity, $field_name, $old_sid, $new_sid, $user->uid, $timestamp, $comment);
|
||||
}
|
||||
else {
|
||||
$transition = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $transition;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains workflow\lib\entity\WorkflowUnitTest.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tests for the Workflow classes.
|
||||
*/
|
||||
class WorkflowExampleTestCase extends DrupalWebTestCase {
|
||||
protected $workflow;
|
||||
public static function getInfo() {
|
||||
return array(
|
||||
'name' => 'Workflow',
|
||||
'description' => 'Ensure that the Workflow API works as expected.',
|
||||
'group' => 'Workflow',
|
||||
);
|
||||
}
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp('workflow'); // Enable any modules required for the test.
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a simpletest_example node using the node form.
|
||||
*/
|
||||
public function testWorkflow() {
|
||||
|
||||
// $workflows = workflow_load_multiple();
|
||||
// dpm($workflows, "These are the current Workflows in the system.");
|
||||
$wid = 1;
|
||||
$workflow = workflow_load_single($wid);
|
||||
$this->assertEqual($workflow->wid, $wid, t('The wid of the Workflow should be the same as we decided.'));
|
||||
$workflow = new Workflow($wid);
|
||||
$this->assertEqual($workflow->wid, $wid, t('The wid of the Workflow should be the same as we decided.'));
|
||||
|
||||
// $creation_state = $workflow->getCreationState();
|
||||
// $this->assertEqual($creation_state, 1, t('The creation_state of wid 1 has value 1.'));
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
function testWorkflowState($sid = 2, $wid = 1) {
|
||||
$this->testId = '2';
|
||||
|
||||
$wf_state = $workflow->createState('State 2');
|
||||
dpm($wf_state, 'This is state ' . $sid . ' of workflow ' . $wid);
|
||||
$workflow = $wf_state->getWorkflow();
|
||||
dpm($workflow, 'This is the workflow of sid ' . $sid);
|
||||
|
||||
|
||||
return;
|
||||
$this->machine->fire_event('goto2');
|
||||
$this->assertEqual($this->machine->get_current_state(), 'step2', t('Current state should change when a valid event is fired.'));
|
||||
|
||||
$this->machine->fire_event('goto2');
|
||||
$this->assertEqual($this->machine->get_current_state(), 'step2', t('Event should not execute if current state is not valid for the specified event.'));
|
||||
|
||||
$this->machine->fire_event('reset');
|
||||
$this->assertEqual($this->machine->get_current_state(), 'step1', t('Event should allow transitions from multiple origins.'));
|
||||
|
||||
$current = $this->machine->get_current_state();
|
||||
$this->machine->fire_event('dont_do_it');
|
||||
$this->assertEqual($current, $this->machine->get_current_state(), t('State should not change when guard function returns FALSE.'));
|
||||
|
||||
$this->machine->fire_event('reset');
|
||||
$this->machine->reset_logs();
|
||||
$this->machine->fire_event('goto2_with_logs');
|
||||
|
||||
$this->assertEqual($this->machine->logs[0], 'guard', t('The guard condition should be the first callback executed.'));
|
||||
$this->assertEqual($this->machine->logs[1], 'before_transition', t('The before_transition callback should be the second callback executed.'));
|
||||
$this->assertEqual($this->machine->logs[2], 'on_exit', t('The on_exit callback should be the third callback executed.'));
|
||||
$this->assertEqual($this->machine->logs[3], 'on_enter', t('The on_enter callback should be the fourth callback executed.'));
|
||||
$this->assertEqual($this->machine->logs[4], 'after_transition', t('The after_transition callback should be the fifth callback executed.'));
|
||||
|
||||
$this->machine->fire_event('reset');
|
||||
$events = $this->machine->get_available_events();
|
||||
$this->assertTrue(in_array('goto2', $events), t('The machine should return a list of available events.'));
|
||||
$this->assertTrue(in_array('goto3', $events), t('The machine should return a list of available events.'));
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user