/** * Call-back function for sorting states by their priority. Lower sort index => lower priority. * @param WorkflowState $a * @param WorkflowState $b */ private static function sortStates(WorkflowState $a, WorkflowState $b) { if ($a->getSortIndex() == $b->getSortIndex()) { return 0; } return $a->getSortIndex() < $b->getSortIndex() ? -1 : 1; }
/** * NB: All of the params are optional for compatibility with db->getObjects() .. Database is done wrong. Don't have time to fix it. * @param Internship $i * @param PHPWS_User $phpwsUser * @param int $timestamp * @param WorkflowState $fromState * @param WorkflowState $toState */ public function __construct(Internship $i = null, PHPWS_User $phpwsUser = null, $timestamp = null, WorkflowState $fromState = null, WorkflowState $toState = null, $note = null) { if (!is_null($i)) { $this->id = 0; $this->internship_id = $i->getId(); $this->username = $phpwsUser->getUsername(); $this->timestamp = $timestamp; $this->from_state = $fromState->getName(); $this->to_state = $toState->getName(); $this->note = $note; } }
/** * NB: All of the params are optional for compatibility with db->getObjects() .. Database is done wrong. Don't have time to fix it. * @param Internship $i * @param PHPWS_User $phpwsUser * @param int $timestamp * @param WorkflowState $fromState * @param WorkflowState $toState */ public function __construct(Internship $i = null, \PHPWS_User $phpwsUser = null, $timestamp = null, WorkflowState $fromState = null, WorkflowState $toState = null, $note = null) { if (!is_null($i)) { $this->id = 0; $this->internship_id = $i->getId(); $this->username = $phpwsUser->getUsername(); $this->timestamp = $timestamp; // Strip namespace from start of from and to states, all four backspaces are required to escaping backslash $this->from_state = preg_replace("/Intern\\\\WorkflowState\\\\/", '', $fromState->getName()); $this->to_state = preg_replace("/Intern\\\\WorkflowState\\\\/", '', $toState->getName()); $this->note = $note; } }
public static function getTransitionsFromState(WorkflowState $state, Internship $i) { $stateName = $state->getName(); $transitions = self::getAllTransitions(); $outgoingTrans = array(); foreach ($transitions as $t) { // Set the actual source state $t->setSourceState($state); if (is_array($t->getSourceState()) && in_array($stateName, $t->getSourceState()) && $t->isApplicable($i)) { $outgoingTrans[] = $t; } else { if (($t->getSourceState() == $stateName || $t->getSourceState() == '*') && $t->isApplicable($i)) { $outgoingTrans[] = $t; } } } uasort($outgoingTrans, array('self', 'sortTransitions')); return $outgoingTrans; }
/** * Save a scheduled transition. If the transition is executed, save as logged transition. */ public function save() { // If executed, save as logged transition. if ($this->is_executed) { return parent::save(); } // Avoid duplicate entries. $this->delete(); // Save (insert or update) a record to the database based upon the schema. drupal_write_record('workflow_scheduled_transition', $this); // Get name of state. if ($state = WorkflowState::load($this->new_sid)) { $message = '@entity_title scheduled for state change to %state_name on %scheduled_date'; $args = array('@entity_type' => $this->entity_type, '@entity_title' => $this->entity->title, '%state_name' => $state->label(), '%scheduled_date' => format_date($this->scheduled)); $uri = entity_uri($this->entity_type, $this->entity); watchdog('workflow', $message, $args, WATCHDOG_NOTICE, l('view', $uri['path'] . '/workflow')); drupal_set_message(t($message, $args)); } }
/** * Implements hook_field_widget_form --> WidgetInterface::formElement(). * * {@inheritdoc} * * Be careful: this 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, $langcode, array &$form, array &$form_state) { $field_name = $this->field['field_name']; $field = $this->instance; $instance = $this->instance; $entity = $this->entity; $entity_type = $this->entity_type; $entity_id = entity_id($entity_type, $entity); if (!$entity) { // If no entity given, do not show a form. E.g., on the field settings page. return $element; } // Capture settings to format the form/widget. $settings_title_as_name = !empty($this->field['settings']['widget']['name_as_title']); // The schedule cannot be shown on a Content add page. $settings_schedule = !empty($this->field['settings']['widget']['schedule']) && $entity_id; $settings_schedule_timezone = !empty($this->field['settings']['widget']['schedule_timezone']); // Show comment, when both Field and Instance allow this. $settings_comment = $this->field['settings']['widget']['comment'] ? 'textarea' : 'hidden'; $workflow = Workflow::load($this->field['settings']['wid']); $current_sid = workflow_node_current_state($entity, $entity_type, $field_name); $current_state = WorkflowState::load($current_sid); $options = array(); $options = $current_state->getOptions($entity_type, $entity); // Determine the default value. If we are in CreationState, use a fast alternative for $workflow->getFirstSid(). $default_value = $current_state->isCreationState() ? key($options) : $current_sid; // Get the scheduling info. This may change the $current_sid on the Form. $scheduled = '0'; $timestamp = REQUEST_TIME; $comment = NULL; if ($settings_schedule) { // Read scheduled information. // 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) as $scheduled_transition) { $scheduled = '1'; $current_sid = $scheduled_transition->sid; $timestamp = $scheduled_transition->scheduled; $comment = $scheduled_transition->comment; break; } } // Fetch the form ID. This is unique for each entity, to allow multiple form per page (Views, etc.). $form_id = $form_state['build_info']['form_id']; $element_scheduled_name = 'workflow_scheduled_' . $form_id; $element_options_name = 'workflow_options_' . $form_id; $element_scheduled_name = 'workflow_scheduled'; $element_options_name = 'workflow_options'; $elt_state_name = 'workflow_scheduled_' . $form_id; $label = $workflow->label(); // Prepare a wrapper. This might be a fieldset. $element['workflow']['#type'] = 'container'; $element['workflow']['#attributes'] = array('class' => array('workflow-form-container')); // 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' => $this->field); $element['workflow']['workflow_instance'] = array('#type' => 'value', '#value' => $this->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); // First of all, we add the default value in the place were 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 to a widget when the only choice is the current sid. if (!$current_state->showWidget($options)) { $element['workflow'][$element_options_name] = workflow_state_formatter($entity_type, $entity, $field, $instance); return $element; } else { $element['workflow'][$element_options_name] = array('#type' => $this->field['settings']['widget']['options'], '#title' => $settings_title_as_name ? t('Change !name state', array('!name' => $label)) : '', '#options' => $options, '#default_value' => $default_value); } // 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 && user_access('schedule workflow transitions')) { global $user; 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()); $hours = format_date($timestamp, 'custom', 'H:i', $timezone); // $element['workflow']['workflow_scheduled'] = array( $element['workflow'][$element_scheduled_name] = array('#type' => 'radios', '#title' => t('Schedule'), '#options' => array('0' => t('Immediately'), '1' => t('Schedule for state change')), '#default_value' => $scheduled, '#attributes' => array('id' => 'scheduled_' . $form_id)); $element['workflow']['workflow_scheduled_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')))); $element['workflow']['workflow_scheduled_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_scheduled_date_time']['workflow_scheduled_hour'] = array('#type' => 'textfield', '#title' => t('Time'), '#maxlength' => 5, '#size' => 6, '#default_value' => $scheduled ? $hours : '00:00'); $element['workflow']['workflow_scheduled_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_scheduled_date_time']['workflow_scheduled_help'] = array('#type' => 'item', '#prefix' => '<br />', '#description' => t('Please enter a time in 24 hour (eg. HH:MM) format. 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' => $settings_comment, '#title' => t('Workflow comment'), '#description' => t('A comment to put in the workflow log.'), '#default_value' => $comment, '#rows' => 2); // The 'add submit' can explicitely set by workflowfield_field_formatter_view(), // to add the submit button on the Content view page and the Workflow history tab. if (!empty($this->instance['widget']['settings']['submit_function'])) { // Add a submit button, but only on Entity View and History page. $element['workflow']['submit'] = array('#type' => 'submit', '#value' => t('Update workflow'), '#executes_submit_callback' => TRUE, '#submit' => array($this->instance['widget']['settings']['submit_function'])); } return $element; }
/** * 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; } }
protected function _allowed_values_string($wid = 0) { $lines = array(); $states = WorkflowState::getStates($wid); $previous_wid = -1; foreach ($states as $state) { // Only show enabled states. if ($state->status) { // 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 = t($state->label()); $states[$state->sid] = check_plain($label); $lines[] = $state->sid . ' | ' . check_plain($label); } } return implode("\n", $lines); }
public function testUndefinedAction() { $this->assertTrue($this->state->getAction('undefined') === null); }
/** * 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; }
/** * Deactivate a Workflow State, moving existing nodes to a given State. * * @param $new_sid * the state ID, to which all affected entities must be moved. * * @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 (@todo Field API): // - 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. // This is called in WorkflowState::deactivate(). // @todo: reparent Workflow Field, whilst deactivating a state. if ($new_sid) { global $user; // A candidate for the batch API. // @TODO: Future updates should seriously consider setting this with batch. $comment = t('Previous state deleted'); 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($entity_type, $entity, $field_name, $current_sid, $new_sid, $user->uid, REQUEST_TIME, $comment); $transition->force($force); // Excute 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); } } // Delete any lingering node to state values. workflow_delete_workflow_node_by_sid($current_sid); // Find out which transitions this state is involved in. $preexisting = array(); foreach (workflow_get_workflow_transitions_by_sid_involved($current_sid) as $data) { $preexisting[$data->sid][$data->target_sid] = TRUE; } // Delete the transitions. foreach ($preexisting as $from => $array) { foreach (array_keys($array) as $target_id) { if ($transition = workflow_get_workflow_transitions_by_sid_target_sid($from, $target_id)) { workflow_delete_workflow_transitions_by_tid($transition->tid); } } } // 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::$states = array(); }
/** * Sets the WorkflowState of this internship. * * @param WorkflowState $state */ public function setState(WorkflowState $state) { $this->state = $state->getName(); }
/** * Gets a state for a given workflow. * * @param $sid * A state ID. * * @return * A WorkflowState object. */ public function getState($sid) { return WorkflowState::load($sid, $this->wid); }
/** * Adds new workflow state. * * @param WorkflowState $state State object to add * * @return \DevelArts\Workflow\Workflow */ public function addState(WorkflowState $state) { $this->states[$state->getName()] = $state; return $this; }
/** * Execute a transition (change state of a node). * @deprecated: workflow_execute_transition() --> WorkflowTransition::execute(). * * @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, */ public function execute($force = FALSE) { global $user; $old_sid = $this->old_sid; $new_sid = $this->new_sid; $entity_type = $this->entity_type; $entity_id = $this->entity_id; $entity = $this->getEntity(); // Entity may not be loaded, yet. $field_name = $this->field_name; if ($old_sid == $new_sid) { // Stop if not going to a different state. // Write comment into history though. if ($this->comment) { $this->stamp = REQUEST_TIME; if (!$field_name) { // @todo D8: remove; this is only for Node API. $entity->workflow_stamp = REQUEST_TIME; workflow_update_workflow_node_stamp($entity_id, $this->stamp); } $result = module_invoke_all('workflow', 'transition pre', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name); $this->save(); if (!$field_name) { // @todo D8: remove; this is only for Node API. unset($entity->workflow_comment); // @todo D8: remove; this line is only for Node API. } $result = module_invoke_all('workflow', 'transition post', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name); } // Clear any references in the scheduled listing. foreach (WorkflowScheduledTransition::load($entity_type, $entity_id, $field_name) as $scheduled_transition) { $scheduled_transition->delete(); } return $new_sid; } if (!$force) { // Make sure this transition is allowed. $result = module_invoke_all('workflow', 'transition permitted', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name); // Did anybody veto this choice? if (in_array(FALSE, $result)) { // If vetoed, quit. return $old_sid; } } // 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' => $this->uid, 'transition' => $this); drupal_alter('workflow_comment', $this->comment, $context); $args = array('%user' => $user->name, '%old' => $old_sid, '%new' => $new_sid); $transition = workflow_get_workflow_transitions_by_sid_target_sid($old_sid, $new_sid); if (!$transition && !$force) { watchdog('workflow', 'Attempt to go to nonexistent transition (from %old to %new)', $args, WATCHDOG_ERROR); return $old_sid; } // Make sure this transition is valid and allowed for the current user. // Check allow-ability of state change if user is not superuser (might be cron). if ($user->uid != 1 && !$force) { if (!workflow_transition_allowed($transition->tid, array_merge(array_keys($user->roles), array('author')))) { watchdog('workflow', 'User %user not allowed to go from state %old to %new', $args, WATCHDOG_NOTICE); return $old_sid; } } // Invoke a callback indicating a transition is about to occur. // Modules may veto the transition by returning FALSE. $result = module_invoke_all('workflow', 'transition pre', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name); // Stop if a module says so. if (in_array(FALSE, $result)) { watchdog('workflow', 'Transition vetoed by module.'); return $old_sid; } // Log the new state in {workflow_node_history}. // This is only valid for Node API. if (!$field_name) { // If the node does not have an existing 'workflow' property, save the $old_sid there, so it can be logged. if (!isset($entity->workflow)) { $entity->workflow = $old_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; } // Log the transition in {workflow_node_history}. $this->is_executed = TRUE; $this->save(); // Register state change with watchdog. if ($state = WorkflowState::load($new_sid)) { $workflow = $state->getWorkflow(); if (!empty($workflow->options['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' => $state->label()); $uri = entity_uri($entity_type, $entity); watchdog('workflow', $message, $args, WATCHDOG_NOTICE, l('view', $uri['path'])); } } // Notify modules that transition has occurred. // Action triggers should take place in response to this callback, not the previous one. module_invoke_all('workflow', 'transition post', $old_sid, $new_sid, $entity, $force, $entity_type, $field_name); // Clear any references in the scheduled listing. foreach (WorkflowScheduledTransition::load($entity_type, $entity_id, $field_name) as $scheduled_transition) { $scheduled_transition->delete(); } return $new_sid; }