#!/usr/bin/php5 * @copyright Copyright (C) 2005 Egressive Limited (www.egressive.com) * @version $Id: egawstats.php 0 2005-08-18 08:07:26Z dave $ * @package egawstats */ class Domain extends PEAR { /** * @var array $domain a mixed array of "name" and "values" * for the current domain. * @access private */ private $domain = array(); /** * @var array $domain_array two dimensional array containing arrays * of parsed domains. * @access private */ private $domain_array = array(); /** * @var array $report_dates two dimensional array containing years and * and months [yyyy][mm] for which the domain yields results. * @access private */ private $report_dates = null; /** * @var object $logger - reference to a PEAR log object * @access private */ private $logger = null; /** * @var string $short_args - one letter command line arguments * obeys GNU args conventions - args take form -a or -b value or -ab value * for multiple arguments * @access private */ private $short_args = "psa"; /** * @var string $long_args - full word command line arguments, more memorable * obeys GNU args conventions - args take form --flag or --flagwithvalue=value * @access private */ private $long_args = array('rebuildpages','rebuildstats','rebuildconfs'); /** * @var array $options_array - array of command line arguments and values * from the Console/Getopts PEAR class * @access private */ private $options_array = array(); /** * @var array $options - array of just the command line arguments and values * @access private */ private $options = array(); public function __construct($logger) { $this->logger = $logger; $this->logger->log(__CLASS__.'->'.__FUNCTION__,PEAR_LOG_DEBUG); // // Error handling // // function callback //$this->setErrorHandling(PEAR_ERROR_CALLBACK, array(&$this,'handleError')); $this->setErrorHandling(PEAR_ERROR_CALLBACK, 'handleError'); } /** * get the command line arguments */ public function get_args() { $this->logger->log(__CLASS__.'->'.__FUNCTION__,PEAR_LOG_DEBUG); $cli_args = new Console_Getopt(); $args = $cli_args->readPHPArgv(); array_shift($args); $this->options_array = $cli_args->getopt2($args,$this->short_args,$this->long_args); $this->options = $this->options_array[0]; //print_r($this->options); } /** * put the command line arguments into an array that's useful * elsewhere in the code... */ public function process_args() { $this->logger->log(__CLASS__.'->'.__FUNCTION__,PEAR_LOG_DEBUG); // first, get the command line arguments $this->get_args(); //print_r($this->options); if (count($this->options) and (count($this->options_array[0]) or count($this->options_array[1]))) { foreach($this->options as $option) { list($key,$value) = $option; if ($value) { $this->logger->log('option['.$key.'] = '.$value,PEAR_LOG_DEBUG); } else { $this->logger->log('option '.$key.' specified',PEAR_LOG_DEBUG); } } } else { $this->logger->log('no command line options supplied',PEAR_LOG_DEBUG); } } /** * put the domain definitions in the configuration files into an * array that's useful elsewhere in the code... */ public function process_configs() { $this->logger->log(__CLASS__.'->'.__FUNCTION__,PEAR_LOG_DEBUG); $conf_files = glob(DOMAIN_DIR.'/*.'.DOMAIN_CONF_SUFFIX); //print_r($conf_files); $config = new Config(); foreach ($conf_files as $conf_file) { $conf =& $config->parseConfig($conf_file,"XML"); } // assign the configuration info to the domain array $this->domain_array = $conf->toArray(); } /** * run the rsync that synchronises the logs on the main machine to the * copy on the machine running awstats */ private function sync_access_logs() { $this->logger->log(__CLASS__.'->'.__FUNCTION__,PEAR_LOG_DEBUG); // the current domain is $this->domain... // check to see that we actually have to sync a remote file for this // domain... if (isset($this->domain['remote']['file']) and isset($this->domain['remote']['user'])) { $command = RSYNC.' '.FLAGS.' '.$this->domain['remote']['user'].'@'. $this->domain['remote']['host'].':'.$this->domain['remote']['path'].'/'. $this->domain['remote']['file'].' '.$this->domain['local']['path'].'/'. $this->domain['local']['file']; if (!is_dir($this->domain['local']['path'])) { $this->logger->log($this->domain['local']['path']." doesn't exist yet, ". 'creating it...',PEAR_LOG_INFO); // if it's not there, make the dir (rwxr-xr-x) if (!mkdir($this->domain['local']['path'],0755)) { $this->logger->log('failed to create dir '.$this->domain['local']['path']. ': '.$command,PEAR_LOG_ERR); return false; } // set appropriate file ownership chown($this->domain['local']['path'],$this->domain['local']['user']); chgrp($this->domain['local']['path'],$this->domain['local']['group']); } // run the command and capture the output... $output = array(); $this->logger->log('rsyncing domain '.$this->domain['name']. ': '.$command,PEAR_LOG_INFO); $return_value = exec($command,$output); //print_r($output); // if the rsync was successful, it'll return a value like this if (strstr($return_value,"total size ")) { $this->logger->log('rsync: success - '.$return_value.' ',PEAR_LOG_INFO); $this->logger->log(' '.$output[11],PEAR_LOG_INFO); $this->logger->log(' '.$output[12],PEAR_LOG_INFO); } else { $this->logger->log('rsync: failed - '.$return_value.' ',PEAR_LOG_ERR); return false; } } // if we don't have a remote file, just move on to the next step... return true; } /** * generate a valid AWSTATs configuration file if it doesn't already exist */ private function create_awstats_config() { $this->logger->log(__CLASS__.'->'.__FUNCTION__,PEAR_LOG_DEBUG); // the current domain is $this->domain... // the filename for this domain's AWSTATs configuration file $filename = AWSCONF_PREFIX.'.'.$this->domain['name'].'.'.AWSCONF_SUFFIX; // add the path, too... $fullname = AWSCONF_DIR.'/'.$filename; // if it doesn't exist, then... if (!file_exists($fullname)) { // construct the conf file contents based on this template... $file_content[] = "#"; $file_content[] = "# site specific settings for ".$this->domain['name']; $file_content[] = "SiteDomain=\"".$this->domain['name']."\""; if (is_array($this->domain['aliases']['alias'])) { $aliases = implode(' ',$this->domain['aliases']['alias']); } else { $aliases = $this->domain['aliases']['alias']; } $aliases = $this->domain['name'].' '.$aliases; $file_content[] = "HostAliases=\"".$aliases."\""; $file_content[] = "SkipHosts=\"".$this->domain['skipHost']."\""; $file_content[] = "LogFile=\"".$this->domain['local']['path']."/". $this->domain['local']['file']."\""; $file_content[] = "DirIcons=\"".$this->domain['iconPath']."\""; $file_content[] = "#"; $file_content[] = "# local defaults"; $file_content[] = "Include \"includes/".AWSTATS_LOCAL_DEFAULTS."\""; $file_content[] = "#"; $file_content[] = "# global defaults (true for all Awstats installs)"; $file_content[] = "Include \"includes/".AWSTATS_GLOBAL_DEFAULTS."\""; foreach ($file_content as $index => $line) { $no = $index + 1; $this->logger->log('conf file '.$no.': '.$line,PEAR_LOG_DEBUG); $file_content[$index] .= "\n"; } // and then attempt to write it... if (is_writable(AWSCONF_DIR)) { // write the file with if ($bytes = file_put_contents($fullname,$file_content)) { $this->logger->log('wrote conf file: '.$fullname. '('.$bytes.' bytes, '.count($file_content).' lines)',PEAR_LOG_INFO); } else { $this->logger->log('failed to write conf file: ' .$fullname.'!',PEAR_LOG_ERR); } } else { $this->logger->log('awstats config directory'.AWSCONF_DIR. ' is unwritable!',PEAR_LOG_ERR); } } else { $this->logger->log('conf file: '.$fullname. ' already exists.',PEAR_LOG_INFO); } return true; } /** * run AWSTATs script to update stats in the database since last run * or, if run for first time, initialise the database */ private function update_stats() { $this->logger->log(__CLASS__.'->'.__FUNCTION__,PEAR_LOG_DEBUG); $command = AWSBIN.' '.AWS_ARGS.' -config='.$this->domain['name']; // run the command and capture the output... $output = array(); $this->logger->log('updating stats for '.$this->domain['name']. ': '.$command,PEAR_LOG_INFO); $return_value = exec($command,$output); //print_r($output); // if the rsync was successful, it'll return a value like this if (strstr($return_value,"Found ")) { $this->logger->log('update stats: success - '. $return_value,PEAR_LOG_INFO); } else { $this->logger->log('update stats: failed - '. $output[2].' ',PEAR_LOG_ERR); return false; } return true; } /** * figure out the months for which AWSTATs are available */ private function get_stats_months() { $this->logger->log(__CLASS__.'->'.__FUNCTION__,PEAR_LOG_DEBUG); if ($this->report_dates == null) { $files = glob(AWS_DB_DIR.'/*.'.AWS_DB_SUFFIX); // the current domain is $this->domain... $this->report_dates = array(); foreach ($files as $filename) { if ($pos = strpos($filename,$this->domain['name'])) { $start = $pos-5; $length = 4; $year = substr($filename,$start,$length); $start = $pos-7; $length = 2; $month = substr($filename,$start,$length); $this->logger->log('found file - '. $filename.' '.$month.'/'.$year,PEAR_LOG_DEBUG); $dates[$year] .= ','.$month; } } //print_r($dates); // sort them so they're in year -> month order... ksort($dates); foreach ($dates as $year => $months_string) { $this->report_dates[$year] = array_slice(explode(',',$months_string),1); } //array_multisort($this->report_dates); reset($this->report_dates); //print_r($this->report_dates); } return true; } /** * generate the AWSTATs output pages in XHTML format for * the specified time period... */ private function produce_xhtml_pages() { $this->logger->log(__CLASS__.'->'.__FUNCTION__,PEAR_LOG_DEBUG); // first make sure that the stats output directory is there // if not, create it and set its ownership... $statsdir = $this->domain['statsPath']; if (!is_dir($statsdir)) { $this->logger->log('making '.$statsdir,PEAR_LOG_INFO); if (!mkdir($statsdir,0755)) { $this->logger->log('failed to create dir '.$statsdir,PEAR_LOG_ERR); return false; } // set appropriate file ownership chown($statsdir,$this->domain['local']['user']); chgrp($statsdir,$this->domain['local']['group']); } if ($this->get_stats_months()) { //print_r($dates); foreach ($this->report_dates as $year => $months) { $yeardir = $this->domain['statsPath'].'/'.$year; if (!is_dir($yeardir)) { $this->logger->log('making '.$yeardir,PEAR_LOG_INFO); if (!mkdir($yeardir,0755)) { $this->logger->log('failed to create dir '.$yeardir,PEAR_LOG_ERR); return false; } // set appropriate file ownership chown($yeardir,$this->domain['local']['user']); chgrp($yeardir,$this->domain['local']['group']); } else { $this->logger->log('found '.$yeardir,PEAR_LOG_INFO); } foreach ($months as $month) { $monthdir = $yeardir.'/'.$month; if (!is_dir($monthdir)) { $this->logger->log('making '.$monthdir,PEAR_LOG_INFO); if (!mkdir($monthdir,0755)) { $this->logger->log('failed to create dir '.$monthdir,PEAR_LOG_ERR); return false; } // set appropriate file ownership chown($monthdir,$this->domain['local']['user']); chgrp($monthdir,$this->domain['local']['group']); } else { $this->logger->log('found '.$monthdir,PEAR_LOG_INFO); } $currentmonth = date('m'); $currentyear = date('Y'); $index_page = $monthdir.'/index.html'; //$this->logger->log("year=$currentyear/$year, month=$currentmonth/$month",PEAR_LOG_DEBUG); if (!is_link($index_page) or ($currentmonth == $month and $currentyear == $year)) { // having found the monthdir, it's time to create the pages! $command = AWSALLBIN.' '.AWSALL_ARGS.' -config='.$this->domain['name']. ' -month='.$month.' -year='.$year. ' -dir='.$monthdir.''; $this->logger->log('generating stats output: '.$command,PEAR_LOG_INFO); $output = array(); $return_value = exec($command,$output); if (strstr($return_value,"Main HTML page is ")) { $idx = count($output) - 2; $this->logger->log('success: '.$output[$idx],PEAR_LOG_INFO); $this->logger->log('creating link for index page',PEAR_LOG_INFO); symlink($monthdir.'/'.AWS_OUT_PREFIX.'.'.$this->domain['name']. '.'.AWS_OUT_SUFFIX,$index_page); } } } } } } /** * generate an XHMTL page with a basic index of statisics results for the relevant * months... */ private function create_xhtml_index() { $this->logger->log(__CLASS__.'->'.__FUNCTION__,PEAR_LOG_DEBUG); // if there are any months worth writing about... if ($this->get_stats_months()) { $currentmonth = date('m'); $currentdate = date('d-m-y'); //print_r($this->report_dates); foreach ($this->report_dates as $year => $months) { $yeardir = $this->domain['statsPath'].'/'.$year; foreach ($months as $month) { $monthdir = $yeardir.'/'.$month; $index_page = $monthdir.'/index.html'; } } /* // the filename for this domain's AWSTATs configuration file $filename = AWS_OUT_INDEX; // add the path, too... $fullname = $this->domain['statsPath'].'/'.$filename; // if it doesn't exist, then... if (!file_exists($fullname)) { // construct the conf file contents based on this template... $file_content[] = ''; $file_content[] = "# site specific settings for ".$this->domain['name']; $file_content[] = "SiteDomain=\"".$this->domain['name']."\""; if (is_array($this->domain['aliases']['alias'])) { $aliases = implode(' ',$this->domain['aliases']['alias']); } else { $aliases = $this->domain['aliases']['alias']; } $aliases = $this->domain['name'].' '.$aliases; $file_content[] = "HostAliases=\"".$aliases."\""; $file_content[] = "SkipHosts=\"".$this->domain['skipHost']."\""; $file_content[] = "LogFile=\"".$this->domain['local']['path']."/". $this->domain['local']['file']."\""; $file_content[] = "DirIcons=\"".$this->domain['iconPath']."\""; $file_content[] = "#"; $file_content[] = "# local defaults"; $file_content[] = "Include \"includes/".AWSTATS_LOCAL_DEFAULTS."\""; $file_content[] = "#"; $file_content[] = "# global defaults (true for all Awstats installs)"; $file_content[] = "Include \"includes/".AWSTATS_GLOBAL_DEFAULTS."\""; foreach ($file_content as $index => $line) { $no = $index + 1; $this->logger->log('conf file '.$no.': '.$line,PEAR_LOG_DEBUG); $file_content[$index] .= "\n"; } // and then attempt to write it... if (is_writable(AWSCONF_DIR)) { // write the file with if ($bytes = file_put_contents($fullname,$file_content)) { $this->logger->log('wrote conf file: '.$fullname. '('.$bytes.' bytes, '.count($file_content).' lines)',PEAR_LOG_INFO); } else { $this->logger->log('failed to write conf file: ' .$fullname.'!',PEAR_LOG_ERR); } } else { $this->logger->log('awstats config directory'.AWSCONF_DIR. ' is unwritable!',PEAR_LOG_ERR); } } else { $this->logger->log('conf file: '.$fullname. ' already exists.',PEAR_LOG_INFO); } return true; */ } } /** * run the whole process... */ public function run() { $this->logger->log(__CLASS__.'->'.__FUNCTION__,PEAR_LOG_DEBUG); // process command line argments, if any $this->process_args(); // get domain configuration files... $this->process_configs(); //$this->domain = $this->domain_array['root']['domain'][0]; foreach ($this->domain_array['root']['domain'] as $this->domain) { // initialise this value $this->report_dates = null; // first check to see if there're any access records that need to be // sync'd $this->sync_access_logs(); // if it doesn't already exist, create a configuration file... $this->create_awstats_config(); // update the statistics based on the current access records // if it hasn't already been created, initialise the stats db $this->update_stats(); // produce XHTML pages $this->produce_xhtml_pages(); // and create an up-to-date index of the relevant reports... $this->create_xhtml_index(); } } } /** * deal with errors in a uniform way... */ function handleError($error) { global $logger; //print 'egstats error: '.$error->getMessage().'\n'; $logger->log(EG_APP.' error: '.$error->getMessage(),'.',PEAR_LOG_ERR); } // initialise logging $logger = &Log::singleton('console','','egstats.php'); $logger->log("***** Starting egstats.php *****",PEAR_LOG_DEBUG); // set up default error handling PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'handleError'); /** * Program logic goes below this */ $conf_filename=""; $domain = new Domain($logger); $domain->run(); // final logging message $logger->log("===== Finishing egstats.php =====",PEAR_LOG_DEBUG); ?>