ContentList.php #1

  • //
  • guest/
  • perforce_software/
  • chronicle/
  • main/
  • application/
  • content/
  • views/
  • helpers/
  • ContentList.php
  • View
  • Commits
  • Open Download .zip Download (15 KB)
<?php
/**
 * View helper that displays a content list.
 *
 * @copyright   2011 Perforce Software. All rights reserved.
 * @license     Please see LICENSE.txt in top-level folder of this distribution.
 * @version     <release>/<patch>
 */
class Content_View_Helper_ContentList extends Zend_View_Helper_Abstract
{
    protected   $_query          = null;
    protected   $_element        = null;
    protected   $_options        = array();
    protected   $_defaultOptions = array(
        'template'      => null,
        'dojoType'      => null,
        'emptyMessage'  => 'No content entries found.',
        'postSort'      => null,
        'rowOptions'    => array(),
        'entryOptions'  => array(),
        'fields'        => array(
            'title'     => array(
                'filters'    => array(),
                'decorators' => array('Value', 'ContentLink')
            )
        ),
        'width'         => null,
        'height'        => null
    );

    /**
     * A magic method which calls through to render; see render method for details.
     *
     * @return  string  The output of our render method.
     */
    public function __toString()
    {
        try {
            return $this->render();
        } catch (Exception $e) {
            return "";
        }
    }

    /**
     * Gets a rendered (decorated and filtered) field value.
     *
     * @param   string          $fieldIndex     The index to the options entry for the field to display.
     * @param   P4Cms_Content   $entry          The entry containing the field to display.
     * @param   array           $options        Options for this specific field/row/entry.
     * @return  string          Returns the display value of the specified field.
     */
    public function getDisplayValue($fieldIndex, $entry, $options)
    {
        $fieldName = $options['field'];

        // if the requested field doesn't exist, silently return an empty value.
        $type = $entry->getContentType();
        if (!$type->hasElement($fieldName)) {
            return '';
        }

        // filter the value.
        $value = $entry->getExpandedValue($fieldName);
        foreach ($this->getFilters($fieldIndex, $entry, $options) as $filter) {
            $value = $filter->filter($value);
        }

        // set the field value on the element for decorators to access
        // note, some elements (e.g. file/image) will ignore attempts to
        // set a value; therefore, decorators will not be able to retrieve
        // the field value from such elements directly - that is why we
        // also set the content entry on enhanced elements.
        $element = clone $type->getFormElement($fieldName);
        $element->setValue($value);
        if ($element instanceof P4Cms_Content_EnhancedElementInterface) {
            $element->setContentRecord($entry);
        }

        // render display value using decorators.
        $content = '';
        foreach ($this->getDecorators($fieldIndex, $entry, $options) as $decorator) {
            $decorator->setElement($element);
            if ($decorator instanceof P4Cms_Content_EnhancedDecoratorInterface) {
                $decorator->setContentRecord($entry);
            }
            $content = $decorator->render($content);
        }

        return $content;
    }

    /**
     * Get the decorator instances for a given field.
     *
     * @param   string          $fieldName  The name of the field for which to obtain decorators.
     * @param   P4Cms_Content   $entry      The entry containing the field to decorate.
     * @param   array           $options    Options for this specific field/row/entry.
     * @return  array           The list of decorators for the field.
     */
    public function getDecorators($fieldName, P4Cms_Content $entry, array $options)
    {
        // will be empty if none were provided
        if (empty($options['decorators'])) {
            return $entry->getContentType()->getDisplayDecorators($fieldName);
        }

        try {
            $element = $this->_getElement();
            $element->setDecorators($options['decorators']);
            return $element->getDecorators();
        } catch (Exception $e) {
            P4Cms_Log::log(
                "Failed to get user-specified decorators for field '"
                . $fieldName . "' when displaying content with id " . $entry->getId()
                . " as part of a list.",
                P4Cms_Log::ERR
            );
        }

        return array();
    }

    /**
     * Get the filter instances for a given field.
     *
     * @param   string          $fieldName  The name of the field for which to obtain filters.
     * @param   P4Cms_Content   $entry      The entry containing the field to filter.
     * @param   array           $options    Options for this specific field/row/entry.
     * @return  array           The list of filters for the field.
     */
    public function getFilters($fieldName, P4Cms_Content $entry, array $options)
    {
        // will be empty if no filters were provided
        if (empty($options['filters'])) {
            return $entry->getContentType()->getDisplayFilters($fieldName);
        }

        try {
            $element = $this->_getElement();
            $element->setFilters($options['filters']);
            return $element->getFilters();
        } catch (Exception $e) {
            P4Cms_Log::log(
                "Failed to get user-specified filters for field '"
                . $fieldName . "' when displaying content with id " . $entry->getId()
                . " as part of a list.",
                P4Cms_Log::ERR
            );
        }

        return array();
    }

    /**
     * Default method called for this view helper.
     *
     * For more information on query construction @see P4Cms_Record_Query
     *
     * The options array can declare a template to use to render content through, an optional
     * message to display if no entries are returned by the query, and a list of fields to render.
     *
     * The fields array can be as simple as:
     *
     *  array('field1', 'field2', ...)
     *
     * Or, more complex with display filters and decorators:
     *
     *  array(
     *      'field1' => array(
     *          'decorators' => array(
     *              'decorator1' => array('option1' => 'value', 'option2'),
     *              ...
     *          ),
     *          'filters'    => array('filter1'...)
     *      ),
     *      'optionIndex' => array(
     *          'field'      => 'field1',
     *          'decorators' => array(
     *              'decorator1' => array('option1' => 'value', 'option2'),
     *              ...
     *          ),
     *          'filters'    => array('filter1'...)
     *      ),
     *      'field2' => array(...)
     *  )
     *
     * Filters can contain options such as 'lucene' or 'categories' to have those modules subscribe
     * to and influence the query.  Note that these options do not work on subfilters.
     *
     * @param   P4Cms_Record_Query|array|null   $query      The query or array to determines the list of content.
     * @param   array                           $options    Optional array of options.
     * @return  Content_View_Helper_ContentList             A helper instance with the correct query/options.
     */
    public function contentList($query, array $options = array())
    {
        // P4Cms_Record::fetchAll normalizes the query for us on use
        $this->_query   = $query;

        $this->_options = $this->_normalizeOptions($options);

        return $this;
    }

    /**
     * Get list of fields configured for display including any
     * options for those fields such as filters and decorators.
     *
     * Options may differ according to the row or content entry we
     * are rendering. Row and entry specific options may be provided
     * via rowOptions and entryOptions respectively. Row options
     * should be keyed on the row number (one-based); whereas entry
     * options should be keyed by entry id.
     *
     * Note: row and entry options will be merged recursively with
     * the standard field options. Entry options will be merged last
     * and therefore take precedence.
     *
     * @param   int             $row    the row number to get field options for.
     * @param   P4Cms_Content   $entry  the entry to get field options for.
     * @return  array           A list of fields and field options.
     */
    public function getFields($row, P4Cms_Content $entry)
    {
        $options      = $this->_options['fields'];
        $rowOptions   = isset($this->_options['rowOptions'][$row]['fields'])
            ? $this->_options['rowOptions'][$row]['fields']
            : array();
        $entryOptions = isset($this->_options['entryOptions'][$entry->getId()]['fields'])
            ? $this->_options['entryOptions'][$entry->getId()]['fields']
            : array();

        // merge options with row-options, then with entry-options.
        return P4Cms_ArrayUtility::mergeRecursive(
            P4CMs_ArrayUtility::mergeRecursive($options, $rowOptions),
            $entryOptions
        );
    }

    /**
     * Accessor for the optional template path.
     *
     * @return string The path to the template.
     */
    public function getTemplate()
    {
        return $this->_options['template'];
    }

    /**
     * Render method for this view helper.
     * If a template has been supplied, renders the content list using the template, otherwise
     * renders the content list as an unordered html list.
     *
     * @return string The rendered list of content.
     */
    public function render()
    {
        $view    = clone $this->view;
        $entries = P4Cms_Content::fetchAll($this->_query);

        // allow caller to sort entries client-side
        // sorting capabilities of server are limited.
        if ($this->_options['postSort']) {
            $entries->sortBy($this->_options['postSort']);
        }

        // tag the page cache so it can be appropriately cleared later
        if (P4Cms_Cache::canCache('page')) {
            P4Cms_Cache::getCache('page')->addTag('p4cms_content')
                                         ->addTag('p4cms_content_type')
                                         ->addTag('p4cms_content_list');
        }

        // if there is a template: clone view, add items, render template
        if ($this->getTemplate()) {
            $view->entries = $entries;
            $view->options = $this->_options;

            return $view->render($this->getTemplate());
        }

        if (!count($entries)) {
            return $this->_options['emptyMessage'];
        }

        $count = 0;
        $html  = $this->openList();
        foreach ($entries as $entry) {
            $count++;
            $html .= '<li class="content-list-entry-' . $count
                  . ' content-list-entry-' . ($count % 2 ? 'odd' : 'even')
                  . ' content-list-type-' . $view->escapeAttr($entry->getContentTypeId()) . '">';
            foreach ($this->getFields($count, $entry) as $field => $options) {
                $html .= $this->getDisplayValue($field, $entry, $options);
            }
            $html .= '</li>' . PHP_EOL;
        }

        $html .= $this->closeList();
        return $html;
    }

    /**
     * Returns the start of the html list.
     *
     * @return string   The opening html for the list of content entries.
     */
    public function openList()
    {
        $dojoType = $this->_options['dojoType']
            ? ' dojoType="' . $this->view->escapeAttr($this->_options['dojoType']) . '"'
            : '';

        // handle the width and height options, if specified
        $width = $this->_options['width'];
        $width = $width
            ? 'width: ' . $this->view->escapeAttr($width)
                . ((string) intval($width) === $width ? 'px' : '') . ';'
            : '';
        $height = $this->_options['height'];
        $height = $height
            ? 'height: ' . $this->view->escapeAttr($height)
                . ((string) intval($height) === $height ? 'px' : '') . ';'
            : '';
        $separator = ($width && $height) ? ' ' : '';
        $style = ($width || $height) ? ' style="' . $width . $separator . $height .'"' : '';

        return '<ul class="content-list"' . $dojoType . $style . '>' . PHP_EOL;
    }

    /**
     * Returns the end of the html list.
     *
     * @return string   The closing html for the list of content entries.
     */
    public function closeList()
    {
        return '</ul>' . PHP_EOL;
    }

    /**
     * Returns a form element for the purpose of loading filters/decorators.
     *
     * return   Zend_Form_Element   a form element for loading filters/decorators.
     */
    protected function _getElement()
    {
        if ($this->_element instanceof Zend_Form_Element) {
            return $this->_element;
        }

        $form = new P4Cms_Form;
        $form->addElement('text', 'loader');

        $this->_element = $form->getElement('loader');

        return $this->_element;
    }

    /**
     * Normalizes options to ensure that it is in a consistent format.
     *
     *  - Ensures options contains defaults.
     *  - Fields is normalized to an array of field-name => field-options
     *  - Field options is normalized to an array declaring optional filters
     *    and decorators to augment presentation of field data.
     *
     * @param   array   $options    The un-normalized fields array.
     * @return  array   The normalized options array.
     */
    protected function _normalizeOptions(array $options)
    {
        $normalized = $options + $this->_defaultOptions;

        // ensure fields is an array.
        if (isset($options['fields']) && is_array($options['fields'])) {
            $normalized['fields'] = array();
        } else {
            $options['fields'] = $this->_defaultOptions['fields'];
        }

        // normalize fields to name/options w. filters and decorators.
        $defaults = array('filters' => array(), 'decorators' => array());
        foreach ($options['fields'] as $name => $value) {
            if (is_numeric($name) && is_string($value)) {
                $name  = $value;
                $value = $defaults;
            }

            // skip invalid field declarations.
            if (!is_string($name) || !is_array($value)) {
                continue;
            }

            // ensure field options has filter/decorator entries.
            $value += $defaults;

            // set field name value
            if (!array_key_exists('field', $value)) {
                $value['field'] = $name;
            }

            // ensure that the filters and decorators options are both arrays
            foreach (array('filters', 'decorators') as $option) {
                if (!is_array($value[$option])) {
                    $value[$option] = array();
                }
            }

            $normalized['fields'][$name] = $value;
        }

        return $normalized;
    }
}
# Change User Description Committed
#1 16170 perforce_software Move Chronicle files to follow new path scheme for branching.
//guest/perforce_software/chronicle/application/content/views/helpers/ContentList.php
#1 8972 Matt Attaway Initial add of the Chronicle source code