views_xml_backend_plugin_query_xml.inc in Views XML Backend 6
Same filename and directory in other branches
Query plugin for views_xml_backend.
File
views_xml_backend_plugin_query_xml.incView source
<?php
/**
* @file
* Query plugin for views_xml_backend.
*/
class views_xml_backend_plugin_query_xml extends views_plugin_query {
/**
* Generate a query and a countquery 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) {
$row_xpath = $this->options['row_xpath'];
$filter_string = '';
if (!empty($this->filter)) {
$filters = array();
foreach ($this->filter as $filter) {
$filters[] = $filter
->generate();
}
/**
* @todo Add an option for the filters to be 'and' or 'or'.
*/
$filter_string = '[' . implode(' and ', $filters) . ']';
}
return $row_xpath . $filter_string;
}
/**
* 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();
$view->build_info['query'] = $this
->query();
$view->build_info['count_query'] = 'count(' . $view->build_info['query'] . ')';
$view->build_info['query_args'] = array();
}
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 = file_directory_path() . '/views_xml_backend';
if (!file_check_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_xml_backend_' . 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'];
}
}
$result = drupal_http_request($uri, array(
'headers' => $headers,
));
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_save_data($result->data, $cache_file_uri, FILE_EXISTS_REPLACE);
cache_set($cache_file, $result->headers);
return $result->data;
}
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['xml_file'])) {
return FALSE;
}
$data = new stdClass();
try {
$data->contents = $this
->fetch_file($this->options['xml_file']);
} catch (Exception $e) {
drupal_set_message(t('Views XML Backend: ' . $e
->getMessage()), 'error');
return;
}
// Allow other modules to alter the data. Could be used for adding Tidy
// support.
// @todo Document this.
drupal_alter('views_xml_backend_data', $data, $view->name);
// When content is empty, parsing it is pointless.
if (!$data->contents) {
if ($this->options['show_errors']) {
drupal_set_message(t('Views XML Backend: File is empty.'), 'warning');
}
return;
}
$use = $this
->errorStart();
// Go!
$this
->parse($view, $data);
$view->execute_time = microtime(TRUE) - $start;
$this
->errorStop($use, $this->options['show_errors']);
}
function parse(&$view, $data) {
$doc = new DOMDocument();
$success = $doc
->loadXML($data->contents);
// If the file fails to load, bail. The appropriate error messages will be
// displayed automatically.
if (!$success) {
return;
}
$xpath = new DOMXPath($doc);
// Create a simplexml object so that we can use
// SimpleXMLElement::getNamespaces().
// Does anyone know a better way to do it?
$simple = simplexml_import_dom($doc);
if (!$simple) {
return;
}
$namespaces = $simple
->getNamespaces(TRUE);
// Register namespaces. Allow for overriding the default namespace.
foreach ($namespaces as $prefix => $namespace) {
if ($prefix === '') {
if (empty($this->options['default_namespace'])) {
$prefix = 'default';
}
else {
$prefix = $this->options['default_namespace'];
}
}
$xpath
->registerNamespace($prefix, $namespace);
}
try {
if ($this->pager
->use_count_query() || !empty($view->get_total_rows)) {
// $this->pager->execute_count_query($count_query);
// Hackish execute_count_query implementation.
$this->pager->total_items = $xpath
->evaluate($view->build_info['count_query']);
if (!empty($this->pager->options['offset'])) {
$this->pager->total_items -= $this->pager->options['offset'];
}
$this->pager
->update_page_info();
}
// Let the pager modify the query to add limits.
//$this->pager->pre_execute($query);
if (!empty($this->limit) || !empty($this->offset)) {
$view->build_info['query'] = '(' . $view->build_info['query'] . ')';
$offset = intval(!empty($this->offset) ? $this->offset : 0);
if (!empty($this->limit)) {
$limit = intval($this->limit) + $offset;
$view->build_info['query'] .= "[position() > {$offset} and not(position() > {$limit})]";
}
else {
$view->build_info['query'] .= "[position() > {$offset}]";
}
}
// Get the rows.
$rows = $xpath
->query($view->build_info['query']);
$result = array();
foreach ($rows as $row) {
$item = new stdClass();
// Query each field per row.
foreach ($this->fields as $field) {
$field_key = $field['field'];
if (!$field_key) {
continue;
}
$node_list = $xpath
->evaluate($field_key, $row);
if ($node_list) {
// Allow multiple values in a field.
if ($field['multiple']) {
$item->{$field_key} = array();
foreach ($node_list as $node) {
$item->{$field_key}[] = $node->nodeValue;
}
}
else {
$item->{$field_key} = $node_list
->item(0)->nodeValue;
}
}
else {
// Make sure all of the fields are set. Allows us to do less error
// checking later on.
$item->{$field_key} = NULL;
}
}
$result[] = $item;
}
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);
} 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 XML Backend');
}
}
}
function add_signature(&$view) {
}
function option_definition() {
$options = parent::option_definition();
$options['xml_file'] = array(
'default' => '',
);
$options['row_xpath'] = array(
'default' => '',
);
$options['default_namespace'] = array(
'default' => '',
);
$options['show_errors'] = array(
'default' => TRUE,
);
return $options;
}
function options_form(&$form, &$form_state) {
$form['xml_file'] = array(
'#type' => 'textfield',
'#title' => t('XML File'),
'#default_value' => $this->options['xml_file'],
'#description' => t("The URL or path to the XML file."),
'#maxlength' => 1024,
);
$form['row_xpath'] = array(
'#type' => 'textfield',
'#title' => t('Row Xpath'),
'#default_value' => $this->options['row_xpath'],
'#description' => t("An xpath function that selects rows."),
'#required' => TRUE,
);
$form['default_namespace'] = array(
'#type' => 'textfield',
'#title' => t('Default namespace'),
'#default_value' => $this->options['default_namespace'],
'#description' => t("If the xml contains a default namespace, it will be accessible as 'default:element'. If you want something different, declare it here."),
'#required' => FALSE,
);
$form['show_errors'] = array(
'#type' => 'checkbox',
'#title' => t('Show XML errors'),
'#default_value' => $this->options['show_errors'],
'#description' => t('If there were any errors during XML parsing, display them. It is recommended to leave this on during development.'),
'#required' => FALSE,
);
}
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;
}
function add_orderby($orderby) {
$this
->add_field($orderby->table_alias, $orderby->options['xpath_selector'], '', $orderby->options);
$this->orderby[] = $orderby;
}
function add_filter($filter) {
$this->filter[] = $filter;
}
/**
* Return info to base the uniqueness of the result on.
*
* @return $cache_info
* Array with query unique data.
*/
function get_cache_info() {
return array(
'xml_file' => $this->options['xml_file'],
'row_xpath' => $this->options['row_xpath'],
'default_namespace' => $this->options['default_namespace'],
);
}
/**
* Start custom error handling.
*
* @return bool
* The previous value of use_errors.
*/
protected function errorStart() {
return libxml_use_internal_errors(TRUE);
}
/**
* Stop custom error handling.
*
* @param bool $use
* The previous value of use_errors.
* @param bool $print
* (Optional) Whether to print errors to the screen. Defaults to TRUE.
*/
protected function errorStop($use, $print = TRUE) {
if ($print) {
foreach (libxml_get_errors() as $error) {
switch ($error->level) {
case LIBXML_ERR_WARNING:
case LIBXML_ERR_ERROR:
$type = 'warning';
break;
case LIBXML_ERR_FATAL:
$type = 'error';
break;
}
$message = t('Views XML Backend: %error on line %num. Error code: %code', array(
'%error' => trim($error->message),
'%num' => $error->line,
'%code' => $error->code,
));
drupal_set_message($message, $type, FALSE);
}
}
libxml_clear_errors();
libxml_use_internal_errors($use);
}
}
Classes
Name | Description |
---|---|
views_xml_backend_plugin_query_xml | @file Query plugin for views_xml_backend. |