diff --git a/administrator/components/com_content/services/services.php b/administrator/components/com_content/services/services.php new file mode 100644 index 0000000000000..6639211e37e3c --- /dev/null +++ b/administrator/components/com_content/services/services.php @@ -0,0 +1,24 @@ +share( + 'ContentContainer', + function (\Joomla\DI\Container $parent) + { + $container = new \Joomla\CMS\Component\ComponentContainer($parent); + $container->registerServiceProvider( + new \Joomla\CMS\Component\Service\Provider\Categories(['table' => '#__content', 'extension' => 'com_content']) + ); + + return $container; + }, + true +); diff --git a/administrator/components/com_fields/Model/FieldModel.php b/administrator/components/com_fields/Model/FieldModel.php index 903eb005becfb..57681c10e2276 100644 --- a/administrator/components/com_fields/Model/FieldModel.php +++ b/administrator/components/com_fields/Model/FieldModel.php @@ -967,7 +967,7 @@ protected function preprocessForm(\JForm $form, $data, $group = 'content') } // Setting the context for the category field - $cat = \JCategories::getInstance(str_replace('com_', '', $component)); + $cat = $this->bootComponent($component)->getCategories(); if ($cat && $cat->get('root')->hasChildren()) { diff --git a/components/com_content/helpers/category.php b/components/com_content/helpers/category.php deleted file mode 100644 index 24983bf982031..0000000000000 --- a/components/com_content/helpers/category.php +++ /dev/null @@ -1,33 +0,0 @@ -_extension = $options['extension']; - $this->_table = $options['table']; - $this->_field = isset($options['field']) && $options['field'] ? $options['field'] : 'catid'; - $this->_key = isset($options['key']) && $options['key'] ? $options['key'] : 'id'; - $this->_statefield = $options['statefield'] ?? 'state'; - - $options['access'] = $options['access'] ?? 'true'; - $options['published'] = $options['published'] ?? 1; - $options['countItems'] = $options['countItems'] ?? 0; - $options['currentlang'] = Multilanguage::isEnabled() ? Factory::getLanguage()->getTag() : 0; - - $this->_options = $options; - - return true; + $this->setOptions($options); } /** @@ -125,7 +113,8 @@ public function __construct($options) * * @return Categories|boolean Categories object on success, boolean false if an object does not exist * - * @since 1.6 + * @since 1.6 + * @deprecated 5.0 Use the ComponentContainerInterface to get the categories */ public static function getInstance($extension, $options = array()) { @@ -136,33 +125,17 @@ public static function getInstance($extension, $options = array()) return self::$instances[$hash]; } - $parts = explode('.', $extension); - $component = 'com_' . strtolower($parts[0]); - $section = count($parts) > 1 ? $parts[1] : ''; - $classname = ucfirst(substr($component, 4)) . ucfirst($section) . 'Categories'; - - if (!class_exists($classname)) - { - $path = JPATH_SITE . '/components/' . $component . '/helpers/category.php'; - - if (!is_file($path)) - { - return false; - } + $parts = explode('.', $extension, 2); - include_once $path; - } + $categories = ComponentHelper::boot($parts[0])->getCategories(count($parts) > 1 ? $parts[1] : ''); - // Check for a possible service from the container otherwise manually instantiate the class - if (\JFactory::getContainer()->exists($classname)) - { - self::$instances[$hash] = \JFactory::getContainer()->get($classname); - } - else + if ($categories) { - self::$instances[$hash] = new $classname($options); + $categories->setOptions($options); } + self::$instances[$hash] = $categories; + return self::$instances[$hash]; } @@ -389,4 +362,41 @@ protected function _load($id) $this->_nodes[$id] = null; } } + + /** + * Sets the options for this service. The given options do overwrite the + * internal options with the same index. The existing options are not cleared + * when they are not specified in the given options array. + * + * @param array $options The new options + * + * @since __DEPLOY_VERSION__ + */ + public function setOptions(array $options) + { + if ($this->_options == $options) + { + return; + } + + // Merge the options to not loose the base config + $options = array_merge($this->_options, $options); + + $this->_extension = $options['extension']; + $this->_table = $options['table']; + $this->_field = isset($options['field']) && $options['field'] ? $options['field'] : 'catid'; + $this->_key = isset($options['key']) && $options['key'] ? $options['key'] : 'id'; + $this->_statefield = $options['statefield'] ?? 'state'; + + $options['access'] = $options['access'] ?? 'true'; + $options['published'] = $options['published'] ?? 1; + $options['countItems'] = $options['countItems'] ?? 0; + $options['currentlang'] = Multilanguage::isEnabled() ? Factory::getLanguage()->getTag() : 0; + + $this->_options = $options; + + // Reset the cache + $this->_nodes = []; + $this->_checkedCategories = []; + } } diff --git a/libraries/src/Component/ComponentContainer.php b/libraries/src/Component/ComponentContainer.php new file mode 100644 index 0000000000000..60674c4a1141c --- /dev/null +++ b/libraries/src/Component/ComponentContainer.php @@ -0,0 +1,50 @@ +has($serviceName)) + { + return null; + } + + return $this->get($serviceName); + } +} diff --git a/libraries/src/Component/ComponentContainerInterface.php b/libraries/src/Component/ComponentContainerInterface.php new file mode 100644 index 0000000000000..58a253355261f --- /dev/null +++ b/libraries/src/Component/ComponentContainerInterface.php @@ -0,0 +1,33 @@ +getNamespaceName(), $from + 11, $to)); } + + /** + * Boots the component with the given name. + * + * @param string $component The component name, eg. com_content. + * @param ContainerInterface $container The container. + * + * @return ComponentContainerInterface The service container + * + * @since __DEPLOY_VERSION__ + */ + public static function boot($component, ContainerInterface $container = null): ComponentContainerInterface + { + // Normalize the component name + $component = strtolower(str_replace('com_', '', $component)); + + // The service name + $serviceName = ucfirst($component) . 'Container'; + + // The container which holds the component container + $container = $container ? : Factory::getContainer(); + + // Check if the service is already available + if ($container->has($serviceName)) + { + return $container->get($serviceName); + } + + // Allow components to register services + $path = JPATH_ADMINISTRATOR . '/components/com_' . $component . '/services/services.php'; + + if (file_exists($path)) + { + // Load the services file + require_once $path; + } + + if (!$container->has($serviceName)) + { + $container->set($serviceName, new LegacyComponentContainer($component)); + } + + // Return the child container + return $container->get($serviceName); + } } diff --git a/libraries/src/Component/LegacyComponentContainer.php b/libraries/src/Component/LegacyComponentContainer.php new file mode 100644 index 0000000000000..389b18429ad59 --- /dev/null +++ b/libraries/src/Component/LegacyComponentContainer.php @@ -0,0 +1,69 @@ +component = $component; + } + + /** + * Returns the category service. If the service is not available + * null is returned. + * + * @param string $section The section + * + * @return Categories|null + * + * @since __DEPLOY_VERSION__ + */ + public function getCategories($section = ''): Categories + { + $classname = ucfirst(substr($this->component, 4)) . ucfirst($section) . 'Categories'; + + if (!class_exists($classname)) + { + $path = JPATH_SITE . '/components/' . $this->component . '/helpers/category.php'; + + if (!is_file($path)) + { + return null; + } + + include_once $path; + } + + return new $classname([]); + } +} diff --git a/libraries/src/Component/Service/Provider/Categories.php b/libraries/src/Component/Service/Provider/Categories.php new file mode 100644 index 0000000000000..9dfc27dd4dfaf --- /dev/null +++ b/libraries/src/Component/Service/Provider/Categories.php @@ -0,0 +1,62 @@ +options = $options; + } + + /** + * Registers the service provider with a DI container. + * + * @param Container $container The DI container. + * + * @return void + * + * @since __DEPLOY_VERSION__ + */ + public function register(Container $container) + { + $container->share( + 'categories', + function (Container $container) + { + return new \Joomla\CMS\Categories\Categories($this->options); + }, + true + ); + } +} diff --git a/libraries/src/MVC/Model/BaseDatabaseModel.php b/libraries/src/MVC/Model/BaseDatabaseModel.php index 7a228251fb9b4..d7a1086bd73f5 100644 --- a/libraries/src/MVC/Model/BaseDatabaseModel.php +++ b/libraries/src/MVC/Model/BaseDatabaseModel.php @@ -10,6 +10,7 @@ defined('JPATH_PLATFORM') or die; +use Joomla\CMS\Component\ComponentContainerInterface; use Joomla\CMS\Component\ComponentHelper; use Joomla\CMS\MVC\Factory\LegacyFactory; use Joomla\CMS\MVC\Factory\MVCFactory; @@ -657,4 +658,19 @@ protected function cleanCache($group = null, $client_id = 0) // Trigger the onContentCleanCache event. \JFactory::getApplication()->triggerEvent($this->event_clean_cache, $options); } + + /** + * Boots the component with the given name. + * + * @param string $component The component name, eg. com_content. + * + * @return ComponentContainerInterface The service container + * + * @since __DEPLOY_VERSION__ + */ + protected function bootComponent($component): ComponentContainerInterface + { + // @Todo move the static call to an interface + return ComponentHelper::boot($component); + } }