class views_json_query_plugin_query_json in Views JSON Query 7
@file Query plugin for views_json_query.
Hierarchy
- class \views_object
- class \views_plugin
- class \views_plugin_query
- class \views_plugin
Expanded class hierarchy of views_json_query_plugin_query_json
1 string reference to 'views_json_query_plugin_query_json'
- views_json_query_views_plugins in ./
views_json_query.views.inc - Implements hook_views_plugins().
File
- ./
views_json_query_plugin_query_json.inc, line 7 - Query plugin for views_json_query.
View source
class views_json_query_plugin_query_json extends views_plugin_query {
/**
* Generate a query from all of the information supplied to the object.
*
* @param $get_count
* Provide a countquery if this is true, otherwise provide a normal query.
*/
function query($get_count = FALSE) {
$filters = array();
if (isset($this->filter)) {
foreach ($this->filter as $filter) {
// set the filter value for grouped filters
$filter->options['value'] = $filter->value;
if ($filter instanceof views_json_query_handler_filter) {
$filters[] = $filter
->generate();
}
}
}
// @todo Add an option for the filters to be 'and' or 'or'.
return $filters;
}
/**
* Builds the necessary info to execute the query.
*/
function build(&$view) {
$view
->init_pager();
// Let the pager modify the query to add limits.
$this->pager
->query();
// Build the JSON file URL.
$json_file = $this->options['json_file'];
if (!empty($view->build_info['substitutions'])) {
$json_file = strtr($json_file, $view->build_info['substitutions']);
}
$parsed = parse_url($json_file);
$query_string = drupal_get_query_array(isset($parsed['query']) ? $parsed['query'] : array());
// Add query string parameters from filters.
if (isset($this->query_string)) {
foreach ($this->query_string as $k => $v) {
$query_string[$k] = $v;
}
}
// Add pagination query string argument.
if ($this->options['enable_pagination_query_parameters']) {
if (isset($view->query->offset)) {
$query_string[$this->options['pagination_offset_query_parameter']] = $view->query->offset;
}
if (isset($view->query->limit)) {
$query_string[$this->options['pagination_limit_query_parameter']] = $view->query->limit;
}
}
// Rebuild the JSON file URL.
$view->query->options['json_file'] = "{$parsed['scheme']}://{$parsed['host']}{$parsed['path']}?" . drupal_http_build_query($query_string);
$view->build_info['query'] = $this
->query();
$view->build_info['query_args'] = array();
}
/**
* Fetch file.
*/
function fetch_file($uri) {
$parsed = parse_url($uri);
// Check for local file.
if (empty($parsed['host'])) {
if (!file_exists($uri)) {
throw new Exception(t('Local file not found.'));
}
return file_get_contents($uri);
}
$destination = 'public://views_json_query';
if (!file_prepare_directory($destination, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
throw new Exception(t('Files directory either cannot be created or is not writable.'));
}
$headers = array();
$cache_file = 'views_json_query_' . md5($uri);
if ($cache = cache_get($cache_file)) {
$last_headers = $cache->data;
if (!empty($last_headers['etag'])) {
$headers['If-None-Match'] = $last_headers['etag'];
}
if (!empty($last_headers['last-modified'])) {
$headers['If-Modified-Since'] = $last_headers['last-modified'];
}
}
// Rebuild the JSON file URL.
$request_options = array(
'headers' => $headers,
);
$request_context_options = array();
if (parse_url($uri, PHP_URL_SCHEME) == 'https') {
foreach ($this->options as $option => $value) {
if (strpos($option, 'ssl_') === 0 && $value) {
$request_context_options['ssl'][substr($option, 4)] = $value;
}
}
}
if ($request_context_options) {
$request_options['context'] = stream_context_create($request_context_options);
}
$result = drupal_http_request($uri, $request_options);
if (isset($result->error)) {
$args = array(
'%error' => $result->error,
'%uri' => $uri,
);
$message = t('HTTP response: %error. URI: %uri', $args);
throw new Exception($message);
}
$cache_file_uri = "{$destination}/{$cache_file}";
if ($result->code == 304) {
if (file_exists($cache_file_uri)) {
return file_get_contents($cache_file_uri);
}
// We have the headers but no cache file. :(
// Run it back.
cache_clear_all($cache_file, 'cache');
return $this
->fetch_file($uri);
}
// As learned from Feeds caching mechanism, save to file.
file_unmanaged_save_data($result->data, $cache_file_uri, FILE_EXISTS_REPLACE);
cache_set($cache_file, $result->headers);
return $result->data;
}
/**
* Execute.
*/
function execute(&$view) {
$start = microtime(TRUE);
// Avoid notices about $view->execute_time being undefined if the query
// doesn't finish.
$view->execute_time = NULL;
// Make sure that an xml file exists.
// This could happen if you come from the add wizard to the actual views
// edit page.
if (empty($this->options['json_file'])) {
return FALSE;
}
$data = new stdClass();
try {
$data->contents = $this
->fetch_file($this->options['json_file']);
} catch (Exception $e) {
if ($this->options['show_errors']) {
drupal_set_message(t('Views Json Query') . ': ' . $e
->getMessage(), 'error');
}
return;
}
// When content is empty, parsing it is pointless.
if (!$data->contents) {
if ($this->options['show_errors']) {
drupal_set_message(t('Views Json Backend: File is empty.'), 'warning');
}
return;
}
// Go!
$ret = $this
->parse($view, $data);
$view->execute_time = microtime(TRUE) - $start;
if (!$ret) {
if (version_compare(phpversion(), '5.3.0', '>=')) {
$tmp = array(
JSON_ERROR_NONE => t('No error has occurred'),
JSON_ERROR_DEPTH => t('The maximum stack depth has been exceeded'),
JSON_ERROR_STATE_MISMATCH => t('Invalid or malformed JSON'),
JSON_ERROR_CTRL_CHAR => t('Control character error, possibly incorrectly encoded'),
JSON_ERROR_SYNTAX => t('Syntax error'),
JSON_ERROR_UTF8 => t('Malformed UTF-8 characters, possibly incorrectly encoded'),
);
if (json_last_error() != 0) {
$msg = $tmp[json_last_error()] . ' - ' . $this->options['json_file'];
drupal_set_message($msg, 'error');
}
}
else {
drupal_set_message(t('Views Json Backend: Parse json error') . ' - ' . $this->options['json_file'], 'error');
}
}
}
/**
* Fetch data in array according to apath.
*
* @param string $apath
* Something like '1/name/0'
*
* @param array $array
*
* @return array
*/
function apath($apath, $array) {
$r =& $array;
$paths = explode('/', trim($apath, '//'));
foreach ($paths as $path) {
if (is_array($r) && isset($r[$path])) {
$r =& $r[$path];
}
elseif (is_object($r)) {
$r =& $r->{$path};
}
else {
break;
}
}
return $r;
}
/**
* Define ops for using in filter.
*/
function ops($op, $l, $r) {
$table = array(
'=' => create_function('$l,$r', 'return $l === $r;'),
'not empty' => create_function('$l,$r', 'return !empty($l);'),
'!=' => create_function('$l,$r', 'return $l !== $r;'),
'contains' => create_function('$l, $r', 'return strpos($l, $r) !== false;'),
'!contains' => create_function('$l, $r', 'return strpos($l, $r) === false;'),
'shorterthan' => create_function('$l, $r', 'return strlen($l) < $r;'),
'longerthan' => create_function('$l, $r', 'return strlen($l) > $r;'),
);
return call_user_func_array($table[$op], array(
$l,
$r,
));
}
/**
* Parse.
*/
function parse(&$view, $data) {
$ret = json_decode($data->contents, FALSE);
if (!$ret) {
return FALSE;
}
// Get rows.
$rows = $this
->apath($this->options['row_apath'], $ret);
// Filter.
foreach ($rows as $k => $row) {
$check = TRUE;
foreach ($view->build_info['query'] as $filter) {
$l = is_object($row) ? $row->{$filter[0]} : $row[$filter[0]];
$check = $this
->ops($filter[1], $l, $filter[2]);
if (!$check) {
break;
}
}
if (!$check) {
unset($rows[$k]);
}
}
try {
if ($this->pager
->use_count_query() || !empty($view->get_total_rows)) {
if (!empty($this->options['total_items_apath'])) {
$this->pager->total_items = $this
->apath($this->options['total_items_apath'], $ret);
}
else {
// Hackish execute_count_query implementation.
$this->pager->total_items = count($rows);
if (!empty($this->pager->options['offset'])) {
$this->pager->total_items -= $this->pager->options['offset'];
}
}
$this->pager
->update_page_info();
}
// Deal with offset & limit.
$offset = !empty($this->offset) ? intval($this->offset) : 0;
$limit = !empty($this->limit) ? intval($this->limit) : 0;
$rows = $limit ? array_slice($rows, $offset, $limit) : array_slice($rows, $offset);
$result = array();
foreach ($rows as $row) {
$new_row = new stdClass();
$new_row = $this
->parse_row(NULL, $row, $row);
$result[] = (object) $new_row;
}
if (!empty($this->orderby)) {
// Array reverse, because the most specific are first.
foreach (array_reverse($this->orderby) as $orderby) {
$orderby
->sort($result);
}
}
$view->result = $result;
$view->total_rows = count($result);
$this->pager
->post_execute($view->result);
return TRUE;
} catch (Exception $e) {
$view->result = array();
if (!empty($view->live_preview)) {
drupal_set_message(time());
drupal_set_message($e
->getMessage(), 'error');
}
else {
debug($e
->getMessage(), 'Views Json Backend');
}
}
}
/**
* Parse row.
*
* A recursive function to flatten the json object.
* Example:
* {person:{name:{first_name:"John", last_name:"Doe"}}}
* becomes:
* $row->person/name/first_name = "John",
* $row->person/name/last_name = "Doe"
*/
function parse_row($parent_key, $parent_row, &$row) {
$props = get_object_vars($parent_row);
foreach ($props as $key => $value) {
if (is_object($value)) {
unset($row->{$key});
$this
->parse_row(is_null($parent_key) ? $key : $parent_key . '/' . $key, $value, $row);
}
else {
if ($parent_key) {
$new_key = $parent_key . '/' . $key;
$row->{$new_key} = $value;
}
else {
$row->{$key} = $value;
}
}
}
return $row;
}
/**
* Add signature.
*/
function add_signature(&$view) {
}
/**
* Option definition.
*/
function option_definition() {
$options = parent::option_definition();
$options['json_file'] = array(
'default' => '',
);
$options['row_apath'] = array(
'default' => '',
);
$options['total_items_apath'] = array(
'default' => '',
);
$options['enable_pagination_query_parameters'] = array(
'default' => FALSE,
);
$options['pagination_offset_query_parameter'] = array(
'default' => 'offset',
);
$options['pagination_limit_query_parameter'] = array(
'default' => 'limit',
);
$options['show_errors'] = array(
'default' => TRUE,
);
$options['ssl_verify_peer'] = array(
'default' => FALSE,
'bool' => TRUE,
);
$options['ssl_allow_self_signed'] = array(
'default' => FALSE,
);
$options['ssl_cafile'] = array(
'default' => NULL,
);
$options['ssl_capath'] = array(
'default' => NULL,
);
$options['ssl_local_cert'] = array(
'default' => NULL,
);
$options['ssl_passphrase'] = array(
'default' => NULL,
);
$options['ssl_CN_match'] = array(
'default' => NULL,
);
$options['ssl_verify_depth'] = array(
'default' => NULL,
);
if (version_compare(PHP_VERSION, '5.0.0') >= 0) {
$options['ssl_ciphers'] = array(
'default' => 'DEFAULT',
);
}
if (version_compare(PHP_VERSION, '5.3.2') >= 0) {
$options['ssl_SNI_enabled'] = array(
'default' => NULL,
);
$options['ssl_SNI_server_name'] = array(
'default' => NULL,
);
}
if (version_compare(PHP_VERSION, '5.4.13') >= 0) {
$options['disable_compression'] = array(
'default' => FALSE,
);
}
if (version_compare(PHP_VERSION, '5.6.0') >= 0) {
$options['ssl_peer_fingerprint'] = array(
'default' => NULL,
);
}
return $options;
}
/**
* Options form.
*/
function options_form(&$form, &$form_state) {
$form['json_file'] = array(
'#type' => 'textfield',
'#title' => t('Json File'),
'#default_value' => $this->options['json_file'],
'#description' => t("The URL or path to the Json file."),
'#maxlength' => 1024,
);
$form['row_apath'] = array(
'#type' => 'textfield',
'#title' => t('Root path'),
'#default_value' => $this->options['row_apath'],
'#description' => t("Root path to records.<br />Root path is just a simple array item find method. Ex:<br /><pre>array('data' => \n\tarray('records' => \n\t\tarray(\n\t\t\tarray('name' => 'yarco', 'sex' => 'male'),\n\t\t\tarray('name' => 'someone', 'sex' => 'male')\n\t\t)\n\t)\n)</pre><br />You want 'records', so root path could be set to 'data/records'. <br />Notice: prefix '/' or postfix '/' will be trimed, so never mind you add it or not."),
'#required' => TRUE,
);
$form['total_items_apath'] = array(
'#type' => 'textfield',
'#title' => t('Records count path'),
'#default_value' => $this->options['total_items_apath'],
'#description' => t("Path to records count."),
'#required' => FALSE,
);
$form['enable_pagination_query_parameters'] = array(
'#type' => 'checkbox',
'#title' => t('Enable pagination query parameters '),
'#default_value' => $this->options['enable_pagination_query_parameters'],
);
$form['pagination_offset_query_parameter'] = array(
'#type' => 'textfield',
'#title' => t('Offset parameter name '),
'#default_value' => $this->options['pagination_offset_query_parameter'],
'#description' => t('The name of the pagination offset parameter'),
'#states' => array(
'visible' => array(
':input[name="query[options][enable_pagination_query_parameters]"]' => array(
'checked' => TRUE,
),
),
),
);
$form['pagination_limit_query_parameter'] = array(
'#type' => 'textfield',
'#title' => t('Limit parameter name '),
'#default_value' => $this->options['pagination_limit_query_parameter'],
'#description' => t('The name of the pagination limit parameter'),
'#states' => array(
'visible' => array(
':input[name="query[options][enable_pagination_query_parameters]"]' => array(
'checked' => TRUE,
),
),
),
);
$form['show_errors'] = array(
'#type' => 'checkbox',
'#title' => t('Show Json errors'),
'#default_value' => $this->options['show_errors'],
'#description' => t('If there were any errors during Json parsing, display them. It is recommended to leave this on during development.'),
'#required' => FALSE,
);
$form['ssl'] = array(
'#type' => 'fieldset',
'#title' => t('SSL Options'),
'#description' => t('Options used for HTTP requests when the Json File is an HTTPS URL.'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#parents' => array(
'query',
'options',
),
);
$form['ssl']['ssl_verify_peer'] = array(
'#type' => 'checkbox',
'#title' => t('Require verification of SSL certificate used. '),
'#default_value' => $this->options['ssl_verify_peer'],
'#required' => FALSE,
);
$form['ssl']['ssl_allow_self_signed'] = array(
'#type' => 'checkbox',
'#title' => t('Allow self-signed certificates.'),
'#default_value' => $this->options['ssl_allow_self_signed'],
'#required' => FALSE,
'#states' => array(
'visible' => array(
':input[name="query[options][ssl_verify_peer]"]' => array(
'checked' => TRUE,
),
),
),
);
$form['ssl']['ssl_cafile'] = array(
'#type' => 'textfield',
'#title' => t('Certificate Authority file'),
'#description' => t('Location of Certificate Authority file on local filesystem which should be used to authenticate the identity of the remote peer.'),
'#default_value' => $this->options['ssl_cafile'],
'#required' => FALSE,
'#states' => array(
'visible' => array(
':input[name="query[options][ssl_verify_peer]"]' => array(
'checked' => TRUE,
),
),
),
);
$form['ssl']['ssl_capath'] = array(
'#type' => 'textfield',
'#title' => t('Certificate Authority path'),
'#description' => t('If cafile is not specified or if the certificate is not found there, the directory pointed to by capath is searched for a suitable certificate. capath must be a correctly hashed certificate directory.'),
'#default_value' => $this->options['ssl_capath'],
'#required' => FALSE,
'#states' => array(
'visible' => array(
':input[name="query[options][ssl_verify_peer]"]' => array(
'checked' => TRUE,
),
),
),
);
$form['ssl']['ssl_local_cert'] = array(
'#type' => 'textfield',
'#title' => t('Local certificate'),
'#description' => t('Path to local certificate file on filesystem. It must be a PEM encoded file which contains your certificate and private key. It can optionally contain the certificate chain of issuers.'),
'#default_value' => $this->options['ssl_local_cert'],
'#required' => FALSE,
);
$form['ssl']['ssl_passphrase'] = array(
'#type' => 'textfield',
'#title' => t('Passphrase'),
'#description' => t('Passphrase with which your local certificate file was encoded.'),
'#default_value' => $this->options['ssl_passphrase'],
'#required' => FALSE,
'#states' => array(
'visible' => array(
':input[name="query[options][ssl_local_cert]"]' => array(
'filled' => TRUE,
),
),
),
);
$form['ssl']['ssl_CN_match'] = array(
'#type' => 'textfield',
'#title' => t('Common Name'),
'#description' => t('Common Name we are expecting. PHP will perform limited wildcard matching. If the Common Name does not match this, the connection attempt will fail.'),
'#default_value' => $this->options['ssl_CN_match'],
'#required' => FALSE,
);
$form['ssl']['ssl_verify_depth'] = array(
'#type' => 'textfield',
'#title' => t('Maximum certificate chain depth'),
'#description' => t('Abort if the certificate chain is too deep. Leave empty to disable verification.'),
'#default_value' => $this->options['ssl_verify_depth'],
'#required' => FALSE,
);
if (version_compare(PHP_VERSION, '5.0.0') >= 0) {
$form['ssl']['ssl_ciphers'] = array(
'#type' => 'textfield',
'#title' => t('Ciphers'),
'#description' => t('Sets the list of available ciphers. The format of the string is described in <a href="@url">ciphers(1)</a>.', array(
'@url' => 'http://www.openssl.org/docs/apps/ciphers.html#CIPHER_LIST_FORMAT',
)),
'#default_value' => $this->options['ssl_ciphers'],
'#required' => TRUE,
);
}
if (version_compare(PHP_VERSION, '5.3.2') >= 0) {
$form['ssl']['ssl_SNI_enabled'] = array(
'#type' => 'checkbox',
'#title' => t('Enable SNI'),
'#description' => t('Enabling SNI allows multiple certificates on the same IP address.'),
'#default_value' => $this->options['ssl_SNI_enabled'],
'#required' => FALSE,
);
$form['ssl']['ssl_SNI_server_name'] = array(
'#type' => 'textfield',
'#title' => t('SNI Server Name'),
'#description' => t('If set, then this value will be used as server name for server name indication. If this value is not set, then the server name is guessed based on the hostname used when opening the stream.'),
'#default_value' => $this->options['ssl_SNI_server_name'],
'#required' => FALSE,
'#states' => array(
'visible' => array(
':input[name="query[options][ssl_SNI_enabled]"]' => array(
'checked' => TRUE,
),
),
),
);
}
if (version_compare(PHP_VERSION, '5.4.13') >= 0) {
$form['ssl']['ssl_disable_compression'] = array(
'#type' => 'checkbox',
'#title' => t('Disable TLS compression. This can help mitigate the CRIME attack vector.'),
'#default_value' => $this->options['ssl_disable_compression'],
'#required' => FALSE,
);
}
if (version_compare(PHP_VERSION, '5.6.0') >= 0) {
$form['ssl']['ssl_peer_fingerprint'] = array(
'#type' => 'textfield',
'#title' => t('Peer Fingerprint'),
'#description' => t("Aborts when the remote certificate digest doesn't match the specified hash. Leave empty to disable verification. The length will determine which hashing algorithm is applied, either 'md5' (32) or 'sha1' (40)"),
'#default_value' => $this->options['ssl_peer_fingerprint'],
'#required' => FALSE,
);
}
}
/**
* Add field.
*/
function add_field($table, $field, $alias = '', $params = array()) {
$alias = $field;
// Add field info array.
if (empty($this->fields[$field])) {
$this->fields[$field] = array(
'field' => $field,
'table' => $table,
'alias' => $alias,
) + $params;
}
return $field;
}
/**
* Add Order By.
*/
function add_orderby($orderby) {
$this->orderby[] = $orderby;
}
/**
* Add Filter.
*/
function add_filter($filter) {
$this->filter[] = $filter;
}
/**
* Return info to base the uniqueness of the result on.
*
* @return array
* Array with query unique data.
*/
function get_cache_info() {
return array(
'json_file' => $this->options['json_file'],
'row_apath' => $this->options['row_apath'],
);
}
}
Members
Name![]() |
Modifiers | Type | Description | Overrides |
---|---|---|---|---|
views_json_query_plugin_query_json:: |
function | Add field. | ||
views_json_query_plugin_query_json:: |
function | Add Filter. | ||
views_json_query_plugin_query_json:: |
function | Add Order By. | ||
views_json_query_plugin_query_json:: |
function |
Add signature. Overrides views_plugin_query:: |
||
views_json_query_plugin_query_json:: |
function | Fetch data in array according to apath. | ||
views_json_query_plugin_query_json:: |
function |
Builds the necessary info to execute the query. Overrides views_plugin_query:: |
||
views_json_query_plugin_query_json:: |
function |
Execute. Overrides views_plugin_query:: |
||
views_json_query_plugin_query_json:: |
function | Fetch file. | ||
views_json_query_plugin_query_json:: |
function | Return info to base the uniqueness of the result on. | ||
views_json_query_plugin_query_json:: |
function | Define ops for using in filter. | ||
views_json_query_plugin_query_json:: |
function |
Options form. Overrides views_plugin_query:: |
||
views_json_query_plugin_query_json:: |
function |
Option definition. Overrides views_object:: |
||
views_json_query_plugin_query_json:: |
function | Parse. | ||
views_json_query_plugin_query_json:: |
function | Parse row. | ||
views_json_query_plugin_query_json:: |
function |
Generate a query from all of the information supplied to the object. Overrides views_plugin_query:: |
||
views_object:: |
public | property | Handler's definition. | |
views_object:: |
public | property | Except for displays, options for the object will be held here. | 1 |
views_object:: |
function | Collect this handler's option definition and alter them, ready for use. | ||
views_object:: |
public | function | Views handlers use a special construct function. | 4 |
views_object:: |
public | function | Destructor. | 2 |
views_object:: |
public | function | 1 | |
views_object:: |
public | function | ||
views_object:: |
public | function | Always exports the option, regardless of the default value. | |
views_object:: |
public | function | Set default options on this object. | 1 |
views_object:: |
public | function | Set default options. | |
views_object:: |
public | function | Let the handler know what its full definition is. | |
views_object:: |
public | function | Unpack options over our existing defaults, drilling down into arrays so that defaults don't get totally blown away. | |
views_object:: |
public | function | Unpack a single option definition. | |
views_object:: |
public | function | Unpacks each handler to store translatable texts. | |
views_object:: |
public | function | ||
views_plugin:: |
public | property | The current used views display. | |
views_plugin:: |
public | property | The plugin name of this plugin, for example table or full. | |
views_plugin:: |
public | property | The plugin type of this plugin, for example style or query. | |
views_plugin:: |
public | property |
The top object of a view. Overrides views_object:: |
1 |
views_plugin:: |
public | function | Provide a list of additional theme functions for the theme info page. | |
views_plugin:: |
public | function | Return the human readable name of the display. | |
views_plugin:: |
public | function | Provide a full list of possible theme templates used by this style. | |
views_plugin:: |
public | function | Validate that the plugin is correct and can be saved. | 3 |
views_plugin_query:: |
public | property | A pager plugin that should be provided by the display. | 1 |
views_plugin_query:: |
public | function | Let modules modify the query just prior to finalizing it. | 1 |
views_plugin_query:: |
public | function | Get aggregation info for group by queries. | 1 |
views_plugin_query:: |
public | function | Returns the according entity objects for the given query results. | 1 |
views_plugin_query:: |
public | function | Constructor; Create the basic query object and fill with default values. | 1 |
views_plugin_query:: |
public | function |
Handle any special handling on the validate form. Overrides views_plugin:: |
1 |
views_plugin_query:: |
public | function |
Validate the options form. Overrides views_plugin:: |
|
views_plugin_query:: |
public | function | Render the pager, if necessary. | |
views_plugin_query:: |
public | function | Control how all WHERE and HAVING groups are put together. | |
views_plugin_query:: |
public | function | Set a LIMIT on the query, specifying a maximum number of results. | |
views_plugin_query:: |
public | function | Set an OFFSET on the query, specifying a number of results to skip | |
views_plugin_query:: |
public | function | Create a new grouping for the WHERE or HAVING clause. | |
views_plugin_query:: |
public | function |
Returns the summary of the settings in the display. Overrides views_plugin:: |