/** * 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; }
/** * Implements hook_field_update() -> FieldItemInterface::update(). * * @todo: in course of time, this is not used anymore... * It is called also from hook_field_insert(), since we need $nid to store {workflow_node_history}. * We cannot use hook_field_presave(), since $nid is not yet known at that moment. * * "Contrary to the old D7 hooks, the methods do not receive the parent entity * "or the langcode of the field values as parameters. If needed, those can be accessed * "by the getEntity() and getLangcode() methods on the Field and FieldItem classes. */ public function update(&$items) { // ($entity_type, $entity, $field, $instance, $langcode, &$items) { // @todo: apparentlly, in course of time, this is not used anymore. Restore or remove. $field_name = $this->field['field_name']; $wid = $this->field['settings']['wid']; $new_state = WorkflowState::load($sid = _workflow_get_sid_by_items($items), $wid); // @todo D8: remove below lines. $entity = $this->entity; $entity_type = $this->entity_type; $entity_id = entity_id($entity_type, $entity); if (!$entity) { // No entity available, we are on the field Settings page - 'default value' field. // This is hidden from the admin, because the default value can be different for every user. } elseif (!$entity_id && $entity_type == 'comment') { // Not possible: a comment on a non-existent node. } elseif ($entity_id && $this->entity_type == 'comment') { // This happens when we are on an entity's comment. $referenced_entity_type = 'node'; // Comments only exist on nodes. $referenced_entity = entity_load_single($referenced_entity_type, $entity_id); // Comments only exist on nodes. // Submit the data. $items is reset by reference to normal value, and is magically saved by the field itself. $form = array(); $form_state = array(); $widget = new WorkflowDefaultWidget($this->field, $this->instance, $referenced_entity_type, $referenced_entity); $widget->submit($form, $form_state, $items); // $items is a proprietary D7 parameter. // Remember: we are on a comment form, so the comment is saved automatically, but the referenced entity is not. // @todo: probably we'd like to do this form within the Widget, but that does not know // wether we are on a comment or a node form. // // Widget::submit() returns the new value in a 'sane' state. // Save the referenced entity, but only is transition succeeded, and is not scheduled. $old_sid = _workflow_get_sid_by_items($referenced_entity->{$field_name}['und']); $new_sid = _workflow_get_sid_by_items($items); if ($old_sid != $new_sid) { $referenced_entity->{$field_name}['und'] = $items; entity_save($referenced_entity_type, $referenced_entity); } } elseif ($this->entity_type != 'comment') { if (isset($items[0]['value'])) { // A 'normal' options.module-widget is used, and $items[0]['value'] is already properly set. } elseif (isset($items[0]['workflow'])) { // The WorkflowDefaultWidget is used. // Submit the data. $items is reset by reference to normal value, and is magically saved by the field itself. $form = array(); $form_state = array(); $widget = new WorkflowDefaultWidget($this->field, $this->instance, $entity_type, $entity); $widget->submit($form, $form_state, $items); // $items is a proprietary D7 parameter. } else { drupal_set_message('error in WorkfowItem->update()', 'error'); } } // A 'normal' node add page. // We should not be here, since we only do inserts after $entity_id is known. // $current_sid = Workflow::load($wid)->getCreationSid(); }
/** * Save (update/insert) a Workflow State into table workflow_states. * * @deprecated: workflow_update_workflow_states() --> WorkflowState->save() */ public function save() { $sid = $this->sid; // Convert all properties to an array, the previous ones, too. $data['sid'] = $this->sid; $data['wid'] = $this->wid; $data['weight'] = $this->weight; $data['sysid'] = $this->sysid; $data['state'] = $this->state; $data['status'] = $this->status; if (!empty($this->sid) && count(WorkflowState::load($sid)) > 0) { drupal_write_record('workflow_states', $data, 'sid'); } else { drupal_write_record('workflow_states', $data); } // Update the page cache. self::$states[$sid] = $this; }
/** * 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); }
/** * 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; }