<?php /** * @package Joomla.Administrator * @subpackage com_actionlogs * * @copyright Copyright (C) 2005 - 2019 Open Source Matters, Inc. All rights reserved. * @license GNU General Public License version 2 or later; see LICENSE.txt */ defined('_JEXEC') or die; use Joomla\CMS\Date\Date; use Joomla\CMS\Factory; use Joomla\CMS\Filesystem\Path; use Joomla\CMS\Language\Text; use Joomla\CMS\Router\Route; use Joomla\String\StringHelper; /** * Actionlogs component helper. * * @since 3.9.0 */ class ActionlogsHelper { /** * Array of characters starting a formula * * @var array * @since 3.9.7 */ private static $characters = array('=', '+', '-', '@'); /** * Method to convert logs objects array to an iterable type for use with a CSV export * * @param array|Traversable $data The logs data objects to be exported * * @return array|Generator For PHP 5.5 and newer, a Generator is returned; PHP 5.4 and earlier use an array * * @since 3.9.0 * @throws InvalidArgumentException */ public static function getCsvData($data) { if (!is_iterable($data)) { throw new InvalidArgumentException( sprintf( '%s() requires an array or object implementing the Traversable interface, a %s was given.', __METHOD__, gettype($data) === 'object' ? get_class($data) : gettype($data) ) ); } if (version_compare(PHP_VERSION, '5.5', '>=')) { // Only include the PHP 5.5 helper in this conditional to prevent the potential of parse errors for PHP 5.4 or earlier JLoader::register('ActionlogsHelperPhp55', __DIR__ . '/actionlogsphp55.php'); return ActionlogsHelperPhp55::getCsvAsGenerator($data); } $disabledText = Text::_('COM_ACTIONLOGS_DISABLED'); $rows = array(); // Header row $rows[] = array('Id', 'Message', 'Date', 'Extension', 'User', 'Ip'); foreach ($data as $log) { $date = new Date($log->log_date, new DateTimeZone('UTC')); $extension = strtok($log->extension, '.'); static::loadTranslationFiles($extension); $rows[] = array( 'id' => $log->id, 'message' => self::escapeCsvFormula(strip_tags(static::getHumanReadableLogMessage($log, false))), 'date' => $date->format('Y-m-d H:i:s T'), 'extension' => self::escapeCsvFormula(Text::_($extension)), 'name' => self::escapeCsvFormula($log->name), 'ip_address' => self::escapeCsvFormula($log->ip_address === 'COM_ACTIONLOGS_DISABLED' ? $disabledText : $log->ip_address) ); } return $rows; } /** * Load the translation files for an extension * * @param string $extension Extension name * * @return void * * @since 3.9.0 */ public static function loadTranslationFiles($extension) { static $cache = array(); $extension = strtolower($extension); if (isset($cache[$extension])) { return; } $lang = Factory::getLanguage(); $source = ''; switch (substr($extension, 0, 3)) { case 'com': default: $source = JPATH_ADMINISTRATOR . '/components/' . $extension; break; case 'lib': $source = JPATH_LIBRARIES . '/' . substr($extension, 4); break; case 'mod': $source = JPATH_SITE . '/modules/' . $extension; break; case 'plg': $parts = explode('_', $extension, 3); if (count($parts) > 2) { $source = JPATH_PLUGINS . '/' . $parts[1] . '/' . $parts[2]; } break; case 'pkg': $source = JPATH_SITE; break; case 'tpl': $source = JPATH_BASE . '/templates/' . substr($extension, 4); break; } $lang->load($extension, JPATH_ADMINISTRATOR, null, false, true) || $lang->load($extension, $source, null, false, true); if (!$lang->hasKey(strtoupper($extension))) { $lang->load($extension . '.sys', JPATH_ADMINISTRATOR, null, false, true) || $lang->load($extension . '.sys', $source, null, false, true); } $cache[$extension] = true; } /** * Get parameters to be * * @param string $context The context of the content * * @return mixed An object contains content type parameters, or null if not found * * @since 3.9.0 */ public static function getLogContentTypeParams($context) { $db = Factory::getDbo(); $query = $db->getQuery(true) ->select('a.*') ->from($db->quoteName('#__action_log_config', 'a')) ->where($db->quoteName('a.type_alias') . ' = ' . $db->quote($context)); $db->setQuery($query); return $db->loadObject(); } /** * Get human readable log message for a User Action Log * * @param stdClass $log A User Action log message record * @param boolean $generateLinks Flag to disable link generation when creating a message * * @return string * * @since 3.9.0 */ public static function getHumanReadableLogMessage($log, $generateLinks = true) { static $links = array(); $message = Text::_($log->message_language_key); $messageData = json_decode($log->message, true); // Special handling for translation extension name if (isset($messageData['extension_name'])) { static::loadTranslationFiles($messageData['extension_name']); $messageData['extension_name'] = Text::_($messageData['extension_name']); } // Translating application if (isset($messageData['app'])) { $messageData['app'] = Text::_($messageData['app']); } // Translating type if (isset($messageData['type'])) { $messageData['type'] = Text::_($messageData['type']); } $linkMode = Factory::getApplication()->get('force_ssl', 0) >= 1 ? Route::TLS_FORCE : Route::TLS_IGNORE; foreach ($messageData as $key => $value) { // Convert relative url to absolute url so that it is clickable in action logs notification email if ($generateLinks && StringHelper::strpos($value, 'index.php?') === 0) { if (!isset($links[$value])) { $links[$value] = Route::link('administrator', $value, false, $linkMode); } $value = $links[$value]; } $message = str_replace('{' . $key . '}', $value, $message); } return $message; } /** * Get link to an item of given content type * * @param string $component * @param string $contentType * @param integer $id * @param string $urlVar * * @return string Link to the content item * * @since 3.9.0 */ public static function getContentTypeLink($component, $contentType, $id, $urlVar = 'id') { // Try to find the component helper. $eName = str_replace('com_', '', $component); $file = Path::clean(JPATH_ADMINISTRATOR . '/components/' . $component . '/helpers/' . $eName . '.php'); if (file_exists($file)) { $prefix = ucfirst(str_replace('com_', '', $component)); $cName = $prefix . 'Helper'; JLoader::register($cName, $file); if (class_exists($cName) && is_callable(array($cName, 'getContentTypeLink'))) { return $cName::getContentTypeLink($contentType, $id); } } if (empty($urlVar)) { $urlVar = 'id'; } // Return default link to avoid having to implement getContentTypeLink in most of our components return 'index.php?option=' . $component . '&task=' . $contentType . '.edit&' . $urlVar . '=' . $id; } /** * Load both enabled and disabled actionlog plugins language file. * * It is used to make sure actions log is displayed properly instead of only language items displayed when a plugin is disabled. * * @return void * * @since 3.9.0 */ public static function loadActionLogPluginsLanguage() { $lang = Factory::getLanguage(); $db = Factory::getDbo(); // Get all (both enabled and disabled) actionlog plugins $query = $db->getQuery(true) ->select( $db->quoteName( array( 'folder', 'element', 'params', 'extension_id' ), array( 'type', 'name', 'params', 'id' ) ) ) ->from('#__extensions') ->where('type = ' . $db->quote('plugin')) ->where('folder = ' . $db->quote('actionlog')) ->where('state IN (0,1)') ->order('ordering'); $db->setQuery($query); try { $rows = $db->loadObjectList(); } catch (RuntimeException $e) { $rows = array(); } if (empty($rows)) { return; } foreach ($rows as $row) { $name = $row->name; $type = $row->type; $extension = 'Plg_' . $type . '_' . $name; $extension = strtolower($extension); // If language already loaded, don't load it again. if ($lang->getPaths($extension)) { continue; } $lang->load($extension, JPATH_ADMINISTRATOR, null, false, true) || $lang->load($extension, JPATH_PLUGINS . '/' . $type . '/' . $name, null, false, true); } // Load com_privacy too. $lang->load('com_privacy', JPATH_ADMINISTRATOR, null, false, true); } /** * Escapes potential characters that start a formula in a CSV value to prevent injection attacks * * @param mixed $value csv field value * * @return mixed * * @since 3.9.7 */ protected static function escapeCsvFormula($value) { if ($value == '') { return $value; } if (in_array($value[0], self::$characters, true)) { $value = ' ' . $value; } return $value; } }