Location: PHPKode > scripts > Periodic site maintenance > periodic-site-maintenance/as_nightjobs.php
<?php
/**
* @package as_nightjobs.php - all daily site/DB maintenance jobs in one module
* @version 1.00.001
* first creation date: 26.11.2008 (dd.mm.yyyy)
* modified 26.01.2009
* @Author Alexander Selifonov,  <hide@address.com>
* @link http://www.selifan.ru
**/
class CNightJobs {
  var $_folder_backups = '';
  var $_shrinklist = array(); # tables to "shrink" list (delete old records based on "date" field value and max days)
  var $_shrinkdays = 30;
  var $_keepbackups = 10; # days to keep backup files
  var $_bkp_prefix = 'db'; # file prefix for backup BIG tables
  var $_bkp_prefix2 = 'dblist'; # prefix for "small" tables backups (lists etc.)
  var $_dblist = false; # database list to compute summary DB size
  var $_tlist = array(); # BIG tables
  var $_tlist2 = array(); # small tables
  var $_nobackup = array(); # these tables won't be backed up (audit log etc.)
  var $_userfunc = ''; # user defined func with specific maintenance actions
  var $_rootfolder = ''; # root folder (or an array of root folders, if more than one site)
  var $_sitesizetable = ''; # internal table for saving site size statistics (day by day)
  var $_maxsitesize = 0; # maximal site size (KB), according to Your ISP's tariff plan
  var $_sizethreshold = 10; # Report ATTENTION if site's going out of space in NN days
  var $_apptitle = '';
  var $_cleanfolders = array();
  var $_alarmcode = 0;
  var $_adminemail=''; # job results will be sent to this email address
  var $_emailcharset = 'iso-8859-1';
  var $_emailfrom = '';
  var $_errors = '';

  function CNightJobs($apptitle='') {
    $this->_apptitle = $apptitle;
  }
  /**
  * sets table list for DB operations (optimize, backup)
  *
  * @param array $tblist table list 1 (big tables)
  * @param array $tblist2 table list 2 (small tables)
  */
  function SetTablesList($tblist, $tblist2=0) {
    if(is_array($tblist)) $this->_tlist = $tblist;
    if(is_array($tblist2)) $this->_tlist2 = $tblist2;
  }
  /**
  * Setting DB backup parameters
  *
  * @param mixed $back_folder folder for saved backup files
  * @param mixed $bckpdays how many days files will be kept
  * @param array $nobackup table list to
  */
  function SetBackupParameters($back_folder, $bckpdays=10,$nobackup=null, $prefix='',$prefix2='') {
    if(is_string($back_folder)) $this->_folder_backups=$back_folder;
    $this->_keepbackups = $bckpdays;
    if(is_array($nobackup)) $this->_nobackup = $nobackup;
    if(!empty($prefix)) $this->_bkp_prefix = $prefix;
    if(!empty($prefix2)) $this->_bkp_prefix2 = $prefix2;
  }
  /**
  * add a folder to be cleaned from old (temp) files
  *
  * @param mixed $folder folder
  * @param mixed $mask file wildcard
  * @param mixed $days delete files mofified more than N days ago (0=delete all)
  */
  function AddFolderToClean($folder,$mask='',$days=0) {
    $this->_cleanfolders[] = array($folder,$mask,$days);
  }
  function AddTableToShrink($tablename, $datefield) {
    $this->_shrinklist[] = array($tablename, $datefield);
  }
  function TableShrinkDays($days) { $this->_shrinkdays = $days; }
  function SetEmailParameters($email,$charset='') {
    $this->_adminemail = $email;
    if(!empty($charset)) $this->_emailcharset = $charset;
  }
  function SetUserFunction($udf) { $this->_userfunc = $udf; }
  /**
  * passes parameters for computing site size and making growth forecast
  *
  * @param string/array $rootfolder Your site "root" folder(s), to count files's size
  * @param string $tablename table name where to save "today values"
  * @param array $dblist if Your site has more than one database, pass it's names list
  */
  function SiteSizeParameters($rootfolder, $sitesizetable='',$maxsitesize=0, $dblist=false) {
    $this->_rootfolder = $rootfolder;
    $this->_sitesizetable = $sitesizetable;
    $this->_maxsitesize = $maxsitesize*1000; # KB to MB
    $this->_dblist = $dblist;
    # TODO $dblist, not used yet
  }
  /**
  * run all tasks and send a report
  */
  function Exec() {
    $this->_errors='';
    $msg = "Started: ".date('d.m.Y H:i:s')."\n-------------------------------\n";
    if($this->_keepbackups>0 && !empty($this->_folder_backups) && count($this->_tlist)>0)
      $msg.=$this->DeleteOldBackups();
    if(count($this->_shrinklist)>0) $msg .= $this->ShrinkTables();
    $msg .= $this->OptimizeTables();

    if(count($this->_cleanfolders)>0) $msg .= $this->CleanFolders();

    if(!empty($this->_folder_backups) && substr($this->_folder_backups,-1) != '/'){ $this->_folder_backups = $this->_folder_backups.'/'; }

    if(!empty($this->_folder_backups) && count($this->_tlist)>0)
      $msg .= $this->BackupDataTables($this->_tlist,$this->_bkp_prefix);
    if(!empty($this->_folder_backups) && count($this->_tlist2)>0)
      $msg .= $this->BackupDataTables($this->_tlist2,$this->_bkp_prefix2);

    if(!empty($this->_rootfolder)) {
      $sitesize = $this->GetCurrentSiteSize();
      if(empty($this->_sitesizetable)) {
        $msg .= 'Summary Space occupied by Site (DB and/or files): '.number_format($sitesize,1)." KB\n";
      }
      else { # get average site growth and forecast "out of space"
        $sspace = $this->SiteGrowthStatistics();
        if($sspace!='') $msg .= "Site space information :\n$sspace\n";
      }
    }

    if(!empty($this->_userfunc) && function_exists($this->_userfunc))
       $msg.= call_user_func($this->_userfunc);
    if(!empty($this->_errors)) $msg .="Found errors while performing job:\n".$this->_errors;
    $msg .= "\n-------------------------------\nFinished: ".date('d.m.Y H:i:s');
    if(!empty($this->_adminemail) && empty($_SERVER['REMOTE_ADDR'])) {
      $subj = $this->_apptitle.(empty($this->_apptitle)?'Site ':', ').' Maintenance job log';
      if($this->_alarmcode==1) $subj .= '(WARNINGS)';
      elseif($this->_alarmcode>=2) $subj .= '(ALARMS !)';
      $headers  = 'MIME-Version: 1.0'."\r\nContent-type: text/html; charset={$this->_emailcharset}\r\n";
      if($this->_emailfrom) $headers .= "From: {$this->_emailfrom}\r\n";
      $sendmsg = str_replace("\n","<br />\n",$msg);
      @mail($this->_adminemail,$subj,$sendmsg,$headers);
    }
    if(!empty($_SERVER['REMOTE_ADDR'])) $msg = nl2br($msg);
    echo $msg;
    return $msg;
  }

  function CleanFolders() {
    $ret = '';
    foreach($this->_cleanfolders as $ofld) { $ret .= $this->CleanFolder($ofld[0],$ofld[1],$ofld[2]); }
    return $ret;
  }
  function ShrinkTables() {
    global $as_dbengine;
    $ret = '';
    foreach($this->_shrinklist as $sht) {
      $as_dbengine->sql_query("DELETE FROM {$sht[0]} WHERE TO_DAYS({$sht[1]})+{$this->_shrinkdays}<TO_DAYS(NOW())");
      if($as_dbengine->sql_error()) $this->_errors.= "ShrinkTables error for {$sht[0]}: ".$as_dbengine->sql_error()."\n";
      else {
        $cnt = $as_dbengine->affected_rows();
        $ret .= "Deleting obsolete records from {$sht[0]}, deleted: $cnt.\n";
      }
    }
    return $ret;
  }
  /**
  * performs data backup from all tables listed in _tlist excluding _nobackup members
  *
  * @param string $prefix - backup filename prefix
  */
  function BackupDataTables($tblist, $prefix='') {
    global $as_dbengine;
    $gzip = 1; # try to make gzipped backup
    if(empty($this->_folder_backups)) return '';
    $savefolder = $this->_folder_backups;
    $savename = $savefolder.$prefix.date('Y-m-d').'.xml';
    $as_dbengine->SaveDDLMode(true); // ñîõðàíèòü îïåðàòîðû Create table ...
    $as_dbengine->CreateContents(true); // create table list in the beginning of backup file
    $ret = "Backup data ...\n";
#  $as_dbengine->SetVerbose(true);
    ob_start();
    $bckplist = array();
    foreach($tblist as $tbname) {
      if(!is_array($this->_nobackup) || !in_array($tbname, $this->_nobackup)) $bckplist[] = $tbname;
    }
    $result = $as_dbengine->BckpBackupTables($bckplist,$savename,$gzip);
    $savename = $as_dbengine->outputfile;
    ob_end_clean();
    if(file_exists($savename) ) {
      $ret .= "$savename - Backup file created OK.\n";
    }
    else {
      $ret .= "$savename - Backup file was not created !\n";
      $this->_alarmcode = max(1,$this->_alarmcode);
    }
    return $ret;
  }
  /**
  *  cleans up backup folder from files older than $days
  *
  * @param integer $days - days to keep backup files
  */
  function DeleteOldBackups() {
    $days = intval($this->_keepbackups);
    if(empty($days) || empty($this->_folder_backups)) return '';
    $ret = $this->CleanFolder($this->_folder_backups,$this->_bkp_prefix.'*.gz,'.$this->_folder_backups.$this->_bkp_prefix.'*.xml',$days);
    $ret .= $this->CleanFolder($this->_folder_backups,$this->_bkp_prefix2.'*.gz,'.$this->_folder_backups.$this->_bkp_prefix2.'*.xml',$days);
    return $ret;
  }
  /**
  * Deletes files in specified folder, by mask (all files if mask empty), older than NN days (if days>0)
  *
  * @param string $folder - folder to clean
  * @param string $mask - file mask(s) if many - comma delimited: *.bak,*.tmp,...
  * @param mixed $days - delete only files modified more than $days ago (0 means delete ALL)
  */
  function CleanFolder($folder, $mask='',$days=0) {
    $days = intval($days);
    $watermark = ($days)? strtotime("-$days days") : '';
    $darr = array();
    $ret = '';
    if(substr($folder,-1) == '/'){ $folder = substr($folder,0,-1);  }
    if(empty($mask)) $mask='*.*';
    if (is_dir($folder)) {
      foreach (glob($folder.'/{'.$mask.'}', GLOB_BRACE) as $filename) {
        if(is_file($filename) && ($watermark==='' || filemtime($filename)<$watermark)) {
          $darr[] = $filename;
        }
      }
    }
    foreach($darr as $fname) {
      $ok = unlink($fname);
      $ret .= $fname. ($ok? ' deleted': ' - deletion error !')."\n";
    }
    return $ret;
  }
  /**
  * calls OPTIMIZE TABLE/ANALYZE TABLE for all tables listed in tlist or in internal vars _tlist,_tlist2
  *
  * @param mixed $tlist - if array passed, tables from it will be optimized, otherwise - from class variable _tlist
  */
  function OptimizeTables($tlist=''){
    global $as_dbengine;
    $cnt = 0;
    if(is_array($tlist)) foreach($tlist as $tname) {
      $as_dbengine->sql_query("OPTIMIZE TABLE $tname");
      $as_dbengine->sql_query("ANALYZE TABLE $tname");
      $cnt++;
    }
    else {
      foreach($this->_tlist as $tname) {
        $as_dbengine->sql_query("OPTIMIZE TABLE $tname");
        $as_dbengine->sql_query("ANALYZE TABLE $tname");
        $cnt++;
      }
      foreach($this->_tlist2 as $tname) {
        $as_dbengine->sql_query("OPTIMIZE TABLE $tname");
        $as_dbengine->sql_query("ANALYZE TABLE $tname");
        $cnt++;
      }
    }
    return "Optimized tables: $cnt\n";
  }
  /**
  * cleanup sessions folder from old session data files
  * Files older than 1 days are deleted
  */
  function CleanupSessions() {
    $foldsess = ini_get('session.save_path');
    $ret = '';
    if(!empty($foldsess) && is_dir($foldsess)) {
      $this->CleanFolder($foldsess,'',1);
      $ret = "Sessions folder cleaned\n";
    }
    return $ret;
  }
  /**
  * Computes current site size (KB in MySQL and KB in files)
  * and saves into special table (for analysis needs)
  * @returns number - summary site size (KiloBytes)
  */
  function GetCurrentSiteSize() {
    global $as_dbengine;
    if(is_string($this->_rootfolder)) $sz_file = $this->GetFolderSize($this->_rootfolder);
    elseif(is_array($this->_rootfolder)) {
      $sz_file = 0; foreach($this->_rootfolder as $folder) { $sz_file += $this->GetFolderSize($folder); }
    }
    if(is_array($this->_dblist) && count($this->_dblist)>0) {
      $sz_db=0;
      foreach($this->_dblist as $dbname) { $sz_db += $this->GetDatabaseSize($dbname); }
    }
    else { $sz_db = $this->GetDatabaseSize(); }
    $sz_file=round($sz_file/1000,1); # make KBytes
    $sz_db=round($sz_db/1000,1);
    if(!empty($this->_sitesizetable)) {
    if(!$as_dbengine->IsTableExist($this->_sitesizetable))
      $as_dbengine->sql_query("CREATE TABLE {$this->_sitesizetable}
       ( rid BIGINT NOT NULL AUTO_INCREMENT,
       rdate DATE not null default 0,
       sizefile DECIMAL(14,1) NOT NULL DEFAULT 0,
       sizedb DECIMAL(14,1) NOT NULL DEFAULT 0,
       primary key(rid))
      ");
      $recid = $as_dbengine->GetQueryResult($this->_sitesizetable,'rid','rdate=SYSDATE()');
      if(!empty($recid)) $as_dbengine->sql_query("UPDATE {$this->_sitesizetable} SET sizefile=$sz_file, sizedb=$sz_db  WHERE rid=$recid");
      else $as_dbengine->sql_query("INSERT INTO {$this->_sitesizetable} (rdate,sizefile,sizedb) VALUES(SYSDATE(),$sz_file,$sz_db)");
      if($as_dbengine->sql_error()) $this->_errors.= "GetCurrentSiteSize error : ".$as_dbengine->sql_error()."\n";
    }
    return ($sz_file+$sz_db);
  }

  /**
  * Shows current site space, "day" grouth and estimated days to reach space limit
  */
  function SiteGrowthStatistics() {
    global $as_dbengine, $as_iface;
    # compute estimated days before site (database + files) reaches provider's limit
    $lnk= $as_dbengine->sql_query("SELECT rdate, TO_DAYS(rdate) days, sizefile, sizedb FROM {$this->_sitesizetable} ORDER BY rdate DESC LIMIT 0,5");
    $dt=array(); $ii=0;
    while(is_resource($lnk) && ($dta=$as_dbengine->fetch_assoc($lnk))) { $dt[$ii++] = $dta; }
    if(count($dt)<1) return ''; # no data for estimation
    $lastsize = $dt[0]['sizefile']+$dt[0]['sizedb'];
    $ret = "Space occupied by site, KB: ".number_format($lastsize,1)."\n";
    if($lastsize>$this->_maxsitesize) {
      $this->_alarmcode = max($this->_alarmcode,2);
      $ret .="ALARM : site is out of space : current limit is ".number_format($this->_maxsitesize)." KB !\n";
    }
    if(count($dt)<2) return $ret; # no data for estimation
    $delta_f = $dt[0]['sizefile'] - $dt[1]['sizefile'];
    $delta_d = $dt[0]['sizedb'] - $dt[1]['sizedb'];
    $delta = $delta_f+$delta_d;
    $ret .= "Growth in files, KB: ".number_format($delta_f,1)."\n";
    $ret.= "Growth in DB, KB: ".number_format($delta_d,1)."\n";
    $ret.= "Summary Growth, KB: ".number_format($delta,1)."\n";
    if($this->_maxsitesize>0 && $lastsize < $this->_maxsitesize) {
      $lastn = count($dt)-1;
      $sumkb = $dt[0]['sizefile']+$dt[0]['sizedb'] - $dt[$lastn]['sizefile'] - $dt[$lastn]['sizedb'];
      $sumd = $dt[0]['days'] - $dt[$lastn]['days'];
      $kb_per_day = ($sumd>0)? $sumkb/$sumd : 0; # average growth per day
      if($kb_per_day>0) {
        $remainspace = $this->_maxsitesize - $lastsize;
        $remaindays = round($remainspace/$kb_per_day,0);
        if($remaindays <= $this->_sizethreshold) {
          $this->_alarmcode = max($this->_alarmcode,1);
          $ret .="WARNING : ";
        }
        $ret .="Your site may go out of space (".number_format($this->_maxsitesize)." KB) in $remaindays days\n";
        if(date('Y-m-d')!=$dt[0]['rdate']) $ret .= "(Stats computed for ".$dt[0]['rdate'].")\n";
        $ret .= "(Average growth per day : ".number_format($kb_per_day,1)." KB)\n";
      }
    }
    return $ret;
  }
  /**
  * counts current database size in KB, by summing all tables sizes (MySQL only !)
  * @returns number (Bytes)
  */
  function GetDatabaseSize($dbname = '') {
    global $as_dbengine;
    $curdb = $as_dbengine->CurrentDbName();
    if(!empty($dbname)) {
      $result=$as_dbengine->select_db($dbname);
      if(!$result) {
        $this->_errors.= "GetDatabaseSize error while changing to DB $dbname: ".$as_dbengine->sql_error()."\n";
        return 0;
      }
    }
    $lnk =$as_dbengine->sql_query("SHOW TABLE STATUS");
    $ret = 0;
    while(is_resource($lnk) && ($r=$as_dbengine->fetch_assoc($lnk))) {
      if(isset($r['Data_length'])) $ret +=intval($r['Data_length']);
    }
    if(is_resource($lnk)) $as_dbengine->free_result($lnk);
    if(!empty($dbname)) $as_dbengine->select_db($curdb); # get back !
    $db2 = $as_dbengine->CurrentDbName();
    return $ret;
  }

  /**
  * counts summary size for passed folder, or for root folder no parameter passed
  * @returns number (Bytes)
  * based on source from on http://www.weberdev.com/get_example-4561.html
  */
  function GetFolderSize($d ='.') {
    // © kasskooye and patricia benedetto
    $sf=0;
    $h = @opendir($d);
    if($h==0)return 0;
    while ($f=readdir($h)){
      if ( $f!= '..') {
        $sf+=filesize($nd=$d.'/'.$f);
        if($f!='.'&&is_dir($nd)){
          $sf+=CNightJobs::GetFolderSize($nd);
        }
      }
    }
    closedir($h);
    return $sf ;
  }

} # end of CNightJobs class def.

?>
Return current item: Periodic site maintenance