You are here

services_views.module in Services Views 7

Same filename and directory in other branches
  1. 6 services_views.module

Provides a generic but powerful API for web services.

File

services_views.module
View source
<?php

/**
 * @file
 * Provides a generic but powerful API for web services.
 */

/**
 * Implements hook_views_api().
 */
function services_views_views_api() {
  return array(
    'api' => 2.0,
  );
}

/**
 * Implements hook_menu().
 */
function services_views_menu() {
  $menu = array();
  $menu['admin/reports/insecure-view-displays'] = array(
    'title' => 'Insecure View Displays Report',
    'page callback' => 'services_views_insecure_view_displays_report',
    'access arguments' => array(
      'administer views',
    ),
  );
  $menu['admin/structure/services/list/%/view_resource'] = array(
    'title' => 'View Resource Settings',
    'page callback' => 'services_views_view_resource_settings',
    'page arguments' => array(
      4,
    ),
    'access arguments' => array(
      'administer services',
    ),
    'type' => MENU_LOCAL_TASK,
  );
  return $menu;
}

/**
 * Implements hook_services_resources().
 */
function services_views_services_resources() {
  $resources['views'] = array();
  $resources['views']['retrieve'] = array(
    'help' => 'Retrieves a view.',
    'file' => array(
      'type' => 'inc',
      'module' => 'services_views',
      'name' => 'services_views.resource',
    ),
    'callback' => 'services_views_retrieve',
    'access callback' => 'services_views_access',
    'access arguments' => array(
      'view',
    ),
    'access arguments append' => TRUE,
    'args' => array(
      array(
        'name' => 'view_name',
        'type' => 'string',
        'description' => 'The name of the view to get.',
        'source' => array(
          'path' => '0',
        ),
        'optional' => FALSE,
      ),
      array(
        'name' => 'display_id',
        'type' => 'string',
        'description' => 'The display ID of the view to get.',
        'source' => array(
          'param' => 'display_id',
        ),
        'optional' => TRUE,
        'default value' => 'default',
      ),
      array(
        'name' => 'args',
        'type' => 'array',
        'description' => 'A list of arguments to pass to the view.',
        'source' => array(
          'param' => 'args',
        ),
        'optional' => TRUE,
        'default value' => array(),
      ),
      array(
        'name' => 'offset',
        'type' => 'int',
        'description' => 'The number of the entry for the page begin with.',
        'source' => array(
          'param' => 'offset',
        ),
        'optional' => TRUE,
        'default value' => 0,
      ),
      array(
        'name' => 'limit',
        'type' => 'int',
        'description' => 'The total number of entries to list.',
        'source' => array(
          'param' => 'limit',
        ),
        'optional' => TRUE,
        'default value' => -1,
      ),
      array(
        'name' => 'format_output',
        'type' => 'bool',
        'description' => 'Whether to return the raw data results or style the results.',
        'source' => array(
          'param' => 'format_output',
        ),
        'optional' => TRUE,
        'default value' => FALSE,
      ),
      array(
        'name' => 'filters',
        'type' => 'array',
        'description' => 'A list of filters to pass to the view.  These are defined by the exposed filters on your view.  Example call: <code>/views/your_view?nid=12345</code>',
        'source' => array(
          'param' => 'filters',
        ),
        'optional' => TRUE,
        'default value' => array(),
      ),
    ),
  );

  // Retrieve all views that have "services" display.
  $views = views_get_enabled_views();
  foreach ($views as $view_name => $view) {
    foreach ($view->display as $view_display => $display) {
      if ($display->display_plugin !== 'services') {
        continue;
      }
      $path = $display->display_options['path'];
      $resources[$path] = array();
      $resources[$path]['index'] = array(
        'view info' => array(
          'view_name' => $view_name,
          'display_id' => $view_display,
        ),
        'help' => filter_xss('View: ' . $view->human_name),
        'file' => array(
          'type' => 'inc',
          'module' => 'services_views',
          'name' => 'services_views.resource',
        ),
        'callback' => 'services_views_execute_view',
        // Reuse services_views_access access callback.
        'access callback' => 'services_views_access',
        'access arguments' => array(
          'view',
          array(
            'view_name' => $view_name,
            'display_id' => $view_display,
          ),
        ),
      );
    }
  }
  return $resources;
}

/**
 * Converts numeric arguments to named arguments.
 *
 * @param array $args
 *   The numeric indexed arguments.
 *
 * @return array
 *   Named arguments.
 */
function services_views_convert_numeric_args_to_named_args(array $args) {
  if (!isset($args[0])) {

    // Not a numeric array.
    return $args;
  }
  $keys = array(
    'view_name',
    'display_id',
    'args',
    'offset',
    'limit',
    'format_output',
    'filters',
  );
  return array_combine($keys, $args);
}

/**
 * Check the access permission to a given views.
 *
 * @param string $op
 *   The operation that's going to be performed.
 * @param array $args
 *   The arguments that will be passed to the callback.
 *
 * @return bool
 *   TRUE if the user is allowed to load the given view.
 */
function services_views_access($op = 'view', array $args = array()) {
  $args = services_views_convert_numeric_args_to_named_args($args);
  switch ($op) {
    case 'view':
      $router_item = menu_get_item();
      $page_arguments = $router_item['page_arguments'];
      $endpoint = $page_arguments[0];
      $prefix = 'services_views_' . $endpoint;
      $whitelist = variable_get($prefix . '_white_list', 0);
      $listed_views = variable_get($prefix . "_view_displays", array());
      $view = views_get_view($args['view_name']);
      if (empty($view)) {
        return services_error(t('View @view could not be found', array(
          '@view' => $args['view_name'],
        )), 404);
      }

      // Determine the display we want to use.
      $display_id = $args['display_id'];
      if (empty($view->display[$display_id]) || $view->display[$display_id]->display_plugin != 'services') {
        $display_id = 'services_clone_' . $display_id;
        if (empty($view->display[$display_id]) || $view->display[$display_id]->display_plugin != 'services') {
          return services_error(t('Display @display on view @view could not be found', array(
            '@display' => $args['display_id'],
            '@view' => $args['view_name'],
          )), 404);
        }
      }
      $listed_view_key = $args['view_name'] . '|' . $display_id;
      if (!empty($whitelist) && empty($listed_views[$listed_view_key])) {
        return services_error(t('Display @display on view @view could not be found', array(
          '@display' => $args['display_id'],
          '@view' => $args['view_name'],
        )), 404);
      }
      elseif (empty($whitelist) && !empty($listed_views[$listed_view_key])) {
        return services_error(t('Display @display on view @view could not be found', array(
          '@display' => $args['display_id'],
          '@view' => $args['view_name'],
        )), 404);
      }
      return $view
        ->access($display_id);
  }
}

/**
 * Implements hook_views_plugins().
 */
function services_views_views_plugins() {
  $plugins = array(
    'display' => array(
      'services' => array(
        'title' => t('Services'),
        'help' => t('Export view to Services.'),
        'handler' => 'views_plugin_display_services',
        'theme' => 'views_view',
        'use ajax' => FALSE,
        'use pager' => TRUE,
        'use more' => TRUE,
        'admin' => t('Services'),
      ),
    ),
  );
  return $plugins;
}

/**
 * Implements hook_services_request_preprocess_alter().
 *
 * Pass "view info" to arguments so view name and display_id can be accessed.
 */
function services_views_services_request_preprocess_alter($controller, &$args, $options) {
  if (isset($controller['view info'])) {
    array_unshift($args, $controller['view info']);
    $args[0]['args'] = array();
    if (!empty($_GET['args'])) {
      $args[0]['args'] = $_GET['args'];
    }
  }
}

/**
 * Alter form views_ui_config_item_form.
 *
 * Form of views field options.
 */
function services_views_form_views_ui_config_item_form_alter(&$form, $form_state) {

  // Make sure this is field options form.
  if ($form_state['type'] != 'field') {
    return;
  }

  // Make sure current display is services display.
  if (get_class($form_state['view']->display_handler) != 'views_plugin_display_services') {
    return;
  }

  // Label checkbox and textfield labels.
  $form['options']['custom_label']['#title'] = t('Set custom value key');
  unset($form['options']['custom_label']['#description']);
  $form['options']['label']['#title'] = t('Custom value key');
}

/**
 * Implements hook_field_formatter_info().
 */
function services_views_field_formatter_info() {
  $formatters['services'] = array(
    'label' => t('Services Raw'),
    'field types' => array(),
    'module' => 'services_views',
    'settings' => array(
      'data_element_key' => '',
      'skip_safe' => 0,
      'skip_empty_values' => 0,
      'skip_text_format' => 1,
      'term_name' => 1,
    ),
  );
  return $formatters;
}

/**
 * Implements hook_field_formatter_info_alter().
 */
function services_views_field_formatter_info_alter(&$formatters) {
  $field_types = field_info_field_types();
  if (!empty($field_types)) {
    $formatters['services']['field types'] = array_keys($field_types);
  }
}

/**
 * Implements hook_field_formatter_settings_form().
 */
function services_views_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'];
  if ($display['type'] == 'services') {
    $element['data_element_key'] = array(
      '#title' => t('Override Element Key'),
      '#description' => t('Defaults to field label.'),
      '#type' => 'textfield',
      '#size' => 20,
      '#default_value' => $settings['data_element_key'],
    );
    $element['skip_safe'] = array(
      '#type' => 'checkbox',
      '#title' => t('Skip safe values'),
      '#default_value' => $settings['skip_safe'],
    );
    $element['skip_empty_values'] = array(
      '#type' => 'checkbox',
      '#title' => t('Skip empty values'),
      '#default_value' => $settings['skip_empty_values'],
    );

    // Add text field settings.
    if ($field['module'] == 'text') {
      $element['skip_text_format'] = array(
        '#type' => 'checkbox',
        '#title' => t('Skip text formats'),
        '#default_value' => $settings['skip_text_format'],
      );
    }

    // Add taxonomy reference field settings.
    if ($field['module'] == 'taxonomy') {
      $element['term_name'] = array(
        '#type' => 'checkbox',
        '#title' => t('Use term name instead of ID'),
        '#default_value' => $settings['term_name'],
      );
    }
  }
  return $element;
}

/**
 * Implements hook_field_formatter_info().
 */
function services_views_field_formatter_settings_summary($field, $instance, $view_mode) {
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'];
  $output = array();
  $key = !empty($settings['data_element_key']) ? $settings['data_element_key'] : $instance['label'];
  $skipsafe = isset($settings['skip_safe']) && $settings['skip_safe'] ? t('Yes') : t('No');
  $skipempty = isset($settings['skip_empty_values']) && $settings['skip_empty_values'] ? t('Yes') : t('No');
  $skipformat = isset($settings['skip_text_format']) && $settings['skip_text_format'] ? t('Yes') : t('No');
  $termname = isset($settings['term_name']) && $settings['term_name'] ? t('Yes') : t('No');
  if ($display['type'] == 'services') {
    $output[] = t('Element key: %key', array(
      "%key" => $key,
    ));
    $output[] = t('Skip safe values: %key', array(
      "%key" => $skipsafe,
    ));
    $output[] = t('Skip empty values: %key', array(
      "%key" => $skipempty,
    ));

    // Add text field settings.
    if ($field['module'] == 'text') {
      $output[] = t('Skip text format: %key', array(
        "%key" => $skipformat,
      ));
    }

    // Add taxonomy reference field settings.
    if ($field['module'] == 'taxonomy') {
      $output[] = t('Use term name: %key', array(
        "%key" => $termname,
      ));
    }
    return implode('<br />', $output);
  }
  else {
    return '';
  }
}

/**
 * Implements hook_field_formatter_view().
 */
function services_views_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $settings = $display['settings'];
  $element = array();
  static $i = 0;
  if ($display['type'] == 'services') {
    foreach ($items as $delta => $item) {

      // Apply the settings filters.
      $filtered_item = array();
      foreach ($item as $key => $val) {
        if ($settings['skip_safe'] && $key == 'safe_value') {
          continue;
        }
        if ($settings['skip_empty_values'] && empty($val)) {
          continue;
        }
        if ($settings['skip_text_format'] && $key == 'format') {
          continue;
        }
        if ($settings['term_name'] && $key == 'tid') {
          $term = taxonomy_term_load($val);
          $val = $term->name;
          $key = count($filtered_item);
        }
        $filtered_item[$key] = $val;
      }
      $element[$delta] = $filtered_item;
    }
  }
  return $element;
}

/**
 * Implements hook_entity_info_alter().
 */
function services_views_entity_info_alter(&$info) {

  // Add a view_mode to field_collections called Services.
  if (isset($info['field_collection_item'])) {
    $info['field_collection_item']['view modes']['services'] = array(
      'label' => t('Services'),
      'custom settings' => FALSE,
    );
  }
}

/**
 * Implements hook_form_alter().
 */
function services_views_form_services_edit_form_endpoint_resources_alter(&$form, &$form_state, $form_id) {
  $endpoint = $form['endpoint_object']['#value']->name;
  foreach (views_get_enabled_views() as $view_name => $view) {
    foreach ($view->display as $view_display_name => $display) {
      if (!empty($display->display_options) && !empty($display->display_options['access']) && $display->display_options['access']['type'] == 'none') {
        $form['resources']['views']['operations']['retrieve']['#description'] .= "<div class='messages warning'>" . t("There are some views with displays that will be exposed that have no access control. This resource may unintentionally leak otherwise inaccessible displays such as panel panes and blocks. See a list <a href='@url'>here</a>. Consider changing which view displays are available to this endpoint in the <a href='@url2'>view resource settings</a>.", array(
          '@url2' => url('admin/structure/services/list/' . $endpoint . '/view_resource'),
          '@url' => url('admin/reports/insecure-view-displays'),
        )) . "</div>";
      }
    }
  }
}

/**
 * Page callback for the Insecure View Displays Report.
 */
function services_views_insecure_view_displays_report() {
  $rows = array();
  foreach (views_get_enabled_views() as $view_name => $view) {
    $displays = array();
    foreach ($view->display as $view_display_name => $display) {
      if (!empty($display->display_options) && !empty($display->display_options['access']) && $display->display_options['access']['type'] == 'none') {
        $displays[] = check_plain($display->display_title);
      }
    }
    if (!empty($displays)) {
      $rows[$view_name] = array(
        'view' => l(check_plain($view->human_name), "admin/structure/views/view/{$view_name}"),
        'displays' => implode(', ', $displays),
      );
    }
  }
  ksort($rows);
  return array(
    'header' => array(
      '#markup' => "<div class='messages warning'>" . t('This report contains all the views displays that currently have their access property set to "none". This, coupled with the Services Views "views: retrieve" resource can potentially unintentionally leak information because certain view display types (such as block and panel pane displays) do not have a direct route to the display be default. Additionally, other forms of access control on these view types are typically used via a "wrapping" module. Consider adding an access restriction to each of these displays if possible.') . "</div>",
    ),
    'table' => array(
      '#theme' => 'table',
      '#header' => array(
        t('View'),
        t('Displays'),
      ),
      '#rows' => $rows,
      '#empty' => t('There are no insecure view displays.'),
      '#sticky' => TRUE,
    ),
  );
}

/**
 * Page callback for configuration page for a service endpoint's view resource.
 */
function services_views_view_resource_settings($endpoint_name) {
  $endpoint = services_endpoint_load($endpoint_name);
  if (!$endpoint) {
    return MENU_NOT_FOUND;
  }
  return drupal_get_form('services_views_view_resource_settings_form', $endpoint_name);
}

/**
 * Services views resource (endpoint specific) views display whitelist form.
 */
function services_views_view_resource_settings_form($form, &$form_state, $endpoint_name) {
  $views = array();
  $prefix = "services_views_" . $endpoint_name;
  $is_whitelist = variable_get($prefix . '_white_list', 0);
  $display_exceptions = variable_get($prefix . "_view_displays", array());
  foreach (views_get_enabled_views() as $view_name => $view) {
    foreach ($view->display as $view_display_name => $display) {
      if ($display->display_plugin != 'services') {
        continue;
      }
      $views[$view_name . '|' . $view_display_name] = check_plain($view->human_name) . ': ' . check_plain($display->display_title);
    }
  }
  $form = array();
  $form[$prefix . "_white_list"] = array(
    '#type' => 'radios',
    '#title' => t('Filter method'),
    '#options' => array(
      "0" => t('All views displays except those checked'),
      "1" => t('Only the views displays checked'),
    ),
    '#default_value' => $is_whitelist,
  );
  $form[$prefix . "_view_displays"] = array(
    '#type' => 'checkboxes',
    '#title' => t('Views to filter'),
    '#options' => $views,
    '#default_value' => $display_exceptions,
  );
  return system_settings_form($form);
}
function services_views_convert_white_list_to_clones() {
  $endpoints = services_endpoint_load_all();
  $displays_to_clone = array();

  // Compile a list of all view displays that need to be cloned across all
  // endpoints.
  foreach ($endpoints as $endpoint) {

    // If the whole endpoint is disabled, ignore it.
    if (!empty($endpoint->disabled)) {
      continue;
    }

    // If the views resource is disabled, ignore this endpoint.
    $resources = services_get_resources($endpoint->name);
    if (empty($resources['views']['endpoint']) || empty($resources['views']['endpoint']['operations']['retrieve']['enabled'])) {
      continue;
    }
    $skipped_endpoints = array();
    $prefix = "services_views_" . $endpoint->name;
    $is_whitelist = variable_get($prefix . "_white_list", array());
    $view_display_exceptions = variable_get($prefix . "_view_displays", array());
    if (empty($is_whitelist) && empty($view_display_exceptions)) {
      $skipped_endpoints[] = $endpoint->name;
      continue;
    }
    foreach (views_get_enabled_views() as $view_name => $view) {
      $displays_to_clone[$view_name] = array();
      foreach ($view->display as $view_display_name => $display) {
        $view_key = $view_name . '|' . $view_display_name;

        // If it's a services view, lets not clone it.
        if ($display->display_plugin == 'services') {
          continue;
        }

        // If we are whitelisting and the item is not been whitelisted...
        if (!empty($is_whitelist) && empty($view_display_exceptions[$view_key])) {
          continue;
        }

        // If we are blacklisting and the item has been blacklisted...
        if (empty($is_whitelist) && !empty($view_display_exceptions[$view_key])) {
          continue;
        }

        // If we haven't encountered this display before, clone it.
        if (empty($displays_to_clone[$view_name]) || empty($displays_to_clone[$view_name][$view_display_name])) {
          $view = views_get_view($view_name, TRUE);
          $displays_to_clone[$view_name][$view_display_name] = services_views_clone_display($view, $view_display_name);
        }

        // Remove the old white/blacklist record and add the new one.
        $cloned_display_name = $displays_to_clone[$view_name][$view_display_name];
        $cloned_view_key = $view_name . '|' . $cloned_display_name;
        $view_display_exceptions[$cloned_view_key] = empty($is_whitelist) ? 0 : $cloned_view_key;
        $view_display_exceptions[$view_key] = empty($is_whitelist) ? $view_key : 0;
      }
    }
    variable_set($prefix . "_view_displays", $view_display_exceptions);
  }

  // Display a message complaining about cloning all displays and security.
  if (!empty($skipped_endpoints)) {
    $count = count($skipped_endpoints);
    if ($count > 1) {
      $skipped_endpoints[$count - 1] = 'and ' . $skipped_endpoints[$count - 1];
    }
    $message = t('Cowardly refusing to make a clone of all view displays. Please configure the specific views you would like to have cloned in the !endpoints. There will be no new views cloned over to a Services display. If all displays need to be cloned, restore to the latest backup, and update your settings white list them all and try again.', array(
      '!endpoints' => format_plural(count($skipped_endpoints), '!endpoint_list endpoint', '!endpoint_list endpoints', array(
        '!endpoint_list' => implode(', ', $skipped_endpoints),
      )),
    ));
    drupal_set_message($message, 'warning');
  }
}
function services_views_clone_display($view, $display_name) {
  $display = $view->display[$display_name];
  $display_names = array_keys($view->display);
  $new_display_name = substr('services_clone_' . $display_name, 0, 62);
  $count = 0;
  while (in_array($new_display_name, $display_names)) {
    $new_display_name = substr('services_clone_' . $display_name, 0, 62) . '_' . ++$count;
  }
  $view
    ->add_display('services', "Services Clone of " . $display->display_title, $new_display_name);
  $new_display = $view->display[$new_display_name];
  $new_display->display_options = $display->display_options;
  $new_display->display_options['path'] = $view->name . '/' . $display_name;
  $view
    ->save();
  return $new_display_name;
}

Functions

Namesort descending Description
services_views_access Check the access permission to a given views.
services_views_clone_display
services_views_convert_numeric_args_to_named_args Converts numeric arguments to named arguments.
services_views_convert_white_list_to_clones
services_views_entity_info_alter Implements hook_entity_info_alter().
services_views_field_formatter_info Implements hook_field_formatter_info().
services_views_field_formatter_info_alter Implements hook_field_formatter_info_alter().
services_views_field_formatter_settings_form Implements hook_field_formatter_settings_form().
services_views_field_formatter_settings_summary Implements hook_field_formatter_info().
services_views_field_formatter_view Implements hook_field_formatter_view().
services_views_form_services_edit_form_endpoint_resources_alter Implements hook_form_alter().
services_views_form_views_ui_config_item_form_alter Alter form views_ui_config_item_form.
services_views_insecure_view_displays_report Page callback for the Insecure View Displays Report.
services_views_menu Implements hook_menu().
services_views_services_request_preprocess_alter Implements hook_services_request_preprocess_alter().
services_views_services_resources Implements hook_services_resources().
services_views_views_api Implements hook_views_api().
services_views_views_plugins Implements hook_views_plugins().
services_views_view_resource_settings Page callback for configuration page for a service endpoint's view resource.
services_views_view_resource_settings_form Services views resource (endpoint specific) views display whitelist form.