1 <?php
2 /**
3 * @package Joomla.Platform
4 * @subpackage Log
5 *
6 * @copyright Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 */
9
10 defined('JPATH_PLATFORM') or die;
11
12 jimport('joomla.filesystem.file');
13 jimport('joomla.filesystem.folder');
14
15 /**
16 * Joomla! Formatted Text File Log class
17 *
18 * This class is designed to use as a base for building formatted text files for output. By
19 * default it emulates the Syslog style format output. This is a disk based output format.
20 *
21 * @since 11.1
22 */
23 class JLogLoggerFormattedtext extends JLogLogger
24 {
25 /**
26 * The format which each entry follows in the log file.
27 *
28 * All fields must be named in all caps and be within curly brackets eg. {FOOBAR}.
29 *
30 * @var string
31 * @since 11.1
32 */
33 protected $format = '{DATETIME} {PRIORITY} {CLIENTIP} {CATEGORY} {MESSAGE}';
34
35 /**
36 * The parsed fields from the format string.
37 *
38 * @var array
39 * @since 11.1
40 */
41 protected $fields = array();
42
43 /**
44 * The full filesystem path for the log file.
45 *
46 * @var string
47 * @since 11.1
48 */
49 protected $path;
50
51 /**
52 * Constructor.
53 *
54 * @param array &$options Log object options.
55 *
56 * @since 11.1
57 */
58 public function __construct(array &$options)
59 {
60 // Call the parent constructor.
61 parent::__construct($options);
62
63 // The name of the text file defaults to 'error.php' if not explicitly given.
64 if (empty($this->options['text_file']))
65 {
66 $this->options['text_file'] = 'error.php';
67 }
68
69 // The name of the text file path defaults to that which is set in configuration if not explicitly given.
70 if (empty($this->options['text_file_path']))
71 {
72 $this->options['text_file_path'] = JFactory::getConfig()->get('log_path');
73 }
74
75 // False to treat the log file as a php file.
76 if (empty($this->options['text_file_no_php']))
77 {
78 $this->options['text_file_no_php'] = false;
79 }
80
81 // Build the full path to the log file.
82 $this->path = $this->options['text_file_path'] . '/' . $this->options['text_file'];
83
84 // Use the default entry format unless explicitly set otherwise.
85 if (!empty($this->options['text_entry_format']))
86 {
87 $this->format = (string) $this->options['text_entry_format'];
88 }
89
90 // Build the fields array based on the format string.
91 $this->parseFields();
92 }
93
94 /**
95 * Method to add an entry to the log.
96 *
97 * @param JLogEntry $entry The log entry object to add to the log.
98 *
99 * @return void
100 *
101 * @since 11.1
102 * @throws RuntimeException
103 */
104 public function addEntry(JLogEntry $entry)
105 {
106 // Initialise the file if not already done.
107 $this->initFile();
108
109 // Set some default field values if not already set.
110 if (!isset($entry->clientIP))
111 {
112 // Check for proxies as well.
113 if (isset($_SERVER['REMOTE_ADDR']))
114 {
115 $entry->clientIP = $_SERVER['REMOTE_ADDR'];
116 }
117 elseif (isset($_SERVER['HTTP_X_FORWARDED_FOR']))
118 {
119 $entry->clientIP = $_SERVER['HTTP_X_FORWARDED_FOR'];
120 }
121 elseif (isset($_SERVER['HTTP_CLIENT_IP']))
122 {
123 $entry->clientIP = $_SERVER['HTTP_CLIENT_IP'];
124 }
125 }
126
127 // If the time field is missing or the date field isn't only the date we need to rework it.
128 if ((strlen($entry->date) != 10) || !isset($entry->time))
129 {
130 // Get the date and time strings in GMT.
131 $entry->datetime = $entry->date->toISO8601();
132 $entry->time = $entry->date->format('H:i:s', false);
133 $entry->date = $entry->date->format('Y-m-d', false);
134 }
135
136 // Get a list of all the entry keys and make sure they are upper case.
137 $tmp = array_change_key_case(get_object_vars($entry), CASE_UPPER);
138
139 // Decode the entry priority into an English string.
140 $tmp['PRIORITY'] = $this->priorities[$entry->priority];
141
142 // Fill in field data for the line.
143 $line = $this->format;
144
145 foreach ($this->fields as $field)
146 {
147 $line = str_replace('{' . $field . '}', (isset($tmp[$field])) ? $tmp[$field] : '-', $line);
148 }
149
150 // Write the new entry to the file.
151 $line .= "\n";
152
153 if (!JFile::append($this->path, $line))
154 {
155 throw new RuntimeException('Cannot write to log file.');
156 }
157 }
158
159 /**
160 * Method to generate the log file header.
161 *
162 * @return string The log file header
163 *
164 * @since 11.1
165 */
166 protected function generateFileHeader()
167 {
168 $head = array();
169
170 // Build the log file header.
171
172 // If the no php flag is not set add the php die statement.
173 if (empty($this->options['text_file_no_php']))
174 {
175 // Blank line to prevent information disclose: https://bugs.php.net/bug.php?id=60677
176 $head[] = '#';
177 $head[] = '#<?php die(\'Forbidden.\'); ?>';
178 }
179
180 $head[] = '#Date: ' . gmdate('Y-m-d H:i:s') . ' UTC';
181 $head[] = '#Software: ' . JPlatform::getLongVersion();
182 $head[] = '';
183
184 // Prepare the fields string
185 $head[] = '#Fields: ' . strtolower(str_replace('}', '', str_replace('{', '', $this->format)));
186 $head[] = '';
187
188 return implode("\n", $head);
189 }
190
191 /**
192 * Method to initialise the log file. This will create the folder path to the file if it doesn't already
193 * exist and also get a new file header if the file doesn't already exist. If the file already exists it
194 * will simply open it for writing.
195 *
196 * @return void
197 *
198 * @since 11.1
199 * @throws RuntimeException
200 */
201 protected function initFile()
202 {
203 // We only need to make sure the file exists
204 if (JFile::exists($this->path))
205 {
206 return;
207 }
208
209 // Make sure the folder exists in which to create the log file.
210 JFolder::create(dirname($this->path));
211
212 // Build the log file header.
213 $head = $this->generateFileHeader();
214
215 if (!JFile::write($this->path, $head))
216 {
217 throw new RuntimeException('Cannot write to log file.');
218 }
219 }
220
221 /**
222 * Method to parse the format string into an array of fields.
223 *
224 * @return void
225 *
226 * @since 11.1
227 */
228 protected function parseFields()
229 {
230 $this->fields = array();
231 $matches = array();
232
233 // Get all of the available fields in the format string.
234 preg_match_all('/{(.*?)}/i', $this->format, $matches);
235
236 // Build the parsed fields list based on the found fields.
237 foreach ($matches[1] as $match)
238 {
239 $this->fields[] = strtoupper($match);
240 }
241 }
242 }
243