array( 'title' => t('Administer workflow'), 'description' => t('Administer workflow configurations.'), ), 'participate in workflow' => array( 'title' => t('Participate in workflow'), 'description' => t('Role is shown on workflow admin pages.'), ), ); } /** * Implements hook_user_role_insert(). * Make sure new roles are allowed to participate in workflows by default. */ function workflow_admin_ui_user_role_insert($role) { user_role_change_permissions($role->rid, array('participate in workflow' => 1)); } /** * Implements hook_menu(). */ function workflow_admin_ui_menu() { $items['admin/config/workflow/workflow'] = array( 'title' => 'Workflow', 'access arguments' => array('administer workflow'), 'page callback' => 'drupal_get_form', 'page arguments' => array('workflow_admin_ui_types_form'), 'description' => 'Allows the creation and assignment of arbitrary workflows to node types.', ); $items['admin/config/workflow/workflow/%workflow'] = array( 'access arguments' => array('administer workflow'), 'page callback' => 'drupal_get_form', 'page arguments' => array('workflow_admin_ui_overview_form', 4), 'type' => MENU_CALLBACK, ); $items['admin/config/workflow/workflow/add'] = array( 'access arguments' => array('administer workflow'), 'page callback' => 'drupal_get_form', 'page arguments' => array('workflow_admin_ui_add_form'), 'type' => MENU_CALLBACK, ); $items['admin/config/workflow/workflow/edit/%workflow'] = array( 'title' => 'Edit', 'access arguments' => array('administer workflow'), 'page callback' => 'drupal_get_form', 'page arguments' => array('workflow_admin_ui_edit_form', 5), 'type' => MENU_CALLBACK, ); $items["admin/config/workflow/workflow/delete/%workflow"] = array( 'title' => 'Delete', 'access arguments' => array('administer workflow'), 'page callback' => 'drupal_get_form', 'page arguments' => array('workflow_admin_ui_delete_form',5), 'type' => MENU_CALLBACK, ); $items["admin/config/workflow/workflow/transitions/%workflow"] = array( 'title' => 'Transitions', 'access arguments' => array('administer workflow'), 'page callback' => 'drupal_get_form', 'page arguments' => array('workflow_admin_ui_transitions_form',5), 'type' => MENU_CALLBACK, ); $items["admin/config/workflow/workflow/perm_summary/%workflow"] = array( 'title' => 'Permission Summary', 'access arguments' => array('administer workflow'), 'page callback' => 'workflow_admin_ui_view_permissions', 'page arguments' => array(5), 'type' => MENU_CALLBACK, ); return $items; } /** * Implements hook_theme(). */ function workflow_admin_ui_theme() { return array( 'workflow_admin_ui_transitions_form' => array('render element' => 'form'), 'workflow_admin_ui_edit_form' => array('render element' => 'form'), 'workflow_admin_ui_types_form' => array('render element' => 'form'), 'workflow_admin_ui_overview_form' => array('render element' => 'form'), ); } /** * Form builder. Create the form for adding/editing a workflow. * * @param $name * Name of the workflow if editing. * @param $add * Boolean, if true edit workflow name. * * @return * HTML form. */ function workflow_admin_ui_add_form($form, &$form_state, $name = NULL) { $form = array(); $form['wf_name'] = array( '#type' => 'textfield', '#title' => t('Workflow Name'), '#maxlength' => '254', '#required' => TRUE, '#default_value' => $name, ); $form['submit'] = array( '#type' => 'submit', '#value' => t('Add Workflow'), ); return $form; } /** * Validate the workflow add form. * * @see workflow_add_form() */ function workflow_admin_ui_add_form_validate($form, &$form_state) { $workflow_name = $form_state['values']['wf_name']; // Make sure workflow name is not a duplicate. $workflows = array(); foreach (workflow_get_workflows() as $data) { $workflows[$data->wid] = check_plain(t($data->name)); } if (!empty($workflows)) { $workflows = array_flip($workflows); if (array_key_exists($workflow_name, $workflows)) { form_set_error('wf_name', t('A workflow with the name %name already exists. Please enter another name for your new workflow.', array('%name' => $workflow_name))); } } } /** * Submit handler for the workflow add form. * * @see workflow_add_form() */ function workflow_admin_ui_add_form_submit($form, &$form_state) { $workflow_name = $form_state['values']['wf_name']; $workflow = array( 'name' => $workflow_name, 'tab_roles' => '', 'options' => serialize(array()), ); workflow_update_workflows($workflow); watchdog('workflow', 'Created workflow %name', array('%name' => $workflow->name)); drupal_set_message(t('The workflow %name was created. You should set the options for your workflow.', array('%name' => $workflow->name)), 'status'); $form_state['wid'] = $workflow->wid; $form_state['redirect'] = 'admin/config/workflow/workflow/edit/' . $workflow->wid; } /** * Form builder. Create form for confirmation of workflow deletion. * * @param $wid * The workflow object to delete. * @return * Form definition array. * */ function workflow_admin_ui_delete_form($form, &$form_state, $workflow) { // If we don't have a workflow that goes with this, return to the admin pg. if ($workflow) { // Let's add some breadcrumbs. workflow_admin_ui_breadcrumbs($workflow); $form['wid'] = array('#type' => 'value', '#value' => $workflow->wid); return confirm_form( $form, t('Are you sure you want to delete %title? All nodes that have a workflow state associated with this workflow will ' . 'have those workflow states removed.', array('%title' => $workflow->name)), !empty($_GET['destination']) ? $_GET['destination'] : 'admin/config/workflow/workflow', t('This action cannot be undone.'), t('Delete ' . $workflow->name), // This seems to get check_plain'ed. t('Cancel') ); } else { drupal_goto('admin/config/workflow/workflow'); } } /** * Submit handler for workflow deletion form. * * @see workflow_delete_form() */ function workflow_admin_ui_delete_form_submit($form, &$form_state) { if ($form_state['values']['confirm'] == 1 && $workflow = workflow_get_workflows_by_wid($form_state['values']['wid'])) { workflow_delete_workflows_by_wid($form_state['values']['wid']); watchdog('workflow', 'Deleted workflow %name with all its states', array('%name' => $workflow->name)); drupal_set_message(t('The workflow %name with all its states was deleted.', array('%name' => $workflow->name))); $form_state['redirect'] = 'admin/config/workflow/workflow'; } } /** * View workflow permissions by role * * @param $workflow * The workflow object. */ function workflow_admin_ui_view_permissions($workflow) { // If we don't have a workflow at this point, go back to admin pg. if ($workflow) { // Place some breadcrumbs. workflow_admin_ui_breadcrumbs($workflow); $name = $workflow->name; $all = array(); $roles = workflow_admin_ui_get_roles(); foreach ($roles as $role => $value) { $all[$role]['name'] = $value; } // TODO return to this, this looks similar to actions stuff (transitions) - should be it's own function. $result = db_query( 'SELECT t.roles, s1.state AS state_name, s2.state AS target_state_name ' . 'FROM {workflow_transitions} t ' . 'INNER JOIN {workflow_states} s1 ON s1.sid = t.sid ' . 'INNER JOIN {workflow_states} s2 ON s2.sid = t.target_sid ' . 'WHERE s1.wid = :wid ' . 'ORDER BY s1.weight ASC , s1.state ASC , s2.weight ASC , s2.state ASC', array(':wid' => $workflow->wid)); foreach ($result as $data) { foreach (explode(',', $data->roles) as $role) { $all[$role]['transitions'][] = array(check_plain(t($data->state_name)), WORKFLOW_ARROW, check_plain(t($data->target_state_name))); } } $header = array(t('From'), '', t('To')); $output = ''; // TODO we should theme out the html here. foreach ($all as $role => $value) { if (!empty($value['name'])) { $output .= '

' . t('%role may do these transitions:', array('%role' => $value['name'])) . '

'; } if (!empty($value['transitions'])) { $output .= theme('table', array('header' => $header, 'rows' => $value['transitions'])) . '

'; } else { $output .= '
' . t('None') . '

'; } } return $output; } else { drupal_goto('admin/config/workflow/workflow'); } } /** * Theme the workflow permissions view. */ function theme_workflow_admin_ui_view_permissions($variables) { $header = $variables['header']; $all = $variables['all']; $output = ''; foreach ($all as $role => $value) { if (!empty($value['name'])) { $output .= '

' . t('%role may do these transitions:', array('%role' => $value['name'])) . '

'; } if (!empty($value['transitions'])) { $output .= theme('table', array('header' => $header, 'rows' => $value['transitions'])) . '

'; } else { $output .= '
' . t('None') . '

'; } } return $output; } /** * Helper function. Create breadcrumbs. * * @param $workflow * The workflow object. * @param $extra (optional) * The link to the extra item to add to the end of the breadcrumbs. * @return * none. */ function workflow_admin_ui_breadcrumbs($workflow, $extra = NULL) { $bc = array(l(t('Home'), '')); $bc[] = l(t('Configuration'), 'admin/config'); $bc[] = l(t('Workflow'), 'admin/config/workflow'); $bc[] = l(t('Workflow'), 'admin/config/workflow/workflow'); $bc[] = l(t($workflow->name), "admin/config/workflow/workflow/$workflow->wid"); if ($extra) { $bc[] = $extra; } drupal_set_breadcrumb($bc); } /** * Menu callback. Edit a workflow's properties. * * @param $wid * The workflow object.. * @return * HTML form and permissions table. */ function workflow_admin_ui_edit_form($form, $form_state, $workflow = NULL) { $form = array(); // If we don't have a workflow by this point, we need to go back // to creating one at admin/config/workflow/workflow/add // I think the menu loader won't allow this to happen. if (!$workflow) { drupal_goto('admin/config/workflow/workflow/add'); } // Create breadcrumbs. workflow_admin_ui_breadcrumbs($workflow); $noyes = array(t('No'), t('Yes')); $form['wid'] = array('#type' => 'value', '#value' => $workflow->wid); $form['#workflow'] = $workflow; $form['basic'] = array( '#type' => 'fieldset', '#title' => t('Workflow information'), ); $form['basic']['wf_name'] = array( '#type' => 'textfield', '#default_value' => $workflow->name, '#title' => t('Workflow Name'), '#maxlength' => '254', '#required' => TRUE, ); $form['basic']['name_as_title'] = array( '#type' => 'radios', '#options' => $noyes, '#attributes' => array('class' => array('container-inline')), '#title' => t('Use the workflow name as the title of the workflow form?'), '#default_value' => isset($workflow->options['name_as_title']) ? $workflow->options['name_as_title'] : 0, '#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.'), ); $form['comment'] = array( '#type' => 'fieldset', '#title' => t('Comment for Workflow Log'), ); $form['comment']['comment_log_node'] = array( '#type' => 'radios', '#options' => $noyes, '#attributes' => array('class' => array('container-inline')), '#title' => t('Show a comment field in the workflow section of the editing form?'), '#default_value' => isset($workflow->options['comment_log_node']) ? $workflow->options['comment_log_node'] : 0, '#description' => t('On the node editing form, a Comment form can be shown 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.'), ); $form['comment']['comment_log_tab'] = array( '#type' => 'radios', '#options' => $noyes, '#attributes' => array('class' => array('container-inline')), '#title' => t('Show a comment field in the workflow section of the workflow tab form?'), '#default_value' => isset($workflow->options['comment_log_tab']) ? $workflow->options['comment_log_tab'] : 0, '#description' => t('On the workflow tab, a Comment form can be shown 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.'), ); $form['watchdog'] = array( '#type' => 'fieldset', '#title' => t('Watchdog Logging for Workflow'), ); $form['watchdog']['watchdog_log'] = array( '#type' => 'radios', '#options' => $noyes, '#attributes' => array('class' => array('container-inline')), '#title' => t('Log informational watchdog messages when a transition is executed (state of a node is changed)?'), '#default_value' => isset($workflow->options['watchdog_log']) ? $workflow->options['watchdog_log'] : 0, '#description' => t('Optionally log transition state changes to watchdog.'), ); $form['tab'] = array( '#type' => 'fieldset', '#title' => t('Workflow tab permissions'), '#collapsible' => TRUE, '#collapsed' => FALSE, ); $form['tab']['tab_roles'] = array( '#type' => 'checkboxes', '#options' => workflow_admin_ui_get_roles(), '#default_value' => explode(',', $workflow->tab_roles), '#description' => t('Select any roles that should have access to the workflow tab on nodes that have a workflow.'), ); $form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration')); return $form; } /** * Theme the workflow editing form. * * @see workflow_edit_form() */ function theme_workflow_admin_ui_edit_form($variables) { $output = ''; $form = $variables['form']; $wid = $form['wid']['#value']; $workflow = $form['#workflow']; // If we don't have a workflow here, we need to go back to admin. if ($workflow) { drupal_set_title(t('Edit workflow %name', array('%name' => $workflow->name)), PASS_THROUGH); $output .= drupal_render($form['wf_name']); $output .= drupal_render_children($form); return $output; } else { drupal_goto('admin/config/workflow/workflow'); } } /** * Validate the workflow editing form. * * @see workflow_edit_form() */ function workflow_admin_ui_edit_form_validate($form_id, $form_state) { $wid = $form_state['values']['wid']; // Make sure workflow name is not a duplicate. if (array_key_exists('wf_name', $form_state['values']) /*&& $form_state['values']['wf_name'] != ''*/) { $workflow_name = $form_state['values']['wf_name']; $workflows = array(); foreach (workflow_get_workflows() as $data) { $workflows[$data->wid] = check_plain(t($data->name)); } $workflows = array_flip($workflows); if (array_key_exists($workflow_name, $workflows) && $wid != $workflows[$workflow_name]) { form_set_error('wf_name', t('A workflow with the name %name already exists. Please enter another name for this workflow.', array('%name' => $workflow_name))); } } } /** * Submit handler for the workflow editing form. * * @see workflow_edit_form() */ function workflow_admin_ui_edit_form_submit($form, &$form_state) { if (isset($form_state['values']['transitions'])) { workflow_update_transitions($form_state['values']['transitions']); } $options = array( 'comment_log_node' => $form_state['values']['comment_log_node'], 'comment_log_tab' => $form_state['values']['comment_log_tab'], 'name_as_title' => $form_state['values']['name_as_title'], 'watchdog_log' => $form_state['values']['watchdog_log'], ); $workflow = array( 'wid' => $form_state['values']['wid'], 'name' => $form_state['values']['wf_name'], 'tab_roles' => array_filter($form_state['values']['tab_roles']), 'options' => serialize($options), ); workflow_update_workflows($workflow); drupal_set_message(t('The workflow was updated.')); $form_state['redirect'] = 'admin/config/workflow/workflow/' . $form_state['values']['wid']; } /** * Menu callback. Edit a workflow's transitions. * * @param $wid * The workflow object. * @return * HTML form and permissions table. */ function workflow_admin_ui_transitions_form($form, $form_state, $workflow) { // Make sure we have a workflow. if ($workflow) { // Create breadcrumbs. workflow_admin_ui_breadcrumbs($workflow); $form = array(); $form['wid'] = array('#type' => 'value', '#value' => $workflow->wid); $form['#workflow'] = $workflow; $form['transitions'] = workflow_admin_ui_transition_grid_form($workflow); $form['submit'] = array('#type' => 'submit', '#value' => t('Save configuration')); return $form; } } /** * Validate the workflow editing form. * * @see workflow_edit_form() */ function workflow_admin_ui_transitions_form_validate($form, $form_state) { $wid = $form['wid']['#value']; $workflow = $form['#workflow']; // Make sure 'author' is checked for (creation) -> [something]. $creation_id = workflow_get_creation_state_by_wid($wid); if (isset($form_state['values']['transitions'][$creation_id]) && is_array($form_state['values']['transitions'][$creation_id])) { foreach ($form_state['values']['transitions'][$creation_id] as $to => $roles) { if ($roles['author']) { $author_has_permission = TRUE; break; } } } $state_count = db_query('SELECT COUNT(sid) FROM {workflow_states} WHERE wid = :wid', array(':wid' => $wid))->fetchField(); if (empty($author_has_permission) && $state_count > 1) { form_set_error('transitions', t('Please give the author permission to go from %creation to at least one state!', array('%creation' => t('(creation)')))); } } /** * Theme the workflow editing form. * * @see workflow_edit_form() */ function theme_workflow_admin_ui_transitions_form($variables) { $output = ''; $form = $variables['form']; $wid = $form['wid']['#value']; $workflow = $form['#workflow']; // If we don't have a workflow here, we need to go back to admin. if ($workflow) { drupal_set_title(t('Edit workflow %name transitions', array('%name' => $workflow->name)), PASS_THROUGH); $output .= drupal_render($form['wf_name']); $states = workflow_get_workflow_states_by_wid($wid, array('status' => 1)); if ($states) { $roles = workflow_admin_ui_get_roles(); $header = array(array('data' => t('From / To') . '  ' . WORKFLOW_ARROW)); $rows = array(); foreach ($states as $state) { $state_id = $state->sid; $name = $state->state; // Don't allow transition TO (creation). if ($name != t('(creation)')) { $header[] = array('data' => check_plain(t($name))); } $row = array(array('data' => check_plain(t($name)))); foreach ($states as $nested_state) { $nested_state_id = $nested_state->sid; $nested_name = $nested_state->state; if ($nested_name == t('(creation)')) { // Don't allow transition TO (creation). continue; } if ($nested_state_id != $state_id) { // Need to render checkboxes for transition from $state to $nested_state. $from = $state_id; $to = $nested_state_id; $cell = ''; foreach ($roles as $rid => $role_name) { $cell .= drupal_render($form['transitions'][$from][$to][$rid]); } $row[] = array('data' => $cell); } else { $row[] = array('data' => ''); } } $rows[] = $row; } $output .= theme('table', array('header' => $header, 'rows' => $rows)); } else { $output = t('There are no states defined for this workflow.'); } $output .= drupal_render_children($form); return $output; } } /** * Submit handler for the workflow editing form. * * @see workflow_edit_form() */ function workflow_admin_ui_transitions_form_submit($form, &$form_state) { $workflow = $form['#workflow']; if (isset($form_state['values']['transitions'])) { workflow_update_transitions($form_state['values']['transitions']); } drupal_set_message(t('The workflow was updated.')); $form_state['redirect'] = 'admin/config/workflow/workflow/' . $form_state['values']['wid']; } /** * Form builder. Build the grid of transitions for defining a workflow. * * @param int $wid * The workflow object. */ function workflow_admin_ui_transition_grid_form($workflow) { $form = array('#tree' => TRUE); $roles = workflow_admin_ui_get_roles(); $states = workflow_get_workflow_states_by_wid($workflow->wid, array('status' => 1)); if (!$states) { $form['error'] = array( '#type' => 'markup', '#value' => t('There are no states defined for this workflow.'), ); return $form; } foreach ($states as $state1) { $state_id = $state1->sid; $name = $state1->state; foreach ($states as $state2) { $nested_state_id = $state2->sid; $nested_name = $state2->state; if ($nested_name == t('(creation)')) { // Don't allow transition TO (creation). continue; } if ($nested_state_id != $state_id) { // Need to generate checkboxes for transition from $state to $nested_state. $from = $state_id; $to = $nested_state_id; foreach ($roles as $rid => $role_name) { $checked = FALSE; if ($transition = workflow_get_workflow_transitions_by_sid_target_sid($from, $to)) { $checked = workflow_transition_allowed($transition->tid, $rid); } $form[$from][$to][$rid] = array( '#type' => 'checkbox', '#title' => check_plain($role_name), '#default_value' => $checked, ); } } } } return $form; } /** * Implements hook_workflow_operations(). * Might as well eat our own cooking. */ function workflow_admin_ui_workflow_operations($op, $workflow = NULL, $state = NULL) { switch ($op) { case 'top_actions': // Build a link to each workflow. $workflows = workflow_get_workflows(); $alt = t('Add a new workflow'); $actions = array( 'add-workflow' => array( 'title' => t('Add workflow'), // @TODO: It might be more sane to go to the "settings" page. 'href' => 'admin/config/workflow/workflow/add', 'attributes' => array('alt' => $alt, 'title' => $alt), ), ); foreach ($workflows as $workflow) { $alt = t('Work with @wf', array('@wf' => $workflow->name)); $actions[drupal_html_class($workflow->name)] = array( 'title' => t($workflow->name), 'href' => "admin/config/workflow/workflow/$workflow->wid", 'attributes' => array('alt' => $alt, 'title' => $alt), ); } return $actions; case 'workflow': $actions = array( 'workflow_settings' => array( 'title' => t('Settings'), 'href' => "admin/config/workflow/workflow/edit/$workflow->wid", 'attributes' => array('alt' => t('Edit the @wf settings', array('@wf' => $workflow->name))), ), 'workflow_transitions' => array( 'title' => t('Transitions'), 'href' => "admin/config/workflow/workflow/transitions/$workflow->wid", 'attributes' => array('alt' => t('Edit the @wf transitios', array('@wf' => $workflow->name))), ), 'workflow_permission_summary' => array( 'title' => t('Summary'), 'href' => "admin/config/workflow/workflow/perm_summary/$workflow->wid", 'attributes' => array('alt' => t('See a summary of the @wf transitios', array('@wf' => $workflow->name))), ), 'workflow_overview_delete' => array( 'title' => t('Delete'), 'href' => "admin/config/workflow/workflow/delete/$workflow->wid", 'attributes' => array('alt' => t('Delete the @wf', array('@wf' => $workflow->name))), ), ); foreach ($actions as $name => $link) { $actions[$name]['attributes']['title'] = $actions[$name]['attributes']['alt']; } return $actions; } } /** * Menu callback. Create the main workflow page, which gives an overview * of workflows and workflow states. * Replaced by http://drupal.org/node/1367530. */ function workflow_admin_ui_overview_form($form, $form_state, $workflow) { $bc = array(l(t('Home'), '')); $bc[] = l(t('Configuration'), 'admin/config'); $bc[] = l(t('Workflow'), 'admin/config/workflow'); $bc[] = l(t('Workflow'), 'admin/config/workflow/workflow'); drupal_set_breadcrumb($bc); $form = array('#tree' => TRUE); $form['wid'] = array('#type' => 'value', '#value' => $workflow->wid); $form['#wf_name'] = $workflow->name; // Get the state objects and make the array keys the same as the sids. $states = workflow_get_workflow_states_by_wid($workflow->wid); $sids = array(); foreach ($states as $state) { $sids[] = $state->sid; } $states = array_combine($sids, $states); // Save the list in the form for later. $form['#workflow_states'] = $states; // Allow modules to insert their own workflow operations. $links = module_invoke_all('workflow_operations', 'workflow', $workflow); drupal_set_title(t('Workflow: ') . t($workflow->name)); $links_args = array( 'links' => $links, 'attributes' => array('class' => array('inline', 'action-links')), ); $form['action-links'] = array( '#type' => 'markup', '#markup' => theme('links', $links_args), ); // Build select options for reassigning states. // We put a blank state first for validation. $state_list = array('' => ' '); foreach ($states as $sid => $state) { // Leave out the creation state and any inactive states. if ($state->sysid == 0 && $state->status == 1) { $state_list[$sid] = $state->state; } } // Is this the last state available? $form['#last'] = count($state_list) == 2; // Dummy object for new state item. $states[] = (object) array( 'sid' => 0, 'state' => '', 'status' => 1, 'sysid' => 0, 'weight' => 99, ); foreach ($states as $state) { // Allow modules to insert state operations. $state_links = module_invoke_all('workflow_operations', 'state', $workflow, $state); $form['states'][$state->sid] = array( 'state' => array( '#type' => 'textfield', '#size' => 30, '#maxlength' => 255, '#default_value' => $state->state, ), 'weight' => array( '#type' => 'weight', '#delta' => 20, '#default_value' => $state->weight, '#title-display' => 'invisible', '#attributes' => array('class' => array('state-weight')), ), 'status' => array( '#type' => 'checkbox', '#default_value' => $state->status, ), // Save the original status for the validation handler. 'orig_status' => array( '#type' => 'value', '#value' => $state->status, ), 'reassign' => array( '#type' => 'select', '#options' => $state_list, ), 'count' => array( '#type' => 'value', '#value' => count(workflow_get_workflow_node_by_sid($state->sid)), ), 'ops' => array( '#type' => 'markup', '#markup' => theme('links', array('links' => $state_links)), ), 'sysid' => array( '#type' => 'value', '#value' => $state->sysid, ), ); // Don't let the creation state change weight or status or name. if ($state->sid == $workflow->creation_state) { $form['states'][$state->sid]['weight']['#value'] = -50; $form['states'][$state->sid]['sysid']['#value'] = 1; $form['states'][$state->sid]['state']['#disabled'] = TRUE; $form['states'][$state->sid]['status']['#disabled'] = TRUE; $form['states'][$state->sid]['reassign']['#disabled'] = TRUE; } } $form['instructions'] = array( '#type' => 'markup', '#markup' => '

' . t('You may enter a new state name in the empty space. Check the "Active" box to make it effective. You may also drag it to the appropriate position.') . '

' . '

' . t('A state not marked as active will not be shown as available in the workflow settings.') . '

' . '

' . t('If you wish to inactivate a state that has content (i.e. count is not zero), then you need to select a state to which to reassign that content.') . '

' ); $form['actions'] = array('#type' => 'actions'); $form['actions']['submit'] = array( '#type' => 'submit', '#value' => t('Save Changes')); return $form; } function theme_workflow_admin_ui_overview_form($variables) { $form = $variables['form']; $output = ''; $table_id = 'workflow_admin_ui_overview'; $table = array( 'rows' => array(), 'header' => array( t('State'), t('Weight'), t('Active'), t('Reassign'), t('Count'), array('data' => t('Operations'), 'class' => 'state-ops'), ), 'attributes' => array('id' => $table_id, 'style' => 'width: auto;'), ); // The out put needs to have the action links at the top. $output .= drupal_render($form['action-links']); // Iterate over each element in our $form['states'] array foreach (element_children($form['states']) as $id) { // We are now ready to add each element of our $form data to the rows // array, so that they end up as individual table cells when rendered // in the final table. We run each element through the drupal_render() // function to generate the final html markup for that element. $table['rows'][] = array( 'data' => array( // Add our 'name' column array('data' => drupal_render($form['states'][$id]['state']), 'class' => 'state-name'), // Add our 'weight' column drupal_render($form['states'][$id]['weight']), // Add our 'status' column array('data' => drupal_render($form['states'][$id]['status']), 'class' => 'state-status'), // Add our 'reassign' column array('data' => drupal_render($form['states'][$id]['reassign']), 'class' => 'state-reassign'), // Add our 'count' column array('data' => $form['states'][$id]['count']['#value'], 'class' => 'state-count'), // Add our 'operations' column array('data' => drupal_render($form['states'][$id]['ops']), 'class' => 'state-ops'), // Add our 'sysid' column drupal_render($form['states'][$id]['sysid']), ), // To support the tabledrag behaviour, we need to assign each row of the // table a class attribute of 'draggable'. This will add the 'draggable' // class to the element for that row when the final table is // rendered. 'class' => array('draggable'), ); } $output .= theme('table', $table); // And then render any remaining form elements (such as our submit button) $output .= drupal_render_children($form); // We now call the drupal_add_tabledrag() function in order to add the // tabledrag.js goodness onto our page. // // For a basic sortable table, we need to pass it: // - the $table_id of our element, // - the $action to be performed on our form items ('order'), // - a string describing where $action should be applied ('siblings'), // - and the class of the element containing our 'weight' element. drupal_add_tabledrag($table_id, 'order', 'sibling', 'state-weight'); return $output; } /** * Validation handler for the state form. */ function workflow_admin_ui_overview_form_validate($form, &$form_state) { // Get the workflow id. $wid = $form_state['values']['wid']; // Get the state objects. $states = $form['#workflow_states']; // Because the form elements were keyed with the item ids from the database, // we can simply iterate through the submitted values. foreach ($form_state['values']['states'] as $sid => $item) { // Do they want to deactivate the state? if (($item['status'] != $item['orig_status']) && ($item['status'] == 0)) { // Does that state have nodes in it? if ($item['count'] > 0 && empty($item['reassign'])) { if ($form['#last']) { drupal_set_message(t('Since you are deleting the last available workflow state in this workflow, all content items which are in that state will have their workflow state removed.'), 'warning'); } else { form_set_error("states'][$sid]['reassign'", t('The %state state has content; you must reassign the content to another state.', array('%state' => $states[$sid]->state))); } } } } } /** * Submission handler for the state form. */ function workflow_admin_ui_overview_form_submit($form, &$form_state) { // Get the workflow id, then save it for the next round. $wid = $form_state['values']['wid']; $wf_name = $form['#wf_name']; // Get the state objects. $states = $form['#workflow_states']; // Because the form elements were keyed with the item ids from the database, // we can simply iterate through the submitted values. foreach ($form_state['values']['states'] as $id => $item) { $item['sid'] = $id; $item['wid'] = $wid; // Is there not a new state name? if (empty($item['state'])) { // No new state entered, so skip it. continue; } // Is this a new state? if ($id == 0) { // Remove the key for the updating. unset($item['sid']); } // Do they want to deactivate the state? if ($item['status'] == 0) { // Does that state have nodes in it? if ($item['count'] > 0) { if ($form['#last']) { $new_sid = NULL; drupal_set_message(t('Removing workflow states from content in the %workflow.', array('%workflow' => $wf_name))); } else { // Prepare the state delete function. $new_sid = $item['reassign']; drupal_set_message(t('Reassigning content from %old_state to %new_state.', array('%old_state' => $states[$id]->state, '%new_state' => $states[$new_sid]->state))); } // Delete the old state without orphaning nodes, move them to the new state. workflow_delete_workflow_states_by_sid($id, $new_sid); $args = array( '%state' => $states[$id]->state, '%workflow' => $wf_name, ); watchdog('workflow', 'Deactivated workflow state %state in %workflow.', $args); drupal_set_message(t('The workflow state %state was deleted from %workflow.', $args)); } } // Call the update function. workflow_update_workflow_states($item); } $form_state['redirect'] = "admin/config/workflow/workflow/$wid"; } /** * Form builder. Allow administrator to map workflows to content types * and determine placement. */ function workflow_admin_ui_types_form($form) { $form = array(); // Allow modules to insert their own action links. $links = module_invoke_all('workflow_operations', 'top_actions', NULL); $links_args = array( 'links' => $links, 'attributes' => array('class' => array('inline', 'action-links')), ); $form['action-links'] = array( '#type' => 'markup', '#markup' => theme('links', $links_args), ); $these_workflows = array(); foreach (workflow_get_workflows() as $data) { // Don't allow workflows with no states. $states = workflow_get_workflow_states_by_wid($data->wid, array('status' => 1)); // There should always be a creation state. if (count($states) == 1) { // That's all, so let's remind them to create some states. drupal_set_message(t('%workflow has no states defined, so it cannot be assigned to content yet.', array('%workflow' => ucwords($data->name))), 'warning'); // Skip allowing this workflow. continue; } // Also check for transitions at least out of the creation state. // This always gets at least the "from" state. $transitions = workflow_allowable_transitions($data->creation_state, 'to'); if (count($transitions) == 1) { // That's all, so let's remind them to create some transitions. drupal_set_message(t('%workflow has no transitions defined, so it cannot be assigned to content yet.', array('%workflow' => ucwords($data->name))), 'warning'); // Skip allowing this workflow. continue; } // Add this workflow to the allowed list. $these_workflows[$data->wid] = t($data->name); } $workflows = array(t('None')) + $these_workflows; if (count($workflows) == 1) { drupal_set_message(t('You must create at least one workflow before content can be assigned to a workflow.')); return $form; } $type_map = workflow_get_workflow_type_map(); $form['#tree'] = TRUE; $form['help'] = array( '#type' => 'item', '#title' => '

' . t('Content Type Mapping') . '

', '#markup' => t('Each content type may have a separate workflow. The form for changing workflow state can be ' . 'displayed when editing a node, editing a comment for a node, or both.'), ); $placement_options = array( 'node' => t('Post'), 'comment' => t('Comment'), ); foreach (node_type_get_names() as $type => $name) { $form[$type]['workflow'] = array( '#type' => 'select', '#options' => $workflows, '#default_value' => isset($type_map[$type]) ? $type_map[$type] : 0, ); $form[$type]['placement'] = array( '#type' => 'checkboxes', '#options' => $placement_options, '#default_value' => variable_get('workflow_' . $type, array()), ); } $form['submit'] = array( '#type' => 'submit', '#value' => t('Save workflow mapping'), '#weight' => 100, ); return $form; } /** * Theme the workflow type mapping form. */ function theme_workflow_admin_ui_types_form($variables) { $output = ''; $form = $variables['form']; // Do the action links at the top. $output .= drupal_render($form['action-links']); $header = array(t('Content Type'), t('Workflow'), t('Display Workflow Form on:')); $rows = array(); foreach (node_type_get_names() as $type => $name) { $rows[] = array( check_plain(t($name)), drupal_render($form[$type]['workflow']), drupal_render($form[$type]['placement']), ); } $output .= drupal_render($form['help']); $output .= theme('table', array( 'header' => $header, 'rows' => $rows, 'attributes' => array('style' => 'width: auto; clear: both;'), )); return $output . drupal_render_children($form); } /** * Submit handler for workflow type mapping form. * * @see workflow_types_form() */ function workflow_admin_ui_types_form_submit($form, &$form_state) { workflow_admin_ui_types_save($form_state['values']); drupal_set_message(t('The workflow mapping was saved.')); } /** * Get a list of roles. * * @return * Array of role names keyed by role ID, including the 'author' role. */ function workflow_admin_ui_get_roles() { static $roles = NULL; if (!$roles) { $roles = array('author' => 'author'); $list = user_roles(FALSE, 'participate in workflow'); foreach ($list as $rid => $name) { $roles[$rid] = check_plain($name); } } return $roles; } /** * Save mapping of workflow to node type. E.g., the story node type is using the Foo workflow. * * @param $form_state['values'] */ function workflow_admin_ui_types_save($form_values) { // Empty the table so that types no longer under workflow go away. workflow_delete_workflow_type_map_all(); $node_types = node_type_get_names(); foreach ($node_types as $type => $name) { $data = array( 'type' => $type, 'wid' => $form_values[$type]['workflow'], ); workflow_insert_workflow_type_map($data); variable_set('workflow_' . $type, array_keys(array_filter(($form_values[$type]['placement'])))); // If this type uses workflow, make sure pre-existing nodes are set // to the workflow's creation state. if ($form_values[$type]['workflow']) { workflow_initialize_nodes($type, $name); } } } /** * Initialize all pre-existing nodes of a type to their first state.. * * @param $type - node type * @param $name - node type name */ function workflow_initialize_nodes($type, $name) { // Build the select query. // We want all published nodes of this type that don't already have a workflow state. $query = db_select('node', 'n'); $query->leftJoin('workflow_node', 'wn', 'wn.nid = n.nid'); // Add the fields. $query->addField('n', 'nid'); // Add conditions. $query->condition('n.type', $type); $query->condition('n.status', 1); $query->isNull('wn.sid'); $nids = $query->execute()->fetchCol(); $how_many = count($nids); if ($how_many == 0) { return; } $comment = t('Pre-existing content set to initial state.'); // Get the initial state for this content type. $first_state = db_query("SELECT s.sid " . "FROM {workflow_type_map} m " . "INNER JOIN {workflow_states} s ON s.wid = m.wid " . "WHERE m.type = :type AND s.sysid <> :creation " . "ORDER BY s.weight ASC ", array(':type' => $type, ':creation' => WORKFLOW_CREATION) )->fetchField(0); // Load them all up. $nodes = node_load_multiple($nids); foreach ($nodes as $node) { // Force it to transition to the first state and get a history record. workflow_execute_transition($node, $first_state, $comment, TRUE); } return; drupal_set_message(t('!count @type nodes have been initialized.', array('@type' => $name, '!count' => $how_many))); } /** * Update the transitions for a workflow. * * @param array $transitions from values. * Transitions, for example: * 18 => array( * 20 => array( * 'author' => 1, * 1 => 0, * 2 => 1, * ) * ) * means the transition from state 18 to state 20 can be executed by * the node author or a user in role 2. The $transitions array should * contain ALL transitions for the workflow. */ function workflow_update_transitions($transitions = array()) { // Empty string is sometimes passed in instead of an array. if (!$transitions) { return; } foreach ($transitions as $from => $to_data) { foreach ($to_data as $to => $role_data) { foreach ($role_data as $role => $can_do) { if ($can_do) { $transition = array( 'sid' => $from, 'target_sid' => $to, 'roles' => $role, ); workflow_update_workflow_transitions($transition); } else { $roles = array(); if ($transition = workflow_get_workflow_transitions_by_sid_target_sid($from, $to)) { $roles = explode(',', $transition->roles); $tid = $transition->tid; if (($i = array_search($role, $roles)) !== FALSE) { unset($roles[$i]); workflow_update_workflow_transitions_roles($tid, $roles); } } } } } } workflow_delete_workflow_transitions_by_roles(''); }