diff --git a/libraries/classmap.php b/libraries/classmap.php index 25a81188cc56f..bc1c0d5c07945 100644 --- a/libraries/classmap.php +++ b/libraries/classmap.php @@ -38,7 +38,7 @@ JLoader::registerAlias('JViewCategories', '\\Joomla\\CMS\\View\\Categories', '4.0'); JLoader::registerAlias('JViewCategory', '\\Joomla\\CMS\\View\\Category', '4.0'); JLoader::registerAlias('JViewCategoryfeed', '\\Joomla\\CMS\\View\\CategoryFeed', '4.0'); -JLoader::registerAlias('JViewLegacy', '\\Joomla\\CMS\\View\\View', '4.0'); +JLoader::registerAlias('JViewLegacy', '\\Joomla\\CMS\\View\\HtmlView', '4.0'); JLoader::registerAlias('JControllerAdmin', '\\Joomla\\CMS\\Controller\\Admin', '4.0'); JLoader::registerAlias('JControllerLegacy', '\\Joomla\\CMS\\Controller\\Controller', '4.0'); JLoader::registerAlias('JControllerForm', '\\Joomla\\CMS\\Controller\\Form', '4.0'); diff --git a/libraries/src/CMS/Model/Model.php b/libraries/src/CMS/Model/Model.php index d282a091eb23d..e87409568d960 100644 --- a/libraries/src/CMS/Model/Model.php +++ b/libraries/src/CMS/Model/Model.php @@ -535,6 +535,26 @@ public function loadHistory($version_id, Table &$table) return $table->bind($rowArray); } + /** + * Method to check if the given record is checked out by the current user + * + * @param \stdClass $item The record to check + * + * @return bool + */ + public function isCheckedOut($item) + { + $table = $this->getTable(); + $checkedOutField = $table->getColumnAlias('checked_out'); + + if (property_exists($item, $checkedOutField) && $item->{$checkedOutField} != \JFactory::getUser()->id) + { + return true; + } + + return false; + } + /** * Method to auto-populate the model state. * diff --git a/libraries/src/CMS/View/AbstractView.php b/libraries/src/CMS/View/AbstractView.php new file mode 100644 index 0000000000000..a77b51eda05ab --- /dev/null +++ b/libraries/src/CMS/View/AbstractView.php @@ -0,0 +1,244 @@ +_name)) + { + if (array_key_exists('name', $config)) + { + $this->_name = $config['name']; + } + else + { + $this->_name = $this->getName(); + } + } + + // Set the component name if passed + if (!empty($config['option'])) + { + $this->option = $config['option']; + } + } + + /** + * Execute and display a template script. + * + * @param string $tpl The name of the template file to parse; automatically searches through the template paths. + * + * @return mixed A string if successful, otherwise an Error object. + * + * @since 3.0 + */ + abstract public function display($tpl = null); + + /** + * Method to get data from a registered model or a property of the view + * + * @param string $property The name of the method to call on the model or the property to get + * @param string $default The name of the model to reference or the default value [optional] + * + * @return mixed The return value of the method + * + * @since 3.0 + */ + public function get($property, $default = null) + { + // If $model is null we use the default model + if (is_null($default)) + { + $model = $this->_defaultModel; + } + else + { + $model = strtolower($default); + } + + // First check to make sure the model requested exists + if (isset($this->_models[$model])) + { + // Model exists, let's build the method name + $method = 'get' . ucfirst($property); + + // Does the method exist? + if (method_exists($this->_models[$model], $method)) + { + // The method exists, let's call it and return what we get + return $this->_models[$model]->$method(); + } + } + + // Degrade to \JObject::get + return parent::get($property, $default); + } + + /** + * Method to get the model object + * + * @param string $name The name of the model (optional) + * + * @return Model The model object + * + * @since 3.0 + */ + public function getModel($name = null) + { + if ($name === null) + { + $name = $this->_defaultModel; + } + + return $this->_models[strtolower($name)]; + } + + /** + * Method to add a model to the view. We support a multiple model single + * view system by which models are referenced by classname. A caveat to the + * classname referencing is that any classname prepended by \JModel will be + * referenced by the name without \JModel, eg. \JModelCategory is just + * Category. + * + * @param Model $model The model to add to the view. + * @param boolean $default Is this the default model? + * + * @return Model The added model. + * + * @since 3.0 + */ + public function setModel($model, $default = false) + { + $name = strtolower($model->getName()); + $this->_models[$name] = $model; + + if ($default) + { + $this->_defaultModel = $name; + } + + return $model; + } + + /** + * Method to get the view name + * + * The model name by default parsed using the classname, or it can be set + * by passing a $config['name'] in the class constructor + * + * @return string The name of the model + * + * @since 3.0 + * @throws \Exception + */ + public function getName() + { + if (empty($this->_name)) + { + $reflection = new \ReflectionClass($this); + + if ($viewNamespace = $reflection->getNamespaceName()) + { + $pos = strrpos($viewNamespace, '\\'); + + if ($pos !== false) + { + $this->_name = strtolower(substr($viewNamespace, $pos)); + } + } + else + { + $className = get_class($this); + $viewPos = strpos($className, 'View'); + + if ($viewPos != false) + { + $this->_name = strtolower(substr($className, $viewPos + 4)); + } + } + + if (empty($this->_name)) + { + throw new \Exception(\JText::_('JLIB_APPLICATION_ERROR_VIEW_GET_NAME'), 500); + } + } + + return $this->_name; + } +} + + diff --git a/libraries/src/CMS/View/Categories.php b/libraries/src/CMS/View/Categories.php index 23370a2a48243..dc3ec44156778 100644 --- a/libraries/src/CMS/View/Categories.php +++ b/libraries/src/CMS/View/Categories.php @@ -15,7 +15,7 @@ * * @since 3.2 */ -class Categories extends View +class Categories extends HtmlView { /** * State data diff --git a/libraries/src/CMS/View/Category.php b/libraries/src/CMS/View/Category.php index 22db10a3f24fd..a565beb8fbc91 100644 --- a/libraries/src/CMS/View/Category.php +++ b/libraries/src/CMS/View/Category.php @@ -15,7 +15,7 @@ * * @since 3.2 */ -class Category extends View +class Category extends HtmlView { /** * State data diff --git a/libraries/src/CMS/View/CategoryFeed.php b/libraries/src/CMS/View/CategoryFeed.php index f5cc884b0dc85..3245814cbc6da 100644 --- a/libraries/src/CMS/View/CategoryFeed.php +++ b/libraries/src/CMS/View/CategoryFeed.php @@ -15,7 +15,7 @@ * * @since 3.2 */ -class CategoryFeed extends View +class CategoryFeed extends HtmlView { /** * Execute and display a template script. diff --git a/libraries/src/CMS/View/FormView.php b/libraries/src/CMS/View/FormView.php new file mode 100644 index 0000000000000..1330e5c6eedad --- /dev/null +++ b/libraries/src/CMS/View/FormView.php @@ -0,0 +1,249 @@ +helpLink = $config['help_link']; + } + + if (isset($config['toolbar_icon'])) + { + $this->toolbarIcon = $config['toolbar_icon']; + } + else + { + $this->toolbarIcon = 'pencil-2 ' . $this->getName() . '-add'; + } + + // Set default value for $canDo to avoid fatal error if child class doesn't set value for this property + $this->canDo = new \JObject; + } + + /** + * Execute and display a template script. + * + * @param string $tpl The name of the template file to parse; automatically searches through the template paths. + * + * @return mixed + */ + public function display($tpl = null) + { + // Prepare view data + $this->initializeView(); + + // Check for errors. + if (count($errors = $this->get('Errors'))) + { + throw new \JViewGenericdataexception(implode("\n", $errors), 500); + } + + // Build toolbar + $this->addToolbar(); + + // Render the view + return parent::display($tpl); + } + + /** + * Prepare view data + * + * @return void + */ + protected function initializeView() + { + $this->form = $this->get('Form'); + $this->item = $this->get('Item'); + $this->state = $this->get('State'); + + // Set default toolbar title + if ($this->item->id) + { + $this->toolbarTitle = \JText::_(strtoupper($this->option . '_MANAGER_' . $this->getName() . '_EDIT')); + } + else + { + $this->toolbarTitle = \JText::_(strtoupper($this->option . '_MANAGER_' . $this->getName() . '_NEW')); + } + } + + /** + * Add the page title and toolbar. + * + * @return void + * + * @since 1.6 + */ + protected function addToolbar() + { + \JFactory::getApplication()->input->set('hidemainmenu', true); + + $user = \JFactory::getUser(); + $userId = $user->id; + $isNew = ($this->item->id == 0); + $viewName = $this->getName(); + $checkedOut = $this->getModel()->isCheckedOut($this->item); + $canDo = $this->canDo; + + \JToolbarHelper::title( + $this->toolbarTitle, + $this->toolbarIcon + ); + + // For new records, check the create permission. + if ($isNew && $canDo->get('core.create')) + { + \JToolbarHelper::saveGroup( + [ + ['apply', $viewName . '.apply'], + ['save', $viewName . '.save'], + ['save2new', $viewName . '.save2new'] + ], + 'btn-success' + ); + + \JToolbarHelper::cancel($viewName . '.cancel'); + } + else + { + // Since it's an existing record, check the edit permission, or fall back to edit own if the owner. + if (property_exists($this->item, 'created_by')) + { + $itemEditable = $canDo->get('core.edit') || ($canDo->get('core.edit.own') && $this->item->created_by == $userId); + } + else + { + $itemEditable = $canDo->get('core.edit'); + } + + $toolbarButtons = []; + + // Can't save the record if it's checked out and editable + if (!$checkedOut && $itemEditable) + { + $toolbarButtons[] = ['apply', $viewName . '.apply']; + $toolbarButtons[] = ['save', $viewName . '.save']; + + // We can save this record, but check the create permission to see if we can return to make a new one. + if ($canDo->get('core.create')) + { + $toolbarButtons[] = ['save2new', $viewName . '.save2new']; + } + } + + // If checked out, we can still save + if ($canDo->get('core.create')) + { + $toolbarButtons[] = ['save2copy', $viewName . '.save2copy']; + } + + \JToolbarHelper::saveGroup( + $toolbarButtons, + 'btn-success' + ); + + if (ComponentHelper::isEnabled('com_contenthistory') && $this->state->params->get('save_history', 0) && $itemEditable) + { + \JToolbarHelper::versions($this->option . '.' . $viewName, $this->item->id); + } + + if (!$isNew && $this->previewLink) + { + \JToolbarHelper::preview($this->previewLink, \JText::_('JGLOBAL_PREVIEW'), 'eye', 80, 90); + } + + \JToolbarHelper::cancel($viewName . '.cancel', 'JTOOLBAR_CLOSE'); + } + + \JToolbarHelper::divider(); + + if ($this->helpLink) + { + \JToolbarHelper::help($this->helpLink); + } + } +} diff --git a/libraries/src/CMS/View/View.php b/libraries/src/CMS/View/HtmlView.php similarity index 77% rename from libraries/src/CMS/View/View.php rename to libraries/src/CMS/View/HtmlView.php index d3d2cad1643ff..b34180c167b71 100644 --- a/libraries/src/CMS/View/View.php +++ b/libraries/src/CMS/View/HtmlView.php @@ -10,41 +10,15 @@ defined('JPATH_PLATFORM') or die; -use Joomla\CMS\Model\Model; - /** - * Base class for a Joomla View + * Base class for a Joomla Html View * * Class holding methods for displaying presentation data. * * @since 2.5.5 */ -class View extends \JObject +class HtmlView extends AbstractView { - /** - * The active document object - * - * @var \JDocument - * @since 3.0 - */ - public $document; - - /** - * The name of the view - * - * @var array - * @since 3.0 - */ - protected $_name = null; - - /** - * Registered models - * - * @var array - * @since 3.0 - */ - protected $_models = array(); - /** * The base path of the view * @@ -53,14 +27,6 @@ class View extends \JObject */ protected $_basePath = null; - /** - * The default model - * - * @var string - * @since 3.0 - */ - protected $_defaultModel = null; - /** * Layout name * @@ -133,18 +99,7 @@ class View extends \JObject */ public function __construct($config = array()) { - // Set the view name - if (empty($this->_name)) - { - if (array_key_exists('name', $config)) - { - $this->_name = $config['name']; - } - else - { - $this->_name = $this->getName(); - } - } + parent::__construct($config); // Set the charset (used by the variable escaping functions) if (array_key_exists('charset', $config)) @@ -169,6 +124,10 @@ public function __construct($config = array()) // User-defined dirs $this->_setPath('template', $config['template_path']); } + elseif (is_dir($this->_basePath . '/View')) + { + $this->_setPath('template', $this->_basePath . '/View/' . $this->getName() . '/tmpl'); + } elseif (is_dir($this->_basePath . '/view')) { $this->_setPath('template', $this->_basePath . '/view/' . $this->getName() . '/tmpl'); @@ -241,69 +200,6 @@ public function escape($var) return htmlspecialchars($var, ENT_COMPAT, $this->_charset); } - /** - * Method to get data from a registered model or a property of the view - * - * @param string $property The name of the method to call on the model or the property to get - * @param string $default The name of the model to reference or the default value [optional] - * - * @return mixed The return value of the method - * - * @since 3.0 - */ - public function get($property, $default = null) - { - // If $model is null we use the default model - if (is_null($default)) - { - $model = $this->_defaultModel; - } - else - { - $model = strtolower($default); - } - - // First check to make sure the model requested exists - if (isset($this->_models[$model])) - { - // Model exists, let's build the method name - $method = 'get' . ucfirst($property); - - // Does the method exist? - if (method_exists($this->_models[$model], $method)) - { - // The method exists, let's call it and return what we get - $result = $this->_models[$model]->$method(); - - return $result; - } - } - - // Degrade to \JObject::get - $result = parent::get($property, $default); - - return $result; - } - - /** - * Method to get the model object - * - * @param string $name The name of the model (optional) - * - * @return mixed \JModelLegacy object - * - * @since 3.0 - */ - public function getModel($name = null) - { - if ($name === null) - { - $name = $this->_defaultModel; - } - - return $this->_models[strtolower($name)]; - } - /** * Get the layout. * @@ -328,62 +224,6 @@ public function getLayoutTemplate() return $this->_layoutTemplate; } - /** - * Method to get the view name - * - * The model name by default parsed using the classname, or it can be set - * by passing a $config['name'] in the class constructor - * - * @return string The name of the model - * - * @since 3.0 - * @throws \Exception - */ - public function getName() - { - if (empty($this->_name)) - { - $classname = get_class($this); - $viewpos = strpos($classname, 'View'); - - if ($viewpos === false) - { - throw new \Exception(\JText::_('JLIB_APPLICATION_ERROR_VIEW_GET_NAME'), 500); - } - - $this->_name = strtolower(substr($classname, $viewpos + 4)); - } - - return $this->_name; - } - - /** - * Method to add a model to the view. We support a multiple model single - * view system by which models are referenced by classname. A caveat to the - * classname referencing is that any classname prepended by \JModel will be - * referenced by the name without \JModel, eg. \JModelCategory is just - * Category. - * - * @param Model $model The model to add to the view. - * @param boolean $default Is this the default model? - * - * @return Model The added model. - * - * @since 3.0 - */ - public function setModel($model, $default = false) - { - $name = strtolower($model->getName()); - $this->_models[$name] = $model; - - if ($default) - { - $this->_defaultModel = $name; - } - - return $model; - } - /** * Sets the layout name to use * @@ -492,7 +332,7 @@ public function loadTemplate($tpl = null) // Load the language file for the template $lang = \JFactory::getLanguage(); $lang->load('tpl_' . $template, JPATH_BASE, null, false, true) - || $lang->load('tpl_' . $template, JPATH_THEMES . "/$template", null, false, true); + || $lang->load('tpl_' . $template, JPATH_THEMES . "/$template", null, false, true); // Change the template folder if alternative layout is in different template if (isset($layoutTemplate) && $layoutTemplate != '_' && $layoutTemplate != $template) @@ -537,10 +377,8 @@ public function loadTemplate($tpl = null) return $this->_output; } - else - { - throw new \Exception(\JText::sprintf('JLIB_APPLICATION_ERROR_LAYOUTFILE_NOT_FOUND', $file), 500); - } + + throw new \Exception(\JText::sprintf('JLIB_APPLICATION_ERROR_LAYOUTFILE_NOT_FOUND', $file), 500); } /** @@ -580,7 +418,15 @@ public function loadHelper($hlp = null) */ protected function _setPath($type, $path) { - $component = \JApplicationHelper::getComponentName(); + if ($this->option) + { + $component = $this->option; + } + else + { + $component = \JApplicationHelper::getComponentName(); + } + $app = \JFactory::getApplication(); // Clear out the prior search dirs diff --git a/libraries/src/CMS/View/ListView.php b/libraries/src/CMS/View/ListView.php new file mode 100644 index 0000000000000..1e14119903cc3 --- /dev/null +++ b/libraries/src/CMS/View/ListView.php @@ -0,0 +1,276 @@ +toolbarTitle = $config['toolbar_title']; + } + else + { + $this->toolbarTitle = strtoupper($this->option . '_MANAGER_' . $this->getName()); + } + + if (isset($config['toolbar_icon'])) + { + $this->toolbarIcon = $config['toolbar_icon']; + } + else + { + $this->toolbarIcon = strtolower($this->getName()); + } + + if (isset($config['supports_batch'])) + { + $this->supportsBatch = $config['supports_batch']; + } + + if (isset($config['help_link'])) + { + $this->helpLink = $config['help_link']; + } + + // Set default value for $canDo to avoid fatal error if child class doesn't set value for this property + $this->canDo = new \JObject; + } + + /** + * Execute and display a template script. + * + * @param string $tpl The name of the template file to parse; automatically searches through the template paths. + * + * @return mixed A string if successful, otherwise an Error object. + */ + public function display($tpl = null) + { + // Prepare view data + $this->initializeView(); + + // Check for errors. + if (count($errors = $this->get('Errors'))) + { + throw new \JViewGenericdataexception(implode("\n", $errors), 500); + } + + // Build toolbar + $this->addToolbar(); + + // Render the view + return parent::display($tpl); + } + + /** + * Prepare view data + * + * @return void + */ + protected function initializeView() + { + $componentName = substr($this->option, 4); + $helperClass = ucfirst($componentName . 'Helper'); + + // Include the component helpers. + \JLoader::register($helperClass, JPATH_COMPONENT . '/helpers/' . $componentName . '.php'); + \JHtml::addIncludePath(JPATH_COMPONENT . '/helpers/html'); + + if ($this->getLayout() !== 'modal') + { + if (is_callable($helperClass . '::addSubmenu')) + { + call_user_func(array($helperClass, 'addSubmenu'), $this->getName()); + } + + $this->sidebar = \JHtmlSidebar::render(); + } + + $this->items = $this->get('Items'); + $this->pagination = $this->get('Pagination'); + $this->state = $this->get('State'); + $this->filterForm = $this->get('FilterForm'); + $this->activeFilters = $this->get('ActiveFilters'); + } + + /** + * Add the page title and toolbar. + * + * @return void + * + * @since 1.6 + */ + protected function addToolbar() + { + $canDo = $this->canDo; + $user = \JFactory::getUser(); + + // Get the toolbar object instance + $bar = \JToolbar::getInstance('toolbar'); + + $viewName = $this->getName(); + $singularViewName = \Joomla\String\Inflector::getInstance()->toSingular($viewName); + + \JToolbarHelper::title(\JText::_($this->toolbarTitle), $this->toolbarIcon); + + if ($canDo->get('core.create')) + { + \JToolbarHelper::addNew($singularViewName . '.add'); + } + + if (($canDo->get('core.edit')) || ($canDo->get('core.edit.own'))) + { + \JToolbarHelper::editList($singularViewName . '.edit'); + } + + if ($canDo->get('core.edit.state')) + { + \JToolbarHelper::publish($viewName . '.publish', 'JTOOLBAR_PUBLISH', true); + \JToolbarHelper::unpublish($viewName . '.unpublish', 'JTOOLBAR_UNPUBLISH', true); + + if (isset($this->items[0]->featured)) + { + \JToolbarHelper::custom($viewName . '.featured', 'featured.png', 'featured_f2.png', 'JFEATURE', true); + \JToolbarHelper::custom($viewName . '.unfeatured', 'unfeatured.png', 'featured_f2.png', 'JUNFEATURE', true); + } + + \JToolbarHelper::archiveList($viewName . '.archive'); + \JToolbarHelper::checkin($viewName . '.checkin'); + } + + // Add a batch button + if ($this->supportsBatch && $user->authorise('core.create', $this->option) + && $user->authorise('core.edit', $this->option) + && $user->authorise('core.edit.state', $this->option)) + { + $title = \JText::_('JTOOLBAR_BATCH'); + + // Instantiate a new \JLayoutFile instance and render the batch button + $layout = new \JLayoutFile('joomla.toolbar.batch'); + + $dhtml = $layout->render(array('title' => $title)); + $bar->appendButton('Custom', $dhtml, 'batch'); + } + + if ($this->state->get('filter.published') == -2 && $canDo->get('core.delete')) + { + \JToolbarHelper::deleteList('JGLOBAL_CONFIRM_DELETE', $viewName . '.delete', 'JTOOLBAR_EMPTY_TRASH'); + } + elseif ($canDo->get('core.edit.state')) + { + \JToolbarHelper::trash($viewName . '.trash'); + } + + if ($user->authorise('core.admin', $this->option) || $user->authorise('core.options', $this->option)) + { + \JToolbarHelper::preferences($this->option); + } + + if ($this->helpLink) + { + \JToolbarHelper::help($this->helpLink); + } + } +}