| Server IP : 54.36.91.62 / Your IP : 216.73.217.112 Web Server : Apache System : Linux webm013.cluster127.gra.hosting.ovh.net 5.15.206-ovh-vps-grsec-zfs-classid #1 SMP Fri May 15 02:41:25 UTC 2026 x86_64 User : coopiak ( 151928) PHP Version : 8.3.23 Disable Function : _dyuweyrj4,_dyuweyrj4r,dl MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : OFF | Pkexec : OFF Directory : /home/coopiak/www/cj79373/plugins/content/engage/ |
Upload File : |
<?php
/**
* @package AkeebaEngage
* @copyright Copyright (c)2020-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
defined('_JEXEC') or die();
use Akeeba\Engage\Admin\Model\Comments;
use Akeeba\Engage\Site\Helper\Meta;
use FOF40\Container\Container;
use FOF40\Input\Input;
use FOF40\Layout\LayoutHelper;
use FOF40\JoomlaAbstraction\CacheCleaner;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Date\Date;
use Joomla\CMS\Form\Form;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Table\Content;
use Joomla\CMS\Table\Table;
use Joomla\Component\Categories\Administrator\Model\CategoryModel;
use Joomla\Component\Content\Administrator\Model\ArticleModel;
use Joomla\Registry\Registry;
/**
* Akeeba Engage – Configure and show comments in Joomla core content (articles) and categories
*
* @package AkeebaEngage
* @copyright Copyright (c)2020-2021 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
class plgContentEngage extends CMSPlugin
{
/**
* Database driver
*
* @var JDatabaseDriver|null
*/
protected $db;
/**
* Application objecy
*
* @var CMSApplication
*/
protected $app;
/**
* Should this plugin be allowed to run?
*
* If the runtime dependencies are not met the plugin effectively self-disables even if it's published. This
* prevents a WSOD should the user e.g. uninstall a library or the component without unpublishing the plugin first.
*
* @var bool
*/
private $enabled = true;
/**
* The Akeeba Engage component container
*
* @var Container|null
*/
private $container;
/**
* A cache of basic article information keyed to the asset ID
*
* @var object[]
*/
private $cachedArticles = [];
/**
* The keys to the settings known to Akeeba Engage (see forms/engage.xml)
*
* @var string[]
*/
private $parametersKeys = [];
/**
* Default values of the component's parameters
*
* @var array
*/
private $parameterDefaults = [];
/**
* Cache of parameters per article ID
*
* @var array
*/
private $parametersCache = [];
/**
* Constructor
*
* @param object &$subject The object to observe
* @param array $config An optional associative array of configuration settings.
*
* @return void
*/
public function __construct(&$subject, $config = [])
{
if (!defined('FOF40_INCLUDED') && !@include_once(JPATH_LIBRARIES . '/fof40/include.php'))
{
$this->enabled = false;
}
if (!ComponentHelper::isEnabled('com_engage'))
{
$this->enabled = false;
}
parent::__construct($subject, $config);
$this->loadLanguage();
$this->autoCleanSpam();
}
public function onContentBeforeDisplay(?string $context, &$row, &$params, ?int $page = 0): string
{
return $this->renderCommentCount($params, $row, $context, true);
}
/**
* Returns the content to display after an article. Used to render the comments interface.
*
* @param string|null $context The context of the content being prepared. We only repond to
* 'com_content.article'
* @param object $row A simple object with the article information
* @param object $params The category parameters, computed through the categories' hierarchy
* @param int|null $page Page number for multi-page articles
*
* @return string
*/
public function onContentAfterDisplay(?string $context, &$row, &$params, ?int $page = 0): string
{
return
$this->renderCommentCount($params, $row, $context, false) .
$this->renderComments($params, $row, $context);
}
/**
* Runs when Joomla is preparing a form. Used to add extra fields to the Category edit page.
*
* Please note that due to frontend editing this MUST run in both the front- and backend of the site.
*
* @param Form $form The Joomla Form object we are manipulating
* @param object $data The data assigned to the form.
*
* @return bool Always true (we never fail preparing the form)
*/
public function onContentPrepareForm(Form $form, $data): bool
{
if (!in_array($form->getName(), ['com_categories.categorycom_content', 'com_content.article']))
{
return true;
}
// Add the registration fields to the form.
Form::addFormPath(__DIR__ . '/forms');
$form->loadFile('engage', false);
return true;
}
/**
* Triggered when Joomla is saving content. Used to save the Engage configuration.
*
* @param string|null $context Context for the content being saved
* @param Table|object $table Joomla table object where the content is being saved to
* @param bool $isNew Is this a new record?
* @param object $data Data being saved
*
* @return bool
*/
public function onContentBeforeSave(?string $context, $table, $isNew = false, $data = null)
{
if (!in_array($context, ['com_categories.category', 'com_content.article']))
{
return true;
}
if (!isset($data['engage']))
{
return true;
}
$key = ($context === 'com_categories.category') ? 'params' : 'attribs';
$params = @json_decode($table->{$key}, true) ?? [];
$table->{$key} = json_encode(array_merge($params, ['engage' => $data['engage']]));
return true;
}
/**
* Executes after Joomla deleted a content item. Used to delete attached comments.
*
* @param string|null $context
* @param Content|object|mixed $data
*
* @return void
*
* @see https://docs.joomla.org/Plugin/Events/Content#onContentAfterDelete
*/
public function onContentAfterDelete(?string $context, $data)
{
if ($context != 'com_content.article')
{
return;
}
if (!is_object($data))
{
return;
}
if (!property_exists($data, 'asset_id'))
{
return;
}
$assetId = $data->asset_id;
$db = $this->db;
$query = $db->getQuery(true)
->delete($db->qn('#__engage_comments'))
->where($db->qn('asset_id') . ' = ' . $db->q($assetId));
try
{
$db->setQuery($query)->execute();
}
catch (Exception $e)
{
// It's not the end of the world if this fails
}
}
/**
* Triggered when Joomla is loading content. Used to load the Engage configuration.
*
* This is used for both articles and article categories.
*
* @param string|null $context Context for the content being loaded
* @param object $data Data being saved
*
* @return bool
*/
public function onContentPrepareData(?string $context, &$data)
{
if (!in_array($context, ['com_categories.category', 'com_content.article']))
{
return true;
}
$key = ($context === 'com_categories.category') ? 'params' : 'attribs';
if (!isset($data->{$key}) || !isset($data->{$key}['engage']))
{
return true;
}
$data->engage = $data->{$key}['engage'];
unset ($data->{$key}['engage']);
return true;
}
/**
* Returns the asset access information for an asset recognized by this plugin
*
* @param int $assetId The asset ID to get the information for
* @param bool $loadParameters Should I load the comment parameters? (It's slow!)
*
* @return array|null Asset access information. NULL when the asset is invalid or not recognized.
*/
public function onAkeebaEngageGetAssetMeta(int $assetId = 0, bool $loadParameters = false): ?array
{
if (empty($assetId))
{
return null;
}
$row = $this->getArticleByAssetId($assetId, $loadParameters);
if (is_null($row))
{
return null;
}
// Get the link to the article
$container = Container::getInstance('com_engage');
$url = '';
$public_url = Route::link('site', sprintf("index.php?option=com_content&view=article&id=%s&catid=%s", $row->id, $row->catid), false, Route::TLS_IGNORE, true);
if ($container->platform->isFrontend())
{
$url = $public_url;
}
elseif ($container->platform->isBackend())
{
$url = 'index.php?option=com_content&task=article.edit&id=' . $row->id;
}
$publishUp = new Date();
$db = $this->db;
if (!empty($row->publish_up) && ($row->publish_up != $db->getNullDate()))
{
$publishUp = new Date($row->publish_up);
}
return [
'type' => 'article',
'title' => $row->title,
'category' => $row->category_title,
'author_name' => $row->author_name,
'author_email' => $row->author_email,
'url' => $url,
'public_url' => $public_url,
'published' => $this->isRowPublished($row),
'published_on' => $publishUp,
'access' => $row->access ?? 0,
'parent_access' => $row->category_access,
'parameters' => $row->parameters,
];
}
public function onAkeebaEngageGetAssetIDsByTitle(?string $filter): ?array
{
$filter = trim($filter ?? '');
if (empty($filter))
{
return [];
}
if (strpos($filter, '%') === false)
{
$filter = '%' . $filter . '%';
}
try
{
$db = $this->db;
$query = $db->getQuery(true)
->select([$db->qn('asset_id')])
->from($db->qn('#__content'))
->where($db->qn('title') . ' LIKE ' . $db->q($filter));
return $db->setQuery($query)->loadColumn();
}
catch (Exception $e)
{
return [];
}
}
/**
* Triggered when Akeeba Engage cleans the cache after modifying a comment in a way that affects comments display.
*
* @return void
*/
public function onEngageClearCache()
{
/**
* We need to clear the com_content cache.
*
* Sounds a bit too much? Well, this is how Joomla itself does it. For real.
*
* @see ContentModelArticle::cleanCache()
*/
CacheCleaner::clearCacheGroups([
'com_content',
], [0]);
}
/**
* Get the Akeeba Engage container, preloaded for comments display
*
* @return Container
*/
private function getContainer(): Container
{
if (!empty($this->container))
{
return $this->container;
}
$masterContainer = Container::getInstance('com_engage');
$appInput = new Input();
$defaultListLimit = $masterContainer->params->get('default_limit', 20);
$defaultListLimit = ($defaultListLimit == -1) ? 20 : $defaultListLimit;
// Get the container singleton instance
$this->container = Container::getInstance('com_engage', [
// We create a temporary instance to avoid messing with the master container's input
'tempInstance' => true,
// Passing these objects from the master container optimizes the number of database queries
'params' => $masterContainer->params,
'mediaVersion' => $masterContainer->mediaVersion,
// Custom input for the temporary container instance
'input' => [
'option' => 'com_engage',
'view' => 'Comments',
'task' => 'browse',
'asset_id' => 0,
'access' => 0,
'akengage_limitstart' => ($appInput)->getInt('akengage_limitstart', 0),
'akengage_limit' => ($appInput)->getInt('akengage_limit', $defaultListLimit),
'layout' => $this->isAMP() ? 'amp' : 'default',
'tpl' => null,
'tmpl' => null,
'format' => 'html',
],
]);
return $this->container;
}
/**
* WbAMP support. Is this an AMP page?
*
* @return bool
* @see https://weeblr.com/documentation/products.wbamp/1/going-further/api/index.html
*/
private function isAMP(): bool
{
if (!class_exists('\WbAMP'))
{
return false;
}
try
{
return WbAMP::isAMPRequest();
}
catch (Throwable $e)
{
return false;
}
}
/**
* Is this article published?
*
* This takes into account the publish_up and publish_down dates, not just the publish state.
*
* @param stdClass $row The article object returned by ContentModelArticle
*
* @return bool
*/
private function isRowPublished($row)
{
// The article is unpublished, the point is moot
if (($row->state ?? 0) <= 0)
{
return false;
}
$db = $this->db;
// Do we have a publish up date?
if (!empty($row->publish_up) && ($row->publish_up != $db->getNullDate()))
{
try
{
$publishUp = new Date($row->publish_up);
if ($publishUp->toUnix() > time())
{
return false;
}
}
catch (Exception $e)
{
}
}
// Do we have a publish down date?
if (!empty($row->publish_down) && ($row->publish_down != $db->getNullDate()))
{
try
{
$publishDown = new Date($row->publish_down);
if ($publishDown->toUnix() < time())
{
return false;
}
}
catch (Exception $e)
{
}
}
return true;
}
/**
* Returns article information based on the asset ID.
*
* It will try to use cached results to avoid expensive trips to the database.
*
* @param int $assetId The asset ID to use
* @param bool $loadParameters Should I load the comment parameters? (It's slow!)
*
* @return object|null Partial article information. NULL when there is no article associated with this asset ID.
*/
private function getArticleByAssetId(int $assetId, bool $loadParameters = false)
{
if (empty($assetId))
{
return null;
}
$metaKey = md5($assetId . '_' . ($loadParameters ? 'with' : 'without') . '_parameters');
$altMetaKey = md5($assetId . '_with_parameters');
if (isset($this->cachedArticles[$metaKey]))
{
return $this->cachedArticles[$metaKey];
}
if (isset($this->cachedArticles[$altMetaKey]))
{
return $this->cachedArticles[$altMetaKey];
}
$this->cachedArticles[$metaKey] = null;
$db = $this->db;
$query = $db->getQuery(true)
->select([
$db->qn('id'),
])
->from($db->qn('#__content'))
->where($db->qn('asset_id') . ' = ' . $db->q($assetId));
try
{
$articleId = $db->setQuery($query)->loadResult();
}
catch (Exception $e)
{
$articleId = null;
}
if (is_null($articleId))
{
return null;
}
/** @var ContentModelArticle $model */
try
{
if (version_compare(JVERSION, '3.999.999', 'le'))
{
if (!class_exists('ContentModelArticle'))
{
BaseDatabaseModel::addIncludePath(JPATH_BASE . '/components/com_content/models');
}
$model = BaseDatabaseModel::getInstance('Article', 'ContentModel');
}
else
{
/** @var MVCFactoryInterface $factory */
$factory = $this->app->bootComponent('com_content')->getMVCFactory();
/** @var ArticleModel $model */
$model = $factory->createModel('Article', 'Administrator');
}
$row = $model->getItem($articleId);
}
catch (Exception $e)
{
$row = null;
}
if (is_null($row) || ($row === false) || is_object($row) && ($row instanceof Throwable))
{
return null;
}
if (!is_object($row))
{
return null;
}
$this->cacheArticleRow($row, $loadParameters);
return $this->cachedArticles[$metaKey];
}
/**
* Get the keys for the per-category and per-article comment parameters.
*
* These are automatically retrieved from the forms/engage.xml file.
*
* @return array
*/
private function getParametersKeys(): array
{
if (!empty($this->parametersKeys))
{
return $this->parametersKeys;
}
$form = new Form('engage_form');
$formData = file_get_contents(__DIR__ . '/forms/engage.xml');
if (!$form->load($formData))
{
return $this->parametersKeys;
}
$fields = $form->getFieldset('engage');
$this->parametersKeys = array_map(function ($x) {
if (substr($x, 0, 7) == 'engage_')
{
$x = substr($x, 7);
}
return $x;
}, array_keys($fields));
return $this->parametersKeys;
}
/**
* Get the default values for the component parameters.
*
* This is required to set the value of inherited options when the corresponding component parameter does not have a
* concrete value (the user has not yet saved the component's configuration).
*
* @return array
*/
private function getParameterDefaults(): array
{
if (!empty($this->parameterDefaults))
{
return $this->parameterDefaults;
}
$form = new Form('engage_component');
$formData = file_get_contents(JPATH_ADMINISTRATOR . '/components/com_engage/config.xml');
$formData = str_replace('<config', '<form', $formData);
$formData = str_replace('</config', '</form', $formData);
if (!$form->load($formData))
{
return $this->parameterDefaults;
}
$fieldSets = $form->getFieldsets();
$this->parameterDefaults = [];
foreach (array_keys($fieldSets) as $fieldSet)
{
$fields = $form->getFieldset($fieldSet);
foreach ($fields as $name => $field)
{
$this->parameterDefaults[$name] = $field->value ?? null;
}
}
return $this->parameterDefaults;
}
/**
* Get the comment parameters for an article. This method uses caching whereas getParametersForArticle_Real doesn't.
*
* Inherited parameters will be retrieved from the category. If the category has inherited parameters they will
* retrieved from its parent category. If we exhaust parent categories we will retrieve the inherited parameters
* from the component configuration. If the component configuration values are not yet set (e.g. the user has not
* yet saved the component's Options page) we will use the default values defined in config.xml.
*
* @param object $row The article to get the parameters for
*
* @return Registry
*/
private function getParametersForArticle($row): Registry
{
if (!array_key_exists($row->id, $this->parametersCache))
{
$this->parametersCache[$row->id] = $this->getParametersForArticle_Real($row);
}
return $this->parametersCache[$row->id];
}
/**
* Get the comment parameters for an article.
*
* Inherited parameters will be retrieved from the category. If the category has inherited parameters they will
* retrieved from its parent category. If we exhaust parent categories we will retrieve the inherited parameters
* from the component configuration. If the component configuration values are not yet set (e.g. the user has not
* yet saved the component's Options page) we will use the default values defined in config.xml.
*
* @param object $row The article to get the parameters for
*
* @return Registry
*/
private function getParametersForArticle_Real($row): Registry
{
// Create a comment parameters array consisting of null values
$parametersKeys = $this->getParametersKeys();
$ret = array_combine($parametersKeys, array_fill(
0, count($parametersKeys), null
));
// Populate the comment parameters with those defined in the article
$articleParams = new Registry($row->attribs);
$hasInheritedParams = false;
foreach ($parametersKeys as $key)
{
$ret[$key] = $articleParams->get('engage.' . $key, $ret[$key]);
$hasInheritedParams = $hasInheritedParams || $this->isUseGlobal($ret[$key]);
}
// If there are no "Use Global" parameters return what we've got so far.
if (!$hasInheritedParams)
{
return new Registry();
}
// Go through the categories hierarchy, replacing inherited parameters
if (version_compare(JVERSION, '3.999.999', 'le'))
{
if (!class_exists('CategoriesModelCategory'))
{
BaseDatabaseModel::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_categories/models');
}
if (!class_exists('CategoriesTableCategory'))
{
Table::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_categories/tables');
}
}
$catId = $row->catid;
if (version_compare(JVERSION, '3.999.999', 'le'))
{
/** @var CategoriesModelCategory $model */
$model = BaseDatabaseModel::getInstance('Category', 'CategoriesModel');
}
else
{
/** @var MVCFactoryInterface $factory */
$factory = $this->app->bootComponent('com_categories')->getMVCFactory();
/** @var CategoryModel $model */
$model = $factory->createModel('Category', 'Administrator');
}
while (true)
{
$cat = $model->getItem($catId);
$params = new Registry($cat->params);
$hasInheritedParams = false;
foreach ($ret as $k => $v)
{
if (!$this->isUseGlobal($v))
{
continue;
}
$ret[$k] = $params->get('engage.' . $k, $ret[$k]);
$hasInheritedParams = $hasInheritedParams || $this->isUseGlobal($ret[$k]);
}
if (!$hasInheritedParams)
{
return new Registry($ret);
}
if (empty($cat->parent_id))
{
break;
}
$catId = $cat->parent_id;
}
// If I still have inherited parameters go through the component parameters
$defaults = new Registry($this->getParameterDefaults());
foreach ($ret as $k => $v)
{
if (!$this->isUseGlobal($v))
{
continue;
}
$ret[$k] = $this->getContainer()->params->get($k, $defaults->get($k));
}
return new Registry($ret);
}
/**
* Is the value of a settings field equivalent to "Use Global"?
*
* This happens if the value if null, an empty string or the integer value -1.
*
* @param mixed $value The value to check
*
* @return bool
*/
private function isUseGlobal($value): bool
{
if (is_null($value))
{
return true;
}
if (is_string($value) && (trim($value) === ''))
{
return true;
}
if (is_numeric($value))
{
if (((int) $value) === -1)
{
return true;
}
}
return false;
}
/**
* Automatically delete spam comments older than the configured age limit at most once every 8 horus
*
* @return void
*/
private function autoCleanSpam(): void
{
// Run once every 8 hours
$container = $this->getContainer();
$cParams = $container->params;
$lastRun = $cParams->get('spam_lastRun', 0);
$nextRun = $lastRun + (8 * 3600);
if ($nextRun > time())
{
return;
}
// I need to run. Save the current timestamp in the component parameters.
$cParams->set('spam_lastRun', time());
$cParams->save();
// Get the model and delete comments. No problem if we fail for any reason.
try
{
$maxDays = $cParams->get('max_spam_age', 15);
/** @var Comments $model */
$model = $container->factory->model('Comments')->tmpInstance();
$model->cleanSpam($maxDays, 1);
}
catch (Exception $e)
{
return;
}
}
/**
* @param $row
* @param bool $loadParameters
* @param bool $force
*
* @return void
*/
private function cacheArticleRow($row, bool $loadParameters, bool $force = false): void
{
$authorUser = self::getContainer()->platform->getUser($row->created_by);
$metaKey = md5($row->asset_id . '_' . ($loadParameters ? 'with' : 'without') . '_parameters');
if (array_key_exists($metaKey, $this->cachedArticles) && !empty($this->cachedArticles[$metaKey]) && !$force)
{
return;
}
$this->cachedArticles[$metaKey] = (object) [
'id' => $row->id,
'asset_id' => $row->asset_id,
'title' => $row->title,
'alias' => $row->alias,
'state' => $row->state,
'catid' => $row->catid,
'attribs' => $row->attribs,
'publish_up' => $row->publish_up,
'publish_down' => $row->publish_down,
'access' => $row->access,
'category_title' => $row->category_title ?? '',
'category_alias' => $row->category_alias ?? 0,
'category_access' => $row->category_access ?? $row->access,
'author_name' => !empty($row->created_by_alias) ? $row->created_by_alias : $authorUser->name,
'author_email' => $authorUser->email,
'parameters' => $loadParameters ? $this->getParametersForArticle($row) : new Registry(),
];
}
/**
* Get the asset ID given an article ID
*
* @param int|null $id
*
* @return int|null
*
* @since 1.0.0.b3
*/
private function getAssetIdByArticleId(?int $id): ?int
{
if (empty($id))
{
return null;
}
$db = $this->db;
$query = $db->getQuery(true)
->select($db->qn('asset_id'))
->from($db->qn('#__content'))
->where($db->qn('id') . ' = ' . $db->q($id));
$assetId = $db->setQuery($query)->loadResult();
return $assetId ? ((int) $assetId) : null;
}
/**
* @param $params
* @param $row
* @param string|null $context
*
* @return false|string
*/
private function renderComments($params, $row, ?string $context)
{
// We need to be enabled
if (!$this->enabled)
{
return '';
}
// We need to be given the right kind of data
if (!is_object($params) || !($params instanceof Registry) || !is_object($row))
{
return '';
}
// We need to be in the frontend of the site
$container = $this->getContainer();
if (!$container->platform->isFrontend())
{
return '';
}
// We need to have a supported context
if ($context !== 'com_content.article')
{
return '';
}
/**
* When Joomla is rendering an article in a Newsflash module it uses the same context as rendering an article
* through com_content (com_content.article). However, we do NOT want the newsflash articles to display comments!
*
* This is an ugly hack around this problem. It's based on the observation that the newsflash module is passing
* its own module options in the $params parameter to this event. As a result it has the `moduleclass_sfx` key
* defined, whereas this key does not exist when rendering an article through com_content.
*/
if (($params instanceof Registry) && $params->exists('moduleclass_sfx'))
{
return '';
}
if (empty($row->id ?? 0))
{
return '';
}
if (!property_exists($row, 'asset_id'))
{
$row->asset_id = $this->getAssetIdByArticleId($row->id);
}
if (empty($row->asset_id ?? 0))
{
return '';
}
/**
* This neat trick allows me to speed up meta queries on the article.
*
* When this plugin event is called Joomla has already loaded the article for us. I can use this object to
* populate the article meta cache so next time I query the article meta I don't have to go through Joomla's
* article model which saves me a very expensive query.
*/
$this->cacheArticleRow($row, true);
// Am I supposed to display comments?
$commentParams = $this->getParametersForArticle($row);
if ($commentParams->get('comments_show', 1) != 1)
{
return '';
}
/**
* Set a flag that we're allowed to show the comments browse page.
*
* Since this is a container flag it can only be set by backend code, not any request parameter. Since this is
* not set by default it means that the only way to access the comments is going through this plugin. In other
* words, someone trying to bypass the plugin and display all comments regardless would be really disappointed
* at the results of their plan to surreptitiously pull comments.
*/
$container['commentsBrowseEnablingFlag'] = true;
$input = $container->input;
$assetId = $row->asset_id;
$input->set('asset_id', $assetId);
$input->set('filter_order_Dir', $commentParams->get('comments_ordering'));
// Capture the output instead of pushing it to the browser
try
{
@ob_start();
$container->dispatcher->dispatch();
$comments = @ob_get_contents();
@ob_end_clean();
}
catch (Exception $e)
{
$comments = '';
}
return $comments;
}
/**
* Render the comments count
*
* @param Registry|mixed $params
* @param object|mixed $row
* @param string|null $context
* @param bool $before Am I asked to render this before the content?
*
* @return string
*/
private function renderCommentCount($params, $row, ?string $context, bool $before = true): string
{
// We need to be enabled
if (!$this->enabled)
{
return '';
}
// We need to be given the right kind of data
if (!is_object($params) || !($params instanceof Registry) || !is_object($row))
{
return '';
}
// We need to be in the frontend of the site
$container = $this->getContainer();
if (!$container->platform->isFrontend())
{
return '';
}
/**
* When Joomla is rendering an article in a Newsflash module it uses the same context as rendering an article
* through com_content (com_content.article). However, we do NOT want the newsflash articles to display comments!
*
* This is an ugly hack around this problem. It's based on the observation that the newsflash module is passing
* its own module options in the $params parameter to this event. As a result it has the `moduleclass_sfx` key
* defined, whereas this key does not exist when rendering an article through com_content.
*/
$isNewsFlash = ($context === 'com_content.article') && ($params instanceof Registry) && $params->exists('moduleclass_sfx');
// We need to have a supported context
if (!$isNewsFlash && !in_array($context, ['com_content.category', 'com_content.featured']))
{
return '';
}
// Make sure this is really an article
if (!property_exists($row, 'introtext'))
{
return '';
}
if (empty($row->id ?? 0))
{
return '';
}
/**
* Joomla does not make the asset_id available when displaying articles in the featured or blog view display
* modes. Unfortunately, we need to do one extra DB query for each article in this case.
*/
if (!property_exists($row, 'asset_id'))
{
$row->asset_id = $this->getAssetIdByArticleId($row->id);
}
if (empty($row->asset_id ?? 0))
{
return '';
}
/**
* This neat trick allows me to speed up meta queries on the article.
*
* When this plugin event is called Joomla has already loaded the article for us. I can use this object to
* populate the article meta cache so next time I query the article meta I don't have to go through Joomla's
* article model which saves me a very expensive query.
*/
$this->cacheArticleRow($row, true);
// Am I supposed to display comments at all?
$commentParams = $this->getParametersForArticle($row);
$showComments = $commentParams->get('comments_show', 1);
if ($showComments != 1)
{
return '';
}
/**
* Am I supposed to display the comments count?
*
* Uses the keys comments_show_feature, comments_show_category, comments_show_article
*/
$area = substr($context, 12);
$optionKey = sprintf("comments_show_%s", $area);
$showCommentsCount = $commentParams->get($optionKey, 0);
if ($showCommentsCount == 0)
{
return '';
}
if ($before && ($showCommentsCount != 1))
{
return '';
}
if (!$before && ($showCommentsCount != 2))
{
return '';
}
// Use a Layout file to display the appropriate summary
$basePath = __DIR__ . '/layouts';
$layoutFile = sprintf("akeeba.engage.content.%s", $area);
$displayData = [
'container' => $container,
'row' => $row,
'meta' => Meta::getAssetAccessMeta($row->asset_id),
];
return LayoutHelper::render($container, $layoutFile, $displayData, $basePath);
}
}