You are here

workflow_fields.module in Workflow Fields 7

Same filename and directory in other branches
  1. 5 workflow_fields.module
  2. 6 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("WORKFLOW_FIELDS_AUTHOR_ROLE", -1);

/**
 * Implements hook_help().
 */
function workflow_fields_help($section) {
  switch ($section) {
    case 'admin/modules#description':
      return t('Add per-state field settings to workflows.');
  }
}

/**
 * Implements hook_permission().
 */
function workflow_fields_permission() {
  return array(
    'bypass field restrictions' => array(
      'title' => t('Bypass Field Restrictions'),
      'description' => t('Ignore field restrictions configured with Workflow Fields module.'),
    ),
  );
}

/**
 * Administration form that renders a table listing of the fields for workflow's content type.
 */
function workflow_fields_form_workflow_admin_ui_form($form, &$form_state, $workflow, $sid) {
  $wid = $workflow->wid;
  $state = workflow_get_workflow_states_by_sid($sid);

  // Make sure the state actually belongs to the workflow.
  if ($state && $state->wid == $workflow->wid) {

    // Create breadcrumbs.
    workflow_admin_ui_breadcrumbs($workflow);
    drupal_set_title(t('Fields in state:') . ' ' . $state->state);

    // Get all types that are mapped to this workflow.
    $types = workflow_get_workflow_type_map_by_wid($wid);
    if (empty($types)) {
      drupal_set_message(t('To set fields visibility you have to map the workflow to at least a content type.'));
      $form['description'] = array(
        '#type' => 'markup',
        '#markup' => t("There isn't a content type mapped to this workflow."),
      );
      return $form;
    }

    // Form fields to identify current workflow and state.
    $form['wid'] = array(
      '#type' => 'value',
      '#value' => $wid,
    );
    $form['sid'] = array(
      '#type' => 'value',
      '#value' => $sid,
    );
    $form['fields'] = array(
      '#theme' => 'workflow_fields_state',
      '#tree' => TRUE,
      '#types' => array(
        '#type' => 'value',
        '#value' => array(),
      ),
      '#weight' => 98,
    );

    // Gather role ids.
    $rids = array(
      WORKFLOW_FIELDS_AUTHOR_ROLE => t('author'),
    ) + user_roles();
    $userrefs = array();
    $results = db_query("SELECT type, name, visible_roles, editable_roles\n      FROM {workflow_fields} WHERE sid = :sid", array(
      ':sid' => $sid,
    ));
    $permissions = array();
    foreach ($results as $row) {
      $permissions[$row->type][$row->name] = array(
        'visible_roles' => explode(',', $row->visible_roles),
        'editable_roles' => explode(',', $row->editable_roles),
      );
    }

    // For each type, find out all the fields.
    foreach ($types as $values) {
      $type = $values->type;
      $form['fields']['#types']['#value'][] = $type;

      // For each field, add checkboxes for visible and editable for all roles.
      $fields = _workflow_fields_get_fields($type);
      uasort($fields, "_workflow_fields_sort_fields_by_widget_weight");
      foreach ($fields as $field_name => $field) {
        if (!isset($permissions[$type][$field_name])) {
          $perms = array(
            'visible_roles' => array(
              WORKFLOW_FIELDS_AUTHOR_ROLE,
            ),
            'editable_roles' => array(
              WORKFLOW_FIELDS_AUTHOR_ROLE,
            ),
          );
        }
        else {
          $perms = $permissions[$type][$field_name];
        }
        $form['fields'][$type][$field_name] = array(
          '#field_label' => $field['label'],
          '#weight' => isset($field['weight']) ? $field['weight'] : -100,
        );
        $form['fields'][$type][$field_name]['visible'] = array(
          '#type' => 'checkboxes',
          '#options' => $rids,
          '#default_value' => $perms['visible_roles'],
        );
        $form['fields'][$type][$field_name]['editable'] = array(
          '#type' => 'checkboxes',
          '#options' => $rids,
          '#default_value' => $perms['editable_roles'],
        );

        // 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(
      'all',
      'visible',
      'editable',
    ) as $group) {
      $shortcuts[$group]['all'] = array(
        'title' => t($group),
        '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'] = array(
      '#type' => 'submit',
      '#weight' => 99,
      '#value' => t('Save configuration'),
    );
    return $form;
  }
}

/**
 * Submit function for workflow state form.
 */
function workflow_fields_form_workflow_admin_ui_form_submit($form, &$form_state) {
  $wid = $form_state['values']['wid'];
  if (isset($form_state['values']['fields'])) {
    $sid = $form_state['values']['sid'];
    db_delete('workflow_fields')
      ->condition('sid', $sid)
      ->execute();
    $insert = db_insert('workflow_fields')
      ->fields(array(
      'sid',
      'name',
      'type',
      'visible_roles',
      'editable_roles',
    ));
    foreach ($form_state['values']['fields'] as $type => $fields) {
      foreach ($fields as $name => $field) {
        $visible = array_filter($field['visible']);
        $editable = array_filter($field['editable']);
        $insert
          ->values(array(
          'sid' => $sid,
          'name' => $name,
          'type' => $type,
          'visible_roles' => implode(',', $visible),
          'editable_roles' => implode(',', $editable),
        ));
      }
    }
    $insert
      ->execute();
    $state = workflow_get_workflow_states_by_sid($sid);
    drupal_set_message(t('Changes on fields for the state <em>@state</em> made successfully.', array(
      '@state' => $state->state,
    )));
  }
  $form_state['redirect'] = 'admin/config/workflow/workflow/' . $wid;
}

/**
 * Helper function for sorting fields by widget weight.
 */
function _workflow_fields_sort_fields_by_widget_weight($a, $b) {
  if (!isset($a['widget']['weight'])) {
    return -1;
  }
  if (!isset($b['widget']['weight'])) {
    return 1;
  }
  return $a['widget']['weight'] - $b['widget']['weight'];
}

/**
 * Implements hook_theme().
 */
function workflow_fields_theme() {
  return array(
    'workflow_fields_state' => array(
      'render element' => 'element',
    ),
    'workflow_fields_shortcuts' => array(
      'render element' => 'element',
    ),
  );
}

/**
 * Theme function for workflow state form.
 */
function theme_workflow_fields_state($vars) {
  $element =& $vars['element'];
  $header = array(
    t('Content type'),
    t('Field name'),
    t('Visible'),
    t('Editable'),
    t('Select'),
  );
  $rows = array();
  foreach (element_children($element) as $type) {
    $type_info = node_type_get_type($type);
    foreach (element_children($element[$type]) as $field_name) {
      $field_label = $element[$type][$field_name]['#field_label'];
      $rows[] = array(
        $type_info->name . '<br/>(' . $type . ')',
        $field_label . '<br/>(' . $field_name . ')',
        drupal_render($element[$type][$field_name]['visible']),
        drupal_render($element[$type][$field_name]['editable']),
        drupal_render($element[$type][$field_name]['shortcuts']),
      );
    }
  }
  $attributes = array(
    'class' => array(
      'workflow-fields-table',
    ),
  );
  $output = theme('table', compact('header', 'rows', 'attributes')) . '<p />';
  return $output;
}

/**
 * Theme function for field shortcuts on workflow state form.
 */
function theme_workflow_fields_shortcuts($vars) {
  $element =& $vars['element'];
  drupal_add_js(drupal_get_path('module', 'workflow_fields') . '/workflow_fields.js');
  $output = '<div class="form-item">';
  if ($element['#render title']) {
    $output .= '<label>' . t('Select:') . '</label>';
  }
  foreach ($element['#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;
}

/**
 * Implements hook_form_BASE_FORM_ID_alter().
 */
function workflow_fields_form_node_form_alter(&$form, $form_state, $form_id) {
  $node =& $form['#node'];
  $sid = workflow_node_current_state($node);

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

    // Find the path to the field in the form.
    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(
        '#group_children',
      ));
    }
    if ($path === FALSE) {
      continue;
    }
    if ($editable && $visible) {
      if (isset($field['widget']['weight'])) {
        $element =& _workflow_fields_array_path($form, $path);
        $element['#weight'] = $field['widget']['weight'];
      }
      continue;
    }

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

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

      // Standard handling.
      _workflow_fields_process_field($form, $field, $path, $node, $visible, $editable);
    }
  }
}
function _workflow_fields_process_field(&$form, $field, $path, $node, $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'])) {

      // Normal field.
      $renderable = field_view_field('node', $node, $field['field_name'], 'default');
      $parent[$field['field_name'] . '_view'] = array(
        '#type' => 'item',
        '#title' => isset($element['#title']) ? $element['#title'] : '',
        '#markup' => drupal_render($renderable),
        '#weight' => isset($field['widget']['weight']) ? $field['widget']['weight'] : 0,
      );
      if (isset($form['#group_children'][$field['field_name']])) {
        $form['#group_children'][$field['field_name'] . '_view'] = $form['#group_children'][$field['field_name']];
      }
    }

    // If non field, visible but not editable... you should define process.
    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;
}
function _workflow_fields_show_title(&$form, $field, $path, $node, $visible, $editable, $args) {
  $element =& _workflow_fields_array_path($form, $path);
  $parent =& _workflow_fields_array_path($form, array_slice($path, 0, -1));
  $element['#access'] = FALSE;
  $parent['title_view'] = array(
    '#type' => 'item',
    '#title' => isset($element['#title']) ? $element['#title'] : '',
    '#markup' => check_plain($node->title),
    '#weight' => isset($element['#weight']) ? $element['#weight'] : 0,
  );
}

/**
 * Implements hook_workflow_fields().
 */
function workflow_fields_workflow_fields($type) {
  $content = node_type_get_type($type);
  $fields = array();
  if ($content->has_title) {
    $fields['title'] = array(
      'label' => $content->title_label,
      'process' => '_workflow_fields_show_title',
    );
  }
  return $fields;
}
function _workflow_fields_get_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);
    $fields[$type] += field_info_instances('node', $type);
  }
  return $fields[$type];
}

/**
 * Implements hook_workflow_operations().
 */
function workflow_fields_workflow_operations($mode, $workflow, $state = NULL) {
  if ('state' == $mode && $workflow && $state->sid > 0) {
    $state_name = method_exists($state, 'getName') ? $state
      ->getName() : $state->state;
    return array(
      'workflow_fields_manage' => array(
        'title' => t('Workflow fields'),
        'href' => 'admin/config/workflow/workflow/fields_visibility/' . $workflow->wid . '/' . $state->sid,
        'attributes' => array(
          'title' => t('Edit the @state fields visibility', array(
            '@state' => $state_name,
          )),
          'alt' => t('Edit the @state fields visibility', array(
            '@state' => $state_name,
          )),
        ),
      ),
    );
  }
}

/**
 * Implements hook_menu().
 */
function workflow_fields_menu() {
  $items['admin/config/workflow/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',
    ),
  );
  $items['admin/config/workflow/workflow/fields_visibility/%workflow/%'] = array(
    'title' => 'Manage workflow fields',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'workflow_fields_form_workflow_admin_ui_form',
      5,
      6,
    ),
    'access arguments' => array(
      'administer workflow',
    ),
    'type' => MENU_CALLBACK,
  );
  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 WORKFLOW_FIELDS_AUTHOR_ROLE 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[] = WORKFLOW_FIELDS_AUTHOR_ROLE;
    }
  }
  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\n    FROM {workflow_fields} WHERE sid = :sid AND type = :type", array(
    ':sid' => $sid,
    ':type' => $type,
  ));
  foreach ($result as $row) {
    $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;
}

/**
 * Implements hook_field_access().
 */
function workflow_fields_field_access($op, $field, $entity_type, $node, $account) {
  if ($entity_type == 'node' || !$node) {
    $sid = NULL;
    $workflow_fields = array(
      '',
    );

    // Workflow 2+
    if (function_exists('_workflow_info_fields')) {
      $workflow_fields = _workflow_info_fields($node, $entity_type);
      if (0 === count($workflow_fields)) {
        return TRUE;
      }
    }

    // If there is one workflow that gives access to the field, access is granted
    foreach ($workflow_fields as $workflow_field_name => $workflow_field_info) {
      $sid = workflow_node_current_state($node, 'node', $workflow_field_name);
      if (FALSE === $sid) {
        $type_map = workflow_get_workflow_type_map_by_type($node->type);
        if (!$type_map) {
          return;
        }

        // Workflow 1.x
        if (function_exists('workflow_get_creation_state_by_wid')) {
          $sid = workflow_get_creation_state_by_wid($type_map->wid);
        }
        else {
          $workflow = workflow_load($type_map->wid);
          $sid = $workflow
            ->getCreationSid();
        }
      }

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

Functions

Constants

Namesort descending Description
WORKFLOW_FIELDS_AUTHOR_ROLE @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.