shell bypass 403
<?php
/**
* @package Joomla.Plugin
* @subpackage Actionlog.joomla
*
* @copyright (C) 2018 Open Source Matters, Inc. <https://www.joomla.org>
* @license GNU General Public License version 2 or later; see LICENSE.txt
*/
namespace Joomla\Plugin\Actionlog\Joomla\Extension;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Event\AbstractEvent;
use Joomla\CMS\Event\Application;
use Joomla\CMS\Event\Cache;
use Joomla\CMS\Event\Checkin;
use Joomla\CMS\Event\Extension;
use Joomla\CMS\Event\Model;
use Joomla\CMS\Event\User;
use Joomla\CMS\MVC\Factory\MVCFactoryServiceInterface;
use Joomla\CMS\User\UserFactoryAwareTrait;
use Joomla\Component\Actionlogs\Administrator\Helper\ActionlogsHelper;
use Joomla\Component\Actionlogs\Administrator\Plugin\ActionLogPlugin;
use Joomla\Database\DatabaseAwareTrait;
use Joomla\Database\Exception\ExecutionFailureException;
use Joomla\Event\Event;
use Joomla\Event\SubscriberInterface;
use Joomla\Utilities\ArrayHelper;
// phpcs:disable PSR1.Files.SideEffects
\defined('_JEXEC') or die;
// phpcs:enable PSR1.Files.SideEffects
/**
* Joomla! Users Actions Logging Plugin.
*
* @since 3.9.0
*/
final class Joomla extends ActionLogPlugin implements SubscriberInterface
{
use DatabaseAwareTrait;
use UserFactoryAwareTrait;
/**
* Array of loggable extensions.
*
* @var array
* @since 3.9.0
*/
protected $loggableExtensions = [];
/**
* Context aliases
*
* @var array
* @since 3.9.0
*/
protected $contextAliases = ['com_content.form' => 'com_content.article'];
/**
* Flag for loggable Api.
*
* @var boolean
* @since 4.0.0
*/
protected $loggableApi = false;
/**
* Array of loggable verbs.
*
* @var array
* @since 4.0.0
*/
protected $loggableVerbs = [];
/**
* Constructor.
*
* @param array $config An optional associative array of configuration settings
*
* @since 3.9.0
*/
public function __construct(array $config)
{
parent::__construct($config);
$params = ComponentHelper::getComponent('com_actionlogs')->getParams();
$this->loggableExtensions = $params->get('loggable_extensions', []);
$this->loggableApi = $params->get('loggable_api', 0);
$this->loggableVerbs = $params->get('loggable_verbs', []);
}
/**
* Returns an array of events this subscriber will listen to.
*
* @return array
*
* @since 5.2.0
*/
public static function getSubscribedEvents(): array
{
return [
'onContentAfterSave' => 'onContentAfterSave',
'onContentAfterDelete' => 'onContentAfterDelete',
'onContentChangeState' => 'onContentChangeState',
'onApplicationAfterSave' => 'onApplicationAfterSave',
'onExtensionAfterInstall' => 'onExtensionAfterInstall',
'onExtensionAfterUninstall' => 'onExtensionAfterUninstall',
'onExtensionAfterUpdate' => 'onExtensionAfterUpdate',
'onExtensionAfterSave' => 'onExtensionAfterSave',
'onExtensionAfterDelete' => 'onExtensionAfterDelete',
'onUserAfterSave' => 'onUserAfterSave',
'onUserAfterDelete' => 'onUserAfterDelete',
'onUserAfterSaveGroup' => 'onUserAfterSaveGroup',
'onUserAfterDeleteGroup' => 'onUserAfterDeleteGroup',
'onUserAfterLogin' => 'onUserAfterLogin',
'onUserLoginFailure' => 'onUserLoginFailure',
'onUserLogout' => 'onUserLogout',
'onUserAfterRemind' => 'onUserAfterRemind',
'onAfterCheckin' => 'onAfterCheckin',
'onAfterLogPurge' => 'onAfterLogPurge',
'onAfterLogExport' => 'onAfterLogExport',
'onAfterPurge' => 'onAfterPurge',
'onAfterDispatch' => 'onAfterDispatch',
'onJoomlaAfterUpdate' => 'onJoomlaAfterUpdate',
'onUserAfterResetRequest' => 'onUserAfterResetRequest',
'onUserAfterResetComplete' => 'onUserAfterResetComplete',
'onUserBeforeSave' => 'onUserBeforeSave',
'onBeforeTourSaveUserState' => 'onBeforeTourSaveUserState',
];
}
/**
* After save content logging method
* This method adds a record to #__action_logs contains (message, date, context, user)
* Method is called right after the content is saved
*
* @param Model\AfterSaveEvent $event The event instance.
*
* @return void
*
* @since 3.9.0
*/
public function onContentAfterSave(Model\AfterSaveEvent $event): void
{
$context = $event->getContext();
$article = $event->getItem();
$isNew = $event->getIsNew();
if (isset($this->contextAliases[$context])) {
$context = $this->contextAliases[$context];
}
$params = $this->getActionLogParams($context);
// Not found a valid content type, don't process further
if ($params === null) {
return;
}
[$option, $contentType] = explode('.', $params->type_alias);
if (!$this->checkLoggable($option)) {
return;
}
if ($isNew) {
$messageLanguageKey = $params->text_prefix . '_' . $params->type_title . '_ADDED';
$defaultLanguageKey = 'PLG_SYSTEM_ACTIONLOGS_CONTENT_ADDED';
} else {
$messageLanguageKey = $params->text_prefix . '_' . $params->type_title . '_UPDATED';
$defaultLanguageKey = 'PLG_SYSTEM_ACTIONLOGS_CONTENT_UPDATED';
}
// If the content type doesn't have its own language key, use default language key
if (!$this->getApplication()->getLanguage()->hasKey($messageLanguageKey)) {
$messageLanguageKey = $defaultLanguageKey;
}
$id = empty($params->id_holder) ? 0 : $article->{$params->id_holder};
$message = [
'action' => $isNew ? 'add' : 'update',
'type' => $params->text_prefix . '_TYPE_' . $params->type_title,
'id' => $id,
'title' => $article->{$params->title_holder} ?? '',
'itemlink' => ActionlogsHelper::getContentTypeLink($option, $contentType, $id, $params->id_holder, $article),
];
$this->addLog([$message], $messageLanguageKey, $context);
}
/**
* After delete content logging method
* This method adds a record to #__action_logs contains (message, date, context, user)
* Method is called right after the content is deleted
*
* @param Model\AfterDeleteEvent $event The event instance.
*
* @return void
*
* @since 3.9.0
*/
public function onContentAfterDelete(Model\AfterDeleteEvent $event): void
{
$context = $event->getContext();
$article = $event->getItem();
$option = $this->getApplication()->getInput()->get('option');
if (!$this->checkLoggable($option)) {
return;
}
$params = $this->getActionLogParams($context);
// Not found a valid content type, don't process further
if ($params === null) {
return;
}
// If the content type has its own language key, use it, otherwise, use default language key
if ($this->getApplication()->getLanguage()->hasKey(strtoupper($params->text_prefix . '_' . $params->type_title . '_DELETED'))) {
$messageLanguageKey = $params->text_prefix . '_' . $params->type_title . '_DELETED';
} else {
$messageLanguageKey = 'PLG_SYSTEM_ACTIONLOGS_CONTENT_DELETED';
}
$id = empty($params->id_holder) ? 0 : $article->{$params->id_holder};
$message = [
'action' => 'delete',
'type' => $params->text_prefix . '_TYPE_' . $params->type_title,
'id' => $id,
'title' => $article->{$params->title_holder} ?? '',
];
$this->addLog([$message], $messageLanguageKey, $context);
}
/**
* On content change status logging method
* This method adds a record to #__action_logs contains (message, date, context, user)
* Method is called when the status of the article is changed
*
* @param Model\AfterChangeStateEvent $event The event instance.
*
* @return void
*
* @since 3.9.0
*/
public function onContentChangeState(Model\AfterChangeStateEvent $event): void
{
$context = $event->getContext();
$pks = $event->getPks();
$value = $event->getValue();
$option = $this->getApplication()->getInput()->getCmd('option');
if (!$this->checkLoggable($option)) {
return;
}
$params = $this->getActionLogParams($context);
// Not found a valid content type, don't process further
if ($params === null) {
return;
}
[, $contentType] = explode('.', $params->type_alias);
switch ($value) {
case 0:
$messageLanguageKey = $params->text_prefix . '_' . $params->type_title . '_UNPUBLISHED';
$defaultLanguageKey = 'PLG_SYSTEM_ACTIONLOGS_CONTENT_UNPUBLISHED';
$action = 'unpublish';
break;
case 1:
$messageLanguageKey = $params->text_prefix . '_' . $params->type_title . '_PUBLISHED';
$defaultLanguageKey = 'PLG_SYSTEM_ACTIONLOGS_CONTENT_PUBLISHED';
$action = 'publish';
break;
case 2:
$messageLanguageKey = $params->text_prefix . '_' . $params->type_title . '_ARCHIVED';
$defaultLanguageKey = 'PLG_SYSTEM_ACTIONLOGS_CONTENT_ARCHIVED';
$action = 'archive';
break;
case -2:
$messageLanguageKey = $params->text_prefix . '_' . $params->type_title . '_TRASHED';
$defaultLanguageKey = 'PLG_SYSTEM_ACTIONLOGS_CONTENT_TRASHED';
$action = 'trash';
break;
default:
$messageLanguageKey = '';
$defaultLanguageKey = '';
$action = '';
break;
}
// If the content type doesn't have its own language key, use default language key
if (!$this->getApplication()->getLanguage()->hasKey($messageLanguageKey)) {
$messageLanguageKey = $defaultLanguageKey;
}
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select($db->quoteName([$params->title_holder, $params->id_holder]))
->from($db->quoteName($params->table_name))
->whereIn($db->quoteName($params->id_holder), ArrayHelper::toInteger($pks));
$db->setQuery($query);
try {
$items = $db->loadObjectList($params->id_holder);
} catch (\RuntimeException) {
$items = [];
}
$messages = [];
foreach ($pks as $pk) {
$message = [
'action' => $action,
'type' => $params->text_prefix . '_TYPE_' . $params->type_title,
'id' => $pk,
'title' => $items[$pk]->{$params->title_holder},
'itemlink' => ActionlogsHelper::getContentTypeLink($option, $contentType, $pk, $params->id_holder, null),
];
$messages[] = $message;
}
$this->addLog($messages, $messageLanguageKey, $context);
}
/**
* On Saving application configuration logging method.
* Method is called when the application config is being saved
*
* @param Application\AfterSaveConfigurationEvent $event The event instance.
*
* @return void
*
* @since 3.9.0
*/
public function onApplicationAfterSave(Application\AfterSaveConfigurationEvent $event): void
{
$option = $this->getApplication()->getInput()->getCmd('option');
if (!$this->checkLoggable($option)) {
return;
}
$messageLanguageKey = 'PLG_ACTIONLOG_JOOMLA_APPLICATION_CONFIG_UPDATED';
$action = 'update';
$message = [
'action' => $action,
'type' => 'PLG_ACTIONLOG_JOOMLA_TYPE_APPLICATION_CONFIG',
'extension_name' => 'com_config.application',
'itemlink' => 'index.php?option=com_config',
];
$this->addLog([$message], $messageLanguageKey, 'com_config.application');
}
/**
* On installing extensions logging method
* This method adds a record to #__action_logs contains (message, date, context, user)
* Method is called when an extension is installed
*
* @param Extension\AfterInstallEvent $event The event instance.
*
* @return void
*
* @since 3.9.0
*/
public function onExtensionAfterInstall(Extension\AfterInstallEvent $event): void
{
$installer = $event->getInstaller();
$eid = $event->getEid();
$context = $this->getApplication()->getInput()->get('option');
if (!$this->checkLoggable($context)) {
return;
}
$manifest = $installer->get('manifest');
if ($manifest === null) {
return;
}
$extensionType = $manifest->attributes()->type;
// If the extension type has its own language key, use it, otherwise, use default language key
if ($this->getApplication()->getLanguage()->hasKey(strtoupper('PLG_ACTIONLOG_JOOMLA_' . $extensionType . '_INSTALLED'))) {
$messageLanguageKey = 'PLG_ACTIONLOG_JOOMLA_' . $extensionType . '_INSTALLED';
} else {
$messageLanguageKey = 'PLG_ACTIONLOG_JOOMLA_EXTENSION_INSTALLED';
}
$message = [
'action' => 'install',
'type' => 'PLG_ACTIONLOG_JOOMLA_TYPE_' . $extensionType,
'id' => $eid,
'name' => (string) $manifest->name,
'extension_name' => (string) $manifest->name,
];
$this->addLog([$message], $messageLanguageKey, $context);
}
/**
* On uninstalling extensions logging method
* This method adds a record to #__action_logs contains (message, date, context, user)
* Method is called when an extension is uninstalled
*
* @param Extension\AfterUninstallEvent $event The event instance.
*
* @return void
*
* @since 3.9.0
*/
public function onExtensionAfterUninstall(Extension\AfterUninstallEvent $event): void
{
$installer = $event->getInstaller();
$eid = $event->getEid();
$result = $event->getRemoved();
$context = $this->getApplication()->getInput()->get('option');
if (!$this->checkLoggable($context)) {
return;
}
// If the process failed, we don't have manifest data, stop process to avoid fatal error
if ($result === false) {
return;
}
$manifest = $installer->get('manifest');
if ($manifest === null) {
return;
}
$extensionType = $manifest->attributes()->type;
// If the extension type has its own language key, use it, otherwise, use default language key
if ($this->getApplication()->getLanguage()->hasKey(strtoupper('PLG_ACTIONLOG_JOOMLA_' . $extensionType . '_UNINSTALLED'))) {
$messageLanguageKey = 'PLG_ACTIONLOG_JOOMLA_' . $extensionType . '_UNINSTALLED';
} else {
$messageLanguageKey = 'PLG_ACTIONLOG_JOOMLA_EXTENSION_UNINSTALLED';
}
$message = [
'action' => 'install',
'type' => 'PLG_ACTIONLOG_JOOMLA_TYPE_' . $extensionType,
'id' => $eid,
'name' => (string) $manifest->name,
'extension_name' => (string) $manifest->name,
];
$this->addLog([$message], $messageLanguageKey, $context);
}
/**
* On updating extensions logging method
* This method adds a record to #__action_logs contains (message, date, context, user)
* Method is called when an extension is updated
*
* @param Extension\AfterUpdateEvent $event The event instance.
*
* @return void
*
* @since 3.9.0
*/
public function onExtensionAfterUpdate(Extension\AfterUpdateEvent $event): void
{
$installer = $event->getInstaller();
$eid = $event->getEid();
$context = $this->getApplication()->getInput()->get('option');
if (!$this->checkLoggable($context)) {
return;
}
$manifest = $installer->get('manifest');
if ($manifest === null) {
return;
}
$extensionType = $manifest->attributes()->type;
// If the extension type has its own language key, use it, otherwise, use default language key
if ($this->getApplication()->getLanguage()->hasKey('PLG_ACTIONLOG_JOOMLA_' . $extensionType . '_UPDATED')) {
$messageLanguageKey = 'PLG_ACTIONLOG_JOOMLA_' . $extensionType . '_UPDATED';
} else {
$messageLanguageKey = 'PLG_ACTIONLOG_JOOMLA_EXTENSION_UPDATED';
}
$message = [
'action' => 'update',
'type' => 'PLG_ACTIONLOG_JOOMLA_TYPE_' . $extensionType,
'id' => $eid,
'name' => (string) $manifest->name,
'extension_name' => (string) $manifest->name,
];
$this->addLog([$message], $messageLanguageKey, $context);
}
/**
* On Saving extensions logging method.
* Method is called when an extension is being saved
*
* @param Model\AfterSaveEvent $event The event instance.
*
* @return void
*
* @since 3.9.0
*/
public function onExtensionAfterSave(Model\AfterSaveEvent $event): void
{
$context = $event->getContext();
$table = $event->getItem();
$isNew = $event->getIsNew();
[$option] = explode('.', $context);
if (!$this->checkLoggable($option)) {
return;
}
$params = $this->getActionLogParams($context);
// Not found a valid content type, don't process further
if ($params === null) {
return;
}
[, $contentType] = explode('.', $params->type_alias);
if ($isNew) {
$messageLanguageKey = $params->text_prefix . '_' . $params->type_title . '_ADDED';
$defaultLanguageKey = 'PLG_SYSTEM_ACTIONLOGS_CONTENT_ADDED';
} else {
$messageLanguageKey = $params->text_prefix . '_' . $params->type_title . '_UPDATED';
$defaultLanguageKey = 'PLG_SYSTEM_ACTIONLOGS_CONTENT_UPDATED';
}
// If the extension type doesn't have it own language key, use default language key
if (!$this->getApplication()->getLanguage()->hasKey($messageLanguageKey)) {
$messageLanguageKey = $defaultLanguageKey;
}
$id_holder = $params->id_holder;
$title_holder = $params->title_holder;
$message = [
'action' => $isNew ? 'add' : 'update',
'type' => 'PLG_ACTIONLOG_JOOMLA_TYPE_' . $params->type_title,
'id' => $table->$id_holder,
'title' => $table->$title_holder,
'extension_name' => $table->$title_holder,
'itemlink' => ActionlogsHelper::getContentTypeLink($option, $contentType, $table->$id_holder, $id_holder),
];
$this->addLog([$message], $messageLanguageKey, $context);
}
/**
* On Deleting extensions logging method.
* Method is called when an extension is being deleted
*
* @param Model\AfterDeleteEvent $event The event instance.
*
* @return void
*
* @since 3.9.0
*/
public function onExtensionAfterDelete(Model\AfterDeleteEvent $event): void
{
$context = $event->getContext();
$table = $event->getItem();
if (!$this->checkLoggable($this->getApplication()->getInput()->get('option'))) {
return;
}
$params = $this->getActionLogParams($context);
// Not found a valid content type, don't process further
if ($params === null) {
return;
}
$messageLanguageKey = 'PLG_SYSTEM_ACTIONLOGS_CONTENT_DELETED';
$title_holder = $params->title_holder;
$message = [
'action' => 'delete',
'type' => 'PLG_ACTIONLOG_JOOMLA_TYPE_' . $params->type_title,
'title' => $table->$title_holder,
];
$this->addLog([$message], $messageLanguageKey, $context);
}
/**
* On saving user data logging method
*
* Method is called after user data is stored in the database.
* This method logs who created/edited any user's data
*
* @param User\AfterSaveEvent $event The event instance.
*
* @return void
*
* @since 3.9.0
*/
public function onUserAfterSave(User\AfterSaveEvent $event): void
{
$user = $event->getUser();
$isnew = $event->getIsNew();
$context = $this->getApplication()->getInput()->get('option');
$task = $this->getApplication()->getInput()->get('task');
if (!$this->checkLoggable($context)) {
return;
}
if ($task === 'request') {
return;
}
if ($task === 'complete') {
return;
}
$jUser = $this->getApplication()->getIdentity();
if (!$jUser->id) {
$messageLanguageKey = 'PLG_ACTIONLOG_JOOMLA_USER_REGISTERED';
$action = 'register';
// Registration Activation
if ($task === 'activate') {
$messageLanguageKey = 'PLG_ACTIONLOG_JOOMLA_USER_REGISTRATION_ACTIVATE';
$action = 'activaterequest';
}
} elseif ($isnew) {
$messageLanguageKey = 'PLG_SYSTEM_ACTIONLOGS_CONTENT_ADDED';
$action = 'add';
} else {
$messageLanguageKey = 'PLG_SYSTEM_ACTIONLOGS_CONTENT_UPDATED';
$action = 'update';
}
$userId = $jUser->id ?: $user['id'];
$username = $jUser->username ?: $user['username'];
$message = [
'action' => $action,
'type' => 'PLG_ACTIONLOG_JOOMLA_TYPE_USER',
'id' => $user['id'],
'title' => $user['name'],
'itemlink' => 'index.php?option=com_users&task=user.edit&id=' . $user['id'],
'userid' => $userId,
'username' => $username,
'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $userId,
];
// Check if block / unblock comes from Actions on list
if ($task === 'block' || $task === 'unblock') {
$messageLanguageKey = $task === 'block' ? 'PLG_ACTIONLOG_JOOMLA_USER_BLOCK' : 'PLG_ACTIONLOG_JOOMLA_USER_UNBLOCK';
$message['action'] = $task;
}
$this->addLog([$message], $messageLanguageKey, $context, $userId);
// Check if on save a block / unblock has changed
if ($action === 'update') {
$session = $this->getApplication()->getSession();
$data = $session->get('block', null);
if ($data !== null) {
$messageLanguageKey = 'PLG_ACTIONLOG_JOOMLA_USER_UNBLOCK';
$action = 'unblock';
if ($data === 'block') {
$messageLanguageKey = 'PLG_ACTIONLOG_JOOMLA_USER_BLOCK';
$action = 'block';
}
$message['action'] = $action;
$this->addLog([$message], $messageLanguageKey, $context, $userId);
}
}
}
/**
* On deleting user data logging method
*
* Method is called after user data is deleted from the database
*
* @param User\AfterDeleteEvent $event The event instance.
*
* @return void
*
* @since 3.9.0
*/
public function onUserAfterDelete(User\AfterDeleteEvent $event): void
{
$user = $event->getUser();
$context = $this->getApplication()->getInput()->get('option');
if (!$this->checkLoggable($context)) {
return;
}
$messageLanguageKey = 'PLG_SYSTEM_ACTIONLOGS_CONTENT_DELETED';
$message = [
'action' => 'delete',
'type' => 'PLG_ACTIONLOG_JOOMLA_TYPE_USER',
'id' => $user['id'],
'title' => $user['name'],
];
$this->addLog([$message], $messageLanguageKey, $context);
}
/**
* On after save user group data logging method
*
* Method is called after user group is stored into the database
*
* @param Model\AfterSaveEvent $event The event instance.
*
* @return void
*
* @since 3.9.0
*/
public function onUserAfterSaveGroup(Model\AfterSaveEvent $event): void
{
$table = $event->getItem();
$isNew = $event->getIsNew();
// Override context (com_users.group) with the component context (com_users) to pass the checkLoggable
$context = $this->getApplication()->getInput()->get('option');
if (!$this->checkLoggable($context)) {
return;
}
if ($isNew) {
$messageLanguageKey = 'PLG_SYSTEM_ACTIONLOGS_CONTENT_ADDED';
$action = 'add';
} else {
$messageLanguageKey = 'PLG_SYSTEM_ACTIONLOGS_CONTENT_UPDATED';
$action = 'update';
}
$message = [
'action' => $action,
'type' => 'PLG_ACTIONLOG_JOOMLA_TYPE_USER_GROUP',
'id' => $table->id,
'title' => $table->title,
'itemlink' => 'index.php?option=com_users&task=group.edit&id=' . $table->id,
];
$this->addLog([$message], $messageLanguageKey, $context);
}
/**
* On deleting user group data logging method
*
* Method is called after user group is deleted from the database
*
* @param Model\AfterDeleteEvent $event The event instance.
*
* @return void
*
* @since 3.9.0
*/
public function onUserAfterDeleteGroup(Model\AfterDeleteEvent $event): void
{
$group = $event->getItem();
$context = $this->getApplication()->getInput()->get('option');
if (!$this->checkLoggable($context)) {
return;
}
$messageLanguageKey = 'PLG_SYSTEM_ACTIONLOGS_CONTENT_DELETED';
$message = [
'action' => 'delete',
'type' => 'PLG_ACTIONLOG_JOOMLA_TYPE_USER_GROUP',
'id' => $group->id,
'title' => $group->title,
];
$this->addLog([$message], $messageLanguageKey, $context);
}
/**
* Method to log user login success action
*
* @param User\AfterLoginEvent $event The event instance.
*
* @return void
*
* @since 3.9.0
*/
public function onUserAfterLogin(User\AfterLoginEvent $event): void
{
$options = $event->getOptions();
if ($options['action'] === 'core.login.api') {
return;
}
$context = 'com_users';
if (!$this->checkLoggable($context)) {
return;
}
$loggedInUser = $options['user'];
$messageLanguageKey = 'PLG_ACTIONLOG_JOOMLA_USER_LOGGED_IN';
$message = [
'action' => 'login',
'userid' => $loggedInUser->id,
'username' => $loggedInUser->username,
'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $loggedInUser->id,
'app' => 'PLG_ACTIONLOG_JOOMLA_APPLICATION_' . $this->getApplication()->getName(),
];
$this->addLog([$message], $messageLanguageKey, $context, $loggedInUser->id);
}
/**
* Method to log user login failed action
*
* @param User\LoginFailureEvent $event The event instance.
*
* @return void
*
* @since 3.9.0
*/
public function onUserLoginFailure(User\LoginFailureEvent $event): void
{
$response = $event->getAuthenticationResponse();
$context = 'com_users';
if (!$this->checkLoggable($context)) {
return;
}
// Get the user id for the given username
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select($db->quoteName(['id', 'username']))
->from($db->quoteName('#__users'))
->where($db->quoteName('username') . ' = :username')
->bind(':username', $response['username']);
$db->setQuery($query);
try {
$loggedInUser = $db->loadObject();
} catch (ExecutionFailureException) {
return;
}
// Not a valid user, return
if (!isset($loggedInUser->id)) {
return;
}
$messageLanguageKey = 'PLG_ACTIONLOG_JOOMLA_USER_LOGIN_FAILED';
$message = [
'action' => 'login',
'id' => $loggedInUser->id,
'userid' => $loggedInUser->id,
'username' => $loggedInUser->username,
'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $loggedInUser->id,
'app' => 'PLG_ACTIONLOG_JOOMLA_APPLICATION_' . $this->getApplication()->getName(),
];
$this->addLog([$message], $messageLanguageKey, $context, $loggedInUser->id);
}
/**
* Method to log user's logout action
*
* @param User\LogoutEvent $event The event instance.
*
* @return void
*
* @since 3.9.0
*/
public function onUserLogout(User\LogoutEvent $event): void
{
$user = $event->getParameters();
$context = 'com_users';
if (!$this->checkLoggable($context)) {
return;
}
$loggedOutUser = $this->getUserFactory()->loadUserById($user['id']);
if ($loggedOutUser->block) {
return;
}
$messageLanguageKey = 'PLG_ACTIONLOG_JOOMLA_USER_LOGGED_OUT';
$message = [
'action' => 'logout',
'id' => $loggedOutUser->id,
'userid' => $loggedOutUser->id,
'username' => $loggedOutUser->username,
'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $loggedOutUser->id,
'app' => 'PLG_ACTIONLOG_JOOMLA_APPLICATION_' . $this->getApplication()->getName(),
];
$this->addLog([$message], $messageLanguageKey, $context);
}
/**
* Function to check if a component is loggable or not
*
* @param string $extension The extension that triggered the event
*
* @return boolean
*
* @since 3.9.0
*/
protected function checkLoggable($extension)
{
return \in_array($extension, $this->loggableExtensions);
}
/**
* On after Remind username request
*
* Method is called after user request to remind their username.
*
* @param User\AfterRemindEvent $event The event instance.
*
* @return void
*
* @since 3.9.0
*/
public function onUserAfterRemind(User\AfterRemindEvent $event): void
{
$user = $event->getUser();
$context = $this->getApplication()->getInput()->get('option');
if (!$this->checkLoggable($context)) {
return;
}
$message = [
'action' => 'remind',
'type' => 'PLG_ACTIONLOG_JOOMLA_TYPE_USER',
'id' => $user->id,
'title' => $user->name,
'itemlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
'userid' => $user->id,
'username' => $user->name,
'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
];
$this->addLog([$message], 'PLG_ACTIONLOG_JOOMLA_USER_REMIND', $context, $user->id);
}
/**
* On after Check-in request
*
* Method is called after user request to check-in items.
*
* @param Checkin\AfterCheckinEvent $event The event instance.
*
* @return void
*
* @since 3.9.3
*/
public function onAfterCheckin(Checkin\AfterCheckinEvent $event): void
{
$table = $event->getTableName();
$context = 'com_checkin';
$user = $this->getApplication()->getIdentity();
if (!$this->checkLoggable($context)) {
return;
}
$message = [
'action' => 'checkin',
'type' => 'PLG_ACTIONLOG_JOOMLA_TYPE_USER',
'id' => $user->id,
'title' => $user->username,
'itemlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
'userid' => $user->id,
'username' => $user->username,
'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
'table' => str_replace($this->getDatabase()->getPrefix(), '#__', $table),
];
$this->addLog([$message], 'PLG_ACTIONLOG_JOOMLA_USER_CHECKIN', $context, $user->id);
}
/**
* On after log action purge
*
* Method is called after user request to clean action log items.
*
* @return void
*
* @since 3.9.4
*/
public function onAfterLogPurge(): void
{
$context = $this->getApplication()->getInput()->get('option');
$user = $this->getApplication()->getIdentity();
$message = [
'action' => 'actionlogs',
'type' => 'PLG_ACTIONLOG_JOOMLA_TYPE_USER',
'id' => $user->id,
'title' => $user->username,
'itemlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
'userid' => $user->id,
'username' => $user->username,
'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
];
$this->addLog([$message], 'PLG_ACTIONLOG_JOOMLA_USER_LOG', $context, $user->id);
}
/**
* On after log export
*
* Method is called after user request to export action log items.
*
* @return void
*
* @since 3.9.4
*/
public function onAfterLogExport(): void
{
$context = $this->getApplication()->getInput()->get('option');
$user = $this->getApplication()->getIdentity();
$message = [
'action' => 'actionlogs',
'type' => 'PLG_ACTIONLOG_JOOMLA_TYPE_USER',
'id' => $user->id,
'title' => $user->username,
'itemlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
'userid' => $user->id,
'username' => $user->username,
'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
];
$this->addLog([$message], 'PLG_ACTIONLOG_JOOMLA_USER_LOGEXPORT', $context, $user->id);
}
/**
* On after Cache purge
*
* Method is called after user request to clean cached items.
*
* @param Cache\AfterPurgeEvent $event The event instance.
*
* @return void
*
* @since 3.9.4
*/
public function onAfterPurge(Cache\AfterPurgeEvent $event): void
{
$group = $event->getGroup() ?: 'all';
$context = $this->getApplication()->getInput()->get('option');
$user = $this->getApplication()->getIdentity();
if (!$this->checkLoggable($context)) {
return;
}
$message = [
'action' => 'cache',
'type' => 'PLG_ACTIONLOG_JOOMLA_TYPE_USER',
'id' => $user->id,
'title' => $user->username,
'itemlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
'userid' => $user->id,
'username' => $user->username,
'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
'group' => $group,
];
$this->addLog([$message], 'PLG_ACTIONLOG_JOOMLA_USER_CACHE', $context, $user->id);
}
/**
* On after Api dispatched
*
* Method is called after user perform an API request better on onAfterDispatch.
*
* @return void
*
* @since 4.0.0
*/
public function onAfterDispatch(): void
{
if (!$this->getApplication()->isClient('api')) {
return;
}
if ($this->loggableApi === 0) {
return;
}
$verb = $this->getApplication()->getInput()->getMethod();
if (!\in_array($verb, $this->loggableVerbs)) {
return;
}
$context = $this->getApplication()->getInput()->get('option');
if (!$this->checkLoggable($context)) {
return;
}
$user = $this->getApplication()->getIdentity();
$message = [
'action' => 'API',
'verb' => $verb,
'username' => $user->username,
'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
'url' => htmlspecialchars(urldecode($this->getApplication()->get('uri.route')), ENT_QUOTES, 'UTF-8'),
];
$this->addLog([$message], 'PLG_ACTIONLOG_JOOMLA_API', $context, $user->id);
}
/**
* On after CMS Update
*
* Method is called after user update the CMS.
*
* @param Event $event The event instance.
*
* @return void
*
* @since 3.9.21
*
* @TODO: Update to use a real event class
*/
public function onJoomlaAfterUpdate(Event $event): void
{
$arguments = array_values($event->getArguments());
$oldVersion = $arguments[0] ?? '';
$context = $this->getApplication()->getInput()->get('option');
$user = $this->getApplication()->getIdentity();
if (empty($oldVersion)) {
$oldVersion = $this->getApplication()->getLanguage()->_('JLIB_UNKNOWN');
}
$message = [
'action' => 'joomlaupdate',
'type' => 'PLG_ACTIONLOG_JOOMLA_TYPE_USER',
'id' => $user->id,
'title' => $user->username,
'itemlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
'userid' => $user->id,
'username' => $user->username,
'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
'version' => JVERSION,
'oldversion' => $oldVersion,
];
$messageKey = ($user->id) ? 'PLG_ACTIONLOG_JOOMLA_USER_UPDATE' : 'PLG_ACTIONLOG_JOOMLA_SYSTEM_UPDATE';
$this->addLog([$message], $messageKey, $context, $user->id);
}
/**
* Returns the action log params for the given context.
*
* @param string $context The context of the action log
*
* @return ?\stdClass The params
*
* @since 4.2.0
*/
private function getActionLogParams($context): ?\stdClass
{
$component = $this->getApplication()->bootComponent('actionlogs');
if (!$component instanceof MVCFactoryServiceInterface) {
return null;
}
return $component->getMVCFactory()->createModel('ActionlogConfig', 'Administrator')->getLogContentTypeParams($context);
}
/**
* On after Reset password request
*
* Method is called after user request to reset their password.
*
* @param User\AfterResetRequestEvent $event The event instance.
*
* @return void
*
* @since 4.2.9
*/
public function onUserAfterResetRequest(User\AfterResetRequestEvent $event): void
{
$user = $event->getUser();
$context = $this->getApplication()->getInput()->get('option');
if (!$this->checkLoggable($context)) {
return;
}
$message = [
'action' => 'reset',
'type' => 'PLG_ACTIONLOG_JOOMLA_TYPE_USER',
'id' => $user->id,
'title' => $user->name,
'itemlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
'userid' => $user->id,
'username' => $user->name,
'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
];
$this->addLog([$message], 'PLG_ACTIONLOG_JOOMLA_USER_RESET_REQUEST', $context, $user->id);
}
/**
* On after Completed reset request
*
* Method is called after user complete the reset of their password.
*
* @param User\AfterResetCompleteEvent $event The event instance.
*
* @return void
*
* @since 4.2.9
*/
public function onUserAfterResetComplete(User\AfterResetCompleteEvent $event)
{
$user = $event->getUser();
$context = $this->getApplication()->getInput()->get('option');
if (!$this->checkLoggable($context)) {
return;
}
$message = [
'action' => 'complete',
'type' => 'PLG_ACTIONLOG_JOOMLA_TYPE_USER',
'id' => $user->id,
'title' => $user->name,
'itemlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
'userid' => $user->id,
'username' => $user->name,
'accountlink' => 'index.php?option=com_users&task=user.edit&id=' . $user->id,
];
$this->addLog([$message], 'PLG_ACTIONLOG_JOOMLA_USER_RESET_COMPLETE', $context, $user->id);
}
/**
* Method is called before user data is stored in the database
*
* @param User\BeforeSaveEvent $event The event instance.
*
* @return void
*
* @since 5.0.0
*/
public function onUserBeforeSave(User\BeforeSaveEvent $event): void
{
$user = $event->getUser();
$new = $event->getData();
$session = $this->getApplication()->getSession();
$session->set('block', null);
if ($user['block'] !== (int) $new['block']) {
$blockunblock = $new['block'] === '1' ? 'block' : 'unblock';
$session->set('block', $blockunblock);
}
}
/**
* Method is called when a user cancels, completes or skips a tour
*
* @param AbstractEvent $event The event instance.
*
* @return void
*
* @since 5.2.0
*/
public function onBeforeTourSaveUserState(AbstractEvent $event): void
{
$option = $this->getApplication()->getInput()->get('option');
if (!$this->checkLoggable($option)) {
return;
}
$tourId = $event->getArgument('tourId');
$state = $event->getArgument('actionState');
$stepNumber = $event->getArgument('stepNumber');
switch ($state) {
case 'skipped':
$messageLanguageKey = 'PLG_ACTIONLOG_JOOMLA_GUIDEDTOURS_TOURSKIPPED';
break;
case 'completed':
$messageLanguageKey = 'PLG_ACTIONLOG_JOOMLA_GUIDEDTOURS_TOURCOMPLETED';
break;
default:
$messageLanguageKey = 'PLG_ACTIONLOG_JOOMLA_GUIDEDTOURS_TOURDELAYED';
}
// Get the tour from the model to fetch the translated title of the tour
$factory = $this->getApplication()->bootComponent('com_guidedtours')->getMVCFactory();
$tourModel = $factory->createModel(
'Tour',
'Administrator',
['ignore_request' => true]
);
$tour = $tourModel->getItem($tourId);
$message = [
'id' => $tourId,
'title' => $tour->title_translation,
'state' => $state,
'step' => $stepNumber,
];
$this->addLog([$message], $messageLanguageKey, 'com_guidedtours.state');
}
}