diff --git sites/all/modules/workflow/workflow.admin.inc sites/all/modules/workflow/workflow.admin.inc index 037da8c..466b561 100644 --- sites/all/modules/workflow/workflow.admin.inc +++ sites/all/modules/workflow/workflow.admin.inc @@ -122,7 +122,7 @@ function workflow_permissions($wid) { $all[$role]['name'] = $value; } $result = db_query( - 'SELECT t.roles, s1.state AS state_name, s2.state AS target_state_name ' . + 'SELECT t.roles, s1.state AS state_name, s1.sid as state_sid, s1.ref AS state_ref, s2.state AS target_state_name, s2.sid AS target_state_sid, s2.ref AS target_state_ref ' . '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 '. @@ -132,12 +132,13 @@ function workflow_permissions($wid) { while ($data = db_fetch_object($result)) { 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))); + $all[$role]['transitions'][] = array( + 'from' => (object) array('sid' => $data->state_sid, 'name' => $data->state_name, 'ref' => $data->state_ref), + 'to' => (object) array('sid' => $data->target_state_sid, 'name' => $data->target_state_name, 'ref' => $data->target_state_ref), + ); } } - - $header = array(t('From'), '', t('To')); - return theme('workflow_permissions', $header, $all); + return $all; } /** @@ -148,7 +149,11 @@ function theme_workflow_permissions($header, $all) { foreach ($all as $role => $value) { $output .= '

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

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

'; + $rows = array(); + foreach ($value['transitions'] as $t) { + $rows[] = array($t['from']->name, WORKFLOW_ARROW, $t['to']->name); + } + $output .= theme('table', $header, $rows) . '

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

'; @@ -225,7 +230,7 @@ function workflow_edit_form($form_state, $workflow) { '#collapsible' => TRUE, ); $form['permissions']['summary'] = array( - '#value' => workflow_permissions($workflow->wid), + '#value' => theme('workflow_permissions', array(t('From'), '', t('To')), workflow_permissions($workflow->wid)), ); return $form; @@ -689,4 +694,4 @@ function workflow_types_form_submit($form, &$form_state) { drupal_set_message(t('The workflow mapping was saved.')); menu_rebuild(); $form_state['redirect'] = 'admin/build/workflow'; -} \ No newline at end of file +} diff --git sites/all/modules/workflow/workflow.features.inc sites/all/modules/workflow/workflow.features.inc new file mode 100644 index 0000000..91cfbfb --- /dev/null +++ sites/all/modules/workflow/workflow.features.inc @@ -0,0 +1,179 @@ +module, array('workflow', $module_name))) { + $export['features']['workflow'][$workflow->machine_name] = $workflow->machine_name; + continue; + } + // If the workflow is implemented by a module, then add that module as a + // dependancy. + else { + $export['dependencies'][$workflow->module] = $workflow->module; + } + } +} + +/** + * Implementation of hook_features_options(). + */ +function workflow_features_export_options() { + $options = array(); + foreach (workflow_get_all() as $wid => $name) { + $workflow = workflow_load($wid); + $options[$workflow->machine_name] = $workflow->name; + } + return $options; +} + +/** + * Implementation of hook_features_export_render + */ +function workflow_features_export_render($module_name = '', $data, $module_info = array()) { + $code = ' $defaults = array();' . PHP_EOL; + module_load_include('inc', 'workflow', 'workflow.admin'); + foreach ($data as $machine_name) { + $workflow = workflow_load($machine_name); + // Leave workflows implemented by other modules. + if (!in_array($workflow->module, array('workflow', $module_name))) { + continue; + } + $config = array( + 'name' => $workflow->name, + 'machine_name' => $machine_name, + 'states' => workflow_export_states($workflow->wid), + 'roles' => workflow_permissions($workflow->wid), + ); + foreach ($config['states'] as &$state) { + // Ensure the module reference is correct. + $state->module = $module_name; + // We don't want to port sid or wid because that may cause unique reference conflict. + unset($state->sid); + unset($state->wid); + } + // Clean up the code by removing information we won't use. + foreach ($config['roles'] as $idx => &$role) { + if (!isset($role['transitions'])) { + unset($config['roles'][$idx]); + continue; + } + foreach ($role['transitions'] as &$t) { + $t['from'] = $t['from']->ref; + $t['to'] = $t['to']->ref; + } + } + $code .= ' $defaults[] = ' . features_var_export($config, ' ') . ";\n"; + } + $code .= ' return $defaults;' . PHP_EOL; + return array('workflow_defaults' => $code); +} + +/** + * Implementation of hook_features_export_revert + */ +function workflow_features_revert($module) { + $defaults = module_invoke($module, 'workflow_defaults'); + foreach ($defaults as $config) { + $config['module'] = $module; + // Retrive the existing workflow if it exists. + $wid = db_result(db_query("SELECT wid FROM {workflows} WHERE machine_name = '%s'", $config['machine_name'])); + + // At this point $wid will either have a value or be FALSE. + workflow_revert_default($config, $wid); + } +} + +/** + * Implementation of hook_features_export_rebuild(). + */ +function workflow_features_export_rebuild($module) { + drupal_set_message('hook_features_export_rebuild'); +} + +/** + * Get workflow states keyed by ref rather than sid. + */ +function workflow_export_states($wid) { + $result = db_query("SELECT * FROM {workflow_states} WHERE wid = %d ORDER BY weight, ref", $wid); + while ($data = db_fetch_object($result)) { + $states[$data->ref] = $data; + } + return $states; +} + +/** + * Helper function to revert database config back to code config. + */ +function workflow_revert_default($config, $wid = FALSE) { + $config = (object) $config; + if ($wid) { + // Config already exists in database, we're just reverting back to the code. + // Use ref as the database reference. + db_query("UPDATE {workflows} SET name = '%s', machine_name = '%s', module = '%s' WHERE wid = %d", $config->name, $config->machine_name, $config->module, $wid); + db_query("UPDATE {workflow_states} SET module = '%s' WHERE wid = %d", $config->module, $wid); + $states = workflow_export_states($wid); + foreach ($config->states as $state) { + $state['module'] = $config->module; + if (isset($states[$state['ref']])) { + drupal_write_record('workflow_states', $state, array('ref', 'module')); + unset($states[$state['ref']]); + } + // Create a new state that doesn't exist. + else { + drupal_write_record('workflow_states', $state); + } + } + // Delete states no longer used. + foreach ($states as $state) { + $state = workflow_get_state($state->sid); + $new_sid = db_result(db_query('SELECT sid FROM {workflow_states} WHERE wid = %s AND weight >= %d AND status = 1 ORDER BY weight, sid', $state['wid'], $state['weight'])); + workflow_state_delete($state['sid'], $new_sid); + } + } + else { + $wid = workflow_create($config->name, $config->module, $config->ref); + foreach ($config->states as &$state) { + $state['wid'] = $wid; + $state['module'] = $config->module; + // Update the already created (creation) state. + if ($state['state'] == '(creation)') { + drupal_write_record('workflow_states', $state, 'wid'); + continue; + } + // Otherwise create a new state. + drupal_write_record('workflow_states', $state); + } + } + // Rebuild the transition permissions. + $transitions = array(); + $states = array(); + $rs = db_query('SELECT ref, sid FROM {workflow_states} WHERE wid = %d', $wid); + while ($row = db_fetch_object($rs)) { + $states[$row->ref] = $row->sid; + } + $user_roles = array('author' => 'author') + user_roles(); + $roles = array_combine(array_keys($user_roles), array_pad(array(), count($user_roles), 0)); + // Build transition table. + $targets = $states; + foreach ($states as $state) { + foreach ($targets as $target) { + if ($target == $state) continue; + $transitions[$state][$target] = $roles; + } + } + foreach ($config->roles as $rid => $perms) { + if (!isset($perms['transitions'])) continue; + foreach($perms['transitions'] as $transition) { + $from = $states[$transition['from']]; + $to = $states[$transition['to']]; + $transitions[$from][$to][$rid] = 1; + } + } + workflow_update_transitions($transitions); +} diff --git sites/all/modules/workflow/workflow.install sites/all/modules/workflow/workflow.install index f344905..7871be8 100644 --- sites/all/modules/workflow/workflow.install +++ sites/all/modules/workflow/workflow.install @@ -28,8 +28,13 @@ function workflow_schema() { 'fields' => array( 'wid' => array('type' => 'serial', 'not null' => TRUE), 'name' => array('type' => 'varchar', 'length' => '255', 'not null' => TRUE, 'default' => ''), + 'machine_name' => array('type' => 'varchar', 'length' => '255', 'not null' => TRUE), + 'module' => array('type' => 'varchar', 'length' => '128', 'not null' => TRUE, 'default' => 'workflow'), 'tab_roles' => array('type' => 'varchar', 'length' => '60', 'not null' => TRUE, 'default' => ''), 'options' => array('type' => 'text', 'size' => 'big', 'not null' => FALSE)), + 'unique keys' => array( + 'machine_name' => array('machine_name'), + ), 'primary key' => array('wid'), ); $schema['workflow_type_map'] = array( @@ -54,11 +59,16 @@ function workflow_schema() { 'fields' => array( 'sid' => array('type' => 'serial', 'not null' => TRUE), 'wid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0, 'disp-width' => '10'), + 'ref' => array('type' => 'int', 'not null' => TRUE), + 'module' => array('type' => 'varchar', 'length' => '128', 'not null' => TRUE, 'default' => 'workflow'), 'state' => array('type' => 'varchar', 'length' => '255', 'not null' => TRUE, 'default' => ''), 'weight' => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0, 'disp-width' => '4'), 'sysid' => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 0, 'disp-width' => '4'), 'status' => array('type' => 'int', 'size' => 'tiny', 'not null' => TRUE, 'default' => 1, 'disp-width' => '4')), 'primary key' => array('sid'), + 'unique keys' => array( + 'module_ref' => array('module', 'ref'), + ), 'indexes' => array( 'sysid' => array('sysid'), 'wid' => array('wid')), @@ -421,4 +431,24 @@ function workflow_update_6101() { db_change_field($ret, 'workflow_transitions', 'tid', 'tid', array('type' => 'serial', 'not null' => TRUE), array('primary key' => array('tid'))); } return $ret; -} \ No newline at end of file +} + +// Add features module support (exportables) +function workflow_update_6102() { + $ret = array(); + db_add_field($ret, 'workflows', 'machine_name', array('type' => 'varchar', 'length' => '255')); + db_add_field($ret, 'workflows', 'module', array('type' => 'varchar', 'length' => '128', 'not null' => TRUE, 'default' => 'workflow')); + $rs = db_query('SELECT wid, name FROM {workflows}'); + while ($row = db_fetch_object($rs)) { + $machine_name = strtolower(preg_replace('/[^\w\d]/', '_', $row->name)); + db_query("UPDATE {workflows} SET machine_name = '%s' WHERE wid = %d", $machine_name, $row->wid); + } + db_add_unique_key($ret, 'workflows', 'machine_name', array('machine_name')); + + db_add_field($ret, 'workflow_states', 'module', array('type' => 'varchar', 'length' => '128', 'not null' => TRUE, 'default' => 'workflow')); + db_add_field($ret, 'workflow_states', 'ref', array('type' => 'int', 'not null' => TRUE, 'default' => 0)); + $ret[] = update_sql('UPDATE {workflow_states} SET ref = sid'); + db_add_unique_key($ret, 'workflow_states', 'module_ref', array('module', 'ref')); + + return $ret; +} diff --git sites/all/modules/workflow/workflow.module sites/all/modules/workflow/workflow.module index 321e7a0..83e00de 100644 --- sites/all/modules/workflow/workflow.module +++ sites/all/modules/workflow/workflow.module @@ -198,6 +198,21 @@ function workflow_views_api() { } /** + * Implementation of hook_features_api(). + */ +function workflow_features_api() { + return array( + 'workflow' => array( + 'name' => 'Workflow', + 'default_hook' => 'workflow_defaults', + 'default_file' => FEATURES_DEFAULTS_INCLUDED, + 'features_source' => TRUE, + 'file' => drupal_get_path('module', 'workflow') . '/workflow.features.inc', + ), + ); +} + +/** * Implementation of hook_nodeapi(). */ function workflow_nodeapi(&$node, $op, $teaser = NULL, $page = NULL) { @@ -852,7 +867,13 @@ function workflow_workflow($op, $old_state, $new_state, $node) { * Object representing the workflow. */ function workflow_load($wid) { - $workflow = db_fetch_object(db_query('SELECT * FROM {workflows} WHERE wid = %d', $wid)); + if (!is_numeric($wid)) { + $where = "machine_name = '%s'"; + } + else { + $where = 'wid = %d'; + } + $workflow = db_fetch_object(db_query('SELECT * FROM {workflows} WHERE ' . $where, $wid)); $workflow->options = unserialize($workflow->options); return $workflow; } @@ -1034,17 +1055,24 @@ function workflow_get_all() { * @param $name * The name of the workflow. */ -function workflow_create($name) { +function workflow_create($name, $module = 'workflow', $ref = FALSE) { $workflow = array( 'name' => $name, 'options' => serialize(array('comment_log_node' => 1, 'comment_log_tab' => 1)), + 'module' => $module, + // machine_name is a unique identifier used for exporting and importing workflow. + 'machine_name' => strtolower(preg_replace('/[^\w\d]/', '_', $name)), ); drupal_write_record('workflows', $workflow); workflow_state_save(array( 'wid' => $workflow['wid'], 'state' => t('(creation)'), 'sysid' => WORKFLOW_CREATION, - 'weight' => WORKFLOW_CREATION_DEFAULT_WEIGHT)); + 'weight' => WORKFLOW_CREATION_DEFAULT_WEIGHT, + // Ref is a unique identifier used for exporting and importing workflow. + 'ref' => $ref ? $ref : microtime(), + 'module' => $module, + )); // Workflow creation affects tabs (local tasks), so force menu rebuild. menu_rebuild(); return $workflow['wid'];