You are here

class views_xml_backend_plugin_query_xml in Views XML Backend 6

Same name and namespace in other branches
  1. 7 views_xml_backend_plugin_query_xml.inc \views_xml_backend_plugin_query_xml

@file Query plugin for views_xml_backend.

Hierarchy

Expanded class hierarchy of views_xml_backend_plugin_query_xml

1 string reference to 'views_xml_backend_plugin_query_xml'
views_xml_backend_views_plugins in ./views_xml_backend.views.inc
Implementation of hook_views_plugins().

File

./views_xml_backend_plugin_query_xml.inc, line 7
Query plugin for views_xml_backend.

View source
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);
  }

}

Members

Namesort descending Modifiers Type Description Overrides
views_object::$definition property Handler's definition
views_object::$options property Except for displays, options for the object will be held here. 1
views_object::construct function Views handlers use a special construct function so that we can more easily construct them with variable arguments. 6
views_object::destroy function 2
views_object::export_option function 1
views_object::export_options function
views_object::options function Set default options on this object. Called by the constructor in a complex chain to deal with backward compatibility. 1
views_object::set_default_options function Set default options. For backward compatibility, it sends the options array; this is a feature that will likely disappear at some point.
views_object::set_definition function Let the handler know what its full definition is.
views_object::unpack_options function Unpack options over our existing defaults, drilling down into arrays so that defaults don't get totally blown away.
views_object::unpack_translatable function Unpack a single option definition.
views_object::unpack_translatables function Unpacks each handler to store translatable texts.
views_object::_set_option_defaults function
views_plugin::$display property The current used views display.
views_plugin::$plugin_type property The plugin type of this plugin, for example style or query.
views_plugin::$view property The top object of a view. Overrides views_object::$view 1
views_plugin::additional_theme_functions function Provide a list of additional theme functions for the theme information page
views_plugin::theme_functions function Provide a full list of possible theme templates used by this style.
views_plugin::validate function Validate that the plugin is correct and can be saved. 2
views_plugin_query::$pager property A pager plugin that should be provided by the display. 1
views_plugin_query::alter function Let modules modify the query just prior to finalizing it. 1
views_plugin_query::get_aggregation_info function Get aggregation info for group by queries. 1
views_plugin_query::get_preview_info function Return preview info. 1
views_plugin_query::init function Constructor; Create the basic query object and fill with default values. 1
views_plugin_query::options_submit function Handle any special handling on the validate form. Overrides views_plugin::options_submit
views_plugin_query::options_validate function Validate the options form. Overrides views_plugin::options_validate
views_plugin_query::render_pager function Render the pager, if necessary.
views_plugin_query::set_group_operator function Control how all WHERE and HAVING groups are put together.
views_plugin_query::set_limit function Set a LIMIT on the query, specifying a maximum number of results.
views_plugin_query::set_offset function Set an OFFSET on the query, specifying a number of results to skip
views_plugin_query::set_where_group function Create a new grouping for the WHERE or HAVING clause.
views_plugin_query::summary_title function
views_xml_backend_plugin_query_xml::add_field function
views_xml_backend_plugin_query_xml::add_filter function
views_xml_backend_plugin_query_xml::add_orderby function
views_xml_backend_plugin_query_xml::add_signature function Add a signature to the query, if such a thing is feasible. Overrides views_plugin_query::add_signature
views_xml_backend_plugin_query_xml::build function Builds the necessary info to execute the query. Overrides views_plugin_query::build
views_xml_backend_plugin_query_xml::errorStart protected function Start custom error handling.
views_xml_backend_plugin_query_xml::errorStop protected function Stop custom error handling.
views_xml_backend_plugin_query_xml::execute function Executes the query and fills the associated view object with according values. Overrides views_plugin_query::execute
views_xml_backend_plugin_query_xml::fetch_file function
views_xml_backend_plugin_query_xml::get_cache_info function Return info to base the uniqueness of the result on. Overrides views_plugin_query::get_cache_info
views_xml_backend_plugin_query_xml::options_form function Add settings for the ui. Overrides views_plugin_query::options_form
views_xml_backend_plugin_query_xml::option_definition function Information about options for all kinds of purposes will be held here. Overrides views_object::option_definition
views_xml_backend_plugin_query_xml::parse function
views_xml_backend_plugin_query_xml::query function Generate a query and a countquery from all of the information supplied to the object. Overrides views_plugin_query::query