You are here

node_accessibility.module in Node Accessibility 7

Same filename and directory in other branches
  1. 8 node_accessibility.module

Module file for the node accessibility project.

File

node_accessibility.module
View source
<?php

/**
 * @file
 * Module file for the node accessibility project.
 */

/**
 * Implements hook_help().
 */
function node_accessibility_help($path, $arg) {
  $output = '';
  switch ($path) {
    case "admin/help#node_accessibility":
      $output .= '<p>' . t("This module provides accessibilty validation support on node entities.") . '</p>';
      return $output;
  }
}

/**
 * Implements hook_quail_api_permission_alter().
 */
function node_accessibility_quail_api_permission_alter(&$permissions) {
  if (!is_array($permissions)) {
    $permissions = array();
  }
  $permissions['access node accessibility tab'] = array(
    'title' => t("Access Node Accessibility Tab"),
    'description' => t("Grants permissions to access the accessibility tab on node pages."),
  );
  $permissions['perform node accessibility validation'] = array(
    'title' => t("Perform node accessibility validation"),
    'description' => t("Grants permissions to use the perform a manual validation of any given node."),
  );
}

/**
 * Implements hook_action_info().
 */
function node_accessibility_action_info() {
  return array(
    'node_accessibility_validate_action' => array(
      'type' => 'node',
      'label' => t("Accessibility validate content"),
      'configurable' => FALSE,
      'triggers' => array(
        'node_presave',
        'node_insert',
        'node_update',
      ),
    ),
    'node_accessibility_delete_action' => array(
      'type' => 'node',
      'label' => t("Delete accessibility statistics for content"),
      'configurable' => FALSE,
      'triggers' => array(
        'node_presave',
        'node_insert',
        'node_update',
      ),
    ),
  );
}

/**
 * Implements hook_views_api().
 */
function node_accessibility_views_api() {
  return array(
    'api' => 2.0,
    'path' => drupal_get_path('module', 'node_accessibility'),
  );
}

/**
 * Implements hook_menu().
 */
function node_accessibility_menu() {
  $items = array();
  $items['node/%node/accessibility'] = array(
    'title' => "Accessibility",
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'node_accessibility_accessibility_tab_page',
      1,
    ),
    'access callback' => 'node_accessibility_access_accessibility_tab',
    'access arguments' => array(
      1,
    ),
    'file' => 'pages.inc',
    'file path' => drupal_get_path('module', 'node_accessibility') . '/includes',
    'weight' => 8,
    'type' => MENU_LOCAL_TASK,
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
  );
  $items['node/%node/accessibility/%/revision'] = array(
    'title' => "Accessibility",
    'load arguments' => array(
      3,
      3,
    ),
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'node_accessibility_accessibility_tab_page',
      1,
      3,
    ),
    'access callback' => 'node_accessibility_access_accessibility_tab',
    'access arguments' => array(
      1,
    ),
    'file' => 'pages.inc',
    'file path' => drupal_get_path('module', 'node_accessibility') . '/includes',
  );
  return $items;
}

/**
 * Implements hook_menu_alter().
 */
function node_accessibility_menu_alter(&$items) {

  // provide a failsafe way to prevent menu override conflicts
  $alter_menu = variable_get('node_accessibility_alter_revision_menu', TRUE);
  if ($alter_menu) {
    $items['node/%node/revisions']['page callback'] = 'node_accessibility_revision_overview';
  }
}

/**
 * Implements hook_menu().
 */
function node_accessibility_admin_paths() {
  $paths = array();
  $paths['node/*/accessibility'] = TRUE;
  $paths['node/*/accessibility/*/revision'] = TRUE;
  return $paths;
}

/**
 * Implements hook_node_delete().
 */
function node_accessibility_node_delete($node) {
  if (!is_object($node)) {
    if (class_exists('cf_error')) {
      cf_error::invalid_object('node');
    }
    return;
  }
  node_accessibility_delete_node_problems($node->nid, NULL);
}

/**
 * Implements hook_node_revision_delete().
 */
function node_accessibility_node_revision_delete($node) {
  if (!is_object($node)) {
    if (class_exists('cf_error')) {
      cf_error::invalid_object('node');
    }
    return;
  }
  node_accessibility_delete_node_problems($node->nid, $node->vid);
}

/**
 * Implements hook_theme().
 */
function node_accessibility_theme($existing, $type, $theme, $path) {
  $themes = array();
  $themes['node_accessibility_information'] = array(
    'template' => 'node_accessibility_information',
    'variables' => array(
      'node' => NULL,
    ),
    'path' => drupal_get_path('module', 'node_accessibility') . '/includes',
  );
  return $themes;
}

/**
 * Template preprocess function for node_accessibility_information.tpl.php
 */
function template_preprocess_node_accessibility_information(&$variables) {
  $variables['workbench_moderation'] = FALSE;
  $variables['published'] = t("No");
  drupal_add_css(drupal_get_path('module', 'node_accessibility') . '/includes/node_accessibility_information.css');
  if (!isset($variables['node']) || !is_object($variables['node'])) {
    if (class_exists('cf_error')) {
      cf_error::invalid_object('node');
    }
    return;
  }
  if ($variables['node']->status) {
    $variables['published'] = t("Yes");
  }
  if (module_exists('workbench_moderation') && property_exists($variables['node'], 'workbench_moderation')) {
    $variables['workbench_moderation'] = TRUE;
    $variables['live'] = t("No");
    $variables['current'] = t("No");
    if (isset($variables['node']->workbench_moderation['current']) && is_object($variables['node']->workbench_moderation['current'])) {
      if ($variables['node']->workbench_moderation['current']->vid == $variables['node']->vid) {
        $variables['current'] = t("Yes");
      }
    }
    if (isset($variables['node']->workbench_moderation['published']) && is_object($variables['node']->workbench_moderation['published'])) {
      if ($variables['node']->workbench_moderation['published']->vid == $variables['node']->vid) {
        $variables['live'] = t("Yes");
      }
    }
  }
}

/**
 * Checks if user can access the accessibility tab.
 *
 * @param object $node
 *   A node object whose access is to be returned.
 *
 * @return bool
 *   TRUE if user can make conversions using this type, FALSE otherwise.
 */
function node_accessibility_access_accessibility_tab($node) {
  $access = FALSE;
  if (!is_object($node)) {
    if (class_exists('cf_error')) {
      cf_error::invalid_object('node');
    }
    return $access;
  }
  if (node_access('view', $node)) {
    $access = user_access('access node accessibility tab');
    if ($access && node_accessibility_is_enabled($node->type)) {
      $access = TRUE;
    }
    else {
      $access = FALSE;
    }
  }
  return $access;
}

/**
 * Implements hook_form_FORM_ID_alter() for the node type form.
 */
function node_accessibility_form_node_type_form_alter(&$form, &$form_state, $form_id) {
  $node_type_settings_object = FALSE;
  $node_type_settings_array = array(
    'type' => NULL,
    'required' => NULL,
    'standards' => NULL,
  );
  $standards = quail_api_get_standards_list(NULL, 'snippet');
  $display_levels = quail_api_get_display_levels_list(NULL);
  $methods = quail_api_get_validation_methods(NULL);
  $methods_list = quail_api_get_validation_methods_list(NULL);
  $filter_formats = filter_formats();
  $formats = array();
  $options = array();
  foreach ($filter_formats as $filter_format => $filter_format_settings) {
    $formats[$filter_format] = $filter_format_settings->name;
  }
  $default_enabled = 'disabled';
  $default_standards = array();
  $default_method = 'quail_api_method_immediate';
  $default_format = 'full_html';
  if (!empty($form['#node_type']->type)) {
    $node_type = $form['#node_type']->type;
    $node_type_settings = node_accessibility_load_node_type_settings();
    if (isset($node_type_settings[$node_type])) {
      $default_enabled = isset($node_type_settings[$node_type]['required']) && $node_type_settings[$node_type]['required'] ? 'required' : 'optional';
      $default_standards = isset($node_type_settings[$node_type]['standards']) ? $node_type_settings[$node_type]['standards'] : $default_standards;
      $default_method = isset($node_type_settings[$node_type]['method']) ? $node_type_settings[$node_type]['method'] : $default_method;
      $default_format = isset($node_type_settings[$node_type]['format']) ? $node_type_settings[$node_type]['format'] : $default_format;
    }
  }
  $form['node_accessibility_validation'] = array(
    '#type' => 'fieldset',
    '#title' => t("Accessibility Validation"),
    '#description' => t("Provides options for enabled and disabled accessibility validation on text stored in this field."),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#tree' => TRUE,
    '#group' => 'additional_settings',
  );
  $form['node_accessibility_validation']['quail_enabled'] = array(
    '#type' => 'select',
    '#title' => t("Accessibility Validation"),
    '#default_value' => $default_enabled,
    '#options' => array(
      'disabled' => t("Disabled"),
      'optional' => t("Enabled (optional)"),
      'required' => t("Enabled (required)"),
    ),
    '#description' => t("Choose if accessibility validation should be enabled for this node type and if the node type is required to pass accessibility validation."),
  );
  $form['node_accessibility_validation']['method'] = array(
    '#type' => 'select',
    '#title' => t("Validation Method"),
    '#default_value' => $default_method,
    '#options' => $methods_list,
    '#description' => t("Choose the way in which validation is performed."),
    '#dependency' => array(
      'edit-node-accessibility-validation-quail-enabled' => array(
        'optional',
        'required',
      ),
    ),
  );

  // add documentation for each option
  $form['node_accessibility_validation']['method']['#description'] .= '<ul>';
  foreach ($methods as $method => $method_settings) {
    $form['node_accessibility_validation']['method']['#description'] .= '<li>' . $method_settings['human_name'] . ': ' . $method_settings['description'] . '</li>';
  }
  $form['node_accessibility_validation']['method']['#description'] .= '</ul>';
  $form['node_accessibility_validation']['format'] = array(
    '#type' => 'select',
    '#title' => t("Markup Format"),
    '#default_value' => $default_format,
    '#options' => $formats,
    '#description' => t("Choose the filter to use when presenting the results of validation checks. This is only used for the html markup associated with the problem or suggestion."),
    '#dependency' => array(
      'edit-node-accessibility-validation-quail-enabled' => array(
        'optional',
        'required',
      ),
    ),
  );
  $form['node_accessibility_validation']['standards'] = array(
    '#type' => 'checkboxes',
    '#title' => t("Accessibility Standards"),
    '#default_value' => $default_standards,
    '#options' => $standards,
    '#description' => t("Choose which accessibility standards to validate against. It is strongly suggested that only 1 standard should be used because many of the validation tests are performed by more than one standard."),
    '#dependency' => array(
      'edit-node-accessibility-validation-quail-enabled' => array(
        'optional',
        'required',
      ),
    ),
  );
  $form['#submit'][] = 'node_accessibility_node_type_form_submit';
}

/**
 * Handles submitting the accessible content specific node type settings.
 *
 * @param array $form
 *   A form array
 * @param array $form_state
 *   A form state
 */
function node_accessibility_node_type_form_submit($form, &$form_state) {
  if (empty($form_state['values']['type'])) {
    if (class_exists('cf_error')) {
      cf_error::empty_string('form_state[values][type]');
    }
  }
  $node_type_settings = node_accessibility_load_node_type_settings();
  $columns = array(
    'enabled',
    'required',
    'standards',
    'method',
    'format',
  );
  $record = array();
  $record['type'] = $form_state['values']['type'];
  $record['method'] = isset($form_state['values']['node_accessibility_validation']['method']) ? $form_state['values']['node_accessibility_validation']['method'] : 'quail_api_method_immediate';
  $record['format'] = isset($form_state['values']['node_accessibility_validation']['format']) ? $form_state['values']['node_accessibility_validation']['format'] : 'full_html';
  if (isset($form_state['values']['node_accessibility_validation']['quail_enabled'])) {
    $record['required'] = $form_state['values']['node_accessibility_validation']['quail_enabled'] === 'required';
  }
  else {
    $record['required'] = FALSE;
  }
  if (isset($form_state['values']['node_accessibility_validation']['standards'])) {
    foreach ($form_state['values']['node_accessibility_validation']['standards'] as $key => $value) {
      if ($value == '0') {
        unset($form_state['values']['node_accessibility_validation']['standards'][$key]);
      }
    }
    $record['standards'] = $form_state['values']['node_accessibility_validation']['standards'];
  }
  else {
    $record['standards'] = array();
  }
  $node_type_settings[$form_state['values']['type']] = $record;
  variable_set('node_accessibility_node_type_settings', $node_type_settings);
}

/**
 * Implements hook_node_operations().
 */
function node_accessibility_node_operations() {
  $operations = array(
    'node_accessibility_operation_validate' => array(
      'label' => t("Accessibility Validate selected content"),
      'callback' => 'node_accessibility_operation_validate',
    ),
  );
  return $operations;
}

/**
 * Perform validation on any number of nodes.
 *
 * Only nodes that have accessibility enabled will be validated in this way.
 * All other nodes are silently ignored.
 *
 * @param array $nids
 *   An array of node ids
 */
function node_accessibility_operation_validate($nids) {
  $nodes = node_load_multiple($nids);
  $node_type_settings = node_accessibility_load_node_type_settings();
  $result = TRUE;
  foreach ($nodes as $key => $node) {
    if (!is_object($node)) {
      continue;
    }
    if (!array_key_exists($node->type, $node_type_settings)) {
      continue;
    }
    if ($node_type_settings[$node->type] === FALSE) {
      continue;
    }
    $methods = quail_api_get_validation_methods(NULL);
    $database = FALSE;
    $node_settings = $node_type_settings[$node->type];
    if (!empty($node_settings['method']) && is_array($methods) && array_key_exists('database', $methods[$node_settings['method']])) {
      $database = $methods[$node_settings['method']]['database'];
    }
    $results = node_accessibility_perform_validation(array(
      $node,
    ), array(), NULL, NULL);
    if (isset($results[$node->nid][$node->vid]['report'])) {
      $reports = $results[$node->nid][$node->vid]['report'];
      unset($results);
      if ($database && !empty($reports)) {
        $no_failures = TRUE;
        foreach ($reports as $severity => $severity_results) {
          if (isset($severity_results['total']) && $severity_results['total'] > 0) {
            $no_failures = FALSE;
            break;
          }
        }
        if ($no_failures) {
          node_accessibility_delete_node_problems($node->nid, $node->vid);
        }
        else {
          node_accessibility_save_node_problems($node->nid, $node->vid, $reports);
        }
      }
    }
    else {
      $result = FALSE;
    }
  }
  if ($result) {
    drupal_set_message(t("The validation has been performed."));
  }
  else {
    drupal_set_message(t("Unable to perform the validation, something went wrong."), 'error');
  }
}

/**
 * Performs validation on the given nodes and stores the results.
 *
 * @param array $nodes_or_nids
 *   An array of node objects or node ids.
 * @param array $vids
 *   (optional) The an array vids to use during validation, with the following
 *   structure:
 *   - nid: an array of vids With the array key being the node id.
 *     - example: $vids = array('1' => array('2', '3', '4')) such '1' is the
 *     node id and 2, 3, and 4 are the vids for node 1.
 * @param string|null $language
 *   (optional) The language to use during validation
 * @param array|null $display_level
 *   (optional) An array of booleans representing the qual test display levels
 *   (defaults to quail_api_create_quail_display_level_array()).
 *
 * @return array
 *   An array of all test failures, if any.
 */
function node_accessibility_perform_validation($nodes_or_nids, $vids = array(), $language = NULL, $display_level = NULL) {
  if (!is_array($nodes_or_nids)) {
    if (class_exists('cf_error')) {
      cf_error::invalid_array('node_or_nids');
    }
    return array();
  }
  if (!is_array($vids)) {
    if (class_exists('cf_error')) {
      cf_error::invalid_array('vids');
    }
    return array();
  }
  if (count($nodes_or_nids) == 0) {
    return array();
  }
  $node_type_settings = node_accessibility_load_node_type_settings();
  $results = array();
  $standards = quail_api_get_standards(NULL, 'snippet');
  foreach ($nodes_or_nids as $node_or_nid) {
    if (is_object($node_or_nid)) {
      $node = $node_or_nid;
    }
    else {
      $node = node_load($node_or_nid);
      if (!is_object($node)) {
        if (class_exists('cf_error')) {
          cf_error::invalid_variable('node', "Unable to load the node with the following node id: :nid.", array(
            ':nid' => $node_or_nid,
          ));
        }
        continue;
      }
    }
    $node_settings = FALSE;
    if (isset($node_type_settings[$node->type])) {
      $node_settings = $node_type_settings[$node->type];
    }
    if (array_key_exists($node->nid, $vids) && !empty($vids[$node->nid])) {
      $revisions = $vids[$node->nid];
    }
    else {
      $revisions = array(
        $node->vid,
      );
    }
    $results[$node->nid] = array();
    foreach ($revisions as $rid) {
      $results[$node->nid][$rid] = array();
      if ($node->vid == $rid) {
        $revision = $node;
      }
      else {
        $revision = node_load($node->nid, $rid);
        if (!is_object($revision)) {
          if (class_exists('cf_error')) {
            cf_error::invalid_variable('revision', "Unable to load the node revision with the node id of :nid and the revision id of :rid.", array(
              ':nid' => $node->nid,
              ':rid' => $rid,
            ));
          }
          continue;
        }
      }
      if (is_array($node_settings)) {
        $node_view = node_view($revision, 'full', $language);
        $rendered_node = drupal_render($node_view);
        unset($node_view);
        if (!empty($node_settings['standards'])) {
          foreach ($node_settings['standards'] as $standard_name) {
            $results[$revision->nid][$rid] = array_merge($results[$revision->nid][$rid], quail_api_validate_markup($rendered_node, $standards[$standard_name], $display_level));
            if (module_exists('rules')) {
              rules_invoke_event('node_accessibility_after_validating', $revision, $results[$node->nid][$rid]);
            }
          }
        }
      }
    }
  }
  return $results;
}

/**
 * Loads the node type settings table data for the given node type.
 *
 * @return array
 *   An array of values returned by variable_get().
 */
function node_accessibility_load_node_type_settings() {
  static $node_accessibility_node_type_settings;
  if (!isset($node_accessibility_node_type_settings)) {
    $node_accessibility_node_type_settings = variable_get('node_accessibility_node_type_settings', array());
  }
  return $node_accessibility_node_type_settings;
}

/**
 * Saves the node report data to the database.
 *
 * @param int $nid
 *   The node id of the node associated with the given reports.
 * @param int $vid
 *   The node revision id of the node associated with the given reports.
 * @param array $reports
 *   The reports array as returned by the quail library quail api reporter.
 *
 * @return bool
 *   TRUE on success, FALSE otherwise.
 */
function node_accessibility_save_node_problems($nid, $vid, $reports) {
  if (!is_array($reports)) {
    if (class_exists('cf_error')) {
      cf_error::invalid_array('reports');
    }
    return FALSE;
  }
  $tests_known = (array) quail_api_load_tests(array(), 'machine_name');
  $problems = array();
  foreach ($reports as $severity => $severity_results) {
    if ($severity != 'total') {
      foreach ($severity_results as $test_name => $test_results) {
        if ($test_name == 'total') {
          continue;
        }
        if (!array_key_exists($test_name, $tests_known)) {
          if (empty($test_results['body']['title']) || empty($test_results['body']['description'])) {

            // @todo should this send a watchdog warning?
            continue;
          }
          $test_data = array();
          $test_data['machine_name'] = $test_name;
          $test_data['severity'] = $severity;
          $test_data['human_name'] = $test_results['body']['title'];
          $test_data['description'] = $test_results['body']['description'];
          $results = quail_api_save_test($test_data);
          if ($results === FALSE) {
            watchdog('node_accessibility', "Failed to insert :machine_name into quail api tests database table.", array(
              ':machine_name' => $test_name,
            ), WATCHDOG_ERROR);
            continue;
          }

          // The row must be loaded from the database so that the id can be retrieved
          $loaded_test = quail_api_load_tests(array(
            'machine_name' => $test_name,
          ), NULL);
          if (!isset($loaded_test['0']) || !is_object($loaded_test['0'])) {
            watchdog('node_accessibility', "Failed to insert :machine_name problems into node accessibility tests database table because tests_known[:machine_name] is not a valid object.", array(
              ':machine_name' => $test_name,
            ), WATCHDOG_ERROR);
            continue;
          }
          $tests_known[$test_name] = $loaded_test['0'];
        }
        foreach ($test_results['problems'] as $problem_name => $problem) {
          if (empty($problem['line']) || empty($problem['element'])) {

            // @todo should this send a watchdog warning?
            continue;
          }
          $problem_data = array();
          $problem_data['nid'] = $nid;
          $problem_data['vid'] = $vid;
          $problem_data['test_id'] = $tests_known[$test_name]->id;
          $problem_data['line'] = $problem['line'];
          $problem_data['element'] = $problem['element'];
          $problems[] = $problem_data;
        }
      }
    }
  }
  if (!empty($problems)) {
    $results = node_accessibility_replace_problems($nid, $vid, $problems);
    if ($results === FALSE) {
      watchdog('node_accessibility', "Failed to insert :machine_name problems into node accessibility problems database table.", array(
        ':machine_name' => $test_name,
      ), WATCHDOG_ERROR);
    }
  }
}

/**
 * Deletes the node report data from the database.
 *
 * This is primarly used to remove data for a node that no longer has any
 * validation failures.
 *
 * @param int $nid
 *   The node id of the node associated with the given reports.
 * @param int|null $vid
 *   (optional) The node revision id of the node associated with the given
 *   reports.
 *
 * @return bool
 *   TRUE on success, FALSE otherwise.
 */
function node_accessibility_delete_node_problems($nid, $vid = NULL) {
  if (!is_numeric($nid)) {
    if (class_exists('cf_error')) {
      cf_error::invalid_numeric('nid');
    }
    return FALSE;
  }
  if (!is_null($vid) && !is_numeric($vid)) {
    if (class_exists('cf_error')) {
      cf_error::invalid_numeric('vid');
    }
    return FALSE;
  }

  // @todo when vid support is added, be sure to add a conditional check against vid here
  $query = db_delete('node_accessibility_problems');
  $query
    ->condition('nid', $nid);
  if (!is_null($vid)) {
    $query
      ->condition('vid', $vid);
  }
  try {
    return $query
      ->execute();
  } catch (Exception $e) {
    if (class_exists('cf_error')) {
      cf_error::on_query_execution($e);
    }
  }
  return FALSE;
}

/**
 * Returns TRUE if accessibility validation functionality is enabled.
 *
 * @param string $node_type
 *   A node type string.
 *
 * @return bool
 *   The return TRUE when accessibility is enabled for the given node type.
 *   Otherwise, FALSE is returned.
 */
function node_accessibility_is_enabled($node_type) {
  $node_type_settings = node_accessibility_load_node_type_settings();
  if (isset($node_type_settings[$node_type]) && $node_type_settings[$node_type] !== FALSE) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Returns TRUE if accessibility validation functionality is required.
 *
 * @param string $node_type
 *   A node type string
 *
 * @return bool|null
 *   TRUE or FALSE depending on whether or not accessibility validation
 *   functionality is required for the given node type.
 *   NULL is returned if accessibility validation functionality is not enabled.
 */
function node_accessibility_is_required($node_type) {
  $node_type_settings = node_accessibility_load_node_type_settings();
  if (!isset($node_type_settings[$node_type]) || $node_type_settings[$node_type] === FALSE) {
    return NULL;
  }
  if (isset($node_type_settings[$node_type]['required']) && $node_type_settings[$node_type]['required'] === TRUE) {
    return TRUE;
  }
  return FALSE;
}

/**
 * Loads the nodes problem data.
 *
 * @param array $conditions
 *   (optional) An array with the following possible keys:
 *   - 'id' The unique id representing a specific problem.
 *   - 'nid' the node id.
 *   - 'vid' the node revision id.
 *   - 'test_id' a numeric value representing the id of the test the problem is
 *     associated with.
 *   - 'line' a numeric value representing the line number in which a problem
 *     applies to.
 *   - 'live_only' a boolean that specifies whether or not to restrict loading
 *     of problems to live content (active revision). (defaults to FALSE)
 *   - 'unlive_only' a boolean that specifies whether or not to restrict
 *     loading of problems to non-live content (active revision). (defaults to
 *     FALSE)
 *   - 'published_only' a boolean that specifies whether or not to restrict
 *     loading of problems to published content (active revision). (defaults
 *     to FALSE)
 *   - 'unpublished_only' a boolean that specifies whether or not to restrict
 *     loading of problems to unpublished content (active revision). (defaults
 *     to FALSE)
 *   - 'sort_by' the name of a column to sort by.
 *   - 'sort_order' the order in which to sort by ('asc' or 'desc').
 *   - 'node_columns' a boolean that specifies whether or not to load the node
 *     column fields in addition to the node accessibility problems fields.
 *     (defaults to FALSE)
 * @param string|null $keyed
 *   (optional) A string matching one of the following: 'id'
 *   When this is NULL, the default behavior is to return the array exactly as
 *   it was returned by the database call.
 *   When this is a valid string, the key names of the returned array will use
 *   the specified key name.
 *
 * @return array
 *   An array of database results.
 */
function node_accessibility_load_problems($conditions = array(), $keyed = NULL) {
  if (!is_array($conditions)) {
    if (class_exists('cf_error')) {
      cf_error::invalid_array('conditions');
    }
    return array();
  }
  $query = db_select('node_accessibility_problems', 'nap');
  $query
    ->fields('nap');
  $sort_by = 'nap.nid';
  $sort_order = 'ASC';
  if (isset($conditions['sort_order'])) {
    switch ($conditions['sort_order']) {
      case 'ASC':
      case 'DESC':
        $sort_order = $conditions['sort_order'];
        break;
    }
  }
  if (isset($conditions['sort_by']) && is_string($conditions['sort_by'])) {
    switch ($conditions['sort_by']) {
      case 'id':
      case 'nid':
      case 'vid':
      case 'test_id':
      case 'line':
      case 'element':
        $sort_by = 'nap.' . $conditions['sort_by'];
        break;
      default:
        $sort_by = $conditions['sort_by'];
        break;
    }
  }
  $query
    ->orderBy($sort_by, $sort_order);
  $and = NULL;
  $joined = FALSE;
  if (isset($conditions['live_only']) && is_bool($conditions['live_only'])) {
    $query
      ->innerjoin('node', 'n', 'nap.vid = n.vid');
    $joined = TRUE;
  }
  else {
    if (isset($conditions['unlive_only']) && is_bool($conditions['unlive_only'])) {
      $query
        ->innerjoin('node', 'n', 'nap.nid = n.nid');
      $joined = TRUE;
      $and = db_and();
      $and
        ->where('NOT nap.vid = n.vid');
    }
  }
  if (isset($conditions['published_only']) && is_bool($conditions['published_only'])) {
    if (!$joined) {
      $query
        ->innerjoin('node', 'n', 'nap.nid = n.nid');
      $joined = TRUE;
    }
    if (is_null($and)) {
      $and = db_and();
    }
    $and
      ->condition('n.status', 1, '=');
  }
  else {
    if (isset($conditions['unpublished_only']) && is_bool($conditions['unpublished_only'])) {
      if (!$joined) {
        $query
          ->innerjoin('node', 'n', 'nap.nid = n.nid');
        $joined = TRUE;
      }
      if (is_null($and)) {
        $and = db_and();
      }
      $and
        ->condition('n.status', 0, '=');
    }
  }
  if (isset($conditions['id']) && is_numeric($conditions['id'])) {
    if (is_null($and)) {
      $and = db_and();
    }
    $and
      ->condition('nap.id', $conditions['id'], '=');
  }
  if (isset($conditions['nid']) && is_numeric($conditions['nid'])) {
    if (is_null($and)) {
      $and = db_and();
    }
    $and
      ->condition('nap.nid', $conditions['nid'], '=');
  }
  if (isset($conditions['vid']) && is_numeric($conditions['vid'])) {
    if (is_null($and)) {
      $and = db_and();
    }
    $and
      ->condition('nap.vid', $conditions['vid'], '=');
  }
  if (isset($conditions['test_id']) && is_numeric($conditions['test_id'])) {
    if (is_null($and)) {
      $and = db_and();
    }
    $and
      ->condition('nap.test_id', $conditions['test_id'], '=');
  }
  if (!empty($conditions['line'])) {
    if (is_null($and)) {
      $and = db_and();
    }
    $and
      ->condition('nap.line', $conditions['line'], '=');
  }
  if (is_object($and)) {
    $query
      ->condition($and);
  }
  if (isset($conditions['node_columns']) && is_bool($conditions['node_columns'])) {
    if (!$joined) {
      $query
        ->innerjoin('node', 'n', 'nap.nid = n.nid');
      $joined = TRUE;
    }
    $query
      ->fields('n');
  }
  if ($keyed === 'id') {
    $results = array();
    try {
      $records = $query
        ->execute();
    } catch (Exception $e) {
      if (class_exists('cf_error')) {
        cf_error::on_query_execution($e);
      }
      return array();
    }
    foreach ($records as $record) {
      if (!is_object($record)) {
        continue;
      }
      $results[$record->{$keyed}] = $record;
    }
    return $results;
  }
  try {
    return $query
      ->execute()
      ->fetchAll();
  } catch (Exception $e) {
    if (class_exists('cf_error')) {
      cf_error::on_query_execution($e);
    }
  }
  return array();
}

/**
 * This stores validation problems for a single node to the database.
 *
 * This will delete all pre-existing problems for the given node.
 *
 * @param int $nid
 *   The node id.
 * @param int $vid
 *   The node revision id.
 * @param array $problems
 *   An array of arrays of test data containing the test problems
 *   - each nested array should containt the following keys:
 *     - nid: The node id of the node.
 *     - vid: The node revision id of the node.
 *     - test_id: The id of the problem.
 *     - line: The line number of the problem.
 *     - element: The html markup of the problem.
 *
 * @return int|false
 *   The return states of either FALSE, SAVED_NEW, or SAVED_UPDATED.
 */
function node_accessibility_replace_problems($nid, $vid, $problems) {
  if (!is_numeric($nid)) {
    if (class_exists('cf_error')) {
      cf_error::invalid_numeric('nid');
    }
    return FALSE;
  }
  if (!is_numeric($vid)) {
    if (class_exists('cf_error')) {
      cf_error::invalid_numeric('vid');
    }
    return FALSE;
  }
  if (!is_array($problems)) {
    if (class_exists('cf_error')) {
      cf_error::invalid_array('problems');
    }
    return FALSE;
  }
  $result = FALSE;
  $transaction = db_transaction();
  try {
    $query = db_delete('node_accessibility_problems');
    $query
      ->condition('nid', $nid);
    $query
      ->condition('vid', $vid);
    $query
      ->execute();
    foreach ($problems as $problem) {
      $result = node_accessibility_save_problem($problem);

      // @todo handle return errors
    }

    // force transaction to execute
    unset($transaction);
  } catch (Exception $e) {
    $transaction
      ->rollback();
    if (class_exists('cf_error')) {
      cf_error::on_query_execution($e);
    }
    return FALSE;
  }
  return $result;
}

/**
 * This stores a validation problem for a given node to the database.
 *
 * @param array $problem_data
 *   An array of test data with the following keys:
 *   - nid: The node id of the node.
 *   - vid: The node revision id of the node.
 *   - test_id: The id of the problem.
 *   - line: The line number of the problem.
 *   - element: The html markup of the problem.
 *
 * @return int|false
 *   The return states of either FALSE, SAVED_NEW, or SAVED_UPDATED.
 */
function node_accessibility_save_problem($problem_data) {
  if (!is_array($problem_data)) {
    if (class_exists('cf_error')) {
      cf_error::invalid_array('problem_data');
    }
    return FALSE;
  }
  $result = FALSE;
  $columns = array(
    'nid',
    'vid',
    'test_id',
    'line',
    'element',
  );
  foreach ($columns as $key) {
    if (empty($problem_data[$key])) {
      if (class_exists('cf_error')) {
        cf_error::missing_array_key('problem_data', $key);
      }
      return FALSE;
    }
  }
  $data = array();
  $primary_key = array();
  $results = FALSE;
  if (!empty($problem_data['id'])) {
    if (!is_numeric($problem_data['id'])) {
      if (class_exists('cf_error')) {
        cf_error::invalid_numeric('problem_data[id]');
      }
      return FALSE;
    }
    $results = node_accessibility_load_problems(array(
      'id' => $problem_data['id'],
    ), NULL);
    if (is_array($results) && !empty($results)) {
      $data['id'] = $problem_data['id'];
      $primary_key[] = 'id';
    }
    else {

      // if a specific id is requested but does not exist, then it cannot be updated.
      return FALSE;
    }
  }
  $data['nid'] = $problem_data['nid'];
  $data['vid'] = $problem_data['vid'];
  $data['test_id'] = $problem_data['test_id'];
  $data['line'] = $problem_data['line'];
  $data['element'] = $problem_data['element'];
  $result = drupal_write_record('node_accessibility_problems', $data, $primary_key);
  return $result;
}

/**
 * This restructures an array of problems as returned from database calls.
 *
 * The data is converted into a format that can be processed by the quail api
 * theme functions.
 *
 * @param int $nid
 *   A node id the results belong to.
 * @param int $vid
 *   A node revision id the results belong to.
 * @param array|null $display_levels
 *   (optional) An array of display levels as returned by
 *   quail_api_get_display_levels().
 *   If NULL is passed, then this will auto-call
 *   quail_api_get_display_levels().
 *
 * @return array
 *   An array of database results in a format that can be processed by the
 *   quail_api theme functions.
 */
function node_accessibility_restructure_results($nid, $vid, $display_levels = NULL) {
  $problems = (array) node_accessibility_load_problems(array(
    'nid' => $nid,
    'vid' => $vid,
  ), NULL);
  $tests = quail_api_load_tests(array(), 'id');
  $results = array();
  if (!is_array($display_levels)) {
    $display_levels = quail_api_get_display_levels(NULL);
  }
  foreach ($display_levels as $key => $value) {
    if (empty($value['id'])) {
      continue;
    }
    $results[$value['id']] = array(
      'total' => 0,
    );
  }
  foreach ($problems as $problem_key => $problem_data) {
    if (!is_object($problem_data)) {
      continue;
    }
    $test = $tests[$problem_data->test_id];
    if (!isset($results[$test->severity][$test->machine_name])) {
      $results[$test->severity][$test->machine_name] = array();
      $results[$test->severity][$test->machine_name]['body'] = array();
      $results[$test->severity][$test->machine_name]['body']['title'] = $test->human_name;
      $results[$test->severity][$test->machine_name]['body']['description'] = $test->description;
      $results[$test->severity][$test->machine_name]['problems'] = array();
    }
    $problem = array();
    $problem['line'] = $problem_data->line;
    $problem['element'] = $problem_data->element;
    $results[$test->severity][$test->machine_name]['problems'][] = $problem;
    $results[$test->severity]['total']++;
  }
  return $results;
}

/**
 * Implements hook_node_type_delete().
 */
function node_accessibility_node_type_delete($info) {
  if (!is_object($info)) {
    if (class_exists('cf_error')) {
      cf_error::invalid_object('info');
    }
    return;
  }
  $node_type_settings = variable_get('node_accessibility_node_type_settings', array());
  unset($node_type_settings[$info->type]);
  variable_set('node_accessibility_node_type_settings', $node_type_settings);
}

/**
 * Performs accessibility validation on a given node.
 *
 * $param object $node
 *   A node object.
 *
 * @return array|false
 *   An array containing the validation results or FALSE.
 */
function node_accessibility_validate_action($node) {
  if (!is_object($node)) {
    if (class_exists('cf_error')) {
      cf_error::invalid_object('node');
    }
    return FALSE;
  }
  $results = node_accessibility_perform_validation(array(
    $node,
  ));
  $reports = array();
  $node_type_settings = node_accessibility_load_node_type_settings();
  $node_settings = $node_type_settings[$node->type];
  if (isset($results[$node->nid][$node->vid]['report'])) {
    $reports = $results[$node->nid][$node->vid]['report'];
    $methods = quail_api_get_validation_methods(NULL);
    $database = isset($methods[$node_settings['method']]['database']) ? $methods[$node_settings['method']]['database'] : FALSE;
    unset($results);
    if ($database && !empty($reports)) {
      $no_failures = TRUE;
      foreach ($reports as $severity => $severity_results) {
        if (isset($severity_results['total']) && $severity_results['total'] > 0) {
          $no_failures = FALSE;
          break;
        }
      }
      if ($no_failures) {
        node_accessibility_delete_node_problems($node->nid, $node->vid);
      }
      else {
        node_accessibility_save_node_problems($node->nid, $node->vid, $reports);
      }
    }
  }
  watchdog('actions', "Accessibility validating node %nid:%vid.", array(
    '%nid' => $node->nid,
    '%vid' => $node->vid,
  ));
  return $reports;
}

/**
 * Delete accessibility validation statistics for a node.
 *
 * $param object $node
 *   A node object.
 *
 * @return bool
 *   An TRUE on success, FALSE otherwise.
 */
function node_accessibility_delete_action($node) {
  if (!is_object($node)) {
    if (class_exists('cf_error')) {
      cf_error::invalid_object('node');
    }
    return FALSE;
  }
  $result = node_accessibility_delete_node_problems($node->nid, $node->vid);
  if ($result === FALSE) {
    return FALSE;
  }
  watchdog('actions', "Deleted accessibility validation statistics for node %nid:%vid.", array(
    '%nid' => $node->nid,
    '%vid' => $node->vid,
  ));
  return TRUE;
}

/**
 * Wraps or Replaces the node_revision_overview function.
 *
 * This adds add an "accessibility" link.
 *
 * $param object $node
 *   A node object.
 *
 * @return array
 *   An array containing the overview page structure.
 */
function node_accessibility_revision_overview($node) {
  if (!user_access('access node accessibility tab')) {
    return node_revision_overview($node);
  }
  drupal_set_title(t("Revisions for %title", array(
    '%title' => $node->title,
  )), PASS_THROUGH);
  $header = array(
    t("Revision"),
    array(
      'data' => t("Operations"),
      'colspan' => 3,
    ),
  );
  $revisions = node_revision_list($node);
  $rows = array();
  $revert_permission = FALSE;
  if ((user_access('revert revisions') || user_access('administer nodes')) && node_access('update', $node)) {
    $revert_permission = TRUE;
  }
  $delete_permission = FALSE;
  if ((user_access('delete revisions') || user_access('administer nodes')) && node_access('delete', $node)) {
    $delete_permission = TRUE;
  }
  foreach ($revisions as $revision) {
    $row = array();
    $operations = array();
    if ($revision->current_vid > 0) {
      $row[] = array(
        'data' => t("!date by !username", array(
          '!date' => l(format_date($revision->timestamp, 'short'), 'node/' . $node->nid),
          '!username' => theme('username', array(
            'account' => $revision,
          )),
        )) . ($revision->log != '' ? "<p class='revision-log'>" . filter_xss($revision->log) . "</p>" : ""),
        'class' => array(
          'revision-current',
        ),
      );
      $operations[] = array(
        'data' => drupal_placeholder(t("current revision")),
        'class' => array(
          'revision-current',
        ),
        'colspan' => 3,
      );
    }
    else {
      $row[] = t('!date by !username', array(
        '!date' => l(format_date($revision->timestamp, 'short'), 'node/' . $node->nid . '/revisions/' . $revision->vid . '/view'),
        '!username' => theme('username', array(
          'account' => $revision,
        )),
      )) . ($revision->log != '' ? "<p class='revision-log'>" . filter_xss($revision->log) . "</p>" : '');
      $operations[] = l(t("accessibility"), 'node/' . $node->nid . '/accessibility/' . $revision->vid . '/revision');
      if ($revert_permission) {
        $operations[] = l(t("revert"), 'node/' . $node->nid . '/revisions/' . $revision->vid . '/revert');
      }
      if ($delete_permission) {
        $operations[] = l(t("delete"), 'node/' . $node->nid . '/revisions/' . $revision->vid . '/delete');
      }
    }
    $rows[] = array_merge($row, $operations);
  }
  $build['node_revisions_table'] = array(
    '#theme' => 'table',
    '#rows' => $rows,
    '#header' => $header,
  );
  return $build;
}

Functions

Namesort descending Description
node_accessibility_access_accessibility_tab Checks if user can access the accessibility tab.
node_accessibility_action_info Implements hook_action_info().
node_accessibility_admin_paths Implements hook_menu().
node_accessibility_delete_action Delete accessibility validation statistics for a node.
node_accessibility_delete_node_problems Deletes the node report data from the database.
node_accessibility_form_node_type_form_alter Implements hook_form_FORM_ID_alter() for the node type form.
node_accessibility_help Implements hook_help().
node_accessibility_is_enabled Returns TRUE if accessibility validation functionality is enabled.
node_accessibility_is_required Returns TRUE if accessibility validation functionality is required.
node_accessibility_load_node_type_settings Loads the node type settings table data for the given node type.
node_accessibility_load_problems Loads the nodes problem data.
node_accessibility_menu Implements hook_menu().
node_accessibility_menu_alter Implements hook_menu_alter().
node_accessibility_node_delete Implements hook_node_delete().
node_accessibility_node_operations Implements hook_node_operations().
node_accessibility_node_revision_delete Implements hook_node_revision_delete().
node_accessibility_node_type_delete Implements hook_node_type_delete().
node_accessibility_node_type_form_submit Handles submitting the accessible content specific node type settings.
node_accessibility_operation_validate Perform validation on any number of nodes.
node_accessibility_perform_validation Performs validation on the given nodes and stores the results.
node_accessibility_quail_api_permission_alter Implements hook_quail_api_permission_alter().
node_accessibility_replace_problems This stores validation problems for a single node to the database.
node_accessibility_restructure_results This restructures an array of problems as returned from database calls.
node_accessibility_revision_overview Wraps or Replaces the node_revision_overview function.
node_accessibility_save_node_problems Saves the node report data to the database.
node_accessibility_save_problem This stores a validation problem for a given node to the database.
node_accessibility_theme Implements hook_theme().
node_accessibility_validate_action Performs accessibility validation on a given node.
node_accessibility_views_api Implements hook_views_api().
template_preprocess_node_accessibility_information Template preprocess function for node_accessibility_information.tpl.php