styling cleanup, initial stats logging

This commit is contained in:
Colin Mitchell 2012-04-25 20:39:47 -04:00
parent ff0c002dd7
commit ce984610a7
9 changed files with 1040 additions and 134 deletions

View File

@ -62,6 +62,9 @@ class GopherGetter {
$this->errno = "";
$this->logTraffic($this->host, $this->path);
$this->result = $this->cache->get($this->key);
if ( $this->result === FALSE ) {
error_log("tcp://$this->host:$this->port\t$this->input");
@ -84,12 +87,31 @@ class GopherGetter {
fclose($fp);
}
$this->cache->set($this->key, $this->result);
}
return TRUE;
}
function logTraffic($host, $selector) {
/* error_log(LOG_STATS);
if ( ! isset(LOG_STATS) || LOG_STATS != true ) {
return;
}*/
$ip_address = ip2long($_SERVER['REMOTE_ADDR']);
DB::insert('traffic', array(
'hostname' => $host,
'selector' => $selector,
'remote_ip' => $ip_address,
'request_at' => DB::sqleval("NOW()")
));
}
function size() {
return strlen($this->result);
}

View File

@ -1,8 +1,8 @@
#gopher {
font-family:monospace;
white-space: pre;
padding-bottom: 100px;
}
#gopher .as-html {
white-space: normal;
}
@ -23,16 +23,6 @@
display: none;
}
footer {
color: #666;
background: #222;
padding: 8px 0 8px 0;
border-top: 1px solid #000;
}
footer a {
color: #999;
}
footer a:hover {
color: #efefef;
.navbar-fixed-bottom .container {
padding-top: 5px;
}

View File

@ -1,7 +1,19 @@
function GopherParser() {};
// regex for matching gopher entries. it didn't quite work in it's original form (see below)
/**
* regex for matching gopher entries. it didn't quite work in its original form (see below)
*/
GopherParser.prototype.entryPattern = /^(.)(.*?)\t(.*?)\t(.*?)\t(\d+).*/;
/**
* the different sorts of gopher entries we will handle. each entry here is a
* hash with the following keys:
*
* style - general style of the entry -- information, error, link, etc.
* link - true/false, should we link to the selector for this entry?
* icon - an icon class to render with this file. these icons are from
* the Twitter Bootstrap library
*/
GopherParser.prototype.entryTypes = {
info: { style: 'info', link : false },
error: { style: 'error', link : false, icon : 'icon-exclamation-sign' },
@ -19,14 +31,21 @@ GopherParser.prototype.entryTypes = {
};
/**
* determine if we should render the string as a gopher menu or not
* @param d data to test
* @return true if we should parse as gopher menu, false otherwise
*/
GopherParser.prototype.shouldRender = function(d) {
var data = d.split("\n");
return data[0].match(this.entryPattern) !== null;
};
GopherParser.prototype.isBinary = function(d) {
return /[\x00-\x1F]/.test(d);
};
/**
* parse the incoming data as a gopher menu
* @param data text to parse
* @return array of objects which represent the data of the menu
*/
GopherParser.prototype.parseGopher = function(data) {
var lines = [];
data = data.split("\n");
@ -39,9 +58,62 @@ GopherParser.prototype.parseGopher = function(data) {
return lines;
};
/**
* borrowed from phpjs:
* given an item type, return the EntryType used to represent it.
* @param entry type as specified in RFC 1436 section 3.8
* @return entry type object
*/
GopherParser.prototype.getType = function(t) {
switch (t) {
case 'i':
return this.entryTypes.info;
break;
case '3':
return this.entryTypes.error;
break;
case '1':
return this.entryTypes.directory;
break;
case '0':
return this.entryTypes.document;
break;
case '4':
return this.entryTypes.binhex;
break;
case '5':
return this.entryTypes.dosbinary;
break;
case '6':
return this.entryTypes.uuencoded;
break;
case '7':
return this.entryTypes.search;
break;
case '9':
return this.entryTypes.binary;
break;
case 'h':
return this.entryTypes.html;
break;
case 'd':
return this.entryTypes.image;
break;
case 's':
return this.entryTypes.audio;
break;
default:
return this.entryTypes.unknown;
}
};
/**
* parse a line from a gopher menu
* borrowed and modified from phpjs:
* @see http://phpjs.org/functions/gopher_parsedir:833
*
* @param dirent the line to parse
* @return object
*/
GopherParser.prototype.parseEntry = function(dirent) {
//var entryPattern = /^(.)(.*?)\t(.*?)\t(.*?)\t(.*?)\u000d\u000a$/;
@ -51,25 +123,6 @@ GopherParser.prototype.parseEntry = function(dirent) {
// * example 1: entry.title;
// * returns 1: 'All about my gopher site.'
/* Types
* 0 = plain text file
* 1 = directory menu listing
* 2 = CSO search query
* 3 = error message
* 4 = BinHex encoded text file
* 5 = binary archive file
* 6 = UUEncoded text file
* 7 = search engine query
* 8 = telnet session pointer
* 9 = binary file
* g = Graphics file format, primarily a GIF file
* h = HTML file
* i = informational message
* s = Audio file format, primarily a WAV file
*/
var entry = dirent.match(this.entryPattern);
// parse error
@ -77,51 +130,8 @@ GopherParser.prototype.parseEntry = function(dirent) {
return {};
}
var type;
switch (entry[1]) {
case 'i':
type = this.entryTypes.info;
break;
case '3':
type = this.entryTypes.error;
break;
case '1':
type = this.entryTypes.directory;
break;
case '0':
type = this.entryTypes.document;
break;
case '4':
type = this.entryTypes.binhex;
break;
case '5':
type = this.entryTypes.dosbinary;
break;
case '6':
type = this.entryTypes.uuencoded;
break;
case '7':
type = this.entryTypes.search;
break;
case '9':
type = this.entryTypes.binary;
break;
case 'h':
type = this.entryTypes.html;
break;
case 'd':
type = this.entryTypes.image;
break;
case 's':
type = this.entryTypes.audio;
break;
default:
type = this.entryTypes.unknown;
}
return {
type: type,
type: this.getType(entry[1]),
title: entry[2],
path: entry[3],
host: entry[4],
@ -192,22 +202,28 @@ GopherParser.prototype.parseEntry = function(dirent) {
continue;
}
// clean up the path a bit
if ( e.path && e.path[0] != "/" ) {
e.path = "/" + e.path;
}
// the html style link for this entry will be /HOST/SELECTOR
var href = "/" + e.host + e.path;
var text = e.title;
var type = e.type;
var result;
var icon = "";
// if we have an icon class, add it here
if ( type.icon ) {
icon = $("<i />").addClass(type.icon).append("&nbsp;");
}
//
// generate a form for search entries
//
if ( typeof(type.form) !== "undefined" && type.form == true ) {
// handle search input
@ -217,26 +233,26 @@ GopherParser.prototype.parseEntry = function(dirent) {
addClass("gopher-" + type.style).
addClass("form-inline");
$(result).append("<input name='text' class='span3' placeholder='input' />");
var button = $("<button />").attr("type", "submit").addClass("btn").html("Go!");
$(result).append(button).append("<span class='spinny' />");
$(result).
append("<input name='text' class='span3' placeholder='input' />").
append(button);
}
else if ( type.link == false ) { //|| !e.path || e.path == "" ) {
// if there was no path, don't output a URL
// if there was no path, don't output a URL
else if ( type.link == false ) {
result = text;
}
// output a link with the right class/etc
else {
result = $("<a />").
attr("href", href).
addClass("gopher-" + type.style).
html(text).after("<span class='spinny' />");
html(text);
}
// add the output!
$(this).append(icon).append(result).append("<br />");
}
}

View File

@ -2,21 +2,38 @@ $(document).ready(function() {
/**
* on stateChange events, we will get the data from that page
* (stored when loadGopherUri is complete), and re-render the
* gopher output .
* gopher output.
*/
History.Adapter.bind(window,'statechange',function() {
var state = History.getState();
$("#gopher").html(state.data.data).fromGopher();
if ( state.data.data ) {
$("#gopher").html(state.data.data).fromGopher();
// update our breadcrumb nav
updateBreadcrumb(state.data.url);
}
else {
$("#gopher,#breadcrumb").fadeOut().html("");
$("#intro").fadeIn();
}
});
var spin = function() {
$("#spinner img").fadeIn();
};
var unspin = function() {
$("#spinner img").fadeOut();
/**
* very simple methods to manage a 'please wait' spinner
*/
var spinner = {
spin : function() {
$("#spinner img").fadeIn();
},
unspin : function() {
$("#spinner img").fadeOut();
}
};
/**
* output a breadcrumb which will just split the current URI and
* link to each level
@ -64,16 +81,25 @@ $(document).ready(function() {
}
spin();
var displayError = function() {
spinner.unspin();
$("#gopher").show().html("Sorry, there was a problem with your request, please try again.");
$('html,body').animate({scrollTop: $("#gopher").offset().top}, 'slow');
};
spinner.spin();
$.ajax({
url: "/gopher",
type: 'post',
dataType: 'json',
data: data
data: data,
error : function() {
displayError();
}
}).done(function ( data ) {
unspin();
spinner.unspin();
if ( typeof(params.onComplete) !== "undefined" ) {
params.onComplete();
@ -82,10 +108,14 @@ $(document).ready(function() {
// hide the intro text if it is still there
$("#intro").hide();
// did we get valid data? if so, try and render it
// did we get valid data? if not, display the error
if ( data.error ) {
$("#gopher").html(data.error);
}
//
// we got data, let's render it
//
else if ( data.data ) {
// update browser url and store the data hash for
// later use. for now we'll set the title to be the
@ -104,7 +134,12 @@ $(document).ready(function() {
// scroll to the top of the page
$('html, body').animate({ scrollTop: 0 }, 0);
}
//
// at this point, we probably just have a URL for a file, link to it
//
else {
// if it's an image, load in a colorbox
if ( data.image ) {
$.colorbox({photo: true, href: data.url});
}
@ -115,7 +150,7 @@ $(document).ready(function() {
}
}).fail(function(jqXHR, textStatus) {
$("#gopher").html("Sorry, there was a problem with your request, please try again.");
displayError();
});
};
@ -123,18 +158,12 @@ $(document).ready(function() {
/**
* handle clicks on gopher selectors
*/
$("#gopher,#breadcrumb").on("click", "a", function() {
var link = $(this);
$("#gopher,#breadcrumb,#intro .as-html").on("click", "a", function() {
loadGopherUri({
url : $(this).attr("href")
});
return false;
}).on("submit", "form", function() {
var link = $(this);
spin();
loadGopherUri({
url : $(this).attr("action"),
input : $(this).find("input").val()

7
config.php.example Normal file
View File

@ -0,0 +1,7 @@
<?php
DB::$user = '';
DB::$password = '';
DB::$dbName = 'gopher';
?>

View File

@ -4,6 +4,9 @@ require 'Slim/View.php';
require 'GopherGetter.php';
require_once 'meekrodb.2.0.class.php';
require_once 'config.php';
$app = new Slim();
/**
@ -11,6 +14,7 @@ $app = new Slim();
*/
//
// default route
//
@ -36,8 +40,6 @@ $app->get('/file', function () use($app) {
// header("Content-type: application/octet-stream");
// header('Content-Disposition: attachment; filename="' . basename($file) . '"');
error_log("send $path to browser " . mime_content_type($path));
$app->contentType(mime_content_type($path));
header("Content-type: " . mime_content_type($path));
header('Content-Disposition: attachment; filename="' . basename($file) . '"');

836
meekrodb.2.0.class.php Normal file
View File

@ -0,0 +1,836 @@
<?php
/*
Copyright (C) 2008-2011 Sergey Tsalkov (stsalkov@gmail.com)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 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 Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
class DB {
// initial connection
public static $dbName = '';
public static $user = '';
public static $password = '';
public static $host = 'localhost';
public static $port = null;
public static $encoding = 'latin1';
// configure workings
public static $queryMode = 'queryAllRows';
public static $param_char = '%';
public static $success_handler = false;
public static $error_handler = true;
public static $throw_exception_on_error = false;
public static $nonsql_error_handler = null;
public static $throw_exception_on_nonsql_error = false;
// internal
protected static $mdb = null;
public static function getMDB() {
$mdb = DB::$mdb;
if ($mdb === null) {
$mdb = DB::$mdb = new MeekroDB();
}
if ($mdb->queryMode !== DB::$queryMode) $mdb->queryMode = DB::$queryMode;
if ($mdb->param_char !== DB::$param_char) $mdb->param_char = DB::$param_char;
if ($mdb->success_handler !== DB::$success_handler) $mdb->success_handler = DB::$success_handler;
if ($mdb->error_handler !== DB::$error_handler) $mdb->error_handler = DB::$error_handler;
if ($mdb->throw_exception_on_error !== DB::$throw_exception_on_error) $mdb->throw_exception_on_error = DB::$throw_exception_on_error;
if ($mdb->nonsql_error_handler !== DB::$nonsql_error_handler) $mdb->nonsql_error_handler = DB::$nonsql_error_handler;
if ($mdb->throw_exception_on_nonsql_error !== DB::$throw_exception_on_nonsql_error) $mdb->throw_exception_on_nonsql_error = DB::$throw_exception_on_nonsql_error;
return $mdb;
}
public static function query() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'query'), $args); }
public static function quickPrepare() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'quickPrepare'), $args); }
public static function queryFirstRow() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'queryFirstRow'), $args); }
public static function queryOneRow() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'queryOneRow'), $args); }
public static function queryFirstList() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'queryFirstList'), $args); }
public static function queryOneList() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'queryOneList'), $args); }
public static function queryFirstColumn() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'queryFirstColumn'), $args); }
public static function queryOneColumn() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'queryOneColumn'), $args); }
public static function queryFirstField() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'queryFirstField'), $args); }
public static function queryOneField() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'queryOneField'), $args); }
public static function queryRaw() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'queryRaw'), $args); }
public static function queryNull() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'queryNull'), $args); }
public static function queryBuf() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'queryBuf'), $args); }
public static function queryUnbuf() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'queryUnbuf'), $args); }
public static function insert() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'insert'), $args); }
public static function insertIgnore() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'insertIgnore'), $args); }
public static function insertUpdate() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'insertUpdate'), $args); }
public static function replace() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'replace'), $args); }
public static function update() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'update'), $args); }
public static function delete() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'delete'), $args); }
public static function insertId() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'insertId'), $args); }
public static function count() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'count'), $args); }
public static function affectedRows() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'affectedRows'), $args); }
public static function useDB() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'useDB'), $args); }
public static function startTransaction() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'startTransaction'), $args); }
public static function commit() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'commit'), $args); }
public static function rollback() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'rollback'), $args); }
public static function tableList() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'tableList'), $args); }
public static function columnList() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'columnList'), $args); }
public static function sqlEval() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'sqlEval'), $args); }
public static function nonSQLError() { $args = func_get_args(); return call_user_func_array(array(DB::getMDB(), 'nonSQLError'), $args); }
public static function debugMode($handler = true) {
DB::$success_handler = $handler;
}
}
class MeekroDB {
// initial connection
public $dbName = '';
public $user = '';
public $password = '';
public $host = 'localhost';
public $port = null;
public $encoding = 'latin1';
// configure workings
public $queryMode = 'queryAllRows';
public $param_char = '%';
public $success_handler = false;
public $error_handler = true;
public $throw_exception_on_error = false;
public $nonsql_error_handler = null;
public $throw_exception_on_nonsql_error = false;
// internal
public $internal_mysql = null;
public $insert_id = 0;
public $num_rows = 0;
public $affected_rows = 0;
public $queryResult = null;
public $queryResultType = null;
public $old_db = null;
public $current_db = null;
public function __construct($host=null, $user=null, $password=null, $dbName=null, $port=null, $encoding=null) {
if ($host === null) $host = DB::$host;
if ($user === null) $user = DB::$user;
if ($password === null) $password = DB::$password;
if ($dbName === null) $dbName = DB::$dbName;
if ($port === null) $port = DB::$port;
if ($encoding === null) $encoding = DB::$encoding;
$this->host = $host;
$this->user = $user;
$this->password = $password;
$this->dbName = $dbName;
$this->port = $port;
$this->encoding = $encoding;
}
public function get() {
$mysql = $this->internal_mysql;
if ($mysql == null) {
if (! $this->port) $this->port = ini_get('mysqli.default_port');
$this->current_db = $this->dbName;
$mysql = new mysqli($this->host, $this->user, $this->password, $this->dbName, $this->port);
if ($mysql->connect_error) {
$this->nonSQLError('Unable to connect to MySQL server! Error: ' . $mysql->connect_error);
}
$mysql->set_charset($this->encoding);
$this->internal_mysql = $mysql;
}
return $mysql;
}
public function nonSQLError($message) {
if ($this->throw_exception_on_nonsql_error) {
$e = new MeekroDBException($message);
throw $e;
}
$error_handler = is_callable($this->nonsql_error_handler) ? $this->nonsql_error_handler : 'meekrodb_error_handler';
call_user_func($error_handler, array(
'type' => 'nonsql',
'error' => $message
));
}
public function debugMode($handler = true) {
$this->success_handler = $handler;
}
public function insertId() { return $this->insert_id; }
public function affectedRows() { return $this->affected_rows; }
public function count() { $args = func_get_args(); return call_user_func_array(array($this, 'numRows'), $args); }
public function numRows() { return $this->num_rows; }
public function useDB() { $args = func_get_args(); return call_user_func_array(array($this, 'setDB'), $args); }
public function setDB($dbName) {
$db = $this->get();
$this->old_db = $this->current_db;
if (! $db->select_db($dbName)) $this->nonSQLError("Unable to set database to $dbName");
$this->current_db = $dbName;
}
public function startTransaction() {
$this->queryNull('START TRANSACTION');
}
public function commit() {
$this->queryNull('COMMIT');
}
public function rollback() {
$this->queryNull('ROLLBACK');
}
public function escape($str) {
$db = $this->get();
return $db->real_escape_string($str);
}
public function sanitize($value) {
if (is_object($value) && ($value instanceof MeekroDBEval)) {
$value = $value->text;
} else {
if (is_array($value) || is_object($value)) $value = serialize($value);
if (is_string($value)) $value = "'" . $this->escape($value) . "'";
else if (is_null($value)) $value = 'NULL';
else if (is_bool($value)) $value = ($value ? 1 : 0);
}
return $value;
}
protected function formatTableName($table) {
$table = str_replace('`', '', $table);
if (strpos($table, '.')) {
list($table_db, $table_table) = explode('.', $table, 2);
$table = "`$table_db`.`$table_table`";
} else {
$table = "`$table`";
}
return $table;
}
protected function prependCall($function, $args, $prepend) {
array_unshift($args, $prepend);
return call_user_func_array($function, $args);
}
protected function wrapStr($strOrArray, $wrapChar, $escape = false) {
if (! is_array($strOrArray)) {
if ($escape) return $wrapChar . $this->escape($strOrArray) . $wrapChar;
else return $wrapChar . $strOrArray . $wrapChar;
} else {
$R = array();
foreach ($strOrArray as $element) {
$R[] = $this->wrapStr($element, $wrapChar, $escape);
}
return $R;
}
}
public function freeResult($result) {
if (! ($result instanceof MySQLi_Result)) return;
return $result->free();
}
public function update() {
$args = func_get_args();
$table = array_shift($args);
$params = array_shift($args);
$where = array_shift($args);
$buildquery = "UPDATE " . self::formatTableName($table) . " SET ";
$keyval = array();
foreach ($params as $key => $value) {
$value = $this->sanitize($value);
$keyval[] = "`" . $key . "`=" . $value;
}
$buildquery = "UPDATE " . self::formatTableName($table) . " SET " . implode(', ', $keyval) . " WHERE " . $where;
array_unshift($args, $buildquery);
call_user_func_array(array($this, 'queryNull'), $args);
}
public function insertOrReplace($which, $table, $datas, $options=array()) {
$datas = unserialize(serialize($datas)); // break references within array
$keys = null;
if (isset($datas[0]) && is_array($datas[0])) {
$many = true;
} else {
$datas = array($datas);
$many = false;
}
foreach ($datas as $data) {
if (! $keys) {
$keys = array_keys($data);
if ($many) sort($keys);
}
$insert_values = array();
foreach ($keys as $key) {
if ($many && !isset($data[$key])) $this->nonSQLError('insert/replace many: each assoc array must have the same keys!');
$datum = $data[$key];
$datum = $this->sanitize($datum);
$insert_values[] = $datum;
}
$values[] = '(' . implode(', ', $insert_values) . ')';
}
$table = self::formatTableName($table);
$keys_str = implode(', ', $this->wrapStr($keys, '`'));
$values_str = implode(',', $values);
if (isset($options['ignore']) && $options['ignore'] && strtolower($which) == 'insert') {
$this->queryNull("INSERT IGNORE INTO $table ($keys_str) VALUES $values_str");
} else if (isset($options['update']) && $options['update'] && strtolower($which) == 'insert') {
$this->queryNull("INSERT INTO $table ($keys_str) VALUES $values_str ON DUPLICATE KEY UPDATE {$options['update']}");
} else {
$this->queryNull("$which INTO $table ($keys_str) VALUES $values_str");
}
}
public function insert($table, $data) { return $this->insertOrReplace('INSERT', $table, $data); }
public function insertIgnore($table, $data) { return $this->insertOrReplace('INSERT', $table, $data, array('ignore' => true)); }
public function replace($table, $data) { return $this->insertOrReplace('REPLACE', $table, $data); }
public function insertUpdate() {
$args = func_get_args();
$table = array_shift($args);
$data = array_shift($args);
if (! isset($args[0])) { // update will have all the data of the insert
if (isset($data[0]) && is_array($data[0])) { //multiple insert rows specified -- failing!
$this->nonSQLError("Badly formatted insertUpdate() query -- you didn't specify the update component!");
}
$args[0] = $data;
}
if (is_array($args[0])) {
$keyval = array();
foreach ($args[0] as $key => $value) {
$value = $this->sanitize($value);
$keyval[] = "`" . $key . "`=" . $value;
}
$updatestr = implode(', ', $keyval);
} else {
$updatestr = call_user_func_array(array($this, 'parseQueryParams'), $args);
}
return $this->insertOrReplace('INSERT', $table, $data, array('update' => $updatestr));
}
public function delete() {
$args = func_get_args();
$table = self::formatTableName(array_shift($args));
$where = array_shift($args);
$buildquery = "DELETE FROM $table WHERE $where";
array_unshift($args, $buildquery);
call_user_func_array(array($this, 'queryNull'), $args);
}
public function sqleval() {
$args = func_get_args();
$text = call_user_func_array(array($this, 'parseQueryParams'), $args);
return new MeekroDBEval($text);
}
public function columnList($table) {
return $this->queryOneColumn('Field', "SHOW COLUMNS FROM $table");
}
public function tableList($db = null) {
if ($db) $this->useDB($db);
$result = $this->queryFirstColumn('SHOW TABLES');
if ($db && $this->old_db) $this->useDB($this->old_db);
return $result;
}
public function parseQueryParamsOld() {
$args = func_get_args();
$sql = array_shift($args);
$types = array_shift($args);
$types = str_split($types);
foreach ($args as $arg) {
$type = array_shift($types);
$pos = strpos($sql, '?');
if ($pos === false) $this->nonSQLError("Badly formatted SQL query: $sql");
if ($type == 's') $replacement = "'" . $this->escape($arg) . "'";
else if ($type == 'i') $replacement = intval($arg);
else $this->nonSQLError("Badly formatted SQL query: $sql");
$sql = substr_replace($sql, $replacement, $pos, 1);
}
return $sql;
}
public function parseQueryParamsNew() {
$args = func_get_args();
$sql = array_shift($args);
$args_all = $args;
$posList = array();
$pos_adj = 0;
$param_char_length = strlen($this->param_char);
$types = array(
$this->param_char . 'll', // list of literals
$this->param_char . 'ls', // list of strings
$this->param_char . 'l', // literal
$this->param_char . 'li', // list of integers
$this->param_char . 'ld', // list of decimals
$this->param_char . 'lb', // list of backticks
$this->param_char . 's', // string
$this->param_char . 'i', // integer
$this->param_char . 'd', // double / decimal
$this->param_char . 'b', // backtick
$this->param_char . 'ss' // search string (like string, surrounded with %'s)
);
foreach ($types as $type) {
$lastPos = 0;
while (($pos = strpos($sql, $type, $lastPos)) !== false) {
$lastPos = $pos + 1;
if (isset($posList[$pos]) && strlen($posList[$pos]) > strlen($type)) continue;
$posList[$pos] = $type;
}
}
ksort($posList);
foreach ($posList as $pos => $type) {
$type = substr($type, $param_char_length);
$length_type = strlen($type) + $param_char_length;
if ($arg_number_length = strspn($sql, '0123456789', $pos + $pos_adj + $length_type)) {
$arg_number = substr($sql, $pos + $pos_adj + $length_type, $arg_number_length);
if (! isset($args_all[$arg_number])) $this->nonSQLError("Non existent argument reference (arg $arg_number): $sql");
$arg = $args_all[$arg_number];
} else {
$arg_number = 0;
$arg = array_shift($args);
}
if (in_array($type, array('s', 'i', 'd', 'b', 'l'))) {
$array_type = false;
$arg = array($arg);
$type = 'l' . $type;
} else if ($type == 'ss') {
$result = "'%" . $this->escape(str_replace(array('%', '_'), array('\%', '\_'), $arg)) . "%'";
} else {
$array_type = true;
if (! is_array($arg)) $this->nonSQLError("Badly formatted SQL query: $sql -- expecting array, but didn't get one!");
}
if ($type == 'ls') $result = $this->wrapStr($arg, "'", true);
else if ($type == 'li') $result = array_map('intval', $arg);
else if ($type == 'ld') $result = array_map('floatval', $arg);
else if ($type == 'lb') $result = array_map('$this->formatTableName', $arg);
else if ($type == 'll') $result = $arg;
else if (! $result) $this->nonSQLError("Badly formatted SQL query: $sql");
if (is_array($result)) {
if (! $array_type) $result = $result[0];
else $result = '(' . implode(',', $result) . ')';
}
$sql = substr_replace($sql, $result, $pos + $pos_adj, $length_type + $arg_number_length);
$pos_adj += strlen($result) - ($length_type + $arg_number_length);
}
return $sql;
}
public function parseQueryParams() {
$args = func_get_args();
if (count($args) < 2) return $args[0];
if (is_string($args[1]) && preg_match('/^[is]+$/', $args[1]) && substr_count($args[0], '?') > 0)
return call_user_func_array(array($this, 'parseQueryParamsOld'), $args);
else
return call_user_func_array(array($this, 'parseQueryParamsNew'), $args);
}
public function quickPrepare() { $args = func_get_args(); return call_user_func_array(array($this, 'query'), $args); }
public function query() {
$args = func_get_args();
if ($this->queryMode == 'buffered' || $this->queryMode == 'unbuffered') {
return $this->prependCall(array($this, 'queryHelper'), $args, $this->queryMode);
} else {
return call_user_func_array(array($this, 'queryAllRows'), $args);
}
}
public function queryNull() { $args = func_get_args(); return $this->prependCall(array($this, 'queryHelper'), $args, 'null'); }
public function queryRaw() { $args = func_get_args(); return $this->prependCall(array($this, 'queryHelper'), $args, 'buffered'); }
public function queryBuf() { $args = func_get_args(); return $this->prependCall(array($this, 'queryHelper'), $args, 'buffered'); }
public function queryUnbuf() { $args = func_get_args(); return $this->prependCall(array($this, 'queryHelper'), $args, 'unbuffered'); }
protected function queryHelper() {
$args = func_get_args();
$type = array_shift($args);
if ($type != 'buffered' && $type != 'unbuffered' && $type != 'null') {
$this->nonSQLError('Error -- first argument to queryHelper must be buffered or unbuffered!');
}
$is_buffered = ($type == 'buffered');
$is_null = ($type == 'null');
$sql = call_user_func_array(array($this, 'parseQueryParams'), $args);
$db = $this->get();
if ($this->success_handler) $starttime = microtime(true);
$result = $db->query($sql, $is_buffered ? MYSQLI_STORE_RESULT : MYSQLI_USE_RESULT);
if ($this->success_handler) $runtime = microtime(true) - $starttime;
if (!$sql || $error = $this->checkError()) {
if ($this->error_handler) {
$error_handler = is_callable($this->error_handler) ? $this->error_handler : 'meekrodb_error_handler';
call_user_func($error_handler, array(
'type' => 'sql',
'query' => $sql,
'error' => $error
));
}
if ($this->throw_exception_on_error) {
$e = new MeekroDBException($error, $sql);
throw $e;
}
} else if ($this->success_handler) {
$runtime = sprintf('%f', $runtime * 1000);
$success_handler = is_callable($this->success_handler) ? $this->success_handler : 'meekrodb_debugmode_handler';
call_user_func($success_handler, array(
'query' => $sql,
'runtime' => $runtime
));
}
$this->queryResult = $result;
$this->queryResultType = $type;
$this->insert_id = $db->insert_id;
$this->affected_rows = $db->affected_rows;
if ($is_buffered) $this->num_rows = $result->num_rows;
else $this->num_rows = null;
if ($is_null) {
$this->freeResult($result);
$this->queryResult = $this->queryResultType = null;
return null;
}
return $result;
}
public function queryAllRows() {
$args = func_get_args();
$query = call_user_func_array(array($this, 'queryUnbuf'), $args);
$result = $this->fetchAllRows($query);
$this->freeResult($query);
$this->num_rows = count($result);
return $result;
}
public function queryAllArrays() {
$args = func_get_args();
$query = call_user_func_array(array($this, 'queryUnbuf'), $args);
$result = $this->fetchAllArrays($query);
$this->freeResult($query);
$this->num_rows = count($result);
return $result;
}
public function queryOneList() { $args = func_get_args(); return call_user_func_array(array($this, 'queryFirstList'), $args); }
public function queryFirstList() {
$args = func_get_args();
$query = call_user_func_array(array($this, 'queryUnbuf'), $args);
$result = $this->fetchArray($query);
$this->freeResult($query);
return $result;
}
public function queryOneRow() { $args = func_get_args(); return call_user_func_array(array($this, 'queryFirstRow'), $args); }
public function queryFirstRow() {
$args = func_get_args();
$query = call_user_func_array(array($this, 'queryUnbuf'), $args);
$result = $this->fetchRow($query);
$this->freeResult($query);
return $result;
}
public function queryFirstColumn() {
$args = func_get_args();
$results = call_user_func_array(array($this, 'queryAllArrays'), $args);
$ret = array();
if (!count($results) || !count($results[0])) return $ret;
foreach ($results as $row) {
$ret[] = $row[0];
}
return $ret;
}
public function queryOneColumn() {
$args = func_get_args();
$column = array_shift($args);
$results = call_user_func_array(array($this, 'queryAllRows'), $args);
$ret = array();
if (!count($results) || !count($results[0])) return $ret;
if ($column === null) {
$keys = array_keys($results[0]);
$column = $keys[0];
}
foreach ($results as $row) {
$ret[] = $row[$column];
}
return $ret;
}
public function queryFirstField() {
$args = func_get_args();
$row = call_user_func_array(array($this, 'queryFirstList'), $args);
if ($row == null) return null;
return $row[0];
}
public function queryOneField() {
$args = func_get_args();
$column = array_shift($args);
$row = call_user_func_array(array($this, 'queryOneRow'), $args);
if ($row == null) {
return null;
} else if ($column === null) {
$keys = array_keys($row);
$column = $keys[0];
}
return $row[$column];
}
protected function checkError() {
$db = $this->get();
if ($db->error) {
$error = $db->error;
$db->rollback();
return $error;
}
return false;
}
public function fetchRow($result = null) {
if ($result === null) $result = $this->queryResult;
if (! ($result instanceof MySQLi_Result)) return null;
return $result->fetch_assoc();
}
public function fetchAllRows($result = null) {
$A = array();
while ($row = $this->fetchRow($result)) {
$A[] = $row;
}
return $A;
}
public function fetchArray($result = null) {
if ($result === null) $result = $this->queryResult;
if (! ($result instanceof MySQLi_Result)) return null;
return $result->fetch_row();
}
public function fetchAllArrays($result = null) {
$A = array();
while ($row = $this->fetchArray($result)) {
$A[] = $row;
}
return $A;
}
}
class WhereClause {
public $type = 'and'; //AND or OR
public $negate = false;
public $clauses = array();
public $mdb = null;
function __construct($type, $mdb=null) {
$type = strtolower($type);
if ($type != 'or' && $type != 'and') DB::nonSQLError('you must use either WhereClause(and) or WhereClause(or)');
$this->type = $type;
if ($mdb === null) $this->mdb = DB::getMDB();
else if ($mdb instanceof MeekroDB) $this->mdb = $mdb;
else DB::nonSQLError('the second argument to new WhereClause() must be an instance of class MeekroDB');
}
function add() {
$args = func_get_args();
if ($args[0] instanceof WhereClause) {
$this->clauses[] = $args[0];
return $args[0];
} else {
$r = call_user_func_array(array($this->mdb, 'parseQueryParams'), $args);
$this->clauses[] = $r;
return $r;
}
}
function negateLast() {
$i = count($this->clauses) - 1;
if (!isset($this->clauses[$i])) return;
if ($this->clauses[$i] instanceof WhereClause) {
$this->clauses[$i]->negate();
} else {
$this->clauses[$i] = 'NOT (' . $this->clauses[$i] . ')';
}
}
function negate() {
$this->negate = ! $this->negate;
}
function addClause($type) {
$r = new WhereClause($type);
$this->add($r);
return $r;
}
function count() {
return count($this->clauses);
}
function text() {
if (count($this->clauses) == 0) return '(1)';
$A = array();
foreach ($this->clauses as $clause) {
if ($clause instanceof WhereClause) $clause = $clause->text();
$A[] = '(' . $clause . ')';
}
$A = array_unique($A);
if ($this->type == 'and') $A = implode(' AND ', $A);
else $A = implode(' OR ', $A);
if ($this->negate) $A = '(NOT ' . $A . ')';
return $A;
}
}
class DBTransaction {
private $committed = false;
function __construct() {
DB::startTransaction();
}
function __destruct() {
if (! $this->committed) DB::rollback();
}
function commit() {
DB::commit();
$this->committed = true;
}
}
class MeekroDBException extends Exception {
protected $query = '';
function __construct($message='', $query='') {
parent::__construct($message);
$this->query = $query;
}
public function getQuery() { return $this->query; }
}
function meekrodb_error_handler($params) {
if (isset($params['query'])) $out[] = "QUERY: " . $params['query'];
if (isset($params['error'])) $out[] = "ERROR: " . $params['error'];
$out[] = "";
if (php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR'])) {
echo implode("\n", $out);
} else {
echo implode("<br>\n", $out);
}
debug_print_backtrace();
die;
}
function meekrodb_debugmode_handler($params) {
echo "QUERY: " . $params['query'] . " [" . $params['runtime'] . " ms]";
if (php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR'])) {
echo "\n";
} else {
echo "<br>\n";
}
}
class MeekroDBEval {
public $text = '';
function __construct($text) {
$this->text = $text;
}
}
?>

10
stats.sql Normal file
View File

@ -0,0 +1,10 @@
DROP TABLE IF EXISTS traffic;
CREATE TABLE traffic (
hostname varchar(100) NOT NULL,
selector varchar(100) NOT NULL,
remote_ip INT UNSIGNED NOT NULL,
request_at datetime NOT NULL
);
CREATE INDEX host_traffic_idx ON traffic(hostname, request_at);
CREATE INDEX host_selector_traffic_idx ON traffic(hostname, selector, request_at);

View File

@ -47,23 +47,21 @@
</div>
</div>
<div class="container-fluid">
<div class="row-fluid">
<div class="container">
<div class="row">
<div id="spinner">
<img src="/assets/img/spinner.gif" />
</div>
<div id="spinner">
<img src="/assets/img/spinner.gif" />
</div>
<div class="span4"></div>
<div class="span8">
<?php if ( ! array_key_exists('data', $this->data) ) { ?>
<div id="intro">
<h1>Gophper -- A Gopher Proxy for Modern Times</h1>
<div id="intro" <?php if ( array_key_exists('data', $this->data) ) {?>class="hide"<?php } ?>>
<h1>Gophper: A Gopher Proxy for Modern Times</h1>
<p>
The <a href="http://en.wikipedia.org/wiki/Gopher_(protocol)">Gopher</a>
protocol was published in 1991. It was popular for some
time, but died off in the late 1990s, mostly because of some
time, but died off in the late 1990s, partially because of some
poor decisions by the University of Minnesota (which owned
the licensing rights), and also because HTTP and HTML was
undoubtedly a better system for many things.
@ -95,18 +93,7 @@
<li>Coming Soon: Stats, etc</li>
</ul>
</p>
</div>
<?php } ?>
<div id="breadcrumb"></div>
<div id="gopher">
<?php
if ( array_key_exists('data', $this->data) ) {
echo $this->data['data'];
}
else {
?>
<div class="as-html">
<h3>Let's Do This!</h3>
<p>If you know where you would like to go, enter the URL
@ -134,8 +121,15 @@
<li><a href="/quix.us">quix.us</a></li>
</ul>
</div> <!-- /as-html -->
</div>
<?php } ?>
<div id="breadcrumb"></div>
<div id="gopher">
<?php
if ( array_key_exists('data', $this->data) ) {
echo $this->data['data'];
}
?>
</div> <!-- /gopher -->
</div> <!-- /span10 -->