You are here

class FacetapiFacet in Facet API 6

Same name and namespace in other branches
  1. 6.3 plugins/facetapi/adapter.inc \FacetapiFacet
  2. 7.2 plugins/facetapi/adapter.inc \FacetapiFacet
  3. 7 plugins/facetapi/adapter.inc \FacetapiFacet

Stores facet data, provides methods that build the facet's render array.

Hierarchy

Expanded class hierarchy of FacetapiFacet

File

./facetapi.adapter.inc, line 211
Defines classes used by the FacetAPI module.

View source
class FacetapiFacet {

  /**
   * A FacetapiAdapter object.
   */
  protected $_adapter;

  /**
   * An array containing the facet definition.
   */
  protected $_facet;

  /**
   * An array of active facets items.
   */
  protected $_active;

  /**
   * The build array for the facet items.
   */
  protected $_build;

  /**
   * Constructor, sets adapter and facet definition.
   *
   * @param $adapter
   *   A FacetapiAdapter object.
   * @param $facet
   *   An array containing the facet definition.
   */
  public function __construct(FacetapiAdapter $adapter, array $facet) {
    $this
      ->setAdapter($adapter)
      ->setFacet($facet);
  }

  /**
   * Sets adapter class.
   *
   * @param $adapter
   *   A FacetapiAdapter object.
   *
   * @return
   *   An instance of this class.
   */
  public function setAdapter(FacetapiAdapter $adapter) {
    $this->_adapter = $adapter;
    return $this;
  }

  /**
   * Returns the adapter class.
   *
   * @return
   *   The FacetapiAdapter class.
   */
  public function getAdapter() {
    return $this->_adapter;
  }

  /**
   * Sets facet definition.
   *
   * @return
   *   An instance of this class.
   */
  public function setFacet(array $facet) {
    $this->_facet = $facet;
    return $this;
  }

  /**
   * Sets facet definition.
   *
   * @return
   *   An array containing the facet definition.
   */
  public function getFacet() {
    return $this->_facet;
  }

  /**
   * Returns the facet's active items.
   *
   * @return
   *   An array of active items.
   */
  public function getActiveItems() {
    if (!isset($this->_active)) {
      $this
        ->processActiveItems();
    }
    return $this->_active;
  }

  /**
   * Returns an array of the facet's active item values, most useful as a form
   * element's default value.
   *
   * @return
   *   An array containing the facet values keyed by position.
   */
  public function getActiveValues() {
    if (!isset($this->_active)) {
      $this
        ->processActiveItems();
    }
    $values = array();
    foreach ($this->_active as $value => $item) {
      $values[$item['pos']] = $value;
    }
    if (!empty($values)) {
      $values = array_combine($values, $values);
    }
    return $values;
  }

  /**
   * Tests whether a facet item is active by passing it's value.
   *
   * NOTE: This method returns an integer instead of a boolean because the value
   * is used by the Facet API's custom sorting functions. It ends up being less
   * code to compare integers than booleans.
   *
   * @param $value
   *   A string containing the facet item's value.
   *
   * @return
   *   An integer, 1 if the item is active, 0 if it is inactive.
   */
  public function itemActive($value) {
    return (int) isset($this->_active[$value]);
  }

  /**
   * Helper function that returns the query string variables for a facet item.
   *
   * @param $values
   *   An array containing the item's values being added to or removed from the
   *   query string dependent on whether or not the item is active.
   * @param $active
   *   An integer flagging whether the item is active or not.
   *
   * @reutrn
   *   An array containing the query string variables.
   */
  public function getQueryString(array $values, $active) {

    // Gets field alias for readability.
    $field_alias = $this->_facet['field alias'];

    // Builds array of query string variables.
    $qstring = $_GET;
    foreach ($values as $value) {
      if ($active && isset($this->_active[$value])) {
        unset($qstring[$field_alias][$this->_active[$value]['pos']]);
      }
      elseif (!$active) {
        if (!isset($qstring[$field_alias])) {
          $qstring[$field_alias] = array();
        }
        elseif (!is_array($qstring[$field_alias])) {
          $qstring[$field_alias] = array(
            (string) $qstring[$field_alias],
          );
        }
        $qstring[$field_alias][] = $value;
      }
    }
    return $qstring;
  }

  /**
   * Returns the facet's render array.
   *
   * @param $realm
   *   An array containing the realm definition.
   *
   * @return
   *   The facet's build array.
   */
  public function build(array $realm) {

    // Builds render array for facet items if necessary.
    if (!isset($this->_build)) {
      $this->_build = $this
        ->buildItems();
      $this
        ->processHierarchy($this->_build)
        ->processQueryStrings($this->_build);
    }

    // Gets searcher since we use it a lot, gets field alias for readability.
    $searcher = $this->_adapter
      ->getSearcher();
    $field_alias = $this->_facet['field alias'];

    // Initializes render array.
    $build = array(
      '#title' => $this->_facet['title'],
      '#description' => $this->_facet['description'],
      '#weight' => $this->_facet['weight'],
      '#adapter' => $this->_adapter,
      '#realm_name' => $realm['name'],
      '#facet' => $this->_facet,
      $field_alias => $this->_build,
    );

    // Adds identifiers to facet.
    $build['#attributes'] = array(
      'class' => "facetapi-facet-{$this->_facet['name']}",
      'id' => "facetapi-facet-{$searcher}-{$realm['name']}-{$this->_facet['name']}",
    );

    // Applies sorting algorithms.
    $this->_sorts = facetapi_facet_sorts_get($this->_adapter, $realm, $this->_facet);
    $this
      ->_sort($build[$field_alias]);
    unset($this->_sorts);

    // Gets available widgets.
    $widgets = facetapi_widgets_get(array(
      'realm' => $realm,
      'facet' => $this->_facet,
    ));

    // Gets widget from settings, finds default if necessary.
    $widget_name = facetapi_facet_widget_get($widgets, $searcher, $realm, $this->_facet);

    // Initializes JavaScript settings.
    $facet_settings = array(
      'searcher' => $searcher,
      'type' => $this->_adapter
        ->getType(),
      'realmName' => $realm['name'],
      'facetName' => $this->_facet['name'],
      'widget' => $widget_name,
      'queryType' => $this->_facet['query type'],
    );

    // Passes render array, JavaScript settings to widget.
    $key = $this->_facet['field alias'];
    if (NULL !== $widget_name && isset($widgets[$widget_name])) {
      $build['#widget'] = $widgets[$widget_name];
      if (facetapi_file_include($build['#widget'])) {
        $build['#widget']['callback']($build, $key, $facet_settings);
      }
    }

    // Adds JavaScript settings.
    $settings['facetapi']['facets'][] = $facet_settings;
    drupal_add_js($settings, 'setting');
    return array(
      $key => $build,
    );
  }

  /**
   * Finds and stores the facet's active items.
   *
   * @return
   *   An instance of this class.
   */
  public function processActiveItems() {
    $this->_active = array();

    // Bails if the facet isn't enabled in any realm.
    if (!facetapi_facet_enabled($this->_adapter
      ->getSearcher(), NULL, $this->_facet['name'])) {
      return $this;
    }

    // Gets active items from query string, normalizes to an array.
    $field_alias = $this->_facet['field alias'];
    if (isset($_GET[$field_alias])) {
      if (is_array($_GET[$field_alias])) {
        $data = $_GET[$field_alias];
      }
      else {
        $data = array(
          (string) $_GET[$field_alias],
        );
      }
    }
    else {
      $data = array();
    }

    // Allows hooks to add additional information to the active item. For
    // example, range queries extract the start and end values from the item.
    $hook = 'facetapi_value_' . $this->_facet['query type'];
    foreach ($data as $key => $value) {
      $this->_active[$value] = array(
        'pos' => $key,
        'value' => $value,
      );
      drupal_alter($hook, $this->_active[$value], $this->_adapter);
    }
    return $this;
  }

  /**
   * Builds the render array for the facet's items.
   *
   * @return
   *   A render array for the facet's items.
   */
  public function buildItems() {
    $build = array();

    // Build array defaults.
    // @todo Use #markup in D7.
    $defaults = array(
      '#type' => 'markup',
      '#value' => '',
      '#indexed_value' => '',
      '#count' => 0,
      '#active' => 0,
      '#item_parents' => array(),
      '#item_children' => array(),
    );

    // Builds render arrays for each item.
    if (NULL !== $this->_facet['field']) {
      $hook = 'facetapi_facet_' . $this->_facet['query type'] . '_build';
      $items = (array) module_invoke($this->_adapter
        ->getModule(), $hook, $this->_adapter, $this->_facet);
    }
    else {
      $items = array();
    }
    foreach (element_children($items) as $value) {

      // @todo Use #markup in D7.
      $item_defaults = array(
        '#value' => $value,
        '#indexed_value' => $value,
        '#active' => $this
          ->itemActive($value),
      );

      // This seems silly, but it maintains the references to the child items
      // stored in the #item_children property.
      $items[$value] = array_merge($defaults, $item_defaults, $items[$value]);
      $build[$value] =& $items[$value];
    }

    // Maps the IDs to human readable values via the mapping callback.
    if (!empty($this->_facet['map callback']) && function_exists($this->_facet['map callback'])) {
      $map = call_user_func($this->_facet['map callback'], array_keys($build));
      array_walk($build, 'facetapi_ids_replace', $map);
    }
    return $build;
  }

  /**
   * Processes hierarchical relationships between the facet items.
   *
   * @param &$build
   *   The facet's render array.
   *
   * @return
   *   An instance of this class.
   */
  public function processHierarchy(&$build) {

    // Builds the hierarchy information if the hierarchy callback is defined.
    if (!empty($this->_facet['hierarchy callback']) && !empty($build)) {
      $parents = $this->_facet['hierarchy callback'](array_keys($build));
      foreach ($parents as $value => $parents) {
        foreach ($parents as $parent) {
          if (isset($build[$parent]) && isset($build[$value])) {

            // Use a reference so we see the updated data.
            $build[$parent]['#item_children'][$value] =& $build[$value];
            $build[$value]['#item_parents'][$parent] = $parent;
          }
        }
      }
    }

    // Tests whether parents have an active child.
    // @todo: Can we make this more efficient?
    do {
      $active = 0;
      foreach ($build as $value => $item) {
        if ($item['#active'] && !empty($item['#item_parents'])) {

          // @todo Can we build facets with multiple parents? Core taxonomy
          // form cannot, so we will need a check here.
          foreach ($item['#item_parents'] as $parent) {
            if (!$build[$parent]['#active']) {
              $active = $build[$parent]['#active'] = 1;
            }
          }
        }
      }
    } while ($active);

    // Strips children whose parents are inactive.
    $build = array_filter($build, 'facetapi_inactive_parent_filter');

    // Returns instance of this class.
    return $this;
  }

  /**
   * Initializes the render array's query string variables.
   *
   * @param &$build
   *   The facet's render array.
   *
   * @return
   *   An instance of this class.
   */
  function processQueryStrings(array &$build) {
    foreach ($build as $value => &$item) {
      $values = array(
        $value,
      );

      // If the item is active an has children, gets the paths for the children.
      // Merges child values with this facet item's value so that unclicking the
      // parent deactivated the children as well.
      if (!empty($item['#active']) && !empty($item['#item_children'])) {
        $this
          ->processQueryStrings($item['#item_children']);
        $values = array_merge(facetapi_child_values_get($item['#item_children']), $values);
      }

      // Formats query string for facet item, sets theme function.
      $item['#query'] = $this
        ->getQueryString($values, $item['#active']);
    }

    // Returns instance of this calss.
    return $this;
  }

  /**
   * Sorts the facet's build array.
   *
   * @param &$build
   *   An array containing the render array.
   */
  protected function _sort(&$build) {
    foreach (element_children($build) as $value) {
      if (!empty($build[$value]['#item_children'])) {
        $this
          ->_sort($build[$value]['#item_children']);
      }
    }
    uasort($build, array(
      $this,
      '_sortCallback',
    ));
  }

  /**
   * Callback for uasort() that applies each sort in the order specified in the
   * admin interface.
   */
  protected function _sortCallback(array $a, array $b) {
    $return = 0;
    foreach ($this->_sorts as $sort) {
      if ($return = $sort['callback']($a, $b)) {
        if (SORT_DESC == $sort['order']) {
          $return *= -1;
        }
        break;
      }
    }
    return $return;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
FacetapiFacet::$_active protected property An array of active facets items.
FacetapiFacet::$_adapter protected property A FacetapiAdapter object.
FacetapiFacet::$_build protected property The build array for the facet items.
FacetapiFacet::$_facet protected property An array containing the facet definition.
FacetapiFacet::build public function Returns the facet's render array.
FacetapiFacet::buildItems public function Builds the render array for the facet's items.
FacetapiFacet::getActiveItems public function Returns the facet's active items.
FacetapiFacet::getActiveValues public function Returns an array of the facet's active item values, most useful as a form element's default value.
FacetapiFacet::getAdapter public function Returns the adapter class.
FacetapiFacet::getFacet public function Sets facet definition.
FacetapiFacet::getQueryString public function Helper function that returns the query string variables for a facet item.
FacetapiFacet::itemActive public function Tests whether a facet item is active by passing it's value.
FacetapiFacet::processActiveItems public function Finds and stores the facet's active items.
FacetapiFacet::processHierarchy public function Processes hierarchical relationships between the facet items.
FacetapiFacet::processQueryStrings function Initializes the render array's query string variables.
FacetapiFacet::setAdapter public function Sets adapter class.
FacetapiFacet::setFacet public function Sets facet definition.
FacetapiFacet::_sort protected function Sorts the facet's build array.
FacetapiFacet::_sortCallback protected function Callback for uasort() that applies each sort in the order specified in the admin interface.
FacetapiFacet::__construct public function Constructor, sets adapter and facet definition.