<?php
/*
* phpWebLogAnalyzer - powerful weblog and analyzer
*
* phpWebLogAnalyzer.inc.php - main file of PHP API
* ____________________________________________________________
*
* Developed by Ondrej Jombik <nepto@platon.sk>
* Copyright (c) 2001-2005 Platon SDG, http://platon.sk/
* All rights reserved.
*
* See README file for more information about this software.
* See COPYING file for license information.
*
* Download the latest version from
* http://platon.sk/projects/phpWebLogAnalyzer/
*/
/* $Platon: phpWebLogAnalyzer/phpWebLogAnalyzer.inc.php,v 1.52 2005/03/23 18:14:52 nepto Exp $ */
define('WEB_LOG_VALUE_SUBSTR_LENGTH', 255);
define('WEB_LOG_SESSION_TIMEOUT_DEFAULT', 1800);
define('WEB_LOG_RAW_LOG_DEFAULT', 1);
define('WEB_LOG_TRUNCATE_NAMES_DEFAULT', 0);
define('WEB_LOG_TRUNCATE_QUERY_STRINGS_DEFAULT', 1);
define('WEB_LOG_CACHE_MAX_PROPERTY_COUNT', 6000);
/* Internally used for _getCount() */
define('WEB_LOG_SESSIONS_COUNT_TYPE', 1);
define('WEB_LOG_ACCESSES_COUNT_TYPE', 2);
/*
* Pear libraries
*/
require_once 'DB.php';
require_once 'Net/Ident.php';
class Web_Log
{
/* Session variables
$_session_timeout - maximum delay between two click from the same site
with the same user agent to count it as one session
$_session_keys - keys of properties (keys to $_props array) which define
session; for each session all values of particular key
are the same
*/
var $_session_timeout;
var $_session_keys = array('remote_addr', 'http_user_agent');
/* Ident variables
$_ident_timeout - timeout for retrieving ident data from socket
*/
var $_ident_timeout;
/* Raw log variable
$_raw_log - use raw log table for logging (it is faster than normal
logging, but it needs some job to transform data from
raw log table to normal table, for example via cron)
*/
var $_raw_log;
/* Short host names variable
$_truncate_names - replace the last component of IP and the first
component of hostname if exists address with '*'
character
*/
var $_truncate_names;
/* Truncated query strings
$_truncate_qstrings - flag to truncate query strings
$_truncate_qstrings_keys - keys to truncate (case unsensitive)
*/
var $_truncate_qstrings;
var $_truncate_qstrings_keys = array(/*'session',*/ 'PHPSESSID');
/* Database variables
$_db - PEAR DB object
$_dsn - database connection URI (db_type://user:pass@host/db_name)
*/
var $_db;
var $_dsn;
/* Cache variables
$_cache_active_sessions - cached result of active sessions query
$_cache_inactive_sessions - cached result of inactive sessions query
*/
//var $_cache_sessions_count;
//var $_cache_raw_log_sessions_count;
var $_cache_active_sessions;
var $_cache_raw_log_active_sessions;
var $_cache_inactive_sessions;
var $_cache_raw_log_inactive_sessions;
/* $_props - properties array {{{
keys: identifiers (when user is specifing some property for some
special action (ingnore property or add property to list
of session definers), it is provided via this identifiers)
values: arrays; keys of each array are described below
key description
------ -----------
column column name in main accesses table
table table name for values storing (in accesses table will be only
ID to this table stored)
var how to get property value; if starts with '$' character, than
it is eval()-ed, if not, than is string passed to getenv()
function to obtain value
value value of property
id id of property; defined only if value is stored in its own
separate table
enabled if empty, property is disabled; no action with that property
is done; by default, all properties are enabled (not disabled)
*/
var $_props = array(
'id' => array(
'column' => 'id',
'table' => '',
'var' => '' ),
'datetime' => array(
'column' => 'datetime',
'table' => '',
'var' => '' ),
'value' => array(
'column' => 'value_id',
'table' => 'weblog_values',
'var' => '' ),
'request_method' => array(
'column' => 'request_method_id',
'table' => 'weblog_request_methods',
/*'var' => '$HTTP_SERVER_VARS["REQUEST_METHOD"]' ),*/
'var' => 'REQUEST_METHOD' ),
'query_string' => array(
'column' => 'query_string_id',
'table' => 'weblog_query_strings',
/*'var' => '$HTTP_SERVER_VARS["QUERY_STRING"]' ),*/
'var' => 'QUERY_STRING' ),
'path_info' => array(
'column' => 'path_info_id',
'table' => 'weblog_path_infos',
'var' => 'PATH_INFO' ),
'path_translated' => array( /* must be retrieved from SERVER_VARS */
'column' => 'path_translated_id',
'table' => 'weblog_paths_translated',
'var' => '$HTTP_SERVER_VARS["PATH_TRANSLATED"]' ),
'content_type' => array(
'column' => 'content_type_id',
'table' => 'weblog_content_types',
'var' => '$HTTP_SERVER_VARS["CONTENT_TYPE"]' ),
'content_length' => array(
'column' => 'content_length',
'table' => '',
'var' => '$HTTP_SERVER_VARS["CONTENT_LENGTH"]' ),
'script_name' => array(
'column' => 'script_name_id',
'table' => 'weblog_script_names',
/*'var' => '$HTTP_SERVER_VARS["SCRIPT_NAME"]' ),*/
'var' => 'SCRIPT_NAME' ),
'server_name' => array(
'column' => 'server_name_id',
'table' => 'weblog_server_names',
'var' => '$HTTP_SERVER_VARS["SERVER_NAME"]' ),
'server_port' => array(
'column' => 'server_port',
'table' => '',
'var' => '$HTTP_SERVER_VARS["SERVER_PORT"]' ),
'server_software' => array(
'column' => 'server_software_id',
'table' => 'weblog_server_softwares',
'var' => '$HTTP_SERVER_VARS["SERVER_SOFTWARE"]' ),
'server_protocol' => array(
'column' => 'server_protocol_id',
'table' => 'weblog_server_protocols',
'var' => '$HTTP_SERVER_VARS["SERVER_PROTOCOL"]' ),
'gateway_interface' => array(
'column' => 'gateway_interface_id',
'table' => 'weblog_gateway_interfaces',
'var' => '$HTTP_SERVER_VARS["GATEWAY_INTERFACE"]' ),
'proxy_addr' => array(
'column' => 'proxy_addr_id',
'table' => 'weblog_remote_addrs',
'var' => '' ),
'proxy_host' => array(
'column' => 'proxy_host_id',
'table' => 'weblog_remote_hosts',
'var' => '' ),
'remote_addr' => array(
'column' => 'remote_addr_id',
'table' => 'weblog_remote_addrs',
'var' => '' ),
'remote_host' => array(
'column' => 'remote_host_id',
'table' => 'weblog_remote_hosts',
'var' => '' ),
'remote_port' => array(
'column' => 'remote_port',
'table' => '',
'var' => '$HTTP_SERVER_VARS["REMOTE_PORT"]' ),
'auth_type' => array(
'column' => 'auth_type_id',
'table' => 'weblog_auth_types',
'var' => 'AUTH_TYPE' ),
'remote_user' => array(
'column' => 'remote_user_id',
'table' => 'weblog_remote_users',
'var' => '$HTTP_SERVER_VARS["REMOTE_USER"]' ),
'remote_ident' => array(
'column' => 'remote_ident_id',
'table' => 'weblog_remote_idents',
'var' => '' ),
'document_root' => array(
'column' => 'document_root_id',
'table' => 'weblog_document_roots',
'var' => '$HTTP_SERVER_VARS["DOCUMENT_ROOT"]' ),
'http_accept' => array(
'column' => 'http_accept_id',
'table' => 'weblog_http_accepts',
'var' => '$HTTP_SERVER_VARS["HTTP_ACCEPT"]' ),
'http_accept_charset' => array(
'column' => 'http_accept_charset_id',
'table' => 'weblog_http_accept_charsets',
'var' => '$HTTP_SERVER_VARS["HTTP_ACCEPT_CHARSET"]' ),
'http_accept_encoding' => array(
'column' => 'http_accept_encoding_id',
'table' => 'weblog_http_accept_encodings',
'var' => '$HTTP_SERVER_VARS["HTTP_ACCEPT_ENCODING"]' ),
'http_accept_language' => array(
'column' => 'http_accept_language_id',
'table' => 'weblog_http_accept_languages',
'var' => '$HTTP_SERVER_VARS["HTTP_ACCEPT_LANGUAGE"]' ),
'http_connection' => array(
'column' => 'http_connection_id',
'table' => 'weblog_http_connections',
'var' => '$HTTP_SERVER_VARS["HTTP_CONNECTION"]' ),
'http_host' => array(
'column' => 'http_host_id',
'table' => 'weblog_http_hosts',
'var' => '$HTTP_SERVER_VARS["HTTP_HOST"]' ),
'http_referer' => array(
'column' => 'http_referer_id',
'table' => 'weblog_http_referers',
'var' => '$HTTP_SERVER_VARS["HTTP_REFERER"]' ),
'http_user_agent' => array(
'column' => 'http_user_agent_id',
'table' => 'weblog_http_user_agents',
'var' => '$HTTP_SERVER_VARS["HTTP_USER_AGENT"]' ),
'script_filename' => array(
'column' => 'script_filename_id',
'table' => 'weblog_script_filenames',
'var' => '$HTTP_SERVER_VARS["SCRIPT_FILENAME"]' ),
'server_admin' => array(
'column' => 'server_admin_id',
'table' => 'weblog_server_admins',
'var' => '$HTTP_SERVER_VARS["SERVER_ADMIN"]' ),
'server_signature' => array(
'column' => 'server_signature_id',
'table' => 'weblog_server_signatures',
'var' => '$HTTP_SERVER_VARS["SERVER_SIGNATURE"]' ),
'request_uri' => array(
'column' => 'request_uri_id',
'table' => 'weblog_request_uris',
'var' => '$HTTP_SERVER_VARS["REQUEST_URI"]' ),
'ident_username' => array(
'column' => 'ident_username_id',
'table' => 'weblog_ident_usernames',
'var' => '' ),
'ident_ostype' => array(
'column' => 'ident_ostype_id',
'table' => 'weblog_ident_ostypes',
'var' => '' ),
'session_id' => array(
'column' => 'session_id',
'table' => '',
'var' => '' )
); /* }}} */
/*
* Web_Log()
*
* Object constructor.
*/
function Web_Log($dsn = '', $session_timeout = 0, $ident_timeout = 0) /* {{{ */
{
if (empty($session_timeout))
$session_timeout = WEB_LOG_SESSION_TIMEOUT_DEFAULT;
$this->_session_timeout = intval($session_timeout);
$this->_ident_timeout = intval($ident_timeout);
$this->_raw_log = WEB_LOG_RAW_LOG_DEFAULT;
$this->_truncate_names = WEB_LOG_TRUNCATE_NAMES_DEFAULT;
$this->_truncate_qstrings = WEB_LOG_TRUNCATE_QUERY_STRINGS_DEFAULT;
unset($this->_db);
unset($this->_dsn);
//unset($this->_cache_sessions_count);
//unset($this->_cache_raw_log_sessions_count);
unset($this->_cache_active_sessions);
unset($this->_cache_raw_log_active_sessions);
unset($this->_cache_inactive_sessions);
unset($this->_cache_raw_log_inactive_sessions);
if (! empty($dsn)) {
$this->setDSN($dsn);
}
foreach (array_keys($this->_props) as $prop) {
$this->_props[$prop]['enabled'] = true;
$this->_props[$prop]['enabled_cache'] = false;
}
} /* }}} */
/*
* destroy()
*
* Destructor. Must be called.
*/
function destroy() /* {{{ */
{
if (isset($this->_db)) {
$this->_db->disconnect();
unset($this->_db);
}
} /* }}} */
/*
* setSessionTimeout(), getSessionTimeout()
*
* Sets/gets session timeout. Session timeout is a maximum delay between
* two click from the same site with the same user agent to count it as
* one session.
*/
function setSessionTimeout($timeout) /* {{{ */
{
$this->_session_timeout = intval($timeout);
} /* }}} */
function getSessionTimeout() /* {{{ */
{
return $this->_session_timeout;
} /* }}} */
/*
* setSessionKeys(), getSessionKeys()
*
* Sets/gets session keys.
* If string specified, value will be appended to array.
*/
function setSessionKeys($keys) /* {{{ */
{
if (is_array($keys)) {
$this->_session_keys = $keys;
} elseif (is_string($keys)) {
array_push($this->_session_keys, $keys);
}
} /* }}} */
function getSessionKeys() /* {{{ */
{
return $this->_session_keys;
} /* }}} */
/*
* enableRawLog(), disableRawLog()
*
* Enable/disable of logging to raw acesses table.
*/
function enableRawLog() /* {{{ */
{
$this->_raw_log = 1;
} /* }}} */
function disableRawLog() /* {{{ */
{
$this->_raw_log = 0;
} /* }}} */
/*
* enableTruncateNames(), disableTruncateNames()
*
* Enable/disable of short IP addresses and hostnames usage.
*/
function enableTruncateNames() /* {{{ */
{
$this->_truncate_names = 1;
} /* }}} */
function disableTruncateNames() /* {{{ */
{
$this->_truncate_names = 0;
} /* }}} */
/*
* enableTruncateQueryStrings(), disableTruncateQueryStrings()
* setTruncateQueryStringsKeys(), getTruncateQueryStringsKeys()
*
* Enable/disable trincating of query strings according to specified
* query strings keys.
*
* Sets/gets truncate query strings keys. If string specified, value
* will be appended to array.
*/
function enableTruncateQueryStrings() /* {{{ */
{
$this->_truncate_qstrings = 1;
} /* }}} */
function disableTruncateQueryStrings() /* {{{ */
{
$this->_truncate_qstrings = 1;
} /* }}} */
function setTruncateQueryStringsKeys($keys) /* {{{ */
{
if (is_array($keys)) {
$this->_truncate_qstrings_keys = $keys;
} elseif (is_string($keys)) {
array_push($this->_truncate_qstrings_keys, $keys);
}
} /* }}} */
function getTruncateQueryStringsKeys() /* {{{ */
{
return $this->_truncate_qstrings_keys;
} /* }}} */
/*
* enablePropertyCache(), disablePropertyCache()
* TODO: getPropertyCache(), setPropertyCache() - ???
* enableAllPropertyCaches(), disableAllPropertyCaches()
*
* Enable/disable of specific property cache.
*/
function enablePropertyCache($property) /* {{{ */
{
if (isset($this->_props[$property]))
$this->_props[$property]['enabled_cache'] = true;
} /* }}} */
function disablePropertyCache($property) /* {{{ */
{
if (isset($this->_props[$property]))
$this->_props[$property]['enabled_cache'] = false;
} /* }}} */
function enableAllPropertyCaches() /* {{{ */
{
foreach (array_keys($this->_props) as $property) {
$this->_props[$property]['enabled_cache'] = true;
}
return true;
} /* }}} */
function disableAllPropertyCaches() /* {{{ */
{
foreach (array_keys($this->_props) as $property) {
$this->_props[$property]['enabled_cache'] = false;
}
return true;
} /* }}} */
/*
* enableProperty(), disableProperty()
*
* Enable/disable loging of specific property.
*/
function enableProperty($property) /* {{{ */
{
if (isset($this->_props[$property]))
$this->_props[$property]['enabled'] = true;
} /* }}} */
function disableProperty($property) /* {{{ */
{
if (isset($this->_props[$property]))
$this->_props[$property]['enabled'] = false;
} /* }}} */
/*
* enableAllProperties(), disableAllProperties()
*
* Enable/disable loging of all properties.
*/
function enableAllProperties() /* {{{ */
{
foreach (array_keys($this->_props) as $property) {
$this->_props[$property]['enabled'] = true;
}
return true;
} /* }}} */
function disableAllProperties() /* {{{ */
{
foreach (array_keys($this->_props) as $property) {
$this->_props[$property]['enabled'] = false;
}
return true;
} /* }}} */
/*
* getPropertyValue(), setPropertyValue(), clearPropertyValue()
*
* Gets/sets specific property value. Property cannot be disabled.
*/
function getPropertyValue($prop_name) /* {{{ */
{
if (isset($this->_props[$prop_name])) {
if (! $this->_props[$prop_name]['enabled'])
return false;
return isset($this->_props[$prop_name]['value'])
? $this->_props[$prop_name]['value']
: false;
}
return false;
} /* }}} */
function setPropertyValue($prop_name, $value) /* {{{ */
{
if (isset($this->_props[$prop_name])) {
$this->_props[$prop_name]['value'] = $value;
return true;
}
return false;
} /* }}} */
function clearPropertyValue($value) /* {{{ */
{
if (isset($this->_props[$prop_name])) {
if (isset($this->_props[$prop_name]['value']))
unset($this->_props[$prop_name]['value']);
return true;
}
return false;
} /* }}} */
/*
* setAllPropertyValues(), clearAllPropertyValues()
* setAllPropertyIDs(), clearAllPropertyIDs()
* setAllProperties(), clearAllProperties()
*
* Set/clear all properties.
*/
function setAllPropertyValues() /* {{{ */
{
/* TODO: this should set only values, not IDs
there should exists also:
1. setAllPropertyIDs() for setting up IDs
2. setAllProperties() for setting up values and than IDs
*/
$this->setStaticValues();
return $this->setDynamicValues();
} /* }}} */
function clearAllPropertyValues() /* {{{ */
{
foreach (array_keys($this->_props) as $property) {
if (isset($this->_props[$property]['value']))
unset($this->_props[$property]['value']);
}
return true;
} /* }}} */
/* NOT IMPLEMENTED YET, see comment above
function setAllPropertyIDs()
{
return $this->setValues();
}
*/
function clearAllPropertyIDs() /* {{{ */
{
foreach (array_keys($this->_props) as $property) {
if (isset($this->_props[$property]['id']))
unset($this->_props[$property]['id']);
}
return true;
} /* }}} */
function setAllProperties() /* {{{ */
{
/* TODO: see comment above */
$this->setStaticValues();
return $this->setDynamicValues();
} /* }}} */
function clearAllProperties() /* {{{ */
{
$ret = true;
$ret &= $this->clearAllPropertyValues();
$ret &= $this->clearAllPropertyIDs();
return $ret;
} /* }}} */
/*
* getPropertiesArray()
*
* Gets whole properties array.
*/
function getPropertiesArray() /* {{{ */
{
return $this->_props;
} /* }}} */
/*
* setDSN()
*
* Gets/sets DSN (db_type://user:pass@host/db_name)
* for database connection.
*/
function getDSN($dsn) /* {{{ */
{
return $this->_dsn;
} /* }}} */
function setDSN($dsn) /* {{{ */
{
$this->_dsn = $dsn;
} /* }}} */
/*
* _checkDB()
*
* Checks if $this->_db is a valid PEAR DB object. If not it creates a new
* one using $this->_dsn variable.
*/
function _checkDB() /* {{{ */
{
if (isset($this->_db)) {
if (DB::isError($this->_db))
return false;
} else {
$this->_db = DB::connect($this->_dsn);
if (DB::isError($this->_db))
return false;
$this->_db->setFetchMode(DB_FETCHMODE_ASSOC);
}
return true;
} /* }}} */
/**
* Reconnects to database.
*
* @return mixed true on success, PEAR_Error on error
* @access public
*/
function reconnect() /* {{{ */
{
unset($this->_db);
if ($this->_checkDB() == false) {
return $this->_db;
}
return true;
} /* }}} */
/*
* _resolveIP()
*
* Resolves IP address to hostname.
*/
function _resolveIP($addr) /* {{{ */
{
$host = '';
$addr_ar = strchr($addr, ',') ? explode(',', $addr) : array($addr);
$addr_idx = count($addr_ar);
while ($addr_idx > 0) {
$addr_idx--;
$addr_ar[$addr_idx] = trim($addr_ar[$addr_idx]);
if (preg_match('/^(\d{1,3}\.){3}\d{1,3}$/', $addr_ar[$addr_idx])) {
$host = @gethostbyaddr($addr_ar[$addr_idx]);
if (! strcmp($host, $addr_ar[$addr_idx]))
$host = '';
break;
}
}
return $host;
} /* }}} */
/*
* _truncateName()
*
* Truncates hostname or IP address.
*/
function _truncateName($name) /* {{{ */
{
$name_ar = explode('.', $name);
if (count($name_ar) > 2) {
if (is_numeric($name_ar[0])) {
array_pop($name_ar);
$name = join('.', $name_ar) . '.*';
} else {
array_shift($name_ar);
$name = '*.' . join('.', $name_ar);
}
}
return $name;
} /* }}} */
function _truncateQueryString($qstring) /* {{{ */
{
$ar_out = array();
$ar = explode('&', $qstring);
foreach ($ar as $part) {
$match = 0;
foreach ($this->_truncate_qstrings_keys as $key) {
if (! strncasecmp($part, $key, strlen($key))) {
$match = 1;
break;
}
}
if ($match == 0)
$ar_out[] = $part;
}
return join('&', $ar_out);
} /* }}} */
/*
* _setXxxValues()
*
* Sets properties values.
*/
function _setBasicValues() /* {{{ */
{
global $HTTP_SERVER_VARS;
global $HTTP_ENV_VARS;
/*
* Sets $_props values according appropriate 'var' if defined.
*/
foreach (array_keys($this->_props) as $prop_name) {
if (! $this->_props[$prop_name]['enabled'])
continue;
if (! empty($this->_props[$prop_name]['var'])) {
$this->_props[$prop_name]['value'] = stripslashes(
$this->_props[$prop_name]['var'][0] == '$'
? eval('return'
. ' (isset('.$this->_props[$prop_name]['var'].')'
. ' ? '.$this->_props[$prop_name]['var'].' : false);')
: getenv($this->_props[$prop_name]['var']));
}
}
if ($this->_truncate_qstrings) {
$req_uri_ar = explode('?', $this->_props['request_uri']['value'], 2);
$http_ref_ar = explode('?', $this->_props['http_referer']['value'], 2);
if (count($this->_truncate_qstrings_keys) == 0) {
$this->_props['query_string']['value'] = '';
$req_uri_ar[1] = '';
$url_ar = parse_url($http_ref_ar[0]);
if (! strcasecmp($url_ar['host'],
$HTTP_SERVER_VARS['HTTP_HOST']))
$http_ref_ar[1] = '';
} else {
$this->_props['query_string']['value'] =
$this->_truncateQueryString($this->_props['query_string']['value']);
if (isset($req_uri_ar[1]))
if (($req_uri_ar[1] = $this->_truncateQueryString(
$req_uri_ar[1])) == '')
array_pop($req_uri_ar);
if (isset($http_ref_ar[1]))
if (($http_ref_ar[1] = $this->_truncateQueryString(
$http_ref_ar[1])) == '')
array_pop($http_ref_ar);
}
$this->_props['request_uri']['value'] = join('?', $req_uri_ar);
$this->_props['http_referer']['value'] = join('?', $http_ref_ar);
}
return true;
} /* }}} */
function _setHostValues() /* {{{ */
{
global $HTTP_SERVER_VARS;
/*
* IP address & proxy server getting.
*/
$remote_addr = '';
$remote_host = '';
$proxy_addr = '';
$proxy_host = '';
if (! empty($HTTP_SERVER_VARS["HTTP_X_FORWARDED_FOR"])) {
$remote_addr = stripslashes($HTTP_SERVER_VARS["HTTP_X_FORWARDED_FOR"]);
$proxy_addr = stripslashes($HTTP_SERVER_VARS["REMOTE_ADDR"]);
}
else {
$remote_addr = stripslashes($HTTP_SERVER_VARS["REMOTE_ADDR"]);
}
/* REMOVE ME */
//$remote_addr .= ", 192.168.1.3, 111.111.2., unknown";
if ($this->_props['remote_addr']['enabled'])
$this->_props['remote_addr']['value'] = $this->_truncate_names
? $this->_truncateName($remote_addr)
: $remote_addr;
if ($this->_props['proxy_addr']['enabled'])
$this->_props['proxy_addr']['value'] = $this->_truncate_names
? $this->_truncateName($proxy_addr)
: $proxy_addr;
/*
* Host names resolving.
*/
if ($this->_props['remote_host']['enabled']) {
$remote_host = empty($HTTP_SERVER_VARS["REMOTE_HOST"])
? $this->_resolveIP($remote_addr)
: stripslashes($HTTP_SERVER_VARS["REMOTE_HOST"]);
$this->_props['remote_host']['value'] = $this->_truncate_names
? $this->_truncateName($remote_host)
: $remote_host;
}
if ($proxy_addr) {
/* XXX: there was a bug, I fixed it, but it is really OK now?
Nepto [26/4/2002] */
if ($this->_props['proxy_host']['enabled']) {
$proxy_host = $this->_resolveIP($proxy_addr);
$this->_props['proxy_host']['value'] = $this->_truncate_names
? $this->_truncateName($proxy_host)
: $proxy_host;
}
}
return true;
} /* }}} */
function _setIdentValues() /* {{{ */
{
global $HTTP_SERVER_VARS;
$remote_addr = stripslashes(
! empty($HTTP_SERVER_VARS['HTTP_X_FORWARDED_FOR'])
? $HTTP_SERVER_VARS['HTTP_X_FORWARDED_FOR']
: $HTTP_SERVER_VARS['REMOTE_ADDR']
);
/* if multiple hosts presents, get the first one */
if (strchr($remote_addr, ',')) {
$remote_addr = explode(',', $remote_addr);
$remote_addr = trim($remote_addr[0]);
}
/*
* Ident protocol stuff.
*/
if ($this->_props['ident_username']['enabled']
|| $this->_props['ident_username']['enabled']) {
$ident = new Net_Ident($remote_addr,
stripslashes($HTTP_SERVER_VARS['REMOTE_PORT']),
stripslashes($HTTP_SERVER_VARS['SERVER_PORT']),
0 /* default */, $this->_ident_timeout);
if (PEAR::isError($ident->query())) {
// print "Ident query failed: " . $ident->error() . ".<br>";
} else {
if ($this->_props['ident_username']['enabled'])
$this->_props['ident_username']['value']
= $ident->getUser();
if ($this->_props['ident_ostype']['enabled'])
$this->_props['ident_ostype']['value']
= $ident->getOsType();
}
}
return true;
} /* }}} */
function _setValueValue($value = 0) /* {{{ */
{
if ($this->_props['value']['enabled'])
$this->_props['value']['value'] = $value;
return true;
} /* }}} */
function _setIDs() /* {{{ */
{
// Counting IDs according to values.
foreach (array_keys($this->_props) as $prop_name) {
if (isset($this->_props[$prop_name]['value'])) {
if (! empty($this->_props[$prop_name]['table'])) {
$id = $this->_getID($prop_name);
if (DB::isError($id))
return $id;
if (intval($id))
$this->_props[$prop_name]['id'] = $id;
} else {
$this->_props[$prop_name]['id'] =
$this->_props[$prop_name]['value'];
}
}
}
return true;
} /* }}} */
function _setSessionIDValue() /* {{{ */
{
if ($this->_props['session_id']['enabled']) {
$session_id = $this->_getSessionID();
if (DB::isError($session_id))
return $session_id;
if (intval($session_id))
$this->_props['session_id']['id'] =
$this->_props['session_id']['value'] =
$session_id;
}
return true;
} /* }}} */
function _setDatetimeValue() /* {{{ */
{
/* TODO: make this database independent (or sysdate() is?) */
if ($this->_props['datetime']['enabled'])
$this->_props['datetime']['id'] =
$this->_props['datetime']['value'] =
'SYSDATE()';
} /* }}} */
/*
* setStaticValues()
*
* Set all enabled properties values.
*/
function setStaticValues($value = 0) /* {{{ */
{
// Set basic values
$this->_setBasicValues();
// Setting basic host values such as remote_host, remote_addr,
// proxy_host, proxy_addr
$this->_setHostValues();
// Setting ident protocol values: ident_username, ident_ostype
$this->_setIdentValues();
// Value property
$this->_setValueValue($value);
} /* }}} */
/*
* setDynamicValues()
*
* Set all enabled properties values.
*/
function setDynamicValues() /* {{{ */
{
if ($this->_checkDB() == false)
return $this->_db;
// If raw log is enabled, set appropriate IDs according to values
if (! $this->_raw_log) {
$this->_setIDs();
}
// Session property. It is counted according to other IDs,
// so it must be after others IDs counting loop above.
$this->_setSessionIDValue();
// Datetime property. It doesn't contain real value,
// but SQL clausule for getting datetime.
$this->_setDatetimeValue();
return true;
} /* }}} */
/*
* registerAccess()
*
* Register access. Set values and write them into web log.
*/
function registerAccess($value = 0) /* {{{ */
{
/* NOTES: containing variable parts
query_string, request_uri, http_referer
*/
$this->setStaticValues();
$ret = $this->setDynamicValues($value);
if ($ret != true)
return $ret;
/*
* Writting values
*/
return $this->writeValues();
} /* }}} */
/**
* Inserts one record to accesses table. Also inserts records into another
* tables if raw is turned off and it is also neccessary.
*
* @return mixed true on success, PEAR_Error on DB error
* @access public
*/
function writeValues() /* {{{ */
{
if ($this->_checkDB() == false)
return $this->_db;
$keys = '';
$vals = '';
foreach (array_keys($this->_props) as $prop_name) {
$sep = ($keys == '' ? '' : ', ');
$val = '';
if ($this->_raw_log) {
if (isset($this->_props[$prop_name]['value'])
&& ! empty($this->_props[$prop_name]['value'])) {
if (! empty($this->_props[$prop_name]['table'])) {
$val = $this->_props[$prop_name]['value'];
$val = substr($val, 0, WEB_LOG_VALUE_SUBSTR_LENGTH);
$val = $this->_db->quoteString($val);
$val = "'$val'";
} else {
$val = $this->_props[$prop_name]['value'];
}
} else {
$val = 0;
}
} else {
if (isset($this->_props[$prop_name]['id'])
&& ! empty($this->_props[$prop_name]['id'])) {
$val = $this->_props[$prop_name]['id'];
// FIXME: nasty datetime quoting hack
if ($prop_name == 'datetime' && strcasecmp('sysdate()',
$this->_props[$prop_name]['value'])) {
$val = "'$val'";
}
} else {
$val = 0;
}
}
if ($val) {
$keys .= $sep . $this->_props[$prop_name]['column'];
$vals .= $sep . $val;
}
}
$accesses_table = $this->_raw_log
? 'weblog_accesses_raw'
: 'weblog_accesses';
$query = "INSERT INTO $accesses_table ($keys) VALUES ($vals)";
//print "<hr>$query<br>\n";
if (DB::isError($result = $this->_db->query($query))) {
return $result;
}
return true;
} /* }}} */
/**
* Performs remote log via HTTP protocol. Remote script shoud returns
* text/plain content type and print 1 on success and 0 on error followed
* by error message on next line(s).
*
* @param string $url URL where to log; data will be passed using
* HTTP POST request method
* @param string $value value to log
* @return mixed true on success, PEAR_Error on failure
* @access public
*/
function remoteLog($url, $value = 0) /* {{{ */
{
$this->setStaticValues($value);
$data = 'data='.rawurlencode(serialize($this));
$host = parse_url($url);
$port = isset($host['port']) ? $host['port'] : 80;
$host = $host['host'];
$response = false;
$request = "POST $url HTTP/1.0\r\n"
."Host: $host\r\n"
."Accept: text/plain\r\n"
."Accept-Language: en\r\n"
."User-Agent: phpWebLogAnalyzer::remoteLog()\r\n"
."Content-type: application/x-www-form-urlencoded\r\n"
."Content-length: ".strlen($data)."\r\n\r\n".$data;
$fp = fsockopen($host, $port);
if ($fp) {
fputs($fp, $request);
if (strpos(fgets($fp, 1024), '200 ')) {
do {
$line = fgets($fp, 1024);
} while ($line != "\r\n");
$response = array();
while ($line = fgets($fp, 1024)) {
$response[] = $line;
}
fclose($fp);
}
}
if ($response == false || count($response) <= 0) {
return new PEAR_Error('Connection error ('.$url.')'); // )
}
$error_code = intval($response[0]);
if ($error_code != 0) {
return true;
}
unset($response[0]);
$error_message = count($response) > 0
? join("\n", $response) : 'Unknown remote log error';
return new PEAR_Error($error_message);
} /* }}} */
/*
* _getID()
*
* Return ID of $value in $table.
* If $value is not in table, record will be inserted and ID returned.
*/
function _getID($property) /* {{{ */
{
$table = $this->_props[$property]['table'];
$value = $this->_props[$property]['value'];
$enabled_cache = $this->_props[$property]['enabled_cache'];
if ($enabled_cache) {
if (! isset($this->_props[$property]['cache'])) {
$count = $this->getPropertyCount($property);
if (DB::isError($count)) {
return $count;
}
$count = intval($count);
if ($count <= 0 || $count > WEB_LOG_CACHE_MAX_PROPERTY_COUNT) {
// setting empty cache
$this->_props[$property]['cache'] = array();
} else {
$query = 'SELECT id, value FROM ' . $table;
if (DB::isError($cache = $this->_db->getAssoc($query)))
return $cache;
$this->_props[$property]['cache'] = $cache;
unset($cache);
}
}
$ret = array_search($value, $this->_props[$property]['cache']);
if ($ret !== false)
return $ret;
}
for ($i = 0; $i < 2; $i++) {
$query = sprintf('SELECT id FROM %s WHERE value = "%s"',
$table, $this->_db->quoteString(
substr($value, 0, WEB_LOG_VALUE_SUBSTR_LENGTH)));
if (DB::isError($id = $this->_db->getOne($query)))
return $id;
if ($id) {
if ($enabled_cache)
$this->_props[$property]['cache'][$id] = $value;
return $id;
}
/* Important note:
We cannot count id before insert (for example with: select
max(id) from $table) because there can be also another connection
to the same database. It queries the same maximal id and insert
appropriate record into table. And our insert will fail.
*/
$query = sprintf('INSERT INTO %s (id, value) VALUES (0, "%s")',
$table, $this->_db->quoteString($value));
if ($i == 0) {
$this->_db->expectError(DB_ERROR_ALREADY_EXISTS);
$result = $this->_db->query($query);
$this->_db->popExpect();
} else {
$result = $this->_db->query($query);
}
/*
* There is a possibility of insert failure. Paraller connection
* could insert the same record into table as we. So if it was
* the first fail, go once again and check if it is record already
* inserted. If it will fails again, than raise error.
*/
if (DB::isError($result)) {
if ($i == 0 && $result->getCode() == DB_ERROR_ALREADY_EXISTS) {
/* This is an expected error. If it will be returned
again we will count it seriously. */
} else {
return $result;
}
} else {
/* No errors, insert succeed. */
break;
}
}
// TODO: LAST_INSERT_ID() is MySQL dependent? Yes it is!
// use appropriate PEAR DB method
$query = 'SELECT LAST_INSERT_ID()';
if (DB::isError($id = $this->_db->getOne($query)))
return $id;
if ($id) {
if ($enabled_cache)
$this->_props[$property]['cache'][$id] = $value;
return $id;
}
return 0;
} /* }}} */
/*
* _getSessionID()
*
* Returns session_id according to session variables of object. Session
* variables are $_session_timeout and $_session_keys.
*/
function _getSessionID() /* {{{ */
{
$where = '';
foreach ($this->_session_keys as $session_key) {
if (isset($this->_props[$session_key])) {
$where .= ($where == '' ? "WHERE " : " AND ");
$where .= $this->_props[$session_key]['column'];
$where .= $this->_raw_log
? (isset($this->_props[$session_key]['value'])
? " = '" . $this->_db->quoteString(substr(
$this->_props[$session_key]['value'],
0, WEB_LOG_VALUE_SUBSTR_LENGTH)) . "'"
: ' is NULL')
: (isset($this->_props[$session_key]['id'])
? " = '" . $this->_props[$session_key]['id'] . "'"
: ' is NULL');
}
}
$accesses_table = $this->_raw_log
? 'weblog_accesses_raw'
: 'weblog_accesses';
$query = 'SELECT id, session_id,'
.' UNIX_TIMESTAMP(SYSDATE()) - UNIX_TIMESTAMP(datetime) AS diff_sec'
.' FROM '.$accesses_table.' '.$where
.' ORDER BY datetime DESC'
.' LIMIT 1';
if (DB::isError($row = $this->_db->getRow($query)))
return $row;
if (is_array($row)
&& ($row['diff_sec'] < $this->_session_timeout)
&& ($row['session_id'])) {
return $row['session_id'];
}
/*
* If raw logging is enabled first look into raw accesses table and
* than if no max session ID cannot be counted look into normal
* accesses table. If raw logging is disabled look only in normal
* table. If although no max session ID is not present returns 1.
*/
foreach (($this->_raw_log
? array('weblog_accesses_raw', 'weblog_accesses')
: array('weblog_accesses')) as $table) {
$query = 'SELECT MAX(session_id) + 1 AS new_session_id'
.' FROM ' . $table;
if (DB::isError($id = $this->_db->getOne($query)))
return $id;
if ($id)
return $id;
}
/* Returns starting session ID with value of 1. */
return 1;
} /* }}} */
/*
* getPropertyCount()
*
* Return number of items in DB for particular property.
*/
function getPropertyCount($prop_name) /* {{{ */
{
if ($this->_checkDB() == false)
return $this->_db;
if (! isset($this->_props[$prop_name])) {
return false;
}
if ($this->_raw_log) {
$column = @$this->_props[$prop_name]['column'];
if (strlen($column) <= 0) {
return false;
}
$query = "SELECT COUNT(DISTINCT $column) FROM weblog_accesses_raw";
} else {
$table = @$this->_props[$prop_name]['table'];
if (strlen($table) <= 0) {
return false;
}
$query = "SELECT COUNT(*) FROM $table";
}
if (DB::isError($count = $this->_db->getOne($query)))
return $count;
if (isset($count))
return $count;
return false;
} /* }}} */
/*
* getSessionsCount()
* getAccessesCount()
*
* Returns counts of sessions/accesses in database
* for defined conditions in $array.
*/
function getSessionsCount($array = '') /* {{{ */
{
return $this->_getCount($array, WEB_LOG_SESSIONS_COUNT_TYPE);
} /* }}} */
function getAccessesCount($array = '') /* {{{ */
{
return $this->_getCount($array, WEB_LOG_ACCESSES_COUNT_TYPE);
} /* }}} */
function _getCount($array = '', $type) /* {{{ */
{
if ($this->_checkDB() == false)
return $this->_db;
$accesses_table = $this->_raw_log
? 'weblog_accesses_raw'
: 'weblog_accesses';
$list_tables = '';
$list_where = '';
if (is_array($array)) {
foreach ($array as $key => $val) {
if (! $this->_raw_log && isset($this->_props[$key]['table'])) {
$list_where .= ($list_where == '' ? '' : ' and ');
$list_where .= $accesses_table.'.'
. $this->_props[$key]['column'] . ' = '
. $this->_props[$key]['table'] . '.id AND '
. $this->_props[$key]['table'] . '.value LIKE '
. $this->_db->quote($val);
$list_tables .= ($list_tables == '' ? '' : ', ');
$list_tables .= $this->_props[$key]['table'];
} elseif (isset($this->_props[$key])) {
$list_where .= ($list_where == '' ? '' : ' and ');
$list_where .= $this->_props[$key]['column'] . ' LIKE '
. $this->_db->quote($val);
}
}
}
switch ($type) {
case WEB_LOG_SESSIONS_COUNT_TYPE:
$select_field = "COUNT(DISTINCT $accesses_table.session_id)";
break;
case WEB_LOG_ACCESSES_COUNT_TYPE:
$select_field = "COUNT($accesses_table.id)";
break;
default:
return false; // error
}
$query = "SELECT $select_field FROM $accesses_table";
if (strlen($list_tables) > 0)
$query .= ", $list_tables";
if (strlen($list_where) > 0)
$query .= " WHERE $list_where";
//echo '[['.$query . ']]<hr>';
if (DB::isError($count = $this->_db->getOne($query)))
return $count;
if (isset($count))
return $count;
return false;
} /* }}} */
/*
* getSessions()
*
* Returns session IDs.
*
* TODO:
* For $state equal 0 all sessions, -1 for inactive sessions, 1 for active.
*/
function _getSessions($active = true) /* {{{ */
{
if ($this->_checkDB() == false)
return $this->_db;
$accesses_table = $this->_raw_log
? 'weblog_accesses_raw'
: 'weblog_accesses';
$query = sprintf('SELECT session_id FROM %s'
.' GROUP BY session_id'
.' HAVING UNIX_TIMESTAMP(SYSDATE())'
.' - UNIX_TIMESTAMP(MAX(datetime)) %s %s'
.' ORDER BY session_id ASC',
$accesses_table,
$active ? '<' : '>=',
$this->_session_timeout);
return $this->_db->getCol($query);
} /* }}} */
/*
* getActiveSessions()
*
* Returns array with IDs of active sessions.
*/
function getActiveSessions() /* {{{ */
{
if ($this->_raw_log) {
if (isset($this->_cache_raw_log_active_sessions))
return $this->_cache_active_sessions;
} else {
if (isset($this->_cache_active_sessions))
return $this->_cache_active_sessions;
}
$active_sessions = $this->_getSessions(true);
if (DB::isError($active_sessions))
return $active_sessions;
if ($this->_raw_log) {
$this->_cache_raw_log_active_sessions = $active_sessions;
} else {
$this->_cache_active_sessions = $active_sessions;
}
return $active_sessions;
} /* }}} */
/*
* getInactiveSessions()
*
* Returns array with IDs of inactive sessions.
*/
function getInactiveSessions() /* {{{ */
{
if ($this->_raw_log) {
if (isset($this->_cache_raw_log_inactive_sessions))
return $this->_cache_inactive_sessions;
} else {
if (isset($this->_cache_inactive_sessions))
return $this->_cache_inactive_sessions;
}
$inactive_sessions = $this->_getSessions(false);
if (DB::isError($inactive_sessions))
return $inactive_sessions;
if ($this->_raw_log) {
$this->_cache_raw_log_inactive_sessions = $inactive_sessions;
} else {
$this->_cache_inactive_sessions = $inactive_sessions;
}
return $inactive_sessions;
} /* }}} */
/*
* Optimize raw table
*/
function optimizeRawTable() /* {{{ */
{
if ($this->_checkDB() == false)
return $this->_db;
if ($this->_db->phptype != 'mysql')
return false;
return $this->_db->query('OPTIMIZE TABLE weblog_accesses_raw');
} /* }}} */
/*
* Raw converting functions
*/
function getAccessesOfSession($session_id) /* {{{ */
{
if ($this->_checkDB() == false)
return $this->_db;
if (is_array($session_id)) {
for ($i = 0; $i < count($session_id); $i++)
$session_id[$i] = intval($session_id[$i]);
}
$accesses_table = $this->_raw_log
? 'weblog_accesses_raw'
: 'weblog_accesses';
$query = "SELECT id FROM $accesses_table WHERE session_id ";
$query .= is_array($session_id)
? 'IN (' . join(', ', $session_id) . ')'
: (intval($session_id) == 0
? 'IS NULL'
: '= ' . intval($session_id));
if (DB::isError($ids = $this->_db->getCol($query)))
return $ids;
return $ids;
} /* }}} */
function readIntoValues($access_id) /* {{{ */
{
if (! $this->_raw_log) {
die("readIntoValues(): not implemented for normal log\n");
}
if ($this->_checkDB() == false)
return $this->_db;
/* TODO: Note: we are selecting all from DB, but probably better will
be to select only enabled properties. */
$query = sprintf('SELECT * FROM %s WHERE id = %s',
'weblog_accesses_raw', intval($access_id));
if (DB::isError($ar = $this->_db->getRow($query)))
return $ar;
foreach (array_keys($this->_props) as $prop) {
//echo "DEBUG: checking property $prop - ";
if (isset($ar[$this->_props[$prop]['column']])) {
$this->_props[$prop]['value'] =
$ar[$this->_props[$prop]['column']];
//echo " is set: ";
//echo $this->_props[$prop]['value'];
//echo "\n";
} else {
//echo " is NOT set\n";
}
}
return true;
} /* }}} */
function deleteSession($session_id) /* {{{ */
{
if ($this->_checkDB() == false)
return $this->_db;
if (is_array($session_id)) {
for ($i = 0; $i < count($session_id); $i++)
$session_id[$i] = intval($session_id[$i]);
}
$accesses_table = $this->_raw_log
? 'weblog_accesses_raw'
: 'weblog_accesses';
$query = "DELETE FROM $accesses_table WHERE session_id ";
$query .= is_array($session_id)
? 'IN (' . join(', ', $session_id) . ')'
: (intval($session_id) == 0
? 'IS NULL'
: '= ' . intval($session_id));
if (DB::isError($res = $this->_db->query($query)))
return $ids;
return true;
} /* }}} */
function convert($debug = 0) /* {{{ */
{
$this->enableRawLog();
$inactive_sessions = $this->getInactiveSessions();
if (DB::isError($inactive_sessions))
return $inactive_sessions;
if ($debug) {
echo "Active sessions: [" . implode($this->getActiveSessions(), '][') . "]\n";
echo "Inactive sessions: [" . implode($inactive_sessions, '][') . "]\n";
}
if (count($inactive_sessions) < 2) {
return true;
}
/* The highest session ID will remain. */
array_pop($inactive_sessions);
$this->enableAllPropertyCaches();
foreach ($inactive_sessions as $sid) {
if ($debug) {
echo "[$sid]::";
}
$ids = $this->getAccessesOfSession($sid);
if (DB::isError($ids))
return $ids;
foreach ($ids as $id) {
$this->enableRawLog();
$this->clearAllProperties();
if (DB::isError($res = $this->readIntoValues($id)))
return res;
$this->setPropertyValue('id', 0);
$this->disableRawLog();
if (DB::isError($res = $this->_setIDs()))
return $res;
if (DB::isError($res = $this->writeValues()))
return $res;
if ($debug) {
echo "[$id]";
}
}
$this->enableRawLog();
if (DB::isError($res = $this->deleteSession($sid)))
return $res;
if ($debug) {
echo "\n";
}
}
$this->optimizeRawTable();
} /* }}} */
}
/* vim: set expandtab tabstop=4 shiftwidth=4:
* vim600: fdm=marker fdl=0 fdc=0
*/
?>
Platon Group <platon@platon.sk> http://platon.sk/
|