2 // Phergie Log Viewer ... Currently designed as a single PHP file in order to make it easy to
3 // 'install' this. Just drop the index.php (or whatever name you wish to rename it to) wherever
4 // you wish, and it will simply work. Sure, it would be nice to structure some of this stuff into
5 // various include files/etc. But right now this is simple enough of a quick log viewer, that it's
9 /********** SETUP **********/
11 // (Change any of these if/as needed for your setup)
12 ini_set('default_charset', 'UTF-8');
13 date_default_timezone_set('UTC');
14 $log = "/PATH/AND/FILENAME/TO/YOUR/LOGFILE/PLEASE.db";
17 /********** PREPARATION **********/
19 $db = new PDO('sqlite:' . $log);
20 if (!is_object($db)) {
21 // Failure, can't access Phergie Log. Bail with an error message, not pretty, but works:
22 echo "ERROR: Cannot access Phergie Log File, please check the configuration & access privileges";
27 /********** DETECTION **********/
29 // Determine the mode of the application and call the appropriate handler function
30 $mode = empty($_GET['m']) ? '' : $_GET['m'];
42 // Exit not really needed here, but reminds us that everything below is support functions:
46 /********** MODES **********/
51 * Provide a list of all channel's that we are logging information for:
53 * @param PDO A PDO object referring to the database
55 * @author Eli White <eli@eliw.com>
57 function show_channels(PDO $db) {
58 // Begin the HTML page:
59 template_header('Channels');
60 echo "\nChannels:\n<ul>\n";
62 // Loop through the database reading in each channel, and echoing out a <li> for it.
63 // only grab actual channels that start with # ... also pre-lowercase everything.
64 // this allows us to 'deal' with variable caps in how the channels were logged.
65 $channels = $db->query("
66 select distinct lower(chan) as c
70 foreach ($channels as $row) {
71 $html = utf8specialchars($row['c']);
72 $url = urlencode($row['c']);
73 echo "<li><a href=\"?m=channel&w={$url}\">{$html}</a></li>\n";
76 // Finish off the page:
84 * Create a calendar view of all days available for this particular channel
86 * NOTE: May get unwieldy if large log files. Perhaps consider in the future
87 * making a paginated version of this? by year? Or a separate 'which year' page
88 * before this? Not to worry about now.
90 * @param PDO A PDO object referring to the database
92 * @author Eli White <eli@eliw.com>
94 function show_days(PDO $db) {
95 $channel = $_GET['w'];
96 $url = urlencode($channel);
98 // Begin the HTML page:
99 template_header('Daily Logs for Channel: ' . utf8specialchars($channel));
102 // Query the database to discover all days that are available for this channel:
104 $prepared = $db->prepare("
105 select distinct date(tstamp) as day
107 where lower(chan) = :chan
109 $prepared->execute(array(':chan' => $channel));
110 foreach ($prepared as $row) {
111 list($y, $m, $d) = explode('-', $row['day']);
112 $data[$y][$m][$d] = "{$y}-{$m}-{$d}";
115 // For now, just loop over them all and provide a list:
117 foreach ($data as $year => $months) {
119 foreach ($months as $month => $days) {
120 // Figure out a few facts about this month:
121 $stamp = mktime(0, 0, 0, $month, 1, $year);
122 $first_weekday = idate('w', $stamp);
123 $days_in_month = idate('t', $stamp);
124 $name = date('F', $stamp);
126 // We have a month ... start a new table:
130 <caption>{$name} {$year}</caption>
131 <tr><th>Sun</th><th>Mon</th><th>Tue</th><th>Wed</th><th>Thu</th><th>Fri</th><th>Sat</th></tr>
133 // Now we need to start looping through the days in this month:
136 // Loop through all day entries, no matter how many blanks we need:
137 for ($d = (-$first_weekday + 1); $d < $days_in_month + 1; $d++) {
138 if (!($rowmod++ % 7)) {
139 // Stop/start a new row:
143 // If this day is pre or post actual month days, make it blank:
144 if (($d < 1) || ($d > $days_in_month)) {
146 } elseif (isset($days[$d])) {
147 // Make a link to the day's log:
148 echo "<a href=\"?m=day&w={$url}&d={$days[$d]}\">{$d}</a>";
150 // Just a dead number:
155 // Finish off any blanks needed for a complete table row:
156 while ($rowmod++ % 7) {
157 echo '<td> </td>';
159 echo "</tr></table></div>\n";
163 // Finish off the page:
171 * Actually show the log for this specific day
173 * @param PDO A PDO object referring to the database
175 * @author Eli White <eli@eliw.com>
177 function show_log(PDO $db) {
178 $channel = $_GET['w'];
180 $parts = explode('-', $day);
181 $formatted_date = "{$parts[0]}-{$parts[1]}-{$parts[2]}";
183 // Begin the HTML page:
184 template_header('Date: ' . utf8specialchars($formatted_date) .
185 ' - Channel: ' . utf8specialchars($channel));
187 // Query the database to get all log lines for this date:
188 $prepared = $db->prepare("
189 select time(tstamp) as t, type, nick, message
191 where lower(chan) = :chan and date(tstamp) = :day
194 $prepared->execute(array(
199 // Loop through each line,
200 foreach ($prepared as $row) {
201 // Prepare some basic details for output:
202 $color = nick_color($row['nick']);
203 $time = utf8specialchars($row['t']);
204 $msg = utf8specialchars($row['message']);
205 $nick = utf8specialchars($row['nick']);
208 // Now change the format of the line based upon the type:
209 switch ($row['type']) {
210 case 4: // PRIVMSG (A Regular Message)
211 echo "[$time] <span style=\"color:#{$color};\"><{$nick}></span> {$msg}<br />\n";
213 case 5: // ACTION (emote)
214 echo "[$time] <span style=\"color:#{$color};\">*{$nick} {$msg}</span><br />\n";
217 echo "[$time] -> {$nick} joined the room.<br />\n";
219 case 2: // PART (leaves channel)
220 echo "[$time] -> {$nick} left the room: {$msg}<br />\n";
222 case 3: // QUIT (quits the server)
223 echo "[$time] -> {$nick} left the server: {$msg}<br />\n";
225 case 6: // NICK (changes their nickname)
226 echo "[$time] -> {$nick} is now known as: {$msg}<br />\n";
228 case 7: // KICK (booted)
229 echo "[$time] -> {$nick} boots {$msg} from the room.<br />\n";
231 case 8: // MODE (changed their mode)
233 case 9: // TOPIC (changed the topic)
234 $type = $type ? $type : 'TOPIC';
235 echo "[$time] -> {$nick}: :{$type}: {$msg}<br />\n";
239 // Finish up the page:
246 * Uses a silly little algorithm to pick a consistent but unique(ish) color for
247 * any given username. NOTE: Augment this in the future to make it not generate
248 * 'close to white' ones, also maybe to ensure uniqueness? (Not allow two to have
249 * colors that are close to each other?)
251 * @return string A CSS valid hex color string
252 * @author Eli White <eli@eliw.com>
254 function nick_color($user) {
255 static $colors = array();
257 if (!isset($colors[$user])) {
258 $colors[$user] = substr(md5($user), 0, 6);
261 return $colors[$user];
267 * Just a quick wrapper around htmlspecialchars
269 * @param string The UTF8 string to escape
270 * @return string An escaped and ready for HTML use string
271 * @author Eli White <eli@eliw.com>
273 function utf8specialchars($string) {
274 return htmlspecialchars($string, ENT_COMPAT, 'UTF-8');
278 /********** TEMPLATES **********/
283 * Echo out the header for each HTML page
285 * @param $title string The title to be used for this page.
287 * @author Eli White <eli@eliw.com>
289 function template_header($title) {
290 $css = template_css();
292 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
293 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
294 <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
296 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
297 <title>Phergie LogViewer - {$title}</title>
298 <style type="text/css" media="all">{$css}</style>
301 <h2>Phergie LogViewer - {$title}</h2>
308 * Echo out the bottom of each HTML page
311 * @author Eli White <eli@eliw.com>
313 function template_footer() {
323 * Generate the CSS used by these HTML pages & return it.
325 * @return string The CSS in question:
326 * @author Eli White <eli@eliw.com>
328 function template_css() {
336 border-collapse: collapse;
337 border: 2px solid black;
341 div.month td, div.month th {
343 vertical-align: bottom;
344 border: 1px solid gray;
352 text-decoration: bold;
353 border: 2px solid black;
357 text-decoration: none;