J3.x

J3.x:Make your own Action Log Plugin

From Joomla! Documentation

Other languages:

Joomla! 3.9 and later allows extensions to log their actions in the User Action Log. This is done by creating a plugin in the actionlog folder.

Please note that if your plugin is not in the actionlog folder you will get untranslated strings in the User Action Log component and / or module when actions of your extension are being displayed.

As a developer, you should be aware that all published actionlog plugins are loaded by Joomla's Action Log system plugin on every page load. The implication to that is that the constructor of your actionlog plugin should be kept to a bare minimum and not do any initialization which could have a performance impact. If you need to perform any kind of heavy initialization try to defer it for the last possibly moment, i.e. when you are about to log an action.

The actionlog plugin is meant to "catch" your extension's actions and log them. The "catching" part depends entirely on your extension. The concept is that each interesting action that needs to be logged must trigger a plugin event which is handled by your actionlog plugin and has the effect of logging said action to the database.

Joomla's MVC only triggers events on content preparation. If you want to see how you can handle those events for your component check the Joomla plugin in the actionlog folder.

For everything else it's advisable to use your own, custom plugin event names. Take extra care to not cause any naming conflicts. For example, a plugin event called onAfterInitialize is BAD because it clashes with Joomla's built-in event by the same name. DO NOT use generic plugin names such as onAfterSave because you are guaranteed to cause a conflict with another extension or Joomla itself. Remember that plugin event names are shared across all plugins in all plugin folders!

The best way to prevent naming clashes is using naming conventions like the following:

  • For components, onComComponentControllerViewnameAfterorbeforeTask e.g. onComExampleControllerItemsAfterBrowse for option=com_example&view=items&task=browse
  • For plugins, onPlgFolderPluginnameAction e.g. onPlgSystemFoobarBaz to log the "baz" action taken by a plugin named foobar in the system folder.

Modules, templates, and libraries are unlikely candidates for actions you might want to log but you can use a similar naming convention:

  • For modules, onModSiteoradminModulenameAction e.g. onModSiteExampleFoo for a frontend (site) module mod_example taking action foo
  • For libraries, onLibLibrarynameAction e.g. onLibExampleFoo for the lib_example library taking action foo
  • For templates, onTplSiteoradminTemplatenameAction e.g. on TplSiteExampleFoo for a frontend template named example taking action foo

If you have a Model used in many of your component's controllers and you would rather log an action taken there you can use the naming convention onComComponentModelModelnameAfterorbeforeMethodname e.g. onComExampleModelItemsBeforeSave to log something that happens after calling the ExampleModelItems::save() method. Again, this convention is arbitrary and the only reason we recommend it is that you can have a reasonable expectation that it won't cause naming clashes. The more specific the event name is the less likely it is to have a conflict with core or third party code.

Logging the actions requires going through a bit of code to perform sanity checks (e.g. correct Joomla version) and do the actual logging. Here is a prototype function you can use in your plugin:

	public function logUserAction($title, $logText, $extension)
	{
		static $joomlaModelAdded = false;

		// User Actions Log is available only under Joomla 3.9+
		if (version_compare(JVERSION, '3.9', 'lt'))
		{
			return;
		}

		// Include required Joomla Model
		if (!$joomlaModelAdded)
		{
			\JModelLegacy::addIncludePath(JPATH_ROOT . '/administrator/components/com_actionlogs/models', 'ActionlogsModel');
			$joomlaModelAdded = true;
		}

		$user = $this->getUser();

		// No log for guest users
		if ($user->guest)
		{
			return;
		}

		$message = array(
			'title'    	  => $title,
			'username' 	  => $user->username,
			'accountlink'     => 'index.php?option=com_users&task=user.edit&id=' . $user->id
		);

		/** @var \ActionlogsModelActionlog $model **/
		try
		{
			$model = \JModelLegacy::getInstance('Actionlog', 'ActionlogsModel');
			$model->addLog(array($message), $logText, $extension, $user->id);
		}
		catch (\Exception $e)
		{
			// Ignore any error
		}
	}

Here is an example plugin method performing user action logging:

/**
	 * Logs the creation of a new backup profile
	 *
	 * @param \Akeeba\Backup\Admin\Controller\Profiles	$controller
	 * @param array										$data
	 * @param int										$id
	 */
	public function onComAkeebaControllerProfilesAfterApplySave($controller, $data, $id)
	{
		// If I have an ID in the request and it's the same of the model, I'm just editing a record
		if (isset($data['id']) && $data['id'] == $id)
		{
			return;
		}

		$profile_title = $data['description'];

		$this->logUserAction($profile_title, 'COM_AKEEBA_LOGS_PROFILE_ADD', 'com_akeeba');
	}

Note that the log title (the second argument to the method) is a translation key. It is defined similar to that:

COM_AKEEBA_LOGS_PROFILE_ADD="User <a href=\"{accountlink}\">{username}</a> created the backup profile {title}"

The {title} variable is the $title parameter in our method. The other two variables are defined and processed by the User Action Logs module and component when rendering the user actions log.

Caveat: Since Joomla 4 has not yet integrated the Action Logs component and plugins the code above will not work on Joomla! 4. If / when Action Logs are ported to Joomla 4 we'll have to revisit this documentation page since neither JModelLegacy is available, nor will the Model name be ActionlogsModelActionlog.