$row->label,
'#states' => unserialize($row->states),
'can_edit' => TRUE,
'custom_sid' => $row->sid,
);
if ($row->init_state != '') {
$state['#init_state'] = $row->init_state;
}
if ($row->entity_type == STATES_ADMIN_ENTITY_NODE) {
$state['#entity'] = 'node';
if ($row->entity_filter !== '') {
$state['#types'] = unserialize($row->entity_filter);
}
}
else if ($row->entity_type == STATES_ADMIN_ENTITY_USER) {
$state['#entity'] = 'user';
if ($row->entity_filter !== '') {
$state['#roles'] = unserialize($row->entity_filter);
}
}
$machines['custom_state_'. $row->sid] = $state;
}
return $machines;
}
/**
* Implementation of hook_perm().
*/
function states_perm() {
return array('administer custom state machines');
}
/**
* Implementation of hook_menu().
*/
function states_menu($may_cache) {
$items = array();
if ($may_cache) {
$items[] = array(
'path' => STATES_ADMIN_UI_PATH,
'title' => t('Custom state machines'),
'description' => t('View, edit, create and delete custom state machines.'),
'callback' => 'drupal_get_form',
'callback arguments' => array('states_admin_overview', NULL),
'access' => user_access('administer custom state machines'),
'type' => MENU_NORMAL_ITEM,
'weight' => -1,
);
$items[] = array(
'path' => STATES_ADMIN_UI_PATH .'/0/list',
'title' => t('Overview'),
'weight' => -5,
'type' => MENU_DEFAULT_LOCAL_TASK,
);
$items[] = array(
'path' => STATES_ADMIN_UI_PATH .'/0/add',
'title' => t('Create a new state machine'),
'type' => MENU_LOCAL_TASK,
'callback' => 'drupal_get_form',
'callback arguments' => array('states_admin_create'),
'access' => user_access('administer custom state machines'),
);
}
else if (arg(0) == 'admin' && arg(1) == 'workflow-ng' && arg(2) == 'state-machines') {
$machines = states_get_machines();
if (isset($machines[arg(3)]) && $machines[arg(3)]['can_edit'] == TRUE) {
$label = $machines[arg(3)]['#label'];
$items[] = array(
//this item lets the menu system create a nicer breadcrumb
'path' => STATES_ADMIN_UI_PATH .'/'. arg(3),
'title' => check_plain($label),
'type' => MENU_CALLBACK,
);
$items[] = array(
'path' => STATES_ADMIN_UI_PATH .'/'. arg(3) .'/edit',
'title' => t('Edit'),
'type' => MENU_CALLBACK,
'callback' => 'drupal_get_form',
'callback arguments' => array('states_admin_edit', arg(3)),
'access' => user_access('administer custom state machines'),
);
$items[] = array(
'path' => STATES_ADMIN_UI_PATH .'/'. arg(3) .'/delete',
'title' => t('Delete'),
'type' => MENU_CALLBACK,
'callback' => 'drupal_get_form',
'callback arguments' => array('states_admin_delete', arg(3)),
'access' => user_access('administer custom state machines'),
);
}
}
return $items;
}
function states_admin_overview() {
drupal_set_title(t('State machines overview'));
states_clear_machine_cache();
$form['nodes_header'] = array('#value' => '
'. t('Content state machines') .'
');
$form['nodes'] = states_admin_overview_table('node');
$form['users_header'] = array('#value' => ''. t('User state machines') .'
');
$form['users'] = states_admin_overview_table('user');
$form['#redirect'] = FALSE;
return $form;
}
function states_admin_overview_table($entity) {
$machines = states_get_machines();
$header = array(t('Label'), t('States'), t('Initial state'), t('Attribute name'), $entity=='node' ? t('Content types') : t('User roles'), t('Operations'));
$rows = array();
foreach ($machines as $name => $machine) {
if ($machine['#entity'] == $entity && isset($machine['can_edit']) && $machine['can_edit'] == TRUE) {
$states = t('(any)');
if ($machine['#states'] != '*') {
$states = theme('item_list', $machine['#states']);
}
$filter = t('(any)');
if ($entity == 'node' && isset($machine['#types'])) {
$filter = array();
$node_names = node_get_types('names');
foreach ($machine['#types'] as $value) {
$filter[] = $node_names[$value];
}
if (count($filter) > 0) {
$filter = theme('item_list', $filter);
}
else {
$filter = t('(none)');
}
}
else if ($entity == 'user' && isset($machine['#roles'])) {
$filter = array();
$role_names = user_roles();
foreach ($machine['#roles'] as $value) {
$filter[] = $role_names[$value];
}
if (count($filter) > 0) {
$filter = theme('item_list', $filter);
}
else {
$filter = t('(none)');
}
}
$ops = array();
$path = STATES_ADMIN_UI_PATH .'/'. $name;
$ops[] = l(t('edit'), $path .'/edit');
$ops[] = l(t('delete'), $path .'/delete');
$rows[] = array(
check_plain($machine['#label']),
$states,
isset($machine['#init_state']) ? check_plain($machine['#init_state']) : t('(none)'),
check_plain(isset($machine['#attribute_name']) ? isset($machine['#attribute_name']) : $name),
$filter,
implode(' ', $ops),
);
}
}
if (count($rows)) {
return array('#value' => theme('table', $header, $rows, array('class' => 'state-configurations')));
}
return array('#value' => t('None.'));
}
function states_admin_trim_value(&$value) {
$value = trim($value);
}
/**
* Helper function for states_admin_create. Addresses a bug in the D5 Forms API;
* multistep forms do not honour default values for checkboxes.
*/
function states_admin_checkboxes_multistep_fix($elements) {
foreach (element_children($elements) as $key) {
if ($elements[$key]['#type'] == 'checkbox' && isset($elements[$key]['#default_value']) && $elements[$key]['#default_value'] != 0) {
$elements[$key]['#attributes']['checked'] = 'checked';
}
}
return $elements;
}
function states_admin_create($form_values = NULL) {
if (!isset($form_values)) {
$form_values = array(
'step' => 0,
'saved' => serialize(array()),
// Some decent default values for a new state machine:
'label' => '',
'entity' => STATES_ADMIN_ENTITY_NODE,
'any_state' => 0,
'state_list' => array(),
'initial_state' => 0,
'any_entity' => 0,
'filters' => array(),
);
}
return states_admin_form($form_values);
}
/**
* The custom state creation / editing multistep form
*/
function states_admin_form($form_values) {
// Merge the saved values into the form_values
$saved = unserialize($form_values['saved']);
unset($form_values['saved']); // remove the saved values from the form values so they aren't in both parts of the merge
if (isset($form_values['unset'])) {
// Changing the any_state option will invalidate the initial_state option and the state_list (this has to be done before the unset; silly FAPI checkbox)
if ($form_values['unset'] == 'any_state' && (($saved['any_state'] == 1) != ($form_values['any_state'] == 1))) {
if ($form_values['any_state'] == 1) {
$form_values['state_list'] = '*';
$form_values['initial_state'] = '';
}
else {
$form_values['state_list'] = array();
$form_values['initial_state'] = 0;
}
}
// To allow checkboxes to be unticked, they have to be zeroed in the saved values, as they only appear in the new values if they are ticked
// Similarly, checkbox groups only appear if one or more was ticked. If they were all unticked, they don't appear in new values.
$saved_unset = $form_values['unset'];
unset($form_values['unset']);
if (is_array($saved[$saved_unset])) {
$saved[$saved_unset] = array();
}
else {
$saved[$saved_unset] = 0;
}
}
$form_values = array_merge($saved, $form_values);
// Do any processing of the results from the previous step
$from_step = $form_values['step'];
// The state list is changed from a newline delimited string into an array. It is the only value not left as-is until the
// form submission. This is because it is much more useful to us as an array.
if ($from_step == STATES_ADMIN_STEP_STATES) {
$states = explode("\n", $form_values['state_list']);
array_walk($states, 'states_admin_trim_value');
$form_values['state_list'] = $states;
}
// Changing the entity type option will invalidate the filters option (as node filters and role filters are very different)
if (($saved['entity'] == STATES_ADMIN_ENTITY_NODE) != ($form_values['entity'] == STATES_ADMIN_ENTITY_NODE)) {
$form_values['filters'] = array();
}
// Move to the next step or previous step
if ($form_values['op'] == t('Back')) {
$step = $from_step - 1;
if ($step == STATES_ADMIN_STEP_FILTERS && $form_values['any_entity'] == 1) {
// The filters step is skipped if any entity is allowed
$step = STATES_ADMIN_STEP_OPTIONS;
}
if ($step == STATES_ADMIN_STEP_STATES && $form_values['any_state'] == 1) {
// The state list step is skipped if any state is allowed
$step = STATES_ADMIN_STEP_BASIC;
}
}
else {
$step = $from_step + 1;
if ($step == STATES_ADMIN_STEP_STATES && $form_values['any_state'] == 1) {
// The state list step is skipped if any state is allowed
$step = STATES_ADMIN_STEP_OPTIONS;
}
if ($step == STATES_ADMIN_STEP_FILTERS && $form_values['any_entity'] == 1) {
// The filters step is skipped if any entity is allowed
$step = STATES_ADMIN_STEP_REVIEW;
}
}
// Store persistent variables
$form_values['step'] = $step;
$form['saved'] = array(
'#type' => 'hidden',
'#value' => serialize($form_values),
);
// Make the new page of the wizard
switch ($step) {
case STATES_ADMIN_STEP_BASIC:
$form['label'] = array(
'#type' => 'textfield',
'#title' => t('Label'),
'#maxlength' => 127,
'#description' => t('Choose an appropriate label for the new state machine.'),
'#required' => TRUE,
'#default_value' => $form_values['label'],
);
$form['entity'] = array(
'#type' => 'select',
'#title' => t('Entity'),
'#options' => array(
STATES_ADMIN_ENTITY_NODE => t('Content'),
STATES_ADMIN_ENTITY_USER => t('User'),
),
'#description' => t('Select the type of entity you want the state machine to track.'),
'#required' => TRUE,
'#default_value' => $form_values['entity'],
);
$form['any_state'] = array(
'#type' => 'checkbox',
'#title' => t('Allow any state'),
'#default_value' => $form_values['any_state'],
'#description' => t('Allows the state machine to have any state, rather than requiring one from a defined pool.'),
);
$form['unset'] = array(
'#type' => 'hidden',
'#value' => 'any_state',
);
$form = states_admin_checkboxes_multistep_fix($form); // Workaround FAPI checkbox multistep bug
break;
case STATES_ADMIN_STEP_STATES:
$form['state_list'] = array(
'#type' => 'textarea',
'#title' => t('Machine states'),
'#description' => t('List the possible states of the state machine, one per line.'),
'#required' => TRUE,
'#default_value' => implode("\n", $form_values['state_list']),
);
break;
case STATES_ADMIN_STEP_OPTIONS:
if ($form_values['any_state'] == 1) {
$form['initial_state'] = array(
'#title' => t('Initial state'),
'#type' => 'textfield',
'#maxlength' => 127,
'#description' => t('Choose a initial state for entities, or leave blank to leave entities with no state.'),
'#default_value' => $form_values['initial_state'],
);
}
else {
$states = $form_values['state_list'];
array_unshift($states, t('(none)'));
$form['initial_state'] = array(
'#title' => t('Initial state'),
'#type' => 'select',
'#options' => $states,
'#description' => t('Choose a initial state for entities.'),
'#required' => TRUE,
'#default_value' => $form_values['initial_state'],
);
}
$form['any_entity'] = array(
'#type' => 'checkbox',
'#default_value' => $form_values['any_entity'],
);
$form['unset'] = array(
'#type' => 'hidden',
'#value' => 'any_entity',
);
if ($form_values['entity'] == STATES_ADMIN_ENTITY_NODE) {
$form['any_entity']['#title'] = t('Allow any content type');
$form['any_entity']['#description'] = t('Allows the state machine to work on all content types, rather than ones from a defined pool.');
}
else {
$form['any_entity']['#title'] = t('Allow any user role');
$form['any_entity']['#description'] = t('Allows the state machine to work on all user roles, rather than ones from a defined pool.');
}
$form = states_admin_checkboxes_multistep_fix($form); // Workaround FAPI checkbox multistep bug
break;
case STATES_ADMIN_STEP_FILTERS:
$form['filters'] = array(
'#type' => 'checkboxes',
'#process' => array('expand_checkboxes' => array(), 'states_admin_checkboxes_multistep_fix' => array()), // Workaround FAPI checkbox multistep bug
'#default_value' => $form_values['filters'],
);
if ($form_values['entity'] == STATES_ADMIN_ENTITY_NODE) {
$form['filters']['#title'] = t('Permitted content types');
$form['filters']['#description'] = t('The state machine is only valid for the checked content types.');
$form['filters']['#options'] = node_get_types('names');
}
else {
$form['filters']['#title'] = t('Permitted user roles');
$form['filters']['#description'] = t('The state machine is only valid for the checked user roles.');
$form['filters']['#options'] = user_roles();
}
$form['unset'] = array(
'#type' => 'hidden',
'#value' => 'filters',
);
break;
case STATES_ADMIN_STEP_REVIEW:
$form['review_label'] = array(
'#prefix' => ''. t('You are about to create the following state machine:') .'
',
'#type' => 'item',
'#title' => t('Label'),
'#value' => $form_values['label'],
);
if (isset($form_values['sid_to_update'])) {
$form['review_label']['#prefix'] = ''. t('You are about to update the state machine to the following:') .'
';
}
$form['review_entity'] = array(
'#type' => 'item',
'#title' => t('Entity'),
'#value' => $form_values['entity'] == STATES_ADMIN_ENTITY_NODE ? t('Content') : t('User'),
);
$form['review_states'] = array(
'#type' => 'item',
'#title' => t('States'),
'#value' => t('(any)'),
);
if ($form_values['any_state'] != 1) {
$form['review_states']['#value'] = theme('item_list', $form_values['state_list']);
}
$form['review_initial'] = array(
'#type' => 'item',
'#title' => t('Initial state'),
'#value' => t('(none)'),
);
if ($form_values['any_state'] == 1) {
if ($form_values['initial_state'] != '') {
$form['review_initial']['#value'] = $form_values['initial_state'];
}
}
else {
if ($form_values['initial_state'] > 0) {
$states = $form_values['state_list'];
$form['review_initial']['#value'] = $states[$form_values['initial_state'] - 1];
}
}
$form['review_filter'] = array(
'#type' => 'item',
'#value' => t('(any)'),
);
if ($form_values['entity'] == STATES_ADMIN_ENTITY_NODE) {
$form['review_filter']['#title'] = t('Content types');
if ($form_values['any_entity'] != 1) {
$node_list = array_filter($form_values['filters']);
foreach ($node_list as $key => $value) {
$node_list[$key] = node_get_types('name', $value);
}
if (count($node_list) > 0) {
$form['review_filter']['#value'] = theme('item_list', $node_list);
}
else {
$form['review_filter']['#value'] = t('(none)');
}
}
}
else {
$form['review_filter']['#title'] = t('User roles');
if ($form_values['any_entity'] != 1) {
$role_names = user_roles();
$role_list = array_filter($form_values['filters']);
foreach ($role_list as $key => $value) {
$role_list[$key] = $role_names[$value];
}
if (count($role_list) > 0) {
$form['review_filter']['#value'] = theme('item_list', $role_list);
}
else {
$form['review_filter']['#value'] = t('(none)');
}
}
}
break;
}
// Put on the form elements common to every page (the next and back buttons)
if ($step != 1) {
$form['back'] = array(
'#type' => 'submit',
'#value' => t('Back'),
);
}
if ($step == STATES_ADMIN_STEP_REVIEW) {
$form['submit'] = array(
'#type' => 'submit',
'#value' => isset($form_values['sid_to_update']) ? t('Update') : t('Create'),
);
}
else {
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Next'),
);
}
// Set the form settings and finish
$form['#redirect'] = FALSE;
$form['#multistep'] = TRUE;
$form['#validate'] = array('states_admin_form_validate' => array());
$form['#submit'] = array('states_admin_form_submit' => array());
return $form;
}
/**
* The custom state creation / editing multistep form validation handler
*/
function states_admin_form_validate($form_id, $form_values) {
// Merge the saved variables into the new ones
$saved = unserialize($form_values['saved']);
unset($form_values['saved']);
$form_values = array_merge($saved, $form_values);
$step = $form_values['step'];
switch ($step) {
case STATES_ADMIN_STEP_STATES:
$states = array();
if (isset($form_values['state_list'])) {
$states = explode("\n", $form_values['state_list']);
}
array_walk($states, 'states_admin_trim_value');
foreach ($states as $state) {
if (strlen($state) > 127) {
form_set_error('state_list', t('The state "!state" exceeds the maximum length of 127.', array('!state' => $state)));
}
}
$states_count = array_count_values($states);
foreach ($states_count as $state => $count) {
if ($count != 1) {
form_set_error('state_list', t('The state "!state" appears more than once.', array('!state' => $state)));
}
}
break;
case STATES_ADMIN_STEP_FILTERS:
$filters = array();
if (isset($form_values['filters'])) {
$filters = $form_values['filters'];
}
$filters_count = array_count_values($filters);
if (count($filters) == $filters_count[0]) {
drupal_set_message(t('You left all boxes unticked. While this is allowed, was it really intended?'));
}
break;
}
}
/**
* The custom state creation / editing multistep form submission handler
*/
function states_admin_form_submit($form_id, $form_values) {
// Merge the saved variables into the new ones
$saved = unserialize($form_values['saved']);
unset($form_values['saved']);
$form_values = array_merge($saved, $form_values);
$step = $form_values['step'];
if ($step == STATES_ADMIN_STEP_REVIEW && $form_values['op'] != t('Back')) {
$label = $form_values['label'];
$entity_type = $form_values['entity'];
$entity_filter = '';
if ($form_values['any_entity'] != 1) {
$entity_filter = serialize(array_keys($form_values['filters']));
}
$states = '*';
if ($form_values['any_state'] != 1) {
$states = $form_values['state_list'];
}
$init_state = '';
if ($form_values['any_state'] == 1) {
$init_state = $form_values['initial_state'];
}
else {
if ($form_values['initial_state'] > 0) {
$init_state = $states[$form_values['initial_state'] - 1];
}
}
if (isset($form_values['sid_to_update'])) {
db_query("UPDATE {states_custom} SET label = '%s', entity_type = %d, entity_filter = '%s', states = '%s', init_state = '%s' WHERE sid = %d",
$label, $entity_type, $entity_filter, serialize($states), $init_state, $form_values['sid_to_update']);
drupal_set_message(t('State machine updated'));
}
else {
db_query("INSERT INTO {states_custom} (label, entity_type, entity_filter, states, init_state) VALUES ('%s', %d, '%s', '%s', '%s')",
$label, $entity_type, $entity_filter, serialize($states), $init_state);
drupal_set_message(t('New state machine created'));
}
drupal_goto(STATES_ADMIN_UI_PATH);
}
}
function states_admin_edit($machine_name, $form_values = NULL) {
if (!isset($form_values)) {
$machines = states_get_machines();
$machine = $machines[$machine_name];
// Transform the state machine definition into form values
$form_values = array(
'step' => 0,
'label' => $machine['#label'],
'entity' => $machine['#entity'] == 'node' ? STATES_ADMIN_ENTITY_NODE : STATES_ADMIN_ENTITY_USER,
'any_state' => $machine['#states'] == '*' ? 1 : 0,
'state_list' => $machine['#states'],
'any_entity' => (isset($machine['#types']) || isset($machine['#roles'])) ? 0 : 1,
'sid_to_update' => $machine['custom_sid'],
'filters' => array(),
);
if ($machine['#states'] == '*') {
$form_values['initial_state'] = isset($machine['#init_state']) ? $machine['#init_state'] : '';
}
else {
$form_values['initial_state'] = isset($machine['#init_state']) ? array_search($machine['#init_state'], $machine['#states']) : FALSE;
if ($form_values['initial_state'] === FALSE) {
$form_values['initial_state'] = 0;
}
else {
$form_values['initial_state'] = $form_values['initial_state'] + 1;
}
}
if ($machine['#entity'] == 'node' && isset($machine['#types']) && count($machine['#types']) > 0) {
$form_values['filters'] = array_combine(array_values($machine['#types']), array_values($machine['#types']));
}
else if ($machine['#entity'] == 'user' && isset($machine['#roles']) && count($machine['#roles']) > 0) {
$form_values['filters'] = array_combine(array_values($machine['#roles']), array_values($machine['#roles']));
}
$form_values['saved'] = serialize($form_values); // So that the form handler thinks nothing has changed
}
return states_admin_form($form_values);
}
function states_admin_delete($machine_name) {
$machines = states_get_machines();
$machine = $machines[$machine_name];
$form = array('custom_sid' => array('#type' => 'value', '#value' => $machine['custom_sid']));
return confirm_form($form,
t('Are you sure you want to delete the state machine "%machine"?', array('%machine' => $machine['#label'])),
array('path' => STATES_ADMIN_UI_PATH),
t('This action cannot be undone.'), t('Delete'), t('Cancel')
);
}
function states_admin_delete_submit($form_id, $form_values) {
db_query("DELETE FROM {states_custom} WHERE sid = %d", $form_values['custom_sid']);
drupal_set_message(t('State machine deleted'));
return STATES_ADMIN_UI_PATH;
}