<?php // Phergie Log Viewer ... Currently designed as a single PHP file in order to make it easy to // 'install' this. Just drop the index.php (or whatever name you wish to rename it to) wherever // you wish, and it will simply work. Sure, it would be nice to structure some of this stuff into // various include files/etc. But right now this is simple enough of a quick log viewer, that it's // just one file. /********** SETUP **********/ // (Change any of these if/as needed for your setup) ini_set('default_charset', 'UTF-8'); date_default_timezone_set('UTC'); $log = "/PATH/AND/FILENAME/TO/YOUR/LOGFILE/PLEASE.db"; /********** PREPARATION **********/ $db = new PDO('sqlite:' . $log); if (!is_object($db)) { // Failure, can't access Phergie Log. Bail with an error message, not pretty, but works: echo "ERROR: Cannot access Phergie Log File, please check the configuration & access privileges"; exit(); } /********** DETECTION **********/ // Determine the mode of the application and call the appropriate handler function $mode = empty($_GET['m']) ? '' : $_GET['m']; switch ($mode) { case 'channel': show_days($db); break; case 'day': show_log($db); break; default: show_channels($db); } // Exit not really needed here, but reminds us that everything below is support functions: exit(); /********** MODES **********/ /** * show_channels * * Provide a list of all channel's that we are logging information for: * * @param PDO A PDO object referring to the database * @return void * @author Eli White <eli@eliw.com> **/ function show_channels(PDO $db) { // Begin the HTML page: template_header('Channels'); echo "\nChannels:\n<ul>\n"; // Loop through the database reading in each channel, and echoing out a <li> for it. // only grab actual channels that start with # ... also pre-lowercase everything. // this allows us to 'deal' with variable caps in how the channels were logged. $channels = $db->query(" select distinct lower(chan) as c from logs where chan like '#%' "); foreach ($channels as $row) { $html = utf8specialchars($row['c']); $url = urlencode($row['c']); echo "<li><a href=\"?m=channel&w={$url}\">{$html}</a></li>\n"; } // Finish off the page: echo "\n</ul>\n"; template_footer(); } /** * show_days * * Create a calendar view of all days available for this particular channel * * NOTE: May get unwieldy if large log files. Perhaps consider in the future * making a paginated version of this? by year? Or a separate 'which year' page * before this? Not to worry about now. * * @param PDO A PDO object referring to the database * @return void * @author Eli White <eli@eliw.com> **/ function show_days(PDO $db) { $channel = $_GET['w']; $url = urlencode($channel); // Begin the HTML page: template_header('Daily Logs for Channel: ' . utf8specialchars($channel)); echo "\n<ul>\n"; // Query the database to discover all days that are available for this channel: $data = array(); $prepared = $db->prepare(" select distinct date(tstamp) as day from logs where lower(chan) = :chan "); $prepared->execute(array(':chan' => $channel)); foreach ($prepared as $row) { list($y, $m, $d) = explode('-', $row['day']); $data[$y][$m][$d] = "{$y}-{$m}-{$d}"; } // For now, just loop over them all and provide a list: ksort($data); foreach ($data as $year => $months) { ksort($months); foreach ($months as $month => $days) { // Figure out a few facts about this month: $stamp = mktime(0, 0, 0, $month, 1, $year); $first_weekday = idate('w', $stamp); $days_in_month = idate('t', $stamp); $name = date('F', $stamp); // We have a month ... start a new table: echo <<<EOTABLE <div class="month"> <table> <caption>{$name} {$year}</caption> <tr><th>Sun</th><th>Mon</th><th>Tue</th><th>Wed</th><th>Thu</th><th>Fri</th><th>Sat</th></tr> EOTABLE; // Now we need to start looping through the days in this month: echo '<tr>'; $rowmod = 0; // Loop through all day entries, no matter how many blanks we need: for ($d = (-$first_weekday + 1); $d < $days_in_month + 1; $d++) { if (!($rowmod++ % 7)) { // Stop/start a new row: echo '</tr><tr>'; } echo '<td>'; // If this day is pre or post actual month days, make it blank: if (($d < 1) || ($d > $days_in_month)) { echo ' '; } elseif (isset($days[$d])) { // Make a link to the day's log: echo "<a href=\"?m=day&w={$url}&d={$days[$d]}\">{$d}</a>"; } else { // Just a dead number: echo $d; } echo '</td>'; } // Finish off any blanks needed for a complete table row: while ($rowmod++ % 7) { echo '<td> </td>'; } echo "</tr></table></div>\n"; } } // Finish off the page: echo "\n</ul>\n"; template_footer(); } /** * show_log * * Actually show the log for this specific day * * @param PDO A PDO object referring to the database * @return void * @author Eli White <eli@eliw.com> **/ function show_log(PDO $db) { $channel = $_GET['w']; $day = $_GET['d']; $parts = explode('-', $day); $formatted_date = "{$parts[0]}-{$parts[1]}-{$parts[2]}"; // Begin the HTML page: template_header('Date: ' . utf8specialchars($formatted_date) . ' - Channel: ' . utf8specialchars($channel)); // Query the database to get all log lines for this date: $prepared = $db->prepare(" select time(tstamp) as t, type, nick, message from logs where lower(chan) = :chan and date(tstamp) = :day order by tstamp asc "); $prepared->execute(array( ':chan' => $channel, ':day' => $day, )); // Loop through each line, foreach ($prepared as $row) { // Prepare some basic details for output: $color = nick_color($row['nick']); $time = utf8specialchars($row['t']); $msg = utf8specialchars($row['message']); $nick = utf8specialchars($row['nick']); $type = false; // Now change the format of the line based upon the type: switch ($row['type']) { case 4: // PRIVMSG (A Regular Message) echo "[$time] <span style=\"color:#{$color};\"><{$nick}></span> {$msg}<br />\n"; break; case 5: // ACTION (emote) echo "[$time] <span style=\"color:#{$color};\">*{$nick} {$msg}</span><br />\n"; break; case 1: // JOIN echo "[$time] -> {$nick} joined the room.<br />\n"; break; case 2: // PART (leaves channel) echo "[$time] -> {$nick} left the room: {$msg}<br />\n"; break; case 3: // QUIT (quits the server) echo "[$time] -> {$nick} left the server: {$msg}<br />\n"; break; case 6: // NICK (changes their nickname) echo "[$time] -> {$nick} is now known as: {$msg}<br />\n"; break; case 7: // KICK (booted) echo "[$time] -> {$nick} boots {$msg} from the room.<br />\n"; break; case 8: // MODE (changed their mode) $type = 'MODE'; case 9: // TOPIC (changed the topic) $type = $type ? $type : 'TOPIC'; echo "[$time] -> {$nick}: :{$type}: {$msg}<br />\n"; } } // Finish up the page: template_footer(); } /** * nick_color * * Uses a silly little algorithm to pick a consistent but unique(ish) color for * any given username. NOTE: Augment this in the future to make it not generate * 'close to white' ones, also maybe to ensure uniqueness? (Not allow two to have * colors that are close to each other?) * * @return string A CSS valid hex color string * @author Eli White <eli@eliw.com> **/ function nick_color($user) { static $colors = array(); if (!isset($colors[$user])) { $colors[$user] = substr(md5($user), 0, 6); } return $colors[$user]; } /** * utf8specialchars * * Just a quick wrapper around htmlspecialchars * * @param string The UTF8 string to escape * @return string An escaped and ready for HTML use string * @author Eli White <eli@eliw.com> **/ function utf8specialchars($string) { return htmlspecialchars($string, ENT_COMPAT, 'UTF-8'); } /********** TEMPLATES **********/ /** * template_header * * Echo out the header for each HTML page * * @param $title string The title to be used for this page. * @return void * @author Eli White <eli@eliw.com> **/ function template_header($title) { $css = template_css(); echo <<<EOHTML <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html lang="en" xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Phergie LogViewer - {$title}</title> <style type="text/css" media="all">{$css}</style> </head> <body> <h2>Phergie LogViewer - {$title}</h2> EOHTML; } /** * template_footer * * Echo out the bottom of each HTML page * * @return void * @author Eli White <eli@eliw.com> **/ function template_footer() { echo <<<EOHTML </body> </html> EOHTML; } /** * template_css * * Generate the CSS used by these HTML pages & return it. * * @return string The CSS in question: * @author Eli White <eli@eliw.com> **/ function template_css() { return <<<EOCSS div.month { float: left; height: 15em; } div.month table { border-collapse: collapse; border: 2px solid black; margin-right: 2em; } div.month td, div.month th { text-align: center; vertical-align: bottom; border: 1px solid gray; width: 2em; height: 1.7em; padding: 1px; margin: 0px; } div.month th { text-decoration: bold; border: 2px solid black; } div.month a { text-decoration: none; } a:visited, a:link { color: blue; } a:active, a:hover { color: red; } EOCSS; }