Skip to content

Instantly share code, notes, and snippets.

@chrisns
Created October 20, 2011 08:06
Show Gist options
  • Save chrisns/1300647 to your computer and use it in GitHub Desktop.
Save chrisns/1300647 to your computer and use it in GitHub Desktop.
--- workflow.admin.inc 2010-03-02 11:32:54.000000000 -0700
+++ workflow.admin.inc 2010-07-23 10:04:17.000000000 -0600
@@ -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 .= '<h3>'. t("%role may do these transitions:", array('%role' => $value['name'])) .'</h3>';
if (!empty($value['transitions'])) {
- $output .= theme('table', $header, $value['transitions']) . '<p></p>';
+ $rows = array();
+ foreach ($value['transitions'] as $t) {
+ $rows[] = array($t['from']->name, WORKFLOW_ARROW, $t['to']->name);
+ }
+ $output .= theme('table', $header, $rows) . '<p></p>';
}
else {
$output .= '<table><tbody><tr class="odd"><td>' . t('None') . '</td><td></tr></tbody></table><p></p>';
@@ -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')
--- workflow.features.inc 2010-07-23 10:48:16.000000000 -0600
+++ workflow.features.inc 2010-07-23 10:43:08.000000000 -0600
@@ -0,0 +1,360 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * The inc file which adds features support
+ */
+
+/**
+ * Implementation of hook_features_export().
+ */
+function workflow_features_export($data, &$export, $module_name) {
+ $export['dependencies']['workflow'] = 'workflow';
+
+ foreach ($data as $machine_name) {
+ $workflow = workflow_load($machine_name);
+ // If the workflow requires workflow_access, add that as a dependency.
+ if (workflow_export_requires_workflow_access($workflow->wid)) {
+ $export['dependencies']['workflow_access'] = 'workflow_access';
+ }
+ // If the Workflow module created the workflow then the config is only in
+ // the database, so add it as an exportable.
+ if (in_array($workflow->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
+ // dependency.
+ 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),
+ 'types' => workflow_export_types($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.
+ if (module_exists('workflow_access')) {
+ $state->access = workflow_export_workflow_access($state->sid);
+ }
+ 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[addslashes($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'));
+ $state['sid'] = $states[$state['ref']]->sid;
+
+ // 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);
+ }
+
+ // Access
+ workflow_import_access($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_import_access($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);
+
+ $types = $config->types;
+ $current_types = array();
+ $ct = db_query("SELECT type FROM {workflow_type_map} WHERE wid = %d", $wid);
+ while($row = db_fetch_object($ct)){
+ $current_types[$row->type] = $row->type;
+ }
+ $all_types = array_merge($types, $current_types);
+ foreach ($types as $type) {
+ $existing = workflow_get_workflow_for_type($type);
+ if(!in_array($type, $types) && (!$existing || $existing == $wid)){
+ db_query("DELETE FROM {workflow_type_map} WHERE type = '%s' AND wid = %d", $type, $wid);
+ }
+ elseif (!$existing) {
+ db_query("DELETE FROM {workflow_type_map} WHERE type = '%s' AND wid = %d", $type, $wid);
+ db_query("INSERT INTO {workflow_type_map} (type, wid) VALUES ('%s', %d)", $type, $wid);
+ }
+ elseif ($existing != $wid) {
+ drupal_set_message(t('Type @type already has a workflow mapped to it. If you wish to change it, visit Workflow settings page', array('@type' => $type)));
+ }
+ }
+}
+
+/**
+ * Set workflow access data
+ */
+function workflow_import_access($state){
+ if (module_exists('workflow_access')) {
+ db_query('DELETE FROM {workflow_access} WHERE sid = %d', $state['sid']);
+
+ foreach ($state['access'] as $record) {
+ if (!isset($record['rid'])) {
+ $record['rid'] = db_result(db_query("SELECT rid FROM {role} WHERE name = '%s'", $record['role']));
+ }
+ if ($record['rid']) {
+ $record['sid'] = $state['sid'];
+ db_query('INSERT INTO {workflow_access} (sid, rid, grant_view, grant_update, grant_delete) VALUES (%d, %d, %d, %d, %d)', $record['sid'], $record['rid'], $record['grant_view'], $record['grant_update'], $record['grant_delete']);
+ }
+ }
+ node_access_needs_rebuild(TRUE);
+ }
+}
+
+/**
+ * Export type map
+ */
+function workflow_export_types($wid) {
+ $result = db_query('SELECT type FROM {workflow_type_map} WHERE wid = %d', $wid);
+ $types = array();
+ while ($type = db_fetch_object($result)) {
+ $types[$type->type] = $type->type;
+ }
+ return $types;
+}
+
+/**
+ * Export workflow access entries
+ */
+function workflow_export_workflow_access($sid) {
+ $access = array();
+ $result = db_query('SELECT roles.name as role, wa.rid, wa.grant_view, wa.grant_update, wa.grant_delete FROM {workflow_access} wa LEFT JOIN {role} roles ON wa.rid = roles.rid WHERE wa.sid = %d', $sid);
+ while ($item = db_fetch_object($result)) {
+ if ($item->rid == -1) {
+ $item->role = 'author';
+ }
+ if ($item->rid > 2) {
+ unset($item->rid);
+ }
+ $access[] = $item;
+ }
+ return $access;
+}
+
+/**
+ * Determine if the workflow requires the workflow_access module.
+ */
+function workflow_export_requires_workflow_access($wid) {
+ $num_rows = db_result(db_query("SELECT COUNT(wa.sid) FROM {workflow_states} ws LEFT JOIN {workflow_access} wa ON ws.sid = wa.sid WHERE wid = %d", $wid));
+ // If there's at least one row in the workflow_access table for this workflow,
+ // then it requires the workflow_access module.
+ return ($num_rows) ? TRUE : FALSE;
+}
diff -up ../workflow_old/workflow.install ./workflow.install
--- workflow.install 2009-06-05 15:33:23.000000000 -0600
+++ workflow.install 2010-07-23 10:04:17.000000000 -0600
@@ -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
--- workflow.module 2010-03-03 11:17:02.000000000 -0700
+++ workflow.module 2010-07-23 10:04:17.000000000 -0600
@@ -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'];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment