/ */ 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; } } } } }