You are here

workflow_fields.module in Workflow Fields 6

Same filename and directory in other branches
  1. 5 workflow_fields.module
  2. 7 workflow_fields.module

This module adds to workflow.module the ability to specify, for each state, which node fields should be visible and/or editable. It is a useful feature when workflows demand that certain information be hidden or read-only to certain roles.

File

workflow_fields.module
View source
<?php

/**
 * @file
 * This module adds to workflow.module the ability to specify, for each state, which node fields should be visible and/or editable.
 * It is a useful feature when workflows demand that certain information be hidden or read-only to certain roles.
 *
 */
define("FIELD_ROLE_AUTHOR", -1);

/**
 * Implementation of hook_help().
 */
function workflow_fields_help($section) {
  switch ($section) {
    case 'admin/modules#description':
      return t('Add per-state CCK field settings to workflows. <em>Note: Requires both workflow.module and content.module</em>.');
  }
}

/**
 * Implementation of hook_perm().
 */
function workflow_fields_perm() {
  return array(
    'bypass field restrictions',
  );
}

/**
 * Implementation of hook_form_alter().
 * Hook on both any CCK node form and on the workflow state form.
 *
 * @param object &$node
 * @return array
 */
function workflow_fields_form_alter(&$form, $form_state, $form_id) {
  if (isset($form['type']) && @$form['type']['#value'] . '_node_form' == $form_id) {
    _workflow_fields_node_form_alter($form, $form_state, $form_id);
  }
  elseif ('workflow_state_add_form' == $form_id) {
    _workflow_fields_state_form_alter($form, $form_state, $form_id);
  }
}

/**
 * Helper functino to alter the workflow state form.
 * Add a table listing the fields for workflow's content type.
 */
function _workflow_fields_state_form_alter(&$form, $form_state, $form_id) {
  $wid = $form['wid']['#value'];
  $sid = isset($form['sid']) ? $form['sid']['#value'] : 0;

  // Get all types that are mapped to this workflow.
  $types = db_query("SELECT type FROM {workflow_type_map} WHERE wid=%d", $wid);
  if (empty($types)) {
    return;
  }

  // Fix some fields if dealing with creation state.
  if (WORKFLOW_CREATION == db_result(db_query("SELECT sysid FROM {workflow_states} WHERE sid=%d", $sid))) {
    $form['sysid'] = array(
      '#type' => 'value',
      '#value' => WORKFLOW_CREATION,
    );
    $form['weight'] = array(
      '#type' => 'value',
      '#value' => WORKFLOW_CREATION_DEFAULT_WEIGHT,
    );
    $form['state'] = array(
      '#type' => 'value',
      '#value' => t('(creation)'),
    );
  }
  $form['#submit'][] = 'workflow_fields_state_form_submit';
  $form['fields'] = array(
    '#theme' => 'workflow_fields_state',
    '#tree' => TRUE,
    '#types' => array(
      '#type' => 'value',
      '#value' => array(),
    ),
    '#weight' => 98,
  );

  // Gather role ids.
  $rids = array(
    FIELD_ROLE_AUTHOR => t('author'),
  );
  $result = db_query("SELECT r.rid, r.name FROM {role} r ORDER BY r.name");
  while ($obj = db_fetch_object($result)) {
    $rids[$obj->rid] = $obj->name;
  }
  $userrefs = array();

  // For each type, find out all the fields.
  while ($values = db_fetch_array($types)) {
    $type = $values['type'];
    $content = content_types($type);
    $form['fields']['#types']['#value'][] = $type;

    // Get all user reference fields.
    $options = $rids;
    foreach ($content['fields'] as $userref) {
      if ($userref['type'] == 'userreference') {
        $options[$userref['field_name']] = $userrefs[$userref['field_name']] = $userref['widget']['label'];
      }
    }

    // For each field, add checkboxes for visible and editable for all roles.
    $fields = _workflow_fields_get_extra_fields($type) + $content['fields'];
    foreach ($fields as $field) {
      $permissions = db_fetch_array(db_query("SELECT visible_roles, editable_roles FROM {workflow_fields} WHERE sid = %d AND name = '%s' AND type = '%s'", intval($sid), $field['field_name'], $type));
      if ($permissions === FALSE) {

        // by default, author has full access to the fields
        $permissions = array(
          'visible_roles' => FIELD_ROLE_AUTHOR,
          'editable_roles' => FIELD_ROLE_AUTHOR,
        );
      }
      $visible = explode(',', $permissions['visible_roles']);
      $editable = explode(',', $permissions['editable_roles']);
      $form['fields'][$type][$field['field_name']]['visible'] = array(
        '#type' => 'checkboxes',
        '#options' => $options,
        '#default_value' => $visible,
      );
      $form['fields'][$type][$field['field_name']]['editable'] = array(
        '#type' => 'checkboxes',
        '#options' => $options,
        '#default_value' => $editable,
      );

      // Selection shortcuts.
      $type_shortcuts = array();
      $type_css = str_replace('_', '-', $type);
      $field_name_css = str_replace('_', '-', $field['field_name']);
      $group = $type_css . '-' . $field_name_css;
      $type_shortcuts[$group]['all'] = array(
        'title' => t('all'),
        'href' => "javascript:Drupal.workflowFields.select('all', '{$group}');",
      );
      $type_shortcuts[$group]['none'] = array(
        'title' => t('none'),
        'href' => "javascript:Drupal.workflowFields.select('none', '{$group}');",
      );
      $type_shortcuts[$group]['toggle'] = array(
        'title' => t('toggle'),
        'href' => "javascript:Drupal.workflowFields.select('toggle', '{$group}');",
      );
      $visible_group = $group . '-visible';
      $type_shortcuts[$group]['visible'] = array(
        'title' => t('visible'),
        'href' => "javascript:Drupal.workflowFields.select('toggle', '{$visible_group}');",
      );
      $editable_group = $group . '-editable';
      $type_shortcuts[$group]['editable'] = array(
        'title' => t('editable'),
        'href' => "javascript:Drupal.workflowFields.select('toggle', '{$editable_group}');",
      );
      $form['fields'][$type][$field['field_name']]['shortcuts'] = array(
        '#type' => 'value',
        '#value' => $type_shortcuts,
        '#theme' => 'workflow_fields_shortcuts',
        '#render title' => false,
      );
    }
  }

  // Selection shortcuts.
  foreach (array(
    '',
    'visible',
    'editable',
  ) as $group) {
    $shortcuts[$group]['all'] = array(
      'title' => $group ? t($group) : t('all'),
      'href' => "javascript:Drupal.workflowFields.select('all', '{$group}');",
    );
    $shortcuts[$group]['none'] = array(
      'title' => t('none'),
      'href' => "javascript:Drupal.workflowFields.select('none', '{$group}');",
    );
    $shortcuts[$group]['toggle'] = array(
      'title' => t('toggle'),
      'href' => "javascript:Drupal.workflowFields.select('toggle', '{$group}');",
    );
    foreach ($rids as $rid => $rname) {
      $shortcuts[$group][$rid] = array(
        'title' => t('role: ') . $rname,
        'href' => "javascript:Drupal.workflowFields.select('{$rid}', '{$group}');",
      );
    }
    if ($userrefs) {
      foreach ($userrefs as $ref => $refname) {
        $ref_css = str_replace('_', '-', $ref);
        $shortcuts[$group][$ref] = array(
          'title' => t('user: ') . $refname,
          'href' => "javascript:Drupal.workflowFields.select('{$ref_css}', '{$group}');",
        );
      }
    }
  }
  $form['shortcuts'] = array(
    '#type' => 'value',
    '#value' => $shortcuts,
    '#theme' => 'workflow_fields_shortcuts',
    '#render title' => true,
    '#weight' => 97,
  );
  $form['submit']['#weight'] = 99;
}

/**
 * Implementation of hook_theme().
 */
function workflow_fields_theme() {
  return array(
    'workflow_fields_state' => array(
      'arguments' => array(
        'form' => NULL,
      ),
    ),
    'workflow_fields_shortcuts' => array(
      'arguments' => array(
        'form' => NULL,
      ),
    ),
  );
}

/**
 * Theme function for workflow state form.
 */
function theme_workflow_fields_state($form) {
  $header = array(
    t('Content type'),
    t('Field name'),
    t('Visible'),
    t('Editable'),
    t('Select'),
  );
  $rows = array();
  foreach ($form['#types']['#value'] as $type) {
    $content = content_types($type);
    $fields = _workflow_fields_get_extra_fields($type) + $content['fields'];
    foreach ($fields as $field) {
      $rows[] = array(
        $content['name'] . '<br/>(' . $type . ')',
        $field['widget']['label'] . '<br/>(' . $field['field_name'] . ')',
        drupal_render($form[$type][$field['field_name']]['visible']),
        drupal_render($form[$type][$field['field_name']]['editable']),
        drupal_render($form[$type][$field['field_name']]['shortcuts']),
      );
    }
  }
  $output = theme('table', $header, $rows, array(
    'class' => 'workflow-fields-table',
  )) . '<p />';
  return $output;
}

/**
 * Theme function for field shortcuts on workflow state form.
 */
function theme_workflow_fields_shortcuts($form) {
  drupal_add_js(drupal_get_path('module', 'workflow_fields') . '/workflow_fields.js');
  $output = '<div class="form-item">';
  if ($form['#render title']) {
    $output .= '<label>' . t('Select: ') . '</label>';
  }
  foreach ($form['#value'] as $group) {
    $links = array();
    foreach ($group as $key => $link) {
      $links[] = "<a href=\"{$link['href']}\">{$link['title']}</a>";
    }
    $output .= implode(', ', $links) . '<br />';
  }
  $output .= '</div>';
  return $output;
}

/**
 * Submit function for workflow state form.
 */
function workflow_fields_state_form_submit($form, &$form_state) {
  if (isset($form_state['values']['fields'])) {
    if (!isset($form_state['values']['sid'])) {
      $form_state['values']['sid'] = db_result(db_query("SELECT sid FROM {workflow_states} WHERE wid = %d AND state = '%s'", $form_state['values']['wid'], $form_state['values']['state']));
    }
    db_query("DELETE FROM {workflow_fields} WHERE sid = %d", intval($form_state['values']['sid']));
    foreach ($form_state['values']['fields'] as $type => $fields) {
      foreach ($fields as $name => $field) {
        $visible = array_filter($field['visible']);
        $editable = array_filter($field['editable']);
        db_query("INSERT INTO {workflow_fields} (sid, name, type, visible_roles, editable_roles) VALUES(%d, '%s', '%s', '%s', '%s')", $form_state['values']['sid'], $name, $type, implode(',', $visible), implode(',', $editable));
      }
    }
  }
}

/**
 * Helper function to alter the node form by hiding/disabling fields depending on the workflow state.
 */
function _workflow_fields_node_form_alter(&$form, $form_state, $form_id) {
  $node = $form['#node'];
  $sid = workflow_node_current_state($node);
  if (!is_numeric($sid)) {
    $sid = db_result(db_query("SELECT sid FROM {workflow_states} ws \n                               LEFT JOIN {workflow_type_map} wtm ON ws.wid = wtm.wid \n                               WHERE wtm.type = '%s' AND ws.sysid = %d", $node->type, WORKFLOW_CREATION));
  }

  // Check for visible/editable flags.
  $content = content_types($node->type);
  $fields = _workflow_fields_get_extra_fields($node->type) + $content['fields'];
  $form['sid'] = array(
    '#type' => 'value',
    '#value' => $sid,
  );
  list($visibles, $editables) = _workflow_fields_compute_permissions($sid, $node->type, $node, 'edit');
  if ($visibles) {
    foreach ($visibles as $field_name => $visible) {
      if (!isset($fields[$field_name])) {
        continue;
      }
      $editable = $editables[$field_name];
      if ($editable && $visible) {
        continue;
      }

      // Find the path to the field in the form.
      $field = $fields[$field_name];
      if (!empty($field['path'])) {
        $path = $field['path'];
        if (!_workflow_fields_array_path_exists($form, $path)) {
          $path = FALSE;
        }
      }
      else {
        $path = _workflow_fields_array_key_path($field_name, $form, array(
          '#field_info',
        ));
      }
      if ($path === FALSE) {
        continue;
      }

      // Render the form for viewing.
      $node =& $form['#node'];
      if (!isset($node->content)) {
        $node = node_build_content($node, FALSE, TRUE);
      }

      // Process the field.
      if (isset($field['process'])) {

        // Does the field need special handling?
        call_user_func_array($field['process'], array(
          &$form,
          $field,
          $path,
          $visible,
          $editable,
          isset($field['process arguments']) ? (array) $field['process arguments'] : array(),
        ));
      }
      else {

        // Standard handling
        _workflow_fields_process_field($form, $field, $path, $visible, $editable);
      }

      // Hide empty fieldsets.
      if (count($path) > 1) {
        _workflow_fields_hide_empty_fieldsets($form[$path[0]]);
      }
    }
  }
}
function _workflow_fields_hide_empty_fieldsets(&$parent) {
  foreach (element_children($parent) as $child) {
    $element =& $parent[$child];
    if (isset($element['#type']) && (!isset($element['#access']) || $element['#access'])) {
      return FALSE;
    }
    if (!_workflow_fields_hide_empty_fieldsets($element)) {
      return FALSE;
    }
  }

  // If we reach here, then all children are empty.
  if (isset($parent['#type']) && $parent['#type'] == 'fieldset') {
    $parent['#access'] = FALSE;
  }
  return TRUE;
}
function _workflow_fields_process_field(&$form, $field, $path, $visible, $editable) {
  $element =& _workflow_fields_array_path($form, $path);
  $parent =& _workflow_fields_array_path($form, array_slice($path, 0, -1));
  if ($visible) {

    // Show the read-only version of the field
    if (!isset($field['extra'])) {

      // Is this a CCK field?
      $parent[$field['field_name'] . '_view'] = array(
        '#type' => 'markup',
        '#value' => content_view_field($field, $form['#node'], FALSE, TRUE),
        '#weight' => isset($element['#weight']) ? $element['#weight'] : $form['#field_info'][$field['field_name']]['widget']['weight'],
      );
    }
    else {

      // Non-CCK
      $node = $form['#node'];
      if (_workflow_fields_array_path_exists($node->content, $path)) {
        $view = _workflow_fields_array_path($node->content, $path);
        $parent[$field['field_name'] . '_view'] = $view;
      }
    }
    if (isset($parent['#type']) && $parent['#type'] == 'fieldset') {
      $parent['#access'] = TRUE;

      // #access of parent can be set to FALSE when all fields are disabled (by hook_field_access)
    }
  }

  // Now hide the field.
  $element['#access'] = FALSE;
}

/**
 * Implementation of hook_workflow_fields().
 *
 * Used to describe non-CCK fields to be handled during form rendering.
 *
 * @param $type
 *   Content type being handled.
 *
 * @return array of keyed field entries
 *   key: field name
 *   'label' (required): field label
 *   'path' (optional): explicit field path
 *   'process' (optional): callback to handle hiding / read-only with signature process(&$form, $field, $path, $visible, $editable)
 *   'process arguments' (optional): array of extra arguments to pass to callback
 *
 */
function workflow_fields_workflow_fields($type) {
  $content = node_get_types('type', $type);
  $fields = array();
  if ($content->has_title) {
    $fields['title'] = array(
      'label' => $content->title_label,
    );
  }
  if ($content->has_body) {
    $fields['body'] = array(
      'label' => $content->body_label,
      'process' => '_workflow_fields_process_body',
    );
  }
  if (module_exists('taxonomy')) {
    $vocabularies = taxonomy_get_vocabularies($type);
    if (!empty($vocabularies)) {
      foreach ($vocabularies as $vid => $vocabulary) {
        $fields['vid_' . $vid] = array(
          'label' => $vocabulary->name,
          'path' => $vocabulary->tags ? array(
            'taxonomy',
            'tags',
            $vid,
          ) : array(
            'taxonomy',
            $vid,
          ),
        );
      }
    }
  }
  return $fields;
}
function _workflow_fields_get_extra_fields($type, $reset = FALSE) {
  static $fields = array();
  if (!isset($fields[$type]) || $reset) {
    $fields[$type] = module_invoke_all('workflow_fields', $type);
    foreach ($fields[$type] as $key => $field) {
      $fields[$type][$key]['field_name'] = $key;
      $fields[$type][$key]['widget']['label'] = $field['label'];
      $fields[$type][$key]['extra'] = TRUE;
    }
    drupal_alter('workflow_fields', $fields, $type);
  }
  return $fields[$type];
}
function _workflow_fields_process_body(&$form, $field, $path, $visible, $editable, $arguments) {
  $form['body_field']['#access'] = FALSE;
  if ($visible) {
    $form['body'] = $form['#node']->content['body'];
    $form['body']['#weight'] = $form['#content_extra_fields']['body_field']['weight'];
  }
}

/**
 * Implementation of hook_workflow_operations().
 */
function workflow_fields_workflow_operations($mode, $wid, $sid = 0) {
  switch ($mode) {
    case 'workflow':
      break;
    case 'state':
      if (WORKFLOW_CREATION == db_result(db_query("SELECT sysid FROM {workflow_states} WHERE wid = %d AND sid = %d", $wid, $sid))) {

        // (creation) state
        return array(
          'workflow_creation_edit' => array(
            'title' => t('Edit'),
            'href' => "admin/build/workflow/state/{$wid}/{$sid}",
          ),
        );
      }
      break;
  }
}

/**
 * Implementation of hook_menu().
 */
function workflow_fields_menu() {
  $items['admin/settings/workflow_fields'] = array(
    'title' => 'Workflow Fields settings',
    'description' => 'Global settings for the behaviour of Workflow Fields.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'workflow_fields_settings',
    ),
    'access arguments' => array(
      'administer workflow',
    ),
  );
  return $items;
}
function workflow_fields_settings() {
  $form['workflow_fields_edit_invisible_when_editing'] = array(
    '#type' => 'checkbox',
    '#title' => t('Edit fields marked as "not visible" but "editable" when editing.'),
    '#default_value' => variable_get('workflow_fields_edit_invisible_when_editing', TRUE),
  );
  $form['workflow_fields_hide_read_only_when_editing'] = array(
    '#type' => 'checkbox',
    '#title' => t('Hide fields marked as "not editable" when editing.'),
    '#default_value' => variable_get('workflow_fields_hide_read_only_when_editing', FALSE),
  );
  return system_settings_form($form);
}

/**
 * Return an array of rids for the current user, given a node.
 * Include FIELD_ROLE_AUTHOR if the current user is the node author.
 */
function _workflow_fields_compute_groups($node = NULL) {
  global $user;
  $groups = array_keys($user->roles);
  if (isset($node) && isset($node->uid)) {
    if ($user->uid == $node->uid) {
      $groups[] = FIELD_ROLE_AUTHOR;
    }
    $content = content_types($node->type);
    foreach ($content['fields'] as $name => $field) {
      if ($field['type'] == 'userreference' && isset($node->{$name})) {
        foreach ($node->{$name} as $uid) {
          if ($uid['uid'] === $user->uid) {
            $groups[] = $name;
          }
        }
      }
    }
  }
  return $groups;
}
function _workflow_fields_compute_permissions($sid, $type, $node, $op) {
  $visibles = array();
  $editables = array();
  $groups = _workflow_fields_compute_groups($node);
  $result = db_query("SELECT name, visible_roles, editable_roles FROM {workflow_fields} WHERE sid = %d AND type = '%s'", $sid, $type);
  global $user;
  while ($row = db_fetch_array($result)) {
    $visible_roles = explode(',', $row['visible_roles']);
    $editable_roles = explode(',', $row['editable_roles']);
    $visibles[$row['name']] = user_access('bypass field restrictions') || (bool) array_intersect($groups, $visible_roles);
    $editables[$row['name']] = user_access('bypass field restrictions') || (bool) array_intersect($groups, $editable_roles);
    if ($op == 'edit') {
      if (variable_get('workflow_fields_hide_read_only_when_editing', FALSE) && !$editables[$row['name']]) {
        $visibles[$row['name']] = FALSE;
      }
      if (variable_get('workflow_fields_edit_invisible_when_editing', TRUE) && $editables[$row['name']]) {
        $visibles[$row['name']] = TRUE;
      }
      if (!$visibles[$row['name']]) {
        $editables[$row['name']] = FALSE;
      }
    }
  }
  return array(
    $visibles,
    $editables,
  );
}
function _workflow_fields_array_key_path($needle, $haystack, $forbidden = array(), $path = array()) {
  foreach ($haystack as $key => $val) {
    if (in_array($key, $forbidden)) {
      continue;
    }
    if (is_array($val) && is_array($sub = _workflow_fields_array_key_path($needle, $val, $forbidden, array_merge($path, (array) $key)))) {
      return $sub;
    }
    elseif ($key === $needle) {
      return array_merge($path, (array) $key);
    }
  }
  return FALSE;
}
function &_workflow_fields_array_path(&$array, $path) {
  $offset =& $array;
  if ($path) {
    foreach ($path as $index) {
      $offset =& $offset[$index];
    }
  }
  return $offset;
}
function _workflow_fields_array_path_exists($array, $path) {
  $offset =& $array;
  if ($path) {
    foreach ($path as $index) {
      if (!array_key_exists($index, $offset)) {
        return FALSE;
      }
      $offset =& $offset[$index];
    }
  }
  return TRUE;
}

/**
 * Implementation of hook_field_access().
 */
function workflow_fields_field_access($op, $field, $account = NULL, $node = NULL) {
  if (!$node) {
    return TRUE;
  }
  $node = node_load($node->nid);

  // The node object can be a views result object too: not good
  $sid = workflow_node_current_state($node);
  if (!$sid) {
    return TRUE;
  }
  if (!is_numeric($sid)) {
    $sid = db_result(db_query("SELECT sid FROM {workflow_states} ws \n                               LEFT JOIN {workflow_type_map} wtm ON ws.wid = wtm.wid \n                               WHERE wtm.type = '%s' AND ws.sysid = %d", $node->type, WORKFLOW_CREATION));
  }

  // Check for visible/editable flags.
  list($visibles, $editables) = _workflow_fields_compute_permissions($sid, $node->type, $node, $op);
  if (!isset($visibles[$field['field_name']])) {
    return TRUE;
  }
  return $op == 'view' ? $visibles[$field['field_name']] : $visibles[$field['field_name']] && $editables[$field['field_name']];
}

Functions

Namesort descending Description
theme_workflow_fields_shortcuts Theme function for field shortcuts on workflow state form.
theme_workflow_fields_state Theme function for workflow state form.
workflow_fields_field_access Implementation of hook_field_access().
workflow_fields_form_alter Implementation of hook_form_alter(). Hook on both any CCK node form and on the workflow state form.
workflow_fields_help Implementation of hook_help().
workflow_fields_menu Implementation of hook_menu().
workflow_fields_perm Implementation of hook_perm().
workflow_fields_settings
workflow_fields_state_form_submit Submit function for workflow state form.
workflow_fields_theme Implementation of hook_theme().
workflow_fields_workflow_fields Implementation of hook_workflow_fields().
workflow_fields_workflow_operations Implementation of hook_workflow_operations().
_workflow_fields_array_key_path
_workflow_fields_array_path
_workflow_fields_array_path_exists
_workflow_fields_compute_groups Return an array of rids for the current user, given a node. Include FIELD_ROLE_AUTHOR if the current user is the node author.
_workflow_fields_compute_permissions
_workflow_fields_get_extra_fields
_workflow_fields_hide_empty_fieldsets
_workflow_fields_node_form_alter Helper function to alter the node form by hiding/disabling fields depending on the workflow state.
_workflow_fields_process_body
_workflow_fields_process_field
_workflow_fields_state_form_alter Helper functino to alter the workflow state form. Add a table listing the fields for workflow's content type.

Constants

Namesort descending Description
FIELD_ROLE_AUTHOR @file This module adds to workflow.module the ability to specify, for each state, which node fields should be visible and/or editable. It is a useful feature when workflows demand that certain information be hidden or read-only to certain roles.