<commit_before><?php
namespace wcf\acp\page;
use wcf\page\AbstractPage;
use wcf\page\MultipleLinkPage;
use wcf\system\event\EventHandler;
use wcf\system\exception\IllegalLinkException;
use wcf\system\Regex;
use wcf\system\request\LinkHandler;
use wcf\system\WCF;
use wcf\util\DirectoryUtil;
use wcf\util\JSON;
use wcf\util\StringUtil;

/**
 * Shows the exception log.
 * 
 * @author	<NAME>
 * @copyright	2001-2018 WoltLab GmbH
 * @license	GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 * @package	WoltLabSuite\Core\Acp\Page
 */
class ExceptionLogViewPage extends MultipleLinkPage {
	/**
	 * @inheritDoc
	 */
	public $activeMenuItem = 'wcf.acp.menu.link.log.exception';
	
	/**
	 * @inheritDoc
	 */
	public $neededPermissions = ['admin.management.canViewLog'];
	
	/**
	 * @inheritDoc
	 */
	public $itemsPerPage = 10;
	
	/**
	 * given exceptionID
	 * @var	string
	 */
	public $exceptionID = '';
	
	/**
	 * @inheritDoc
	 */
	public $forceCanonicalURL = true;
	
	/**
	 * active logfile
	 * @var	string
	 */
	public $logFile = '';
	
	/**
	 * available logfiles
	 * @var	string[]
	 */
	public $logFiles = [];
	
	/**
	 * exceptions shown
	 * @var	array
	 */
	public $exceptions = [];
	
	/**
	 * @inheritDoc
	 */
	public function readParameters() {
		parent::readParameters();
		
		if (isset($_REQUEST['exceptionID'])) $this->exceptionID = StringUtil::trim($_REQUEST['exceptionID']);
		if (isset($_REQUEST['logFile'])) $this->logFile = StringUtil::trim($_REQUEST['logFile']);
		
		$parameters = [];
		if ($this->exceptionID !== '') {
			$parameters['exceptionID'] = $this->exceptionID;
		}
		else if ($this->logFile !== '') {
			$parameters['logFile'] = $this->logFile;
		}
		
		$this->canonicalURL = LinkHandler::getInstance()->getControllerLink(self::class, $parameters);
	}
	
	/**
	 * @inheritDoc
	 */
	public function readData() {
		AbstractPage::readData();
		
		$fileNameRegex = new Regex('(?:^|/)\d{4}-\d{2}-\d{2}\.txt$');
		$this->logFiles = DirectoryUtil::getInstance(WCF_DIR.'log/')->getFiles(SORT_DESC, $fileNameRegex);
		
		if ($this->exceptionID) {
			// search the appropriate file
			foreach ($this->logFiles as $logFile) {
				$contents = file_get_contents($logFile);
				
				if (mb_strpos($contents, '<<<<<<<<'.$this->exceptionID.'<<<<') !== false) {
					$fileNameRegex->match($logFile);
					$matches = $fileNameRegex->getMatches();
					$this->logFile = $matches[0];
					break;
				}
				
				unset($contents);
			}
			
			if (!isset($contents)) {
				$this->logFile = '';
				return;
			}
		}
		else if ($this->logFile) {
			if (!$fileNameRegex->match(basename($this->logFile))) throw new IllegalLinkException();
			if (!file_exists(WCF_DIR.'log/'.$this->logFile)) throw new IllegalLinkException();
			
			$contents = file_get_contents(WCF_DIR.'log/'.$this->logFile);
		}
		else {
			return;
		}
		
		// unify newlines
		$contents = StringUtil::unifyNewlines($contents);
		
		// split contents
		$split = new Regex('(?:^|\n<<<<\n\n)(?:<<<<<<<<([a-f0-9]{40})<<<<\n|$)');
		$contents = $split->split($contents, Regex::SPLIT_NON_EMPTY_ONLY | Regex::CAPTURE_SPLIT_DELIMITER);
		
		// even items become keys, odd items become values
		try {
			$this->exceptions = call_user_func_array('array_merge', array_map(
				function($v) {
					return [$v[0] => $v[1]];
				},
				array_chunk($contents, 2)
			));
		}
		catch (\Exception $e) {
			// logfile contents are pretty malformed, abort
			return;
		}
		
		if ($this->exceptionID) $this->searchPage($this->exceptionID);
		$this->calculateNumberOfPages();
		
		$i = 0;
		$exceptionRegex = new Regex("(?P<date>[MTWFS][a-z]{2}, \d{1,2} [JFMASOND][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} [+-]\d{4})\s*\n".
"Message: (?P<message>.*?)\s*\n".
"PHP version: (?P<phpVersion>.*?)\s*\n".
"WoltLab Suite version: (?P<wcfVersion>.*?)\s*\n".
"Request URI: (?P<requestURI>.*?)\s*\n".
"Referrer: (?P<referrer>.*?)\s*\n".
"User Agent: (?P<userAgent>.*?)\s*\n".
"Peak Memory Usage: (?<peakMemory>\d+)/(?<maxMemory>(?:\d+|-1))\s*\n".
"(?<chain>======\n".
".*)", Regex::DOT_ALL);
		$chainRegex = new Regex("======\n".
"Error Class: (?P<class>.*?)\s*\n".
"Error Message: (?P<message>.*?)\s*\n".
"Error Code: (?P<code>\d+)\s*\n".
"File: (?P<file>.*?) \((?P<line>\d+)\)\s*\n".
"Extra Information: (?P<information>(?:-|[a-zA-Z0-9+/]+={0,2}))\s*\n".
"Stack Trace: (?P<stack>\[[^\n]+\])", Regex::DOT_ALL);
		
		foreach ($this->exceptions as $key => $val) {
			$i++;
			if ($i < $this->startIndex || $i > $this->endIndex) {
				unset($this->exceptions[$key]);
				continue;
			}
			
			if (!$exceptionRegex->match($val)) {
				unset($this->exceptions[$key]);
				continue;
			}
			$matches = $exceptionRegex->getMatches();
			$chainRegex->match($matches['chain'], true, Regex::ORDER_MATCH_BY_SET);
			
			$chainMatches = array_map(function ($item) {
				if ($item['information'] === '-') {
					$item['information'] = null;
				}
				else {
					$item['information'] = unserialize(base64_decode($item['information']), ['allowed_classes' => false]);
				}
				
				$item['stack'] = JSON::decode($item['stack']);
				
				return $item;
			}, $chainRegex->getMatches());
			
			$matches['chain'] = $chainMatches;
			$this->exceptions[$key] = $matches;
		}
	}
	
	/**
	 * @inheritDoc<commit_msg>Show latest exceptions first on exception log page<commit_after><?php
namespace wcf\acp\page;
use wcf\page\AbstractPage;
use wcf\page\MultipleLinkPage;
use wcf\system\event\EventHandler;
use wcf\system\exception\IllegalLinkException;
use wcf\system\Regex;
use wcf\system\request\LinkHandler;
use wcf\system\WCF;
use wcf\util\DirectoryUtil;
use wcf\util\JSON;
use wcf\util\StringUtil;

/**
 * Shows the exception log.
 * 
 * @author	<NAME>
 * @copyright	2001-2018 WoltLab GmbH
 * @license	GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 * @package	WoltLabSuite\Core\Acp\Page
 */
class ExceptionLogViewPage extends MultipleLinkPage {
	/**
	 * @inheritDoc
	 */
	public $activeMenuItem = 'wcf.acp.menu.link.log.exception';
	
	/**
	 * @inheritDoc
	 */
	public $neededPermissions = ['admin.management.canViewLog'];
	
	/**
	 * @inheritDoc
	 */
	public $itemsPerPage = 10;
	
	/**
	 * given exceptionID
	 * @var	string
	 */
	public $exceptionID = '';
	
	/**
	 * @inheritDoc
	 */
	public $forceCanonicalURL = true;
	
	/**
	 * active logfile
	 * @var	string
	 */
	public $logFile = '';
	
	/**
	 * available logfiles
	 * @var	string[]
	 */
	public $logFiles = [];
	
	/**
	 * exceptions shown
	 * @var	array
	 */
	public $exceptions = [];
	
	/**
	 * @inheritDoc
	 */
	public function readParameters() {
		parent::readParameters();
		
		if (isset($_REQUEST['exceptionID'])) $this->exceptionID = StringUtil::trim($_REQUEST['exceptionID']);
		if (isset($_REQUEST['logFile'])) $this->logFile = StringUtil::trim($_REQUEST['logFile']);
		
		$parameters = [];
		if ($this->exceptionID !== '') {
			$parameters['exceptionID'] = $this->exceptionID;
		}
		else if ($this->logFile !== '') {
			$parameters['logFile'] = $this->logFile;
		}
		
		$this->canonicalURL = LinkHandler::getInstance()->getControllerLink(self::class, $parameters);
	}
	
	/**
	 * @inheritDoc
	 */
	public function readData() {
		AbstractPage::readData();
		
		$fileNameRegex = new Regex('(?:^|/)\d{4}-\d{2}-\d{2}\.txt$');
		$this->logFiles = DirectoryUtil::getInstance(WCF_DIR.'log/')->getFiles(SORT_DESC, $fileNameRegex);
		
		if ($this->exceptionID) {
			// search the appropriate file
			foreach ($this->logFiles as $logFile) {
				$contents = file_get_contents($logFile);
				
				if (mb_strpos($contents, '<<<<<<<<'.$this->exceptionID.'<<<<') !== false) {
					$fileNameRegex->match($logFile);
					$matches = $fileNameRegex->getMatches();
					$this->logFile = $matches[0];
					break;
				}
				
				unset($contents);
			}
			
			if (!isset($contents)) {
				$this->logFile = '';
				return;
			}
		}
		else if ($this->logFile) {
			if (!$fileNameRegex->match(basename($this->logFile))) throw new IllegalLinkException();
			if (!file_exists(WCF_DIR.'log/'.$this->logFile)) throw new IllegalLinkException();
			
			$contents = file_get_contents(WCF_DIR.'log/'.$this->logFile);
		}
		else {
			return;
		}
		
		// unify newlines
		$contents = StringUtil::unifyNewlines($contents);
		
		// split contents
		$split = new Regex('(?:^|\n<<<<\n\n)(?:<<<<<<<<([a-f0-9]{40})<<<<\n|$)');
		$contents = $split->split($contents, Regex::SPLIT_NON_EMPTY_ONLY | Regex::CAPTURE_SPLIT_DELIMITER);
		
		// even items become keys, odd items become values
		try {
			$this->exceptions = call_user_func_array('array_merge', array_map(
				function($v) {
					return [$v[0] => $v[1]];
				},
				array_chunk($contents, 2)
			));
		}
		catch (\Exception $e) {
			// logfile contents are pretty malformed, abort
			return;
		}
		
		if ($this->exceptionID) $this->searchPage($this->exceptionID);
		$this->calculateNumberOfPages();
		
		$i = 0;
		$exceptionRegex = new Regex("(?P<date>[MTWFS][a-z]{2}, \d{1,2} [JFMASOND][a-z]{2} \d{4} \d{2}:\d{2}:\d{2} [+-]\d{4})\s*\n".
"Message: (?P<message>.*?)\s*\n".
"PHP version: (?P<phpVersion>.*?)\s*\n".
"WoltLab Suite version: (?P<wcfVersion>.*?)\s*\n".
"Request URI: (?P<requestURI>.*?)\s*\n".
"Referrer: (?P<referrer>.*?)\s*\n".
"User Agent: (?P<userAgent>.*?)\s*\n".
"Peak Memory Usage: (?<peakMemory>\d+)/(?<maxMemory>(?:\d+|-1))\s*\n".
"(?<chain>======\n".
".*)", Regex::DOT_ALL);
		$chainRegex = new Regex("======\n".
"Error Class: (?P<class>.*?)\s*\n".
"Error Message: (?P<message>.*?)\s*\n".
"Error Code: (?P<code>\d+)\s*\n".
"File: (?P<file>.*?) \((?P<line>\d+)\)\s*\n".
"Extra Information: (?P<information>(?:-|[a-zA-Z0-9+/]+={0,2}))\s*\n".
"Stack Trace: (?P<stack>\[[^\n]+\])", Regex::DOT_ALL);
		
		foreach ($this->exceptions as $key => $val) {
			$i++;
			if ($i < $this->startIndex || $i > $this->endIndex) {
				unset($this->exceptions[$key]);
				continue;
			}
			
			if (!$exceptionRegex->match($val)) {
				unset($this->exceptions[$key]);
				continue;
			}
			$matches = $exceptionRegex->getMatches();
			$chainRegex->match($matches['chain'], true, Regex::ORDER_MATCH_BY_SET);
			
			$chainMatches = array_map(function ($item) {
				if ($item['information'] === '-') {
					$item['information'] = null;
				}
				else {
					$item['information'] = unserialize(base64_decode($item['information']), ['allowed_classes' => false]);
				}
				
				$item['stack'] = JSON::decode($item['stack']);
				
				return $item;
			}, $chainRegex->getMatches());
			
			$matches['chain'] = $chainMatches;
			$this->exceptions[$key] = $matches;
		}
		
		// show latest exceptions first
		$this->exceptions = array_reverse($this->exceptions, true);
	}
	
	/**
	 * @inheritDoc
{'fraction_non_alphanumeric': 0.22499059796916135, 'fraction_numerical': 0.007145543437382475, 'mean_word_length': 3.7401960784313726, 'pattern_counts': {'":': 0, '<': 101, '<?xml version=': 0, '>': 127, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3835293', 'n_tokens_mistral': 4407, 'n_tokens_neox': 3809, 'n_words': 635}