Widget.php #1

  • //
  • guest/
  • perforce_software/
  • chronicle/
  • main/
  • library/
  • P4Cms/
  • Widget.php
  • View
  • Commits
  • Open Download .zip Download (14 KB)
<?php
/**
 * Widgets are configurations of a widget-controller in a region.
 * The widget model provides read/write access to an individual
 * widget's configuration information.
 *
 * Each widget has an id that is unique within it's region. The id is
 * is determined by the order that that the widget was defined in.
 *
 * @copyright   2011 Perforce Software. All rights reserved.
 * @license     Please see LICENSE.txt in top-level folder of this distribution.
 * @version     <release>/<patch>
 */
class P4Cms_Widget extends P4Cms_Record_Config
{
    protected static $_storageSubPath = 'widgets';
    protected static $_fields         = array(
        'id',
        'region',
        'type',
        'title',
        'showTitle',
        'order'         => array(
            'default'   => 0
        ),
        'class',
        'asynchronous'  => array(
            'default'   => false
        ),
        'config',
        'addTime'
    );
    protected static $_idField        = 'id';

    protected        $_error          = null;
    protected        $_exception      = null;

    /**
     * Create a new instance of a widget by specifying a widget type.
     * The type can be a type instance or a type id.
     *
     * If caller provides values, they will be merged with the widget
     * type defaults (given values win).
     *
     * @param   string|P4Cms_Widget_Type    $type       the type of widget to create.
     * @param   array                       $values     optional - widget values to use
     * @param   P4Cms_Record_Adapter        $adapter    optional - storage adapter to use.
     * @return  P4Cms_Widget                a newly created widget instance.
     * @throws  P4Cms_Widget_Exception      if the specified type is invalid.
     */
    public static function factory($type, array $values = null, P4Cms_Record_Adapter $adapter = null)
    {
        // lookup type model if id given.
        if (is_string($type) && P4Cms_Widget_Type::exists($type)) {
            $type = P4Cms_Widget_Type::fetch($type);
        }

        // validate type.
        if (!$type instanceof P4Cms_Widget_Type || !$type->isValid()) {
            throw new P4Cms_Widget_Exception(
                "Cannot create widget. The given widget type is invalid."
            );
        }

        // merge caller provided values with type defaults.
        $values = $values ?: array();
        $values = array_merge(
            array(
                'title'     => $type->label,
                'config'    => $type->getDefaults()
            ),
            $values
        );

        // instantiate widget setting type and defaults.
        return static::create($values, $adapter)->setType($type);
    }

    /**
     * Get the widgets contained in a given region.
     *
     * Widgets have a 'region' attribute which we utilize to locate them.
     *
     * @param   string                          $id         the id of the region
     * @param   P4Cms_Record_Query|array|null   $query      optional - query options to augment result.
     * @param   P4Cms_Record_Adapter            $adapter    optional - storage adapter to use.
     * @return  P4Cms_Record                    the requested widget(s)
     */
    public static function fetchByRegion($id, $query = null, P4Cms_Record_Adapter $adapter = null)
    {
        if (empty($id)) {
            throw new InvalidArgumentException(
                'Cannot fetch by region. Region id must be specified.'
            );
        }

        $query = static::_normalizeQuery($query);
        $query->addFilter(P4Cms_Record_Filter::create()->add('region', $id));

        $widgets = new P4Cms_Model_Iterator;
        foreach (static::fetchAll($query, $adapter) as $widget) {
            $type = $widget->getValue('type');
            if (P4Cms_Widget_Type::exists($type) && P4Cms_Widget_Type::fetch($type)->isValid()) {
                $widgets[$widget->getId()] = $widget;
            }
        }

        // put widgets in order. we do this client side as the server lacks
        // a purely numeric sort (negative order for example would be a problem)
        $widgets->sortBy(
            array(
                'order'   => array(P4Cms_Model_Iterator::SORT_NUMERIC),
                'addTime' => array(P4Cms_Model_Iterator::SORT_NUMERIC)
            )
        );

        return $widgets;
    }

    /**
     * Run this widget and return the output.
     *
     * @param   bool    $throwExceptions    optional - defaults to false to prevent widgets from
     *                                      halting execution - if you want to permit exceptions
     *                                      pass true for this argument.
     * @return  string  the output produced by the widget.
     */
    public function run($throwExceptions = false)
    {
        // try to run the widget controller.
        // suppress exceptions unless throw exceptions is true.
        $output = '';
        try {
            $view   = Zend_Layout::getMvcInstance()->getView();
            $type   = $this->getType();
            $params = $type->getRouteParams();
            
            // when an action parameter is included in a page request (e.g. ?action=foo),
            // the action will be retrieved by dispatcher->getActionMethod() because
            // $params['action'] is null -- it's not defined in the module.ini file.  
            // we provide a default 'index' action here to avoid the problem.
            $output = $view->action(
                $params['action'] ?: 'index',
                $params['controller'],
                $params['module'],
                array('widget' => $this)
            );
        } catch (Exception $e) {
            P4Cms_Log::logException("Failed to run widget.", $e);

            $this->_exception = $e;
            $this->_error     = $e->getMessage();

            if ($throwExceptions) {
                throw $e;
            }
        }

        return $output;
    }

    /**
     * Determine if this widget suffered an error during run.
     *
     * @return  bool    true if an error occurred.
     */
    public function hasError()
    {
        return is_string($this->_error) && strlen($this->_error);
    }

    /**
     * Get the error message if an error occurred during run.
     *
     * @return  string                  the error message.
     * @throw   P4Cms_Widget_Exception  if no error occured.
     */
    public function getError()
    {
        if (!$this->hasError()) {
            throw new P4Cms_Widget_Exception(
                "Cannot get error. No error occurred."
            );
        }

        return $this->_error;
    }

    /**
     * Determine if an exception occurred during run.
     *
     * @return  bool    true if an exception occurred.
     */
    public function hasException()
    {
        return $this->_exception instanceof Exception;
    }

    /**
     * Get the exception if one occurred.
     *
     * @return  Exception               the exception that occurred.
     * @throws  P4Cms_Widget_Exception  if no exception occurred.
     */
    public function getException()
    {
        if (!$this->hasException()) {
            throw new P4Cms_Widget_Exception(
                "Cannot get exception. No exception occurred."
            );
        }

        return $this->_exception;
    }

    /**
     * Set the type of this widget.
     *
     * @param   null|string|P4Cms_Widget_Type   $type   the type of widget (either id or instance).
     */
    public function setType($type)
    {
        // normalize type instance to id string.
        if ($type instanceof P4Cms_Widget_Type) {
            $type = $type->getId();
        }

        if (!is_string($type) && $type !== null) {
            throw new InvalidArgumentException(
                "Widget type must be a string, a widget type instance or null."
            );
        }

        return $this->_setValue('type', $type);
    }

    /**
     * Get the type of this widget.
     *
     * @return  P4Cms_Widget_Type       an instance of this widget's type.
     * @throws  P4Cms_Widget_Exception  if no valid type is set.
     */
    public function getType()
    {
        $type = $this->_getValue('type');

        // ensure type id is set.
        if (!is_string($type) || !strlen($type)) {
            throw new P4Cms_Widget_Exception(
                "Cannot get widget type. The type has not been set."
            );
        }

        return P4Cms_Widget_Type::fetch($type);
    }

    /**
     * Whether or not to load this widget asynchronously.
     * If true, widget should not be run during initial page
     * rendering process, but rather via a subsequent http
     * request.
     *
     * @return  bool    true if widget should be loaded asynchronously.
     */
    public function isAsynchronous()
    {
        return (bool) $this->asynchronous;
    }

    /**
     * Save this widget.
     * Extends parent to keep record of the time that the widget is added.
     *
     * @param   string  $description  optional - a description of the change.
     * @return  P4Cms_Record          provides a fluent interface
     */
    public function save($description = null)
    {
        if (!$this->getValue('addTime')) {
            $this->setValue('addTime', microtime(true));
        }

        return parent::save($description);
    }

    /**
     * Collect all default widgets and install any that are missing.
     *
     * @param   P4Cms_Record_Adapter    $adapter    optional - storage adapter to use.
     */
    public static function installDefaults(P4Cms_Record_Adapter $adapter = null)
    {
        // clear the module/theme cache
        P4Cms_Module::clearCache();
        P4Cms_Theme::clearCache();

        $packages = P4Cms_Module::fetchAllEnabled();

        // add the active theme to the packages list
        if (P4Cms_Theme::hasActive()) {
            $packages[] = P4Cms_Theme::fetchActive();
        }

        // install default widgets for each package
        foreach ($packages as $package) {
            static::installPackageDefaults($package, $adapter);
        }
    }

    /**
     * Collect all default widgets from a package and install any that are missing.
     *
     * @param   P4Cms_PackageAbstract   $package          the package whose widgets are to be installed.
     * @param   P4Cms_Record_Adapter    $adapter          optional - storage adapter to use.
     * @param   boolean                 $restoreDelete    optional - restore the deleted widget.
     */
    public static function installPackageDefaults(
        P4Cms_PackageAbstract $package,
        P4Cms_Record_Adapter $adapter = null,
        $restoreDelete = false)
    {
        // if no adapter given, use default.
        $adapter = $adapter ?: static::getDefaultAdapter();

        // collect default widgets that have not been configured,
        // and instantiate the defined widgets
        $regionWidgetConfig = $package->getWidgetConfig();

        foreach ($regionWidgetConfig as $regionId => $widgetConfigs) {
            $widgets = array();
            foreach ($widgetConfigs as $id => $widgetConfig) {
                // create predictable uuid formatted id from package name, region id and the given id.
                $widgetId = $package->getName() . '-' . $regionId . '-' . $id;
                $widgetId = P4Cms_Uuid::fromMd5(md5($widgetId))->get();

                // skip existing widgets
                if (P4Cms_Widget::exists($widgetId, null, $adapter)) {
                    continue;
                }

                // Restore the widget if it has been deleted and $restoreDelete is set
                if ($restoreDelete &&
                    P4Cms_Widget::exists($widgetId, array('includeDeleted' => true), $adapter)
                ) {
                    $widget = P4Cms_Widget::fetch($widgetId, array('includeDeleted' => true), $adapter);
                    $widget->save();
                } else {
                    // try to create and save the widget.
                    try {
                        $type   = isset($widgetConfig['type']) ? $widgetConfig['type'] : null;
                        $widget = P4Cms_Widget::factory($type, $widgetConfig, $adapter);
                        $widget->setId($widgetId)
                               ->setValue('region', $regionId)
                               ->save();
                    } catch (P4Cms_Widget_Exception $e) {
                        continue;
                    }
                }
            }
        }
    }

    /**
     * Remove widgets provided by a package.
     *
     * @param   P4Cms_PackageAbstract   $package    the package whose widgets to be removed
     * @param   P4Cms_Record_Adapter    $adapter    optional - storage adapter to use.
     */
    public static function removePackageDefaults(
        P4Cms_PackageAbstract $package,
        P4Cms_Record_Adapter $adapter = null)
    {
        // if no adapter given, use default.
        $adapter = $adapter ?: static::getDefaultAdapter();

        // collect default regions from the package
        $regionWidgetConfig = $package->getWidgetConfig();

        foreach ($regionWidgetConfig as $regionId => $widgetConfigs) {
            foreach ($widgetConfigs as $id => $widgetConfig) {
                // create predictable uuid formatted id from package name, region id and the given id.
                $widgetId = $package->getName() . '-' . $regionId . '-' . $id;
                $widgetId = P4Cms_Uuid::fromMd5(md5($widgetId))->get();

                // delete the widget
                try {
                    P4Cms_Widget::remove($widgetId, $adapter);
                } catch (Exception $e) {
                    // we can't do much if the delete fails.
                    continue;
                }
            }
        }
    }
}
# Change User Description Committed
#1 16170 perforce_software Move Chronicle files to follow new path scheme for branching.
//guest/perforce_software/chronicle/library/P4Cms/Widget.php
#1 8972 Matt Attaway Initial add of the Chronicle source code