Location: PHPKode > projects > Obsessive Website Statistics > ows/plugins/15_ows_limits.php
<?php
/*
	$Id: 15_ows_limits.php 112 2007-11-21 14:27:19Z randomperson83 $

	Obsessive Web Statistics
    Copyright (C) 2007 Dustin Spicuzza <hide@address.com>

    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 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

	Implements common limits
	
*/


class OWSLimit implements iPlugin, iLimitPlugin{

	var $sort_items = array('id','host','date','time','request_str', 'filename','status','bytes','referrer','agent');
	var $prefix = "";

	// taken from http://en.wikipedia.org/wiki/List_of_HTTP_status_codes
	var $status_codes = array(
		100 => array(100,'100 Continue'),
		101 => array(101,'101 Switching Protocols'),
		102 => array(102,'102 Processing (WebDAV)'),
		
		200 => array(200,'200 OK'),
		201 => array(201,'201 Created'),
		202 => array(202,'202 Accepted'),
		203 => array(203,'203 Non-Authoritative Information (since HTTP/1.1)'),
		204 => array(204,'204 No Content'),
		205 => array(205,'205 Reset Content'),
		206 => array(206,'206 Partial Content'),
		207 => array(207,'207 Multi-Status (WebDAV)'),

		300 => array(300,'300 Multiple Choices'),
		301 => array(301,'301 Moved Permanently'),
		302 => array(302,'302 Found'),
		303 => array(303,'303 See Other (since HTTP/1.1)'),
		304 => array(304,'304 Not Modified'),
		305 => array(305,'305 Use Proxy (since HTTP/1.1)'),
		306 => array(306,'306 Switch Proxy'),
		307 => array(307,'307 Temporary Redirect (since HTTP/1.1)'),

		400 => array(400,'400 Bad Request'),
		401 => array(401,'401 Unauthorized'),
		402 => array(402,'402 Payment Required'),
		403 => array(403,'403 Forbidden'),
		404 => array(404,'404 Not Found'),
		405 => array(405,'405 Method Not Allowed'),
		406 => array(406,'406 Not Acceptable'),
		407 => array(407,'407 Proxy Authentication Required'),
		408 => array(408,'408 Request Timeout'),
		409 => array(409,'409 Conflict'),
		410 => array(410,'410 Gone'),
		411 => array(411,'411 Length Required'),
		412 => array(412,'412 Precondition Failed'),
		413 => array(413,'413 Request Entity Too Large'),
		414 => array(414,'414 Request-URI Too Long'),
		415 => array(415,'415 Unsupported Media Type'),
		416 => array(416,'416 Requested Range Not Satisfiable'),
		417 => array(417,'417 Expectation Failed'),
		422 => array(422,'422 Unprocessable Entity (WebDAV) (RFC 2518)'),
		423 => array(423,'423 Locked (WebDAV)(RFC 2518)'),
		424 => array(424,'424 Failed Dependency (WebDAV) (RFC 2518)'),
		425 => array(425,'425 Unordered Collection'),
		426 => array(426,'426 Upgrade Required (RFC 2817)'),
		449 => array(449,'449 Retry With'),
		
		500 => array(500,'500 Internal Server Error'),
		501 => array(501,'501 Not Implemented'),
		502 => array(502,'502 Bad Gateway'),
		503 => array(503,'503 Service Unavailable'),
		504 => array(504,'504 Gateway Timeout'),
		505 => array(505,'505 HTTP Version Not Supported'),
		506 => array(506,'506 Variant Also Negotiates (RFC 2295)'),
		507 => array(507,'507 Insufficient Storage (WebDAV)'),
		509 => array(509,'509 Bandwidth Limit Exceeded'),
		510 => array(510,'510 Not Extended (RFC 2774)')
		);
	
	// TODO: add more items to this list, and make the list MUCH better!
	// TODO: Make this list easier to maintain/add to, less typing?
	// TODO: Is this even needed? We can already specify these at a dimensional level now
	// do generic types at the top, then move to specific file extensions
	/*var $filetype_items = array(
		'image' => array('image','Image Files','.jpg|.gif|.png|.bmp'),
		'html' => array('html','HTML files'),
		'music' => array('music','Music Files'),
		'flash' => array('flash','Flash Files'),
		
		'.exe' => array('.exe','.exe - Executable file'),
		'.zip' => array('.zip','.zip - Compressed file'),
		'.php' => array('.php','.php - PHP Script Files')
	);
	*/

	
	function __construct(){
		$this->prefix = $this->getPluginId() . '_';
	}
	
	// this should return a unique ID identifying the plugin, should start with an alpha,
	// should use basename instead of just __FILE__ otherwise it could expose path information
	public function getPluginId(){
		return 'p'. md5(basename(__FILE__) . get_class());
	}


	// returns an associative array describing the plugin
	public function getPluginInformation(){
		// automagically increment the revision number :)
		$revision = trim(str_replace('Rev:','',str_replace('$','','$Rev: 112 $')));
		return array(
			'author' => 'Dustin Spicuzza (OWS builtin)',
			'pluginName' => 'Optional Limits Plugin',
			'version' => "1.0.$revision",
			'description' => 'Implements builtin optional limits',
			'url' => 'http://obsessive.sourceforge.net/'
		);
	}
	

	// this function outputs html which is displayed inside of a form
	// submit button is already defined for you. Call show_common_limits
	// to use the standard limit section (by date, by time, exclude bots, etc)
	
	// technically, these all *should* be put in seperate plugins
	public function showLimits(){

		global $sort_items;
		
		$onclick_duplicate = '<span class="addsub" onclick="$(this).parent().after($(this).parent().clone().css(\'display\',\'none\').fadeIn(\'normal\')).after(\'<br/>\');">+</span> ';
		$onclick_duplicate .= '<span class="addsub" onclick="if($(this).parent().parent().children().length < 3){ return;} $(this).parent().fadeOut(\'normal\',function(){if($(this).prev().length) { $(this).prev().remove(); } else if ($(this).next().length) { $(this).next().remove(); } $(this).remove();});">-</span>';
		
		
		echo "<tr><td>Time Limits:</td><td>
		<input type=\"radio\" name=\"" . $this->prefix . "date_sel\" value=\"0\" />None 
		<input type=\"radio\" name=\"" . $this->prefix . "date_sel\" value=\"1\" checked />Dates 
		<input type=\"radio\" name=\"" . $this->prefix . "date_sel\" value=\"2\" /> Time of day 
		<input type=\"radio\" name=\"" . $this->prefix . "date_sel\" value=\"3\" /> Both</td></tr>";
		
		// date limits
		echo "<tr class=\"" . $this->prefix . "cdate\"><td>Date</td><td>" . generate_select_from_array($this->prefix . 'cc_date','thismonth', array(array('today','Today'), array('yesterday','Yesterday'), array('thisweek','This Week'),  array('thismonth','This Month'), array('thisyear','This Year'), array('lastweek','Last Week'), array('lastmonth','Last Month'), array('last7','Last 7 Days'), array('last30','Last 30 Days'), array('last365','Last 365 Days'), array('custom','Custom')));
		
		echo " <span id=\"" . $this->prefix . "ddate\"> date between <input type=\"text\" name=\"" . $this->prefix . "date_from\" class=\"calendarFocus\" value=\"" . date("m/d/Y",strtotime("-1 month")) . "\"/> and <input type=\"text\" name=\"" . $this->prefix . "date_to\" class=\"calendarFocus\" value=\"" . date("m/d/Y") . "\"/></span></td></tr>";
		
		echo "<script type=\"text/javascript\">
		// workaround for FF caching.. 
		$(document).ready(function () {
			$(\"select[@name='" . $this->prefix . "cc_date']\").change(" . $this->prefix . "change_date);
			$(\"input[@name='" . $this->prefix . "date_sel']\").change(" . $this->prefix . "change_tl);
			" . $this->prefix . "change_date();
			" . $this->prefix . "change_tl();
		});
		function " . $this->prefix . "change_tl(){
			
			switch(parseInt($(\"input[@name='" . $this->prefix . "date_sel']:checked\").val())){
				case 0:
					$('." . $this->prefix . "cdate').hide();
					$('." . $this->prefix . "ctime').hide();
					break;
				case 1:
					$('." . $this->prefix . "cdate').show();
					$('." . $this->prefix . "ctime').hide();
					break;
				case 2:
					$('." . $this->prefix . "cdate').hide();
					$('." . $this->prefix . "ctime').show();
					break;
				case 3:
					$('." . $this->prefix . "cdate').show();
					$('." . $this->prefix . "ctime').show();
					break;
			}
		}
		
		function " . $this->prefix . "change_date(){
			if ($(\"select[@name='" . $this->prefix . "cc_date']\").val() == 'custom') 
				$('#" . $this->prefix . "ddate').show(); 
			else 
				$('#" . $this->prefix . "ddate').hide();
		}
		</script>";

		
		// time limits
		echo "<tr class=\"" . $this->prefix . "ctime\" style=\"display:none\"><td>Times between</td><td><input type=\"text\" name=\"" . $this->prefix . "time_from\" /> and <input type=\"text\" name=\"" . $this->prefix . "time_to\" /></td></tr>";
		
		// TODO: add directory/file limits
		//echo "<tr><td>Directory:</td><td><input type=\"text\" value=\"Does not work\" /></td></tr>";


		// bot analysis
		echo "\t<tr><td>Bots:</td><td>" . generate_select_from_array($this->prefix . 'bot','exclude',array(array('any','Show Any'),array('exclude','Exclude Bots'),array('only','Show only bots'))) . "</td></tr>\n";
		
		// sort by
		//echo "<tr class=\"excessive\"><td>Sort By:</td><td><span>" . generate_select_from_array($this->prefix . 'sortby[]','',$this->sort_items,true) . generate_select_from_array($this->prefix . 'ascdesc[]','asc',array(array('asc','Ascending'),array('desc','Descending'))) . "</span> $onclick_duplicate</td></tr>";
		
		// status code
		echo "\t<tr class=\"excessive\"><td>HTTP Status Code</td><td><span>" . generate_select_from_array($this->prefix . 'status[]','',$this->status_codes,true) . " $onclick_duplicate</span></td></tr>\n";
		
		// internal/external referrers
		echo "\t<tr class=\"excessive\"><td>Referrers</td><td><span>" . generate_select_from_array($this->prefix . 'referrer[]','any',array(array('any','Show Any'),array('internal','Internal Referrers'),array('external','External Referrers'),array('blank','Blank Referrers')),false) . " $onclick_duplicate</span></td></tr>\n";
		
	}

	// this function modifies the filter's SQL query. This function should look for 
	// form variables set by showOptions
	public function limitResults($domain,&$query){
	
		
		$date_sel = get_post_var($this->prefix . 'date_sel');
		$date_from = strtotime(get_post_var($this->prefix . 'date_from'));
		$date_to = strtotime(get_post_var($this->prefix . 'date_to'));
		$date_cc = get_post_var($this->prefix . 'cc_date');

		$time_from = strtotime(get_post_var($this->prefix . 'time_from'));
		$time_to = strtotime(get_post_var($this->prefix . 'time_to'));
		
		$bot = get_post_var($this->prefix . 'bot');
		
		$sortby = get_post_var($this->prefix . 'sortby');
		$ascdesc = get_post_var($this->prefix . 'ascdesc');
		
		$status = get_post_var($this->prefix . 'status');
		$referrer = get_post_var($this->prefix . 'referrer');
		
		$options = get_current_website_options();
		
		// giant switch statement to determine the date limits
		if ($date_sel == 1 || $date_sel == 3){
		
			// TODO: Optimize usage for dimension attributes
			$dimension = $query->DIMENSION('date');
		
			switch ($date_cc){
				case 'today':
					$query->WHERE("$dimension.date = '" . date('Y-m-d') ."'");
					break;
				case 'yesterday':
					$query->WHERE("$dimension.date = '" . date('Y-m-d',strtotime('-1 day'))."'");
					break;
				case 'thisweek':
					$query->WHERE("$dimension.date >= '" . date('Y-m-d',strtotime('-' . intval(date('w')) . ' days'))."'");
					break;
				case 'lastweek':
					$lweek_e = strtotime('-' . intval(date('w')) . '-1 days');
					$lweek_s = strtotime('-7 days',$lweek_e);
					$query->WHERE("$dimension.date >= '" . date('Y-m-d',$lweek_s) . "' AND $dimension.date <= '" . date('Y-m-d',$lweek_e) . "'");
					break;
				case 'thismonth':
					$query->WHERE("$dimension.date >= '" . date('Y-m-d',strtotime(date('Y-m').'-01'))."'");
					break;
				case 'lastmonth':
					$lmonth_e = strtotime('-1 day',strtotime(date('Y-m').'-01'));
					$lmonth_s = date('Y-m-01',$lmonth_e);
					$query->WHERE("$dimension.date >= '$lmonth_s' AND $dimension.date <= '" . date('Y-m-d',$lmonth_e) . "'");
					break;
				case 'thisyear':
					$query->WHERE("$dimension.date >= '" . date('Y-m-d',strtotime(date('Y').'-01-01'))."'");
					break;
				case 'last7':
					$query->WHERE("$dimension.date >= '" . date('Y-m-d',strtotime('-7 days'))."'");
					break;
				case 'last30':
					$query->WHERE("$dimension.date >= '" . date('Y-m-d',strtotime('-30 days'))."'");
					break;
				case 'last365':
					$query->WHERE("$dimension.date >= '" . date('Y-m-d',strtotime('-365 days'))."'");
					break;
				case 'custom':
					if ($date_from != -1 && $date_from !== false){
					
						$dsql = "$dimension.date >= '" . date("Y-m-d",$date_from) . "' ";
						if ($date_to != -1 && $date_to !== false){
							if ($date_to < $date_from)
								$dsql = '';
							else
								$dsql .= "AND $dimension.date <= '" . date("Y-m-d",$date_to) . "'";
						}
						
						$query->WHERE($dsql);
						
					}else if ($date_to != -1 && $date_to !== false)
						$query->WHERE("$dimension.date <= '" . date("Y-m-d",$date_to) . "'");
					break;
				default:
					return show_error("Invalid date type specified: " . htmlentities($date_cc) . "!");
			}
		}

		// if either to or from values are invalid, then discard the filter
		if ($date_sel == 2 || $date_sel == 3){
			if ($time_from != -1 && $time_from !== false){
			
				$dimension = $query->DIMENSION('time');
			
				$tsql = "$dimension.time >= '" . date("H:i",$time_from) . "' ";
				if ($time_to != -1 && $time_to !== false){
					if ($time_to < $time_from)
						$tsql = '';
					else
						$tsql .= " AND $dimension.time <= '" . date("H:i",$time_to) . "' ";
				}
				
				$query->WHERE($tsql);
				
			}else if ($time_to != -1 && $time_to !== false){
				$dimension = $query->DIMENSION('date');
				$query->WHERE("$dimension.time <= '" . date("H:i",$time_to) . "'");
			}
		}
		
		
		// bot analysis
		switch ($bot){
			case "exclude":
				$dimension = $query->DIMENSION('bot');
				$query->WHERE("$dimension.is_bot = FALSE");
				break;
			case "only":
				$dimension = $query->DIMENSION('bot');
				$query->WHERE("$dimension.is_bot = TRUE");
				break;	
		}
		
	
		// sort by
		/*
		
			This implementation doesn't make sense in a dimensional context
		
		if (is_array($sortby) && is_array($ascdesc)){
			$sortby = array_unique($sortby);
			foreach ($sortby as $k => $v){
				if (in_array($v, $this->sort_items) && array_key_exists($k, $ascdesc))
					$query->ORDER_BY("$dimension.$v" . (in_array($ascdesc[$k],array('asc','desc')) ? " " . strtoupper($ascdesc[$k]) : ""));
			}
		}*/
	
		// status codes
		if (is_array($status)){
		
			$dimension = $query->DIMENSION('status');
		
			$status = array_unique($status);
			$where = array();
			foreach ($status as $s){
				if (array_key_exists($s,$this->status_codes))
					$where[] = "$dimension.status = $s";
			}
			if (count($where) > 0)
				$query->WHERE(implode($where,' OR '));
		}
		
		// referrer analysis
		if (is_array($referrer)){
			$referrer = array_unique($referrer);
			
			if (!in_array('any',$referrer)){
				
				$where = array();
				
				// internal referrers only
				if (in_array( 'internal', $referrer)){
					$dimension = $query->DIMENSION('referrer');
					$where[] = "$dimension.is_external = 0";
				}
				
				// external referrers only
				if (in_array('external', $referrer)){
					$dimension = $query->DIMENSION('referrer');
					$where[] = "$dimension.is_external = 1";
				}
					
				// blank referrers
				if (in_array('blank',$referrer)){
					$dimension = $query->DIMENSION('referrer');
					$where[] = "$dimension.referrer = ''";
				}
			
				if (count($where) > 0)
					$query->WHERE(implode($where,' OR '));
			}
		}
		
	}
}

register_plugin('limit',new OWSLimit());

?>
Return current item: Obsessive Website Statistics