<?php
/**
 * This file contains the 'systemlog' addon which shows php errors, notices, warnings generated from debugging code.
 *
 * @package Interspire_Addons
 * @subpackage Addons_systemlog
 */

/**
 * Make sure the base Interspire_Addons class is defined.
 */
if (!class_exists('Interspire_Addons', false)) {
	require_once(dirname(dirname(__FILE__)) . '/interspire_addons.php');
}

/**
 * This class handles listing, deleting and processing system log entries.
 * It also puts itself in the tools menu for system-admins to use.
 *
 * @uses Interspire_Addons
 * @uses Interspire_Addons_Exception
 */
class Addons_systemlog extends Interspire_Addons
{
	/**
	 * Set a default for the maximum number of log entries to keep.
	 * This is used when the addon is first installed so we have a default setting.
	 * Of course it can be changed through the admin control panel to be whatever you like.
	 *
	 * @var Array
	 */
	protected $default_settings = ['logsize' => '1000'];

	/**
	 * Admin_Action_Default
	 * This is the method that is run when an admin user views the addon.
	 *
	 * If there are no log entries to worry about, then it will show the 'Addon_systemlog_logs_empty' file from the templates/ folder
	 * and then quickly return out of the function.
	 *
	 * If there are log entries to worry about, it will work out the paging for the top of the list
	 * then show buttons to allow you to delete selected entries or delete all log entries at once
	 *
	 * If you want to view a particular log entry, click the '+' next to the relevant log summary and it will expand to show more detail
	 *
	 * @uses db
	 * @uses template_system
	 * @uses InterspireTemplate::GetTemplatePath()
	 * @uses InterspireTemplate::SetTemplatePath()
	 * @uses InterspireTemplate::ParseTemplate()
	 * @uses InterspireTemplate::Assign()
	 * @uses GetFlashMessages
	 * @uses perpage
	 * @uses show_max_pages
	 * @uses admin_url
	 * @uses GetId
	 *
	 * @return Void Instead of returning anything, the list of log entries (if any) are shown directly.
	 */
	public function Admin_Action_Default()
	{
		$flash_messages = GetFlashMessages();

		$this->template_system->Assign('FlashMessages', $flash_messages, false);

		$number_of_logs = $this->db->FetchOne('SELECT count(logid) AS count FROM ' . $this->db->TablePrefix . 'log_system_system');
		if ($number_of_logs == 0) {
			$curr_template_dir = $this->template_system->GetTemplatePath();

			$this->template_system->SetTemplatePath(SENDSTUDIO_TEMPLATE_DIRECTORY);

			$GLOBALS['Error'] = GetLang('Addon_systemlog_Logs_Empty');
			$error = $this->template_system->ParseTemplate('errormsg', true, '');

			$this->template_system->SetTemplatePath($curr_template_dir);

			$this->template_system->Assign('Addon_systemlog_Logs_Empty', $error , false);
			$this->template_system->ParseTemplate('logs_empty', false, '');
			return;
		}

		$paging = $this->SetupPaging($this->admin_url, $number_of_logs);
		$this->template_system->Assign('Paging', $paging, false);

		$perpage = $this->GetPerPage();

		// paging always starts at '1' - so take one off so we get the right offset.
		$page_number = $this->GetCurrentPage() - 1;

		$offset = $page_number * $perpage;

		$qry = 'SELECT logid, logseverity, logtype, logmodule, logsummary, logdate FROM ' . $this->db->TablePrefix . 'log_system_system ORDER BY logid DESC ' . $this->db->AddLimit($offset, $perpage);
		$result = $this->db->Query($qry);

		$this->template_system->Assign('AddonId', $this->GetId());

		$log_entries = [];
		while ($row = $this->db->Fetch($result)) {
			$row['rowid'] = 'addon_' . $this->GetId() . '_' . $row['logid'];

			if ($row['logmodule'] === '') {
				$row['logmodule'] = 'Internal';
			}

			if (strlen($row['logsummary']) > 170) {
				$row['logsummary'] = substr($row['logsummary'], 0, 166) . ' ...';
			}
			$row['logsummary'] = htmlspecialchars($row['logsummary']);

			$row['logdate'] = gmdate('M d Y H:i:s', $row['logdate']);

			$image = 'error.gif';
			$severity = 'Error';
			switch ($row['logseverity']) {
				case 1:
					$severity = 'Success';
					$image = 'success.gif';
				break;

				case 2:
					$severity = 'Notice';
					$image = 'notice.gif';
				break;

				case 3:
					$severity = 'Warning';
					$image = 'warning.gif';
				break;
			}
			$row['image'] = $image;
			$row['severity'] = $severity;

			$log_entries[] = $row;
		}

		$this->template_system->Assign('AdminUrl', $this->admin_url, false);
		$this->template_system->Assign('TemplateUrl', $this->template_url, false);

		$this->template_system->Assign('logsList', $log_entries, false);

		$this->template_system->ParseTemplate('logs_display', false, '');
	}

	/**
	 * Install
	 * This is called when the addon is installed in the main application.
	 * In this case, it simply sets the default settings and then calls the parent install method to add itself to the database.
	 *
	 * @return True Returns true if all goes ok with the install.
	 * @throws Exception
	 * @throws Throws an Interspire_Addons_Exception if something goes wrong with the install process.*@uses default_settings
	 * @uses Interspire_Addons::Install
	 * @uses Interspire_Addons_Exception
	 *
	 */
	public function Install()
	{
		$this->enabled = true;
		$this->configured = true;
		$this->settings = $this->default_settings;

		try {
			parent::Install();
		} catch (Interspire_Addons_Exception $e) {
			throw new Exception("Unable to install addon {$this->GetId()}" . $e->getMessage());
		}
		return true;
	}

	/**
	 * GetEventListeners
	 * This returns an array of events that the addon listens to.
	 *
	 * This addon uses
	 * - 'IEM_SENDSTUDIOFUNCTIONS_GENERATETEXTMENULINKS' event to put itself into the 'tools' menu at the top
	 *
	 * @see Interspire_Addons::GetEventListeners
	 *
	 * @return Array Returns an array of events, what methods to call and which file to call the method from.
	 */
	public function GetEventListeners()
	{
		$trigger_file = '{%IEM_ADDONS_PATH%}/systemlog/systemlog.php';
		$listeners = [];

		$listeners[] =
			[
				'eventname' => 'IEM_SENDSTUDIOFUNCTIONS_GENERATETEXTMENULINKS',
				'trigger_details' => [
					'Addons_systemlog',
					'GetTextMenuItems',
				],
				'trigger_file' => $trigger_file
			];

		$listeners[] =
			[
				'eventname' => 'IEM_SYSTEM_STARTUP_AFTER',
				'trigger_details' => [
					'Addons_systemlog',
					'SetPruneLog',
				],
				'trigger_file' => $trigger_file
			];

		return $listeners;
	}

	/**
	 * GetTextMenuItems
	 * If the addon is installed & enabled, this method is called
	 * A link to use this addon will then be put into the 'tools' menu at the top of the page.
	 *
	 * @param EventData_IEM_SENDSTUDIOFUNCTIONS_GENERATETEXTMENULINKS $data The existing text menu items. This addon puts itself into the tools menu.
	 *
	 * @uses InterspireEventData
	 * @uses Interspire_Addons::Load
	 * @uses enabled
	 *
	 * @see SendStudio_Functions::GenerateTextMenuLinks
	 *
	 * @return Void The menu is passed in by reference, so it's manipulated directly.
	 *
	 * @uses EventData_IEM_SENDSTUDIOFUNCTIONS_GENERATETEXTMENULINKS
	 */
	public static function GetTextMenuItems(EventData_IEM_SENDSTUDIOFUNCTIONS_GENERATETEXTMENULINKS $data)
	{
		$user = IEM::getCurrentUser();
		if (!$user->Admin()) {
			return;
		}

		try {
			$me = new self();
			$me->Load();
		} catch (Exception) {
			return;
		}

		if (!$me->enabled) {
			return;
		}

		if (!isset($data->data['tools'])) {
			$data->data['tools'] = [];
		}

		$data->data['tools'][] =[
			'text' => GetLang('Addon_systemlog_Menu_Text'),
			'link' => $me->admin_url,
			'description' => GetLang('Addon_systemlog_Menu_Description'),
			'icon' => 'zoom-exclamation'
		];

		unset($me);
	}

	/**
	 * Configure
	 * This method is called when the addon needs to be configured.
	 * It uses the templates/settings.tpl file to show its current settings and display the settings form.
	 *
	 * @uses settings
	 * @uses template_system
	 * @uses InterspireTemplate::Assign
	 * @uses InterspireTemplate::ParseTemplate
	 *
	 * @return String Returns the settings form with the current settings pre-filled.
	 */
	public function Configure()
	{
		foreach ($this->settings as $k => $v) {
			$this->template_system->Assign($k, $v);
		}

		$this->template_system->Assign('SettingsUrl', $this->settings_url, false);
		$this->template_system->Assign('ApplicationUrl', $this->application_url, false);

		return $this->template_system->ParseTemplate('settings', true, '');
	}


	/**
	 * SaveSettings
	 * This is called when the settings form is submitted.
	 * It checks if any values were posted.
	 * It then checks against the settings it should find (from default_settings) to make sure you're not trying to sneak any extra settings in there
	 *
	 * If no form was posted or if you post invalid options, this will return false (which then displays an error message).
	 *
	 * @see Configure
	 * @uses default_settings
	 * @uses db
	 *
	 * @return Bool Returns false if an invalid settings form is posted or if
	 */
	public function SaveSettings()
	{
		$settings = array_intersect_key($_POST, $this->default_settings);

		if (empty($settings)) {
			return false;
		}

		return self::SetSettings($settings);
	}


	/**
	 * SetSettings
	 * Saves the settings to the database.
	 *
	 * @uses db
	 *
	 * @param Array $settings The settings to save for this addon.
	 *
	 * @return Bool True if saved successfully, otherwise false.
	 */
	public static function SetSettings($settings)
	{
		$db = IEM::getDatabase();
		if (!$db) {
			return false;
		}

		$id = str_replace('Addons_', '', __CLASS__);

		if (!isset($settings['logsize'])) {
			return false;
		}

		$settings['logsize'] = abs(intval($settings['logsize']));
		if ($settings['logsize'] == 0) {
			return false;
		}

		$result = $db->Query("UPDATE [|PREFIX|]addons SET configured=1, settings='" . $db->Quote(serialize($settings)) . "' WHERE addon_id='$id'");
		return (bool)$result;
	}

	/**
	 * GetSettings
	 * Retrieves the saved settings from the database.
	 *
	 * @see Configure
	 * @uses db
	 *
	 * @return Array The saved settings.
	 */
	public static function GetSettings()
	{
		$db = IEM::getDatabase();
		if (!$db) {
			return ['logsize' => 1000];
		}

		$id = str_replace('Addons_', '', __CLASS__);
		$settings = $db->FetchOne("SELECT settings FROM [|PREFIX|]addons WHERE addon_id='$id'");
		if (!$settings) {
			return ['logsize' => 1000];
		}
		$settings = unserialize($settings);

		return $settings ?: ['logsize' => 1000];
	}

	/**
	 * Admin_Action_Delete
	 * This is called when the 'delete' option is clicked when viewing a list of log items.
	 * If we call this method directly it won't work as it checks we are doing a form post.
	 *
	 * If that check is ok, then it goes through the id's posted to make sure they are all int values
	 * These values are then given to the database to delete.
	 *
	 * If there are no logs to delete, then it creates a 'flashmessage' to display an error and also redirects the user back to the default action.
	 *
	 * If there are logs to delete then that action is performed then the user is directed back to the default action.
	 *
	 * @see Admin_Action_Default
	 * @uses FlashMessage
	 *
	 * @return Void Doesn't return anything.
	 */
	public function Admin_Action_Delete()
	{
		if (empty($_POST) || empty($_POST['logids'])) {
			FlashMessage(GetLang('Addon_systemlog_no_logs_chosen'), SS_FLASH_MSG_ERROR, $this->admin_url);
			return;
		}

		// make sure we're only going to pass through id's
		$logs_to_delete = [];
		foreach ($_POST['logids'] as $logid) {
			if (is_numeric($logid)) {
				$logs_to_delete[] = $logid;
			}
		}

		if (empty($logs_to_delete)) {
			FlashMessage(GetLang('Addon_systemlog_no_logs_chosen'), SS_FLASH_MSG_ERROR, $this->admin_url);
			return;
		}


		$this->db->Query('DELETE FROM ' . $this->db->TablePrefix . 'log_system_system WHERE logid IN (' . implode(',', $logs_to_delete) . ')');

		FlashMessage(GetLang('Addon_systemlog_logsdeleted'), SS_FLASH_MSG_SUCCESS, $this->admin_url);
	}

	/**
	 * Admin_Action_DeleteAll
	 * This is called when the 'delete all' option is clicked when viewing a list of log items.
	 * If we call this method directly it won't work as it checks we are doing a form post.
	 *
	 * If there are no logs to delete, then it creates a 'flashmessage' to display an error and also redirects the user back to the default action.
	 *
	 * If there are logs to delete then that action is performed then the user is directed back to the default action.
	 *
	 * @see Admin_Action_Default
	 * @uses FlashMessage
	 *
	 * @return Void Doesn't return anything.
	 */
	public function Admin_Action_DeleteAll()
	{
				if(!(isset($_GET['Action']) && $_GET['Action'] == 'DeleteAll')){
			FlashMessage('This method cannot be accessed directly.', SS_FLASH_MSG_ERROR, $this->admin_url);
			return;
				}

		$this->db->Query('truncate ' . $this->db->TablePrefix . 'log_system_system');
		FlashMessage(GetLang('Addon_systemlog_all_logsdeleted'), SS_FLASH_MSG_SUCCESS, $this->admin_url);
	}

	/**
	 * Admin_Action_ViewLog
	 * This is called when a user clicks the '+' next to a log entry to view more detail.
	 * It is called via ajax so there are no page refreshes to show more detail.
	 * It loads the long description from the database and returns it.
	 *
	 * @return String If an invalid id is passed through (or a log entry can't be loaded), this will return a blank string. Otherwise it returns the long description.
	 */
	public function Admin_Action_ViewLog()
	{
		$logid = 0;
		if (isset($_GET['id'])) {
			$logid = (int)$_GET['id'];
		}

		$result = $this->db->Query('SELECT logmsg FROM ' . $this->db->TablePrefix . 'log_system_system WHERE logid=' . $logid);
		if (!$result) {
			return '';
		}
		return $this->db->FetchOne($result);
	}

	/**
	 * SetPruneLog
	 * Sets the Interspire Logger to only keep the configured amount of log entries.
	 * This function is triggered by the IEM_SYSTEM_STARTUP_AFTER event.
	 *
	 * @uses IEM_SYSTEM_STARTUP_AFTER
	 *
	 * @return Void Does not return anything.
	 */
	public static function SetPruneLog()
	{
		$settings = self::GetSettings();
		$logsystem = GetLogSystem();
		if ($logsystem) {
			$logsystem->SetGeneralLogSize($settings['logsize'] ?? 1000);
		}
	}

}
