diff -urpN ..\workflow_old/workflow.admin.inc ./workflow.admin.inc --- ..\workflow_old/workflow.admin.inc 2010-03-02 19:32:54.000000000 +0100 +++ ./workflow.admin.inc 2010-03-16 09:40:11.181285000 +0100 @@ -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,14 @@ 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 +150,11 @@ function theme_workflow_permissions($hea 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 +231,7 @@ function workflow_edit_form($form_state, '#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; @@ -378,6 +384,11 @@ function workflow_state_add_form(&$form_ '#default_value' => $state['weight'], '#description' => t('In listings, the heavier states will sink and the lighter states will be positioned nearer the top.'), ); + // Unique identifier for import/exporting. + $form['ref'] = array( + '#type' => 'hidden', + '#value' => time(), + ); $form['submit'] = array( '#type' => 'submit', '#value' => t('Save') diff -urpN ..\workflow_old/workflow.features.inc ./workflow.features.inc --- ..\workflow_old/workflow.features.inc 1970-01-01 01:00:00.000000000 +0100 +++ ./workflow.features.inc 2010-03-29 15:15:03.468625000 +0200 @@ -0,0 +1,264 @@ +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, + 'tab_roles' => workflow_export_tab_roles($workflow->tab_roles), + 'options' => $workflow->options, + 'states' => workflow_export_states($workflow->wid), + 'roles' => workflow_export_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_revert(). + */ +function workflow_features_revert($module) { + workflow_features_rebuild($module); +} + +/** + * Implementation of hook_features_rebuild(). + */ +function workflow_features_rebuild($module) { + $defaults = module_invoke($module, 'workflow_defaults'); + foreach ($defaults as $config) { + $config['module'] = $module; + // Retrieve 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); + } +} + +/** + * Get workflow roles which can view workflow tab. + * + * @param $tab_roles list of rid separated by comma + * + * @return an array of role name + */ +function workflow_export_tab_roles($tab_roles) { + $roles = user_roles(); + + $tab_roles_name = array(); + $tab_roles_rid = explode(',', $tab_roles); + foreach ($tab_roles_rid as $rid) { + $tab_roles_name[] = $roles[$rid]; + } + + return $tab_roles_name; +} + +/** + * Get workflow roles which can view workflow tab. + * + * @param $tab_roles an array of role name + * + * @return an array of rid + */ +function workflow_import_tab_roles($tab_roles) { + $roles = user_roles(); + $roles_flip = array_flip($roles); + + $tab_roles_rid = array(); + foreach ($tab_roles as $role_name) { + $tab_roles_rid[] = $roles_flip[$role_name]; + } + + return $tab_roles_rid; +} + +/** + * Get workflow states keyed by ref rather than sid. + * + * @param $wid workflow id + */ +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; +} + +/** + * Get workflow permissions keyed by role name rather than rid. + * + * @param $wid workflow id + */ +function workflow_export_permissions($wid) { + $exported_permissions = array(); + + $permissions = workflow_permissions($wid); + foreach ($permissions as $rid => $role) { + $exported_permissions[$role['name']] = $role; + } + + return $exported_permissions; +} + +/** + * 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')); + + // Remove any states that have been deleted. + if (!$state['status']) { + workflow_state_delete($state->sid, $new_sid); + } + unset($states[$state['ref']]); + } + // Create a new state that doesn't exist. + else { + $state['wid'] = $wid; + drupal_write_record('workflow_states', $state); + } + } + // Delete states that no longer exist. + $sids = array(); + foreach ($states as $state) { + // It would be better to move nodes to a different state rather than + // orphan them, so lets move them to the next weight. + $new_sid = db_result(db_query('SELECT sid FROM {workflow_states} WHERE wid = %d AND weight >= %d AND status = 1 ORDER BY weight, sid', $state->wid, $state->weight)); + workflow_state_delete($state->sid, $new_sid); + $sids[] = $state->sid; + } + if (!empty($sids)) { + db_query('DELETE FROM {workflow_states} WHERE sid IN (' . db_placeholders($sids) . ')', $sids); + } + } + 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); + } + } + + workflow_update($wid, $config->name, workflow_import_tab_roles($config->tab_roles), $config->options); + + // 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)); + + // used to work with role name instead of rid during import + $user_roles_flip = array_flip($user_roles); + + // 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 $role_name => $perms) { + if (!isset($perms['transitions'])) continue; + + foreach ($perms['transitions'] as $transition) { + $from = $states[$transition['from']]; + $to = $states[$transition['to']]; + $transitions[$from][$to][$user_roles_flip[$role_name]] = 1; + } + } + workflow_update_transitions($transitions); +} \ No newline at end of file diff -urpN ..\workflow_old/workflow.install ./workflow.install --- ..\workflow_old/workflow.install 2009-06-05 23:33:23.000000000 +0200 +++ ./workflow.install 2010-03-29 15:27:35.624875000 +0200 @@ -28,8 +28,14 @@ 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)), + '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 +60,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 +432,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; +} + +// 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 = drupal_strtolower(preg_replace('/[^\w\d]/', '_', $row->name)); + update_sql("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; } \ No newline at end of file diff -urpN ..\workflow_old/workflow.module ./workflow.module --- ..\workflow_old/workflow.module 2010-03-03 19:17:02.000000000 +0100 +++ ./workflow.module 2010-03-16 09:40:11.181285000 +0100 @@ -195,6 +195,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) { @@ -830,7 +845,13 @@ function workflow_workflow($op, $old_sta * 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; } @@ -1012,17 +1033,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'];