| 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/components/com_engage/Controller/ |
Upload File : |
<?php
/**
* @package AkeebaEngage
* @copyright Copyright (c)2020-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Akeeba\Engage\Site\Controller;
defined('_JEXEC') or die();
use Akeeba\Engage\Admin\Model\Exception\BlatantSpam;
use Akeeba\Engage\Site\Helper\Filter;
use Akeeba\Engage\Site\Helper\Meta;
use Akeeba\Engage\Site\Helper\SignedURL;
use Akeeba\Engage\Site\Model\Comments as CommentsModel;
use Akeeba\Engage\Site\View\Comments\Html;
use Exception;
use FOF40\Container\Container;
use FOF40\Controller\DataController;
use FOF40\Controller\Mixin\PredefinedTaskList;
use FOF40\Model\DataModel\Exception\RecordNotLoaded;
use FOF40\JoomlaAbstraction\CacheCleaner;
use FOF40\View\Exception\AccessForbidden;
use Joomla\CMS\Application\CMSApplication;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Uri\Uri;
use RuntimeException;
class Comments extends DataController
{
use PredefinedTaskList;
/** @inheritDoc */
public function __construct(Container $container, array $config = [])
{
$config['taskPrivileges'] = [
'submit' => 'core.create',
'reportspam' => '@remove',
'reportham' => '@publish',
'possiblespam' => '@publish',
'unsubscribe' => true,
];
parent::__construct($container, $config);
$this->setPredefinedTaskList([
'browse', 'submit', 'edit', 'save', 'publish', 'unpublish', 'remove', 'reportspam', 'reportham',
'possiblespam', 'unsubscribe',
]);
$this->cacheParams = [
'option' => 'CMD',
'view' => 'CMD',
'task' => 'CMD',
'format' => 'CMD',
'layout' => 'CMD',
'asset_id' => 'INT',
'akengage_limitstart' => 'INT',
'akengage_limit' => 'INT',
];
}
/**
* DEBUG: Trigger email sending
*
* Don't worry, this will NOT work on your sites. This code is only accessible when I add 'debug' to the
* setPredefinedTaskList array in the __construct method.
*/
public function debug()
{
$this->disableJoomlaCache();
$comment = $this->getModel();
$comment->load($this->input->getInt('comment_id'));
$this->container->platform->importPlugin('engage');
$this->container->platform->importPlugin('content');
$this->container->platform->runPlugins('onComEngageModelCommentsAfterCreate', [$comment]);
echo "OK";
$this->container->platform->closeApplication();
}
/**
* Submit a new comment
*
* @throws BlatantSpam If the comment was reported to be blatant spam
* @throws Exception
*/
public function submit(): void
{
$this->disableJoomlaCache();
// CSRF prevention
$this->csrfProtection();
$assetId = $this->getAssetId();
$postInput = $this->input->post;
$parentId = $postInput->getInt('parent_id', 0);
$name = $postInput->getString('name', null);
$email = $postInput->getString('email', null);
$comment = $postInput->get('comment', null, 'raw');
$acceptedTos = $postInput->getBool('accept_tos', false);
$returnUrl = $this->getReturnUrl();
$platform = $this->container->platform;
$user = $platform->getUser();
// If the comments are disabled for this asset we will return a Not Authorized error
if (Meta::areCommentsClosed($assetId))
{
throw new AccessForbidden();
}
// Store the comment parameters in the session
$sessionNamespace = $this->container->componentName . '.' . $this->name;
$platform->setSessionVar('name', $name, $sessionNamespace);
$platform->setSessionVar('email', $email, $sessionNamespace);
$platform->setSessionVar('comment', $comment, $sessionNamespace);
// Make sure we have either a user or a name and email
if ($user->guest && (empty($name) || empty($email)))
{
$this->setRedirect($returnUrl, Text::_('COM_ENGAGE_COMMENTS_ERR_NAME_AND_EMAIL_REQUIRED'), 'error');
$this->redirect();
return;
}
// Make sure we have a comment
if (empty($comment))
{
$this->setRedirect($returnUrl, Text::_('COM_ENGAGE_COMMENTS_ERR_COMMENT_REQUIRED'), 'error');
$this->redirect();
return;
}
/** @var CommentsModel $model */
$model = $this->getModel();
// If it's a reply to another comment let's make sure it exists and for the correct asset ID
if ($parentId !== 0)
{
// A non-zero parent ID was provided. Try to load the comment.
$parent = $model->tmpInstance()->getClone();
if (!$parent->load($parentId))
{
throw new AccessForbidden();
}
// Make sure the parent belongs to the same asset ID we're trying to comment on.
if ($parent->asset_id != $assetId)
{
throw new AccessForbidden();
}
}
// Check if the user had to give explicit consent but didn't provide it
if ($user->guest && $this->container->params->get('tos_accept') && !$acceptedTos)
{
$this->setRedirect($returnUrl, Text::_('COM_ENGAGE_COMMENTS_ERR_TOSACCEPT'), 'error');
$this->redirect();
return;
}
// Set up the new comment
$model->reset()->bind([
'asset_id' => $assetId,
'name' => $name,
'email' => $email,
'body' => Filter::filterText($comment),
'enabled' => 1,
'created_by' => null,
'parent_id' => ($parentId == 0) ? null : $parentId,
]);
// Non-admin users may have their comments auto-unpublished by default
if (!$user->authorise('core.manage', 'com_engage'))
{
$model->enabled = $this->container->params->get('default_publish', 1);
}
// If it's a guest user we need to unset the name and email
if (!$user->guest)
{
$model->name = null;
$model->email = null;
$model->created_by = $user->id;
}
// Try to save the comment, checking for CAPTCHA when necessary
try
{
// Populates the IP address and User Agent, required for the spam check
$model->useCaptcha(false);
// This needs to be in the try-catch block in case a guest is using an existing user's email address.
$model->check();
// Spam check
$platform->importPlugin('engage');
$spamResults = $platform->runPlugins('onAkeebaEngageCheckSpam', [$model]);
if (in_array(true, $spamResults, true))
{
$model->enabled = -3;
}
$model->useCaptcha(true);
$model->setState('captcha', $this->input->get('captcha', '', 'raw'));
$model->save();
$model->useCaptcha(false);
$result = $this->triggerEvent('onAfterSubmit', [$model]);
}
catch (Exception $e)
{
$this->setRedirect($returnUrl, $e->getMessage(), 'error');
$this->redirect();
return;
}
$this->cleanCache();
// Clear the session data and redirect back to the asset being commented on.
$platform->unsetSessionVar('name', $sessionNamespace);
$platform->unsetSessionVar('email', $sessionNamespace);
$platform->unsetSessionVar('comment', $sessionNamespace);
// If the user was unsubscribed from comments we need to resubscribe them
$db = $this->container->db;
$query = $db->getQuery(true)
->delete($db->qn('#__engage_unsubscribe'))
->where($db->qn('asset_id') . ' = ' . $db->q($model->asset_id))
->where($db->qn('email') . ' = ' . $db->q($model->getUser()->email));
try
{
$db->setQuery($query)->execute();
}
catch (Exception $e)
{
// Ignore any failures, they are not important.
}
$this->setRedirect($returnUrl, Text::_('COM_ENGAGE_COMMENTS_MSG_SUCCESS'));
}
/**
* Report a message as spam and delete it
*
* It is up to the plugins to make a sensible report of spam to a remote service.
*
* @throws Exception
*/
public function reportspam(): void
{
$this->reportMessage(true);
}
/**
* Report a message as ham (non-spam mistakenly recognized as such) and pubish it
*
* It is up to the plugins to make a sensible report of ham to a remote service.
*
* @throws Exception
*/
public function reportham(): void
{
$this->reportMessage(false);
$this->addCommentFragmentToReturnURL();
}
/**
* Mark a message as possible spam (unpublish with state -3)
*
* @throws Exception
*/
public function possiblespam(): void
{
$this->disableJoomlaCache();
// CSRF prevention
$this->csrfProtection();
$model = $this->getModel()->savestate(false);
$ids = $this->getIDsFromRequest($model, false);
$error = false;
try
{
$status = true;
foreach ($ids as $id)
{
$model->find($id);
$userId = $this->container->platform->getUser()->id;
if ($model->isLocked($userId))
{
$model->checkIn($userId);
}
$model->publish(-3);
}
}
catch (Exception $e)
{
$status = false;
$error = $e->getMessage();
}
$this->cleanCache();
// Redirect
if ($customURL = $this->input->getBase64('returnurl', ''))
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php';
if (!$status)
{
$this->setRedirect($url, $error, 'error');
}
else
{
$this->setRedirect($url);
}
$this->addCommentFragmentToReturnURL();
}
/**
* Unsubscribes a user from notifications regarding a specific content item's comments.
*
* @return void
*/
public function unsubscribe()
{
$this->disableJoomlaCache();
// I need at least a comment ID and an email to unsubscribe
$id = $this->input->getInt('id', 0);
$unsubscribeEmail = $this->input->getString('email', '');
if (($id <= 0) || empty($unsubscribeEmail))
{
throw new AccessForbidden();
}
// Try to load the comment
/** @var CommentsModel $comment */
$comment = $this->getModel()->tmpInstance();
try
{
$comment->findOrFail($id);
}
catch (Exception $e)
{
throw new AccessForbidden();
}
// Validate the token
$this->input->set('asset_id', $comment->asset_id);
$this->csrfProtection();
// Try to unsubscribe -- if already unsubscribed redirect back with an error
$o = (object) [
'asset_id' => $comment->asset_id,
'email' => $unsubscribeEmail,
];
$db = $this->container->db;
try
{
if (!$db->insertObject('#__engage_unsubscribe', $o))
{
throw new RuntimeException('Already unsubscribed');
}
$this->container->platform->runPlugins('onEngageUnsubscribeEmail', [$comment, $unsubscribeEmail]);
$message = Text::sprintf('COM_ENGAGE_COMMENTS_LBL_UNSUBSCRIBED', $unsubscribeEmail);
$msgType = 'info';
}
catch (Exception $e)
{
$message = Text::_('COM_ENGAGE_COMMENTS_ERR_ALREADY_UNSUBSCRIBED');
$msgType = 'error';
}
// Redirect
if ($customURL = $this->input->getBase64('returnurl', ''))
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php';
$this->setRedirect($url, $message, $msgType);
}
/**
* Ensures that we are allowed to display a list of comments.
*
* @return void
*
* @throws AccessForbidden
*/
protected function onBeforeBrowse(): void
{
/**
* If the current user has the core.edit.own privilege we need to cache per user ID instead of per user group.
* The idea is that each user in the group that gives the core.edit.own privilege will see an edit button on
* DIFFERENT comments than the other.
*
* In all other cases we need to cache by user group. All users with the same user group combination will be
* seeing the exact same comments display at all times.
*/
$this->userCaching = $this->container->platform->getUser()->authorise('core.edit.own', 'com_engage') ? 2 : 1;
// Make sure we are allowed to show this page (the content plugin explicitly told us to render it).
if (!isset($this->container['commentsBrowseEnablingFlag']) || !$this->container['commentsBrowseEnablingFlag'])
{
throw new AccessForbidden();
}
// Get the asset_id and assert we have access to it
$assetId = $this->getAssetId();
// Pass the data to the view
/** @var Html $view */
$view = $this->getView();
$sessionNamespace = $this->container->componentName . '.' . $this->name;
$platform = $this->container->platform;
$view->assetId = $assetId;
$view->storedName = $platform->getSessionVar('name', '', $sessionNamespace);
$view->storedEmail = $platform->getSessionVar('email', '', $sessionNamespace);
$view->storedComment = $platform->getSessionVar('comment', '', $sessionNamespace);
}
/**
* Asserts that the user has view access to a published asset. Throws a RuntimeException otherwise.
*
* @param int $assetId
*
* @return void
*
* @throws AccessForbidden
*/
protected function assertAssetAccess(?int $assetId): void
{
// Get the asset access metadata
$assetMeta = Meta::getAssetAccessMeta($assetId);
// Make sure the associated asset is published
if (!$assetMeta['published'])
{
throw new AccessForbidden();
}
// Make sure the user is allowed to view this asset and its parent
$access = $assetMeta['access'];
$parentAccess = $assetMeta['parent_access'];
$platform = $this->container->platform;
$user = $platform->getUser();
if (!is_null($access) && !in_array($access, $user->getAuthorisedViewLevels()))
{
throw new AccessForbidden();
}
if (!is_null($parentAccess) && !in_array($parentAccess, $user->getAuthorisedViewLevels()))
{
throw new AccessForbidden();
}
}
/**
* Runs after publishing a comment. Adjusts the redirection with the published comment's ID in the fragment.
*/
protected function onAfterPublish()
{
$this->disableJoomlaCache();
$this->addCommentFragmentToReturnURL();
$this->cleanCache();
}
/**
* Runs after unpublishing a comment. Adjusts the redirection with the unpublished comment's ID in the fragment.
*/
protected function onAfterUnpublish()
{
$this->disableJoomlaCache();
$this->addCommentFragmentToReturnURL();
$this->cleanCache();
}
/**
* Runs after deleting a comment.
*/
protected function onAfterRemove()
{
$this->disableJoomlaCache();
$this->cleanCache();
}
protected function onBeforeEdit()
{
$this->disableJoomlaCache();
$view = $this->getView();
$view->returnURL = '';
$redirectURL = $this->input->getBase64('returnurl');
$redirectURL = @base64_decode($redirectURL);
if (empty($redirectURL))
{
return;
}
$view->returnURL = $redirectURL;
}
protected function onAfterApplySave(&$data, $id)
{
$this->cleanCache();
}
/** @inheritDoc */
protected function csrfProtection(): bool
{
// First, let's try token validation
try
{
// If I don't have a token fall through to FOF's anti-CSRF protection
$token = $this->input->getString('token');
if (empty($token))
{
throw new RuntimeException('', 0xDEAD);
}
// Do I have a comment ID? Otherwise fall through to FOF's anti-CSRF protection.
/** @var CommentsModel $model */
$model = $this->getModel()->tmpInstance();
$ids = $this->getIDsFromRequest($model);
if (empty($ids))
{
throw new RuntimeException('', 0xDEAD);
}
$id = array_shift($ids);
if (empty($id))
{
throw new RuntimeException('', 0xDEAD);
}
// Load the comment or fall through to FOF's anti-CSRF protection.
$model->findOrFail($id);
// If the token is valid we can return true
$task = $this->input->getCmd('task');
$email = $this->input->getString('email');
$asset_id = $model->asset_id;
$expires = $this->input->getInt('expires');
if (SignedURL::verifyToken($token, $task, $email, $asset_id, $expires))
{
return true;
}
}
catch (RecordNotLoaded $e)
{
// This is raised if the comment ID is invalid. Ignore and fall through to the regular CSRF protection.
}
catch (RuntimeException $e)
{
// If it's not a "fall-through" exception we need to throw it back.
if ($e->getCode() != 0xDEAD)
{
throw $e;
}
}
return parent::csrfProtection();
}
/**
* Get the asset ID from the request and verify it is real
*
* @return int
*
* @throws AccessForbidden
*/
private function getAssetId(): int
{
// Get the asset ID from the request
$assetId = $this->input->getInt('asset_id', 0);
// Make sure the asset ID is non-zero
if (empty($assetId) || ($assetId <= 0))
{
throw new AccessForbidden();
}
// Make sure the asset exists, is published and we have view access to it
$this->assertAssetAccess($assetId);
// Return the asset ID
return $assetId;
}
/**
* Get the decoded return URL
*
* @return string
*/
private function getReturnUrl(): string
{
$returnUrl = $this->input->post->getBase64('returnurl', null);
if (!empty($returnUrl))
{
$returnUrl = @base64_decode($returnUrl);
if ($returnUrl === false)
{
$returnUrl = null;
}
}
$returnUrl = $returnUrl ?? 'index.php';
if (!Uri::isInternal($returnUrl))
{
$returnUrl = 'index.php';
}
return $returnUrl;
}
/**
* Adds the comment's ID to the fragment of the redirection.
*
* This only happens if there is no fragment yet and the ID of the item being edited / published / whatever is not
* zero.
*
* This method directly modifies $this->redirect.
*
* @return void
*/
private function addCommentFragmentToReturnURL(): void
{
if (empty($this->redirect))
{
return;
}
$uri = new Uri($this->redirect);
if (!empty($uri->getFragment()))
{
return;
}
$id = $this->input->getInt('id', 0);
if ($id <= 0)
{
return;
}
$uri->setFragment('akengage-comment-' . $id);
$this->redirect = $uri->toString();
}
/**
* Report a message as ham or spam. The actual reporting is taken care of by the plugins.
*
* @param bool $asSpam True to report as spam, false to report as ham.
*
* @return void
* @throws Exception
*/
private function reportMessage(bool $asSpam = true): void
{
$this->disableJoomlaCache();
$this->csrfProtection();
$model = $this->getModel()->savestate(false);
$ids = $this->getIDsFromRequest($model, false);
$error = null;
$platform = $this->container->platform;
$platform->importPlugin('engage');
try
{
foreach ($ids as $id)
{
$event = $asSpam ? 'onAkeebaEngageReportSpam' : 'onAkeebaEngageReportHam';
$model->find($id);
$platform->runPlugins($event, [$model]);
// If reporting ham also publish the comment
if (!$asSpam)
{
$model->publish();
}
// If reporting as positively spam also delete the comment
else
{
$model->delete();
}
}
}
catch (Exception $e)
{
$error = $e->getMessage();
}
// Clean cached comments display
$this->cleanCache();
// Redirect
if ($customURL = $this->input->getBase64('returnurl', ''))
{
$customURL = base64_decode($customURL);
}
$url = !empty($customURL) ? $customURL : 'index.php';
if (!empty($error))
{
$this->setRedirect($url, $error, 'error');
}
else
{
$message = $asSpam ? 'COM_ENGAGE_COMMENTS_REPORTED_SPAM' : 'COM_ENGAGE_COMMENTS_REPORTED_HAM';
$this->setRedirect($url, Text::_($message));
}
}
/**
* Disables the Joomla cache for this response.
*
* @return void
* @see \Joomla\CMS\MVC\Controller\FormController::edit()
*/
private function disableJoomlaCache(): void
{
try
{
/** @var CMSApplication $app */
$app = Factory::getApplication();
}
catch (Exception $e)
{
return;
}
if (!method_exists($app, 'allowCache'))
{
return;
}
$app->allowCache(false);
}
/**
* Clear the Joomla cache for Akeeba Engage
*
* @return void
*/
private function cleanCache(): void
{
CacheCleaner::clearCacheGroups([
'com_engage',
], [0], 'onEngageClearCache');
}
protected function getACLForApplySave()
{
$model = $this->getModel();
if (!$model->getId())
{
$this->getIDsFromRequest($model, true);
}
$id = $model->getId();
if (!$id)
{
return '@add';
}
if ($this->checkACL('@edit'))
{
return true;
}
$user = $this->container->platform->getUser();
$uid = $model->getFieldValue('created_by', 0);
if (!empty($uid) && !$user->guest && ($user->id == $uid))
{
return '@editown';
}
return false;
}
}