qa-event-logger.php 7.11 KB
Newer Older
Scott committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
<?php
/*
	Question2Answer by Gideon Greenspan and contributors
	http://www.question2answer.org/

	File: qa-plugin/event-logger/qa-event-logger.php
	Description: Event module class for event logger plugin


	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	More about this license: http://www.question2answer.org/license.php
*/

Scott committed
23 24 25 26 27
class qa_event_logger
{
	public function init_queries($table_list)
	{
		if (qa_opt('event_logger_to_database')) {
Scott committed
28
			$tablename = qa_db_add_table_prefix('eventlog');
Scott committed
29 30

			if (!in_array($tablename, $table_list)) {
31
				// table does not exist, so create it
Scott committed
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
				require_once QA_INCLUDE_DIR . 'app/users.php';
				require_once QA_INCLUDE_DIR . 'db/maxima.php';

				return 'CREATE TABLE ^eventlog (' .
					'datetime DATETIME NOT NULL,' .
					'ipaddress VARCHAR (45) CHARACTER SET ascii,' .
					'userid ' . qa_get_mysql_user_column_type() . ',' .
					'handle VARCHAR(' . QA_DB_MAX_HANDLE_LENGTH . '),' .
					'cookieid BIGINT UNSIGNED,' .
					'event VARCHAR (20) CHARACTER SET ascii NOT NULL,' .
					'params VARCHAR (800) NOT NULL,' .
					'KEY datetime (datetime),' .
					'KEY ipaddress (ipaddress),' .
					'KEY userid (userid),' .
					'KEY event (event)' .
					') ENGINE=MyISAM DEFAULT CHARSET=utf8';
48 49 50 51 52 53 54
			} else {
				// table exists: check it has the correct schema
				$column = qa_db_read_one_assoc(qa_db_query_sub('SHOW COLUMNS FROM ^eventlog WHERE Field="ipaddress"'));
				if (strtolower($column['Type']) !== 'varchar(45)') {
					// upgrade to handle IPv6
					return 'ALTER TABLE ^eventlog MODIFY ipaddress VARCHAR(45) CHARACTER SET ascii';
				}
Scott committed
55 56
			}
		}
57 58

		return array();
Scott committed
59
	}
Scott committed
60 61


Scott committed
62 63
	public function admin_form(&$qa_content)
	{
Scott committed
64
		// Process form input
Scott committed
65

Scott committed
66
		$saved = false;
Scott committed
67

Scott committed
68 69 70 71 72
		if (qa_clicked('event_logger_save_button')) {
			qa_opt('event_logger_to_database', (int)qa_post_text('event_logger_to_database_field'));
			qa_opt('event_logger_to_files', qa_post_text('event_logger_to_files_field'));
			qa_opt('event_logger_directory', qa_post_text('event_logger_directory_field'));
			qa_opt('event_logger_hide_header', !qa_post_text('event_logger_hide_header_field'));
Scott committed
73

Scott committed
74
			$saved = true;
Scott committed
75
		}
Scott committed
76

Scott committed
77
		// Check the validity of the currently entered directory (if any)
Scott committed
78

Scott committed
79
		$directory = qa_opt('event_logger_directory');
Scott committed
80

Scott committed
81 82
		$note = null;
		$error = null;
Scott committed
83 84

		if (!strlen($directory))
Scott committed
85
			$note = 'Please specify a directory that is writable by the web server.';
Scott committed
86
		elseif (!file_exists($directory))
Scott committed
87
			$error = 'This directory cannot be found. Please enter the full path.';
Scott committed
88
		elseif (!is_dir($directory))
Scott committed
89
			$error = 'This is a file. Please enter the full path of a directory.';
Scott committed
90
		elseif (!is_writable($directory))
Scott committed
91
			$error = 'This directory is not writable by the web server. Please choose a different directory, use chown/chmod to change permissions, or contact your web hosting company for assistance.';
Scott committed
92

Scott committed
93
		// Create the form for display
Scott committed
94

Scott committed
95 96 97 98 99 100 101 102 103 104
		qa_set_display_rules($qa_content, array(
			'event_logger_directory_display' => 'event_logger_to_files_field',
			'event_logger_hide_header_display' => 'event_logger_to_files_field',
		));

		return array(
			'ok' => ($saved && !isset($error)) ? 'Event log settings saved' : null,

			'fields' => array(
				array(
Scott committed
105
					'label' => 'Log events to <code>' . QA_MYSQL_TABLE_PREFIX . 'eventlog</code> database table',
Scott committed
106 107 108
					'tags' => 'name="event_logger_to_database_field"',
					'value' => qa_opt('event_logger_to_database'),
					'type' => 'checkbox',
Scott committed
109 110
				),

Scott committed
111 112 113 114 115
				array(
					'label' => 'Log events to daily log files',
					'tags' => 'name="event_logger_to_files_field" id="event_logger_to_files_field"',
					'value' => qa_opt('event_logger_to_files'),
					'type' => 'checkbox',
Scott committed
116 117
				),

Scott committed
118 119 120 121 122 123 124 125
				array(
					'id' => 'event_logger_directory_display',
					'label' => 'Directory for log files - enter full path:',
					'value' => qa_html($directory),
					'tags' => 'name="event_logger_directory_field"',
					'note' => $note,
					'error' => qa_html($error),
				),
Scott committed
126

Scott committed
127 128 129 130 131 132 133 134
				array(
					'id' => 'event_logger_hide_header_display',
					'label' => 'Include header lines at top of each log file',
					'type' => 'checkbox',
					'tags' => 'name="event_logger_hide_header_field"',
					'value' => !qa_opt('event_logger_hide_header'),
				),
			),
Scott committed
135

Scott committed
136 137 138 139 140 141 142 143
			'buttons' => array(
				array(
					'label' => 'Save Changes',
					'tags' => 'name="event_logger_save_button"',
				),
			),
		);
	}
Scott committed
144 145


Scott committed
146 147 148
	public function value_to_text($value)
	{
		if (is_array($value))
Scott committed
149 150 151
			$text = 'array(' . count($value) . ')';
		elseif (strlen($value) > 40)
			$text = substr($value, 0, 38) . '...';
Scott committed
152
		else
Scott committed
153
			$text = $value;
Scott committed
154

Scott committed
155 156
		return strtr($text, "\t\n\r", '   ');
	}
Scott committed
157 158


Scott committed
159 160 161
	public function process_event($event, $userid, $handle, $cookieid, $params)
	{
		if (qa_opt('event_logger_to_database')) {
Scott committed
162
			$paramstring = '';
Scott committed
163

Scott committed
164 165 166
			foreach ($params as $key => $value) {
				$paramstring .= (strlen($paramstring) ? "\t" : '') . $key . '=' . $this->value_to_text($value);
			}
Scott committed
167

Scott committed
168
			qa_db_query_sub(
Scott committed
169
				'INSERT INTO ^eventlog (datetime, ipaddress, userid, handle, cookieid, event, params) ' .
Scott committed
170 171 172 173
				'VALUES (NOW(), $, $, $, #, $, $)',
				qa_remote_ip_address(), $userid, $handle, $cookieid, $event, $paramstring
			);
		}
Scott committed
174

Scott committed
175
		if (qa_opt('event_logger_to_files')) {
Scott committed
176
			// Substitute some placeholders if certain information is missing
Scott committed
177
			if (!strlen($userid))
Scott committed
178
				$userid = 'no_userid';
Scott committed
179

Scott committed
180
			if (!strlen($handle))
Scott committed
181
				$handle = 'no_handle';
Scott committed
182

Scott committed
183
			if (!strlen($cookieid))
Scott committed
184
				$cookieid = 'no_cookieid';
Scott committed
185

Scott committed
186
			$ip = qa_remote_ip_address();
Scott committed
187
			if (!strlen($ip))
Scott committed
188
				$ip = 'no_ipaddress';
Scott committed
189

Scott committed
190
			// Build the log file line to be written
Scott committed
191

Scott committed
192
			$fixedfields = array(
Scott committed
193 194 195 196 197 198 199 200
				'Date' => date('Y\-m\-d'),
				'Time' => date('H\:i\:s'),
				'IPaddress' => $ip,
				'UserID' => $userid,
				'Username' => $handle,
				'CookieID' => $cookieid,
				'Event' => $event,
			);
Scott committed
201

Scott committed
202
			$fields = $fixedfields;
Scott committed
203

Scott committed
204 205 206
			foreach ($params as $key => $value) {
				$fields['param_' . $key] = $key . '=' . $this->value_to_text($value);
			}
Scott committed
207

Scott committed
208
			$string = implode("\t", $fields);
Scott committed
209

Scott committed
210
			// Build the full path and file name
Scott committed
211

Scott committed
212
			$directory = qa_opt('event_logger_directory');
Scott committed
213

Scott committed
214 215
			if (substr($directory, -1) != '/')
				$directory .= '/';
Scott committed
216

Scott committed
217
			$filename = $directory . 'q2a-log-' . date('Y\-m\-d') . '.txt';
Scott committed
218

Scott committed
219
			// Open, lock, write, unlock, close (to prevent interference between multiple writes)
Scott committed
220

Scott committed
221
			$exists = file_exists($filename);
Scott committed
222

Scott committed
223
			$file = @fopen($filename, 'a');
Scott committed
224 225 226

			if (is_resource($file)) {
				if (flock($file, LOCK_EX)) {
Scott committed
227 228 229 230 231
					if (!$exists && filesize($filename) === 0 && !qa_opt('event_logger_hide_header')) {
						$string = "Question2Answer " . QA_VERSION . " log file generated by Event Logger plugin.\n" .
							"This file is formatted as tab-delimited text with UTF-8 encoding.\n\n" .
							implode("\t", array_keys($fixedfields)) . "\textras...\n\n" . $string;
					}
Scott committed
232

Scott committed
233
					fwrite($file, $string . "\n");
Scott committed
234
					flock($file, LOCK_UN);
Scott committed
235
				}
Scott committed
236 237

				fclose($file);
Scott committed
238 239 240
			}
		}
	}
Scott committed
241
}