diff --git a/modules/mod_articles_popular/mod_articles_popular.php b/modules/mod_articles_popular/mod_articles_popular.php
deleted file mode 100644
index 0cca23f3b994f..0000000000000
--- a/modules/mod_articles_popular/mod_articles_popular.php
+++ /dev/null
@@ -1,27 +0,0 @@
-
- * @license GNU General Public License version 2 or later; see LICENSE.txt
- */
-
-defined('_JEXEC') or die;
-
-use Joomla\CMS\Component\ComponentHelper;
-use Joomla\CMS\Helper\ModuleHelper;
-use Joomla\CMS\Language\Text;
-use Joomla\Module\ArticlesPopular\Site\Helper\ArticlesPopularHelper;
-
-// Exit early if hits are disabled.
-if (!ComponentHelper::getParams('com_content')->get('record_hits', 1)) {
- echo Text::_('JGLOBAL_RECORD_HITS_DISABLED');
-
- return;
-}
-
-$list = ArticlesPopularHelper::getList($params);
-
-require ModuleHelper::getLayoutPath('mod_articles_popular', $params->get('layout', 'default'));
diff --git a/modules/mod_articles_popular/mod_articles_popular.xml b/modules/mod_articles_popular/mod_articles_popular.xml
index 95e8a9396effb..760ff93f48f4f 100644
--- a/modules/mod_articles_popular/mod_articles_popular.xml
+++ b/modules/mod_articles_popular/mod_articles_popular.xml
@@ -11,7 +11,7 @@
MOD_POPULAR_XML_DESCRIPTION
Joomla\Module\ArticlesPopular
- mod_articles_popular.php
+ services
src
tmpl
diff --git a/modules/mod_articles_popular/services/provider.php b/modules/mod_articles_popular/services/provider.php
new file mode 100644
index 0000000000000..870a9bf02c26f
--- /dev/null
+++ b/modules/mod_articles_popular/services/provider.php
@@ -0,0 +1,42 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+defined('_JEXEC') or die;
+
+use Joomla\CMS\Extension\Service\Provider\HelperFactory;
+use Joomla\CMS\Extension\Service\Provider\Module;
+use Joomla\CMS\Extension\Service\Provider\ModuleDispatcherFactory;
+use Joomla\DI\Container;
+use Joomla\DI\ServiceProviderInterface;
+
+/**
+ * The popular articles module service provider.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+return new class implements ServiceProviderInterface
+{
+ /**
+ * 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->registerServiceProvider(new ModuleDispatcherFactory('\\Joomla\\Module\\ArticlesPopular'));
+ $container->registerServiceProvider(new HelperFactory('\\Joomla\\Module\\ArticlesPopular\\Site\\Helper'));
+
+ $container->registerServiceProvider(new Module());
+ }
+};
diff --git a/modules/mod_articles_popular/src/Dispatcher/Dispatcher.php b/modules/mod_articles_popular/src/Dispatcher/Dispatcher.php
new file mode 100644
index 0000000000000..551ac0450babb
--- /dev/null
+++ b/modules/mod_articles_popular/src/Dispatcher/Dispatcher.php
@@ -0,0 +1,51 @@
+
+ * @license GNU General Public License version 2 or later; see LICENSE.txt
+ */
+
+namespace Joomla\Module\ArticlesPopular\Site\Dispatcher;
+
+use Joomla\CMS\Component\ComponentHelper;
+use Joomla\CMS\Dispatcher\AbstractModuleDispatcher;
+use Joomla\CMS\Helper\HelperFactoryAwareInterface;
+use Joomla\CMS\Helper\HelperFactoryAwareTrait;
+use Joomla\CMS\Language\Text;
+
+// phpcs:disable PSR1.Files.SideEffects
+\defined('JPATH_PLATFORM') or die;
+// phpcs:enable PSR1.Files.SideEffects
+
+/**
+ * Dispatcher class for mod_articles_popular
+ *
+ * @since __DEPLOY_VERSION__
+ */
+class Dispatcher extends AbstractModuleDispatcher implements HelperFactoryAwareInterface
+{
+ use HelperFactoryAwareTrait;
+
+ /**
+ * Returns the layout data.
+ *
+ * @return array
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ protected function getLayoutData()
+ {
+ $data = parent::getLayoutData();
+
+ if (!ComponentHelper::getParams('com_content')->get('record_hits', 1)) {
+ $data['hitsDisabledMessage'] = Text::_('JGLOBAL_RECORD_HITS_DISABLED');
+ } else {
+ $data['list'] = $this->getHelperFactory()->getHelper('ArticlesPopularHelper', $data)->getArticles($data['params'], $data['app']);
+ }
+
+ return $data;
+ }
+}
diff --git a/modules/mod_articles_popular/src/Helper/ArticlesPopularHelper.php b/modules/mod_articles_popular/src/Helper/ArticlesPopularHelper.php
index 9ce5657b7bbce..c3a0f83257b34 100644
--- a/modules/mod_articles_popular/src/Helper/ArticlesPopularHelper.php
+++ b/modules/mod_articles_popular/src/Helper/ArticlesPopularHelper.php
@@ -11,11 +11,18 @@
namespace Joomla\Module\ArticlesPopular\Site\Helper;
use Joomla\CMS\Access\Access;
+use Joomla\CMS\Application\SiteApplication;
+use Joomla\CMS\Cache\CacheControllerFactoryInterface;
+use Joomla\CMS\Cache\Controller\OutputController;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Router\Route;
use Joomla\Component\Content\Administrator\Extension\ContentComponent;
use Joomla\Component\Content\Site\Helper\RouteHelper;
+use Joomla\Component\Content\Site\Model\ArticlesModel;
+use Joomla\Database\DatabaseAwareInterface;
+use Joomla\Database\DatabaseAwareTrait;
+use Joomla\Registry\Registry;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
@@ -24,78 +31,152 @@
/**
* Helper for mod_articles_popular
*
- * @since 1.6
+ * @since __DEPLOY_VERSION__
*/
-abstract class ArticlesPopularHelper
+class ArticlesPopularHelper
{
/**
- * Get a list of popular articles from the articles model
+ * The module instance
*
- * @param \Joomla\Registry\Registry &$params object holding the models parameters
+ * @var \stdClass
*
- * @return mixed
+ * @since __DEPLOY_VERSION__
*/
- public static function getList(&$params)
+ protected $module;
+
+ /**
+ * Constructor.
+ *
+ * @param array $config An optional associative array of configuration settings.
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function __construct($config = [])
+ {
+ $this->module = $config['module'];
+ }
+
+ /**
+ * Retrieve a list of months with archived articles
+ *
+ * @param Registry $params The module parameters.
+ * @param SiteApplication $app The current application.
+ *
+ * @return object[]
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ public function getArticles(Registry $moduleParams, SiteApplication $app)
{
- $app = Factory::getApplication();
+ $cacheKey = md5(serialize([$moduleParams->toString(), $this->module->module, $this->module->id]));
- // Get an instance of the generic articles model
- $model = $app->bootComponent('com_content')
- ->getMVCFactory()->createModel('Articles', 'Site', ['ignore_request' => true]);
+ /** @var OutputController $cache */
+ $cache = Factory::getContainer()->get(CacheControllerFactoryInterface::class)
+ ->createCacheController('output', ['defaultgroup' => 'mod_articles_popular']);
- // Set application parameters in model
- $appParams = $app->getParams();
- $model->setState('params', $appParams);
+ if (!$cache->contains($cacheKey)) {
+ $mvcContentFactory = $app->bootComponent('com_content')->getMVCFactory();
- $model->setState('list.start', 0);
- $model->setState('filter.published', ContentComponent::CONDITION_PUBLISHED);
+ /** @var ArticlesModel $articlesModel */
+ $articlesModel = $mvcContentFactory->createModel('Articles', 'Site', ['ignore_request' => true]);
- // Set the filters based on the module params
- $model->setState('list.limit', (int) $params->get('count', 5));
- $model->setState('filter.featured', $params->get('show_front', 1) == 1 ? 'show' : 'hide');
+ // Set application parameters in model
+ $appParams = $app->getParams();
+ $articlesModel->setState('params', $appParams);
- // This module does not use tags data
- $model->setState('load_tags', false);
+ $articlesModel->setState('list.start', 0);
+ $articlesModel->setState('filter.published', ContentComponent::CONDITION_PUBLISHED);
- // Access filter
- $access = !ComponentHelper::getParams('com_content')->get('show_noauth');
- $authorised = Access::getAuthorisedViewLevels(Factory::getUser()->get('id'));
- $model->setState('filter.access', $access);
+ // Set the filters based on the module params
+ $articlesModel->setState('list.limit', (int) $moduleParams->get('count', 5));
+ $articlesModel->setState('filter.featured', $moduleParams->get('show_front', 1) == 1 ? 'show' : 'hide');
- // Category filter
- $model->setState('filter.category_id', $params->get('catid', []));
+ // This module does not use tags data
+ $articlesModel->setState('load_tags', false);
- // Date filter
- $date_filtering = $params->get('date_filtering', 'off');
+ // Access filter
+ $access = !ComponentHelper::getParams('com_content')->get('show_noauth');
+ $articlesModel->setState('filter.access', $access);
- if ($date_filtering !== 'off') {
- $model->setState('filter.date_filtering', $date_filtering);
- $model->setState('filter.date_field', $params->get('date_field', 'a.created'));
- $model->setState('filter.start_date_range', $params->get('start_date_range', '1000-01-01 00:00:00'));
- $model->setState('filter.end_date_range', $params->get('end_date_range', '9999-12-31 23:59:59'));
- $model->setState('filter.relative_date', $params->get('relative_date', 30));
- }
+ // Category filter
+ $articlesModel->setState('filter.category_id', $moduleParams->get('catid', []));
- // Filter by language
- $model->setState('filter.language', $app->getLanguageFilter());
+ // Date filter
+ $date_filtering = $moduleParams->get('date_filtering', 'off');
- // Ordering
- $model->setState('list.ordering', 'a.hits');
- $model->setState('list.direction', 'DESC');
+ if ($date_filtering !== 'off') {
+ $articlesModel->setState('filter.date_filtering', $date_filtering);
+ $articlesModel->setState('filter.date_field', $moduleParams->get('date_field', 'a.created'));
+ $articlesModel->setState('filter.start_date_range', $moduleParams->get('start_date_range', '1000-01-01 00:00:00'));
+ $articlesModel->setState('filter.end_date_range', $moduleParams->get('end_date_range', '9999-12-31 23:59:59'));
+ $articlesModel->setState('filter.relative_date', $moduleParams->get('relative_date', 30));
+ }
+
+ // Filter by language
+ $articlesModel->setState('filter.language', $app->getLanguageFilter());
+
+ // Ordering
+ $articlesModel->setState('list.ordering', 'a.hits');
+ $articlesModel->setState('list.direction', 'DESC');
- $items = $model->getItems();
+ // Prepare the module output
+ $items = [];
+ $itemParams = new \stdClass();
- foreach ($items as &$item) {
- $item->slug = $item->id . ':' . $item->alias;
+ $itemParams->authorised = Access::getAuthorisedViewLevels($app->getIdentity()->get('id'));
+ $itemParams->access = $access;
- if ($access || \in_array($item->access, $authorised)) {
- // We know that user has the privilege to view the article
- $item->link = Route::_(RouteHelper::getArticleRoute($item->slug, $item->catid, $item->language));
- } else {
- $item->link = Route::_('index.php?option=com_users&view=login');
+ foreach ($articlesModel->getItems() as $item) {
+ $items[] = $this->prepareItem($item, $itemParams);
}
+
+ // Cache the output and return
+ $cache->store($items, $cacheKey);
+
+ return $items;
}
- return $items;
+ // Return the cached output
+ return $cache->get($cacheKey);
+ }
+
+ /**
+ * Prepare the article before render.
+ *
+ * @param object $item The article to prepare
+ * @param \stdClass $params The model item
+ *
+ * @return object
+ *
+ * @since __DEPLOY_VERSION__
+ */
+ private function prepareItem($item, $params): object
+ {
+ $item->slug = $item->id . ':' . $item->alias;
+
+ if ($params->access || \in_array($item->access, $params->authorised)) {
+ // We know that user has the privilege to view the article
+ $item->link = Route::_(RouteHelper::getArticleRoute($item->slug, $item->catid, $item->language));
+ } else {
+ $item->link = Route::_('index.php?option=com_users&view=login');
+ }
+
+ return $item;
+ }
+
+ /**
+ * Get a list of popular articles from the articles model
+ *
+ * @param \Joomla\Registry\Registry &$params object holding the models parameters
+ *
+ * @return mixed
+ *
+ * @since __DEPLOY_VERSION__
+ *
+ * @deprecated 5.0 Use the none static function getArticles
+ */
+ public static function getList(&$params)
+ {
+ return (new self())->getArticles($params, Factory::getApplication());
}
}
diff --git a/modules/mod_articles_popular/tmpl/default.php b/modules/mod_articles_popular/tmpl/default.php
index 9b00f2e24f2f5..a3cf9ce24ea56 100644
--- a/modules/mod_articles_popular/tmpl/default.php
+++ b/modules/mod_articles_popular/tmpl/default.php
@@ -10,7 +10,10 @@
defined('_JEXEC') or die;
-if (!$list) {
+if (!isset($list)) {
+ if (isset($hitsDisabledMessage)) {
+ echo $hitsDisabledMessage;
+ }
return;
}