services_views.module in Services Views 7
Same filename and directory in other branches
Provides a generic but powerful API for web services.
File
services_views.moduleView 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;
}