From d3c8533b94592bb36c6b2fa2e7375c2e612caaaf Mon Sep 17 00:00:00 2001
From: Michael Seevogel <git@michaelseevogel.de>
Date: Mon, 27 Jul 2020 12:24:52 +0200
Subject: [PATCH] backported GoAccess code

---
 .../web/sites/form/web_vhost_domain.tform.php |   2 +-
 server/conf/goaccess_index.php.master         |  73 ++++++
 .../lib/classes/cron.d/150-goaccess.inc.php   | 212 ++++++++++++++++++
 .../plugins-available/apache2_plugin.inc.php  |  67 ++++++
 4 files changed, 353 insertions(+), 1 deletion(-)
 create mode 100644 server/conf/goaccess_index.php.master
 create mode 100644 server/lib/classes/cron.d/150-goaccess.inc.php

diff --git a/interface/web/sites/form/web_vhost_domain.tform.php b/interface/web/sites/form/web_vhost_domain.tform.php
index 7da71281cf..aa02dfe96c 100644
--- a/interface/web/sites/form/web_vhost_domain.tform.php
+++ b/interface/web/sites/form/web_vhost_domain.tform.php
@@ -629,7 +629,7 @@ $form["tabs"]['stats'] = array (
 			'datatype' => 'VARCHAR',
 			'formtype' => 'SELECT',
 			'default' => 'awstats',
-			'value'  => array('webalizer' => 'Webalizer', 'awstats' => 'AWStats', '' => 'None')
+			'value'  => array('awstats' => 'AWStats', 'goaccess' => 'GoAccess', 'webalizer' => 'Webalizer','' => 'None')
 		),
 		//#################################
 		// END Datatable fields
diff --git a/server/conf/goaccess_index.php.master b/server/conf/goaccess_index.php.master
new file mode 100644
index 0000000000..d0a8bf3c84
--- /dev/null
+++ b/server/conf/goaccess_index.php.master
@@ -0,0 +1,73 @@
+<?php
+$yearmonth_text = "Jump to previous stats: ";
+$script = "<script>function load_content(url){var iframe = document.getElementById(\"content\");iframe.src = url;}</script>\n";
+
+if ($handle = opendir('.'))
+{
+        while(false !== ($file = readdir($handle)))
+        {
+                if (substr($file,0,1) != "." && is_dir($file))
+                {
+                        $orderkey = substr($file,0,4).substr($file,5,2);
+                        if (substr($file,5,2) < 10 )
+                        {
+                                $orderkey = substr($file,0,4)."0".substr($file,5,2);
+                        }
+                        $goaprev[$orderkey] = $file;
+                }
+        }
+
+        $month = date("n");
+        $year = date("Y");
+		
+        if (date("d") == 1)
+        {
+                $month = date("m")-1;
+                if (date("m") == 1)
+                {
+                        $year = date("Y")-1;
+                        $month = "12";
+                }
+        }
+
+        $current = $year.$month;
+		if ( $month < 10 ) {
+			$current = $year."0".$month;
+		}
+		$goaprev[$current] = $year."-".$month;
+
+		closedir($handle);
+}
+
+arsort($goaprev);
+
+$options = "";
+foreach ($goaprev as $key => $value)
+{
+
+	if(file_exists($value.'/awsindex.html') && file_exists($value.'/goaindex.html')) {
+		$goaccessindex = 'goaindex.html';
+	} elseif(file_exists($value.'/awsindex.html') && !file_exists($value.'/goaindex.html')) {
+                $goaccessindex = 'awsindex.html';
+	} else {
+		$goaccessindex = 'goaindex.html';
+	}
+	
+	if($key == $current) $options .= "<option selected=\"selected\" value=\"{$goaccessindex}\">{$value}</option>\n";
+	else $options .= "<option value=\"{$value}/{$goaccessindex}\">{$value}</option>\n";
+
+}
+$goaccessindex = 'goaindex.html';
+
+$html = "<!DOCTYPE html>\n<html>\n<head>\n<title>Stats</title>\n";
+$html .= "<style>\nhtml,body {margin:0px;padding:0px;width:100%;height:100%;background-color: #ccc;}\n";
+$html .= "#header\n{\nwidth:100%;margin:0px auto;\nheight:20px;\nposition:fixed;\npadding:4px;\ntext-align:center;\n}\n";
+$html .= "iframe {width:100%;height:95%;margin:0px;margin-top:40px;border:0px;padding:0px;}\n</style>\n</head>\n<body>\n";
+$html .= $script;
+$html .= "<div id=\"header\">{$yearmonth_text}\n";
+$html .= "<select name=\"goadate\" onchange=\"load_content(this.value)\">\n";
+$html .= $options;
+$html .= "</select>\n</div>\n<iframe src=\"{$goaccessindex}\" id=\"content\"></iframe>\n";
+$html .= "</body></html>";
+echo $html;
+?>
diff --git a/server/lib/classes/cron.d/150-goaccess.inc.php b/server/lib/classes/cron.d/150-goaccess.inc.php
new file mode 100644
index 0000000000..fe9e565f5f
--- /dev/null
+++ b/server/lib/classes/cron.d/150-goaccess.inc.php
@@ -0,0 +1,212 @@
+<?php
+
+/*
+Copyright (c) 2013, Marius Cramer, pixcept KG
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice,
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright notice,
+      this list of conditions and the following disclaimer in the documentation
+      and/or other materials provided with the distribution.
+    * Neither the name of ISPConfig nor the names of its contributors
+      may be used to endorse or promote products derived from this software without
+      specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+class cronjob_goaccess extends cronjob {
+
+	// job schedule
+	protected $_schedule = '0 0 * * *';
+
+	/* this function is optional if it contains no custom code */
+	public function onPrepare() {
+		global $app;
+
+		parent::onPrepare();
+	}
+
+	/* this function is optional if it contains no custom code */
+	public function onBeforeRun() {
+		global $app;
+
+		return parent::onBeforeRun();
+	}
+
+	public function onRunJob() {
+		global $app, $conf;
+
+		//######################################################################################################
+		// Create goaccess statistics
+		//######################################################################################################
+
+		$sql = "SELECT domain_id, domain, document_root, web_folder, type, system_user, system_group, parent_domain_id FROM web_domain WHERE (type = 'vhost' or type = 'vhostsubdomain' or type = 'vhostalias') and stats_type = 'goaccess' AND server_id = ?";
+		$records = $app->db->queryAllRecords($sql, $conf['server_id']);
+
+		$web_config = $app->getconf->get_server_config($conf['server_id'], 'web');
+
+		$goaccess_conf_dir = '/etc/';
+		$goaccess_conf_main = $goaccess_conf_dir . 'goaccess.conf';
+
+		if(!file_exists($goaccess_conf_main) || !isset($goaccess_conf_main))
+		{
+			$app->log("No GoAccess base config found. Make sure that GoAccess is installed and that the goaccess.conf does exist in ".$goaccess_conf_dir.".", LOGLEVEL_WARN);
+		}
+
+                /* Check wether the goaccess binary is in path */
+		system('type goaccess', $retval);
+                if ($retval === 0) {
+
+		foreach($records as $rec) {
+			$yesterday = date('Ymd', strtotime("-1 day", time()));
+
+			$log_folder = 'log';
+
+			if($rec['type'] == 'vhostsubdomain' || $rec['type'] == 'vhostalias') {
+				$tmp = $app->db->queryOneRecord('SELECT `domain` FROM web_domain WHERE domain_id = ?', $rec['parent_domain_id']);
+				$subdomain_host = preg_replace('/^(.*)\.' . preg_quote($tmp['domain'], '/') . '$/', '$1', $rec['domain']);
+				if($subdomain_host == '') $subdomain_host = 'web'.$rec['domain_id'];
+				$log_folder .= '/' . $subdomain_host;
+				unset($tmp);
+			}
+
+			$logfile = escapeshellcmd($rec['document_root'].'/' . $log_folder . '/'.$yesterday.'-access.log');
+
+			if(!@is_file($logfile)) {
+				$logfile = escapeshellcmd($rec['document_root'].'/' . $log_folder . '/'.$yesterday.'-access.log.gz');
+				if(!@is_file($logfile)) {
+					continue;
+				}
+			}
+
+			$web_folder = (($rec['type'] == 'vhostsubdomain' || $rec['type'] == 'vhostalias') ? $rec['web_folder'] : 'web');
+			$domain = escapeshellcmd($rec['domain']);
+			$statsdir = escapeshellcmd($rec['document_root'].'/'.$web_folder.'/stats');
+			$goaccess_conf = escapeshellcmd($rec['document_root'].'/log/goaccess.conf');
+
+			/*
+			 In case that you use a different log format, you should use a custom goaccess.conf which you'll have to put into /usr/local/ispconfig/server/conf-custom/.
+			 By default the originally, with GoAccess shipped goaccess.conf from /etc/ will be used along with the log-format value COMBINED. 
+			 */
+
+			if(file_exists("/usr/local/ispconfig/server/conf-custom/goaccess.conf.master") && (!file_exists($goaccess_conf))) {
+				copy("/usr/local/ispconfig/server/conf-custom/goaccess.conf.master", $goaccess_conf);
+			} elseif(!file_exists($goaccess_conf)) {
+				/*
+				 By default the goaccess.conf should get copied by the webserver plugin but in case it wasn't, or it got deleted by accident we gonna copy it again to the destination dir.
+				 Also there was no /usr/local/ispconfig/server/conf-custom/goaccess.conf.master, so we gonna use /etc/goaccess.conf as the base conf.
+				*/
+                        	copy($goaccess_conf_main, $goaccess_conf);
+	                        file_put_contents($goaccess_conf, preg_replace('/^(#)?log-format COMBINED/m', "log-format COMBINED", file_get_contents($goaccess_conf)));
+			}
+
+			/* Update the primary domain name in the title, it could occasionally change */
+			if(is_file($goaccess_conf) && (filesize($goaccess_conf) > 0)) {
+				$goaccess_content = file_get_contents($goaccess_conf);
+				file_put_contents($goaccess_conf, preg_replace('/^(#)?html-report-title(.*)?/m', "html-report-title $domain", file_get_contents($goaccess_conf)));
+			}
+
+
+
+			if(!@is_dir($statsdir)) mkdir($statsdir);
+			$username = escapeshellcmd($rec['system_user']);
+			$groupname = escapeshellcmd($rec['system_group']);
+			$docroot = $rec['document_root'];
+
+			$goa_db_dir = $docroot.'/'.$web_folder.'/stats/.db/';
+			$output_html = $docroot.'/'.$web_folder.'/stats/goaindex.html';
+                        if(!@is_dir($goa_db_dir)) mkdir($goa_db_dir);
+
+                        if(is_link('/var/log/ispconfig/httpd/'.$domain.'/yesterday-access.log')) unlink('/var/log/ispconfig/httpd/'.$domain.'/yesterday-access.log');
+                        symlink($logfile, '/var/log/ispconfig/httpd/'.$domain.'/yesterday-access.log');
+
+
+			chown($statsdir, $username);
+			chgrp($statsdir, $groupname);
+
+			$goamonth = date("n");
+			$goayear = date("Y");
+
+			if (date("d") == 1) {
+				$goamonth = date("m")-1;
+				if (date("m") == 1) {
+					$goayear = date("Y")-1;
+					$goamonth = "12";
+				}
+			}
+
+	
+			if (date("d") == 2) {
+				$goamonth = date("m")-1;
+				if (date("m") == 1) {
+					$goayear = date("Y")-1;
+					$goamonth = "12";
+				}
+
+				$statsdirold = $statsdir."/".$goayear."-".$goamonth."/";
+
+				if(!is_dir($statsdirold)) {
+					mkdir($statsdirold);
+				}
+
+				rename($goa_db_dir, $statsdirold.'db');
+                                mkdir($goa_db_dir);
+
+				$files = scandir($statsdir);
+				foreach ($files as $file) {
+					if (substr($file, 0, 1) != "." && !is_dir("$statsdir"."/"."$file") && substr($file, 0, 1) != "w" && substr($file, 0, 1) != "i") copy("$statsdir"."/"."$file", "$statsdirold"."$file");
+				}
+			}
+			
+                        $app->system->exec_safe("goaccess -f ? --config-file ? --load-from-disk --keep-db-files --db-path=? --output=?", $logfile, $goaccess_conf, $goa_db_dir, $output_html);
+
+			if(!is_file($rec['document_root']."/".$web_folder."/stats/index.php")) {
+				if(file_exists("/usr/local/ispconfig/server/conf-custom/goaccess_index.php.master")) {
+					copy("/usr/local/ispconfig/server/conf-custom/goaccess_index.php.master", $rec['document_root']."/".$web_folder."/stats/index.php");
+				} else {
+					copy("/usr/local/ispconfig/server/conf/goaccess_index.php.master", $rec['document_root']."/".$web_folder."/stats/index.php");
+				}
+			}
+
+			$app->log('Created GoAccess statistics for ' . $domain, LOGLEVEL_DEBUG);
+		
+
+			if(is_file($rec['document_root']."/".$web_folder."/stats/index.php")) {
+				chown($rec['document_root']."/".$web_folder."/stats/index.php", $rec['system_user']);
+				chgrp($rec['document_root']."/".$web_folder."/stats/index.php", $rec['system_group']);
+			}
+
+			$app->system->exec_safe('chown -R ?:? ?', $username, $groupname, $statsdir);
+
+		}
+	} else {
+		$app->log("Stats not generated. The GoAccess binary couldn't be found. Make sure that GoAccess is installed and that it is in \$PATH", LOGLEVEL_WARN);
+	}
+
+		parent::onRunJob();
+	}
+
+	/* this function is optional if it contains no custom code */
+	public function onAfterRun() {
+		global $app;
+
+		parent::onAfterRun();
+	}
+
+}
+
+?>
diff --git a/server/plugins-available/apache2_plugin.inc.php b/server/plugins-available/apache2_plugin.inc.php
index 26f2948256..1bebab55f4 100644
--- a/server/plugins-available/apache2_plugin.inc.php
+++ b/server/plugins-available/apache2_plugin.inc.php
@@ -1863,6 +1863,12 @@ class apache2_plugin {
 			$this->awstats_update($data, $web_config);
 		}
 
+                //* Create GoAccess configuration
+                if($data['new']['stats_type'] == 'goaccess' && ($data['new']['type'] == 'vhost' || $data['new']['type'] == 'vhostsubdomain' || $data['new']['type'] == 'vhostalias')) {
+                        $this->goaccess_update($data, $web_config);
+                }
+
+
 		//* Remove Stats-Folder when Statistics set to none
 		if($data['new']['stats_type'] == '' && ($data['new']['type'] == 'vhost' || $data['new']['type'] == 'vhostsubdomain' || $data['new']['type'] == 'vhostalias')) {
 			$app->file->removeDirectory($data['new']['document_root'].'/web/stats');
@@ -2973,6 +2979,67 @@ class apache2_plugin {
 		}
 	}
 
+	//* Update the GoAccess configuration file
+        private function goaccess_update ($data, $web_config) {
+                global $app;
+
+                $web_folder = $data['new']['web_folder'];
+                if($data['new']['type'] == 'vhost') $web_folder = 'web';
+
+                $goaccess_conf_dir = '/etc/';
+                $goaccess_conf_main = $goaccess_conf_dir.'goaccess.conf';
+
+                if(!is_dir($data['new']['document_root']."/" . $web_folder . "/stats/")) mkdir($data['new']['document_root']."/" . $web_folder . "/stats/.db");
+                $goaccess_conf = escapeshellcmd($data['new']['document_root'].'/log/goaccess.conf');
+
+
+                /*
+                In case that you use a different log format, you should use a custom goaccess.conf which you'll have to put into /usr/local/ispconfig/server/conf-custom/.
+                By default the originaly with GoAccess shipped goaccess.conf from /etc/ will be used along with the log-format value COMBINED. 
+                */
+                if(file_exists("/usr/local/ispconfig/server/conf-custom/goaccess.conf.master") && (!file_exists($goaccess_conf))) {
+                        copy("/usr/local/ispconfig/server/conf-custom/goaccess.conf.master", $goaccess_conf);
+                } elseif(!file_exists($goaccess_conf)) {
+                        /*
+                         By default the goaccess.conf should get copied by the webserver plugin but in case it wasn't, or it got deleted by accident we gonna copy it again to the destination dir.
+                         Also there was no /usr/local/ispconfig/server/conf-custom/goaccess.conf.master, so we gonna use /etc/goaccess.conf as the base conf.
+                        */
+                        copy($goaccess_conf_main, $goaccess_conf);
+                        file_put_contents($goaccess_conf, preg_replace('/^(#)?log-format COMBINED/m', "log-format COMBINED", file_get_contents($goaccess_conf)));
+                }
+
+                if(file_exists($goaccess_conf)) {
+                        $domain = escapeshellcmd($data['new']['domain']);
+                        file_put_contents($goaccess_conf, preg_replace('/^(#)?html-report-title(.*)/m', "html-report-title $domain", file_get_contents($goaccess_conf)));
+                }
+
+                if(is_file($goaccess_conf) && (filesize($goaccess_conf) > 0)) {
+                        $app->log('Created GoAccess config file: '.$goaccess_conf, LOGLEVEL_DEBUG);
+                } else {
+                        $app->log("No GoAccess base config found. Make sure that GoAccess is installed and that the goaccess.conf does exist in ".$goaccess_conf_dir.".", LOGLEVEL_WARN);
+                }
+
+                if(is_file($data['new']['document_root']."/" . $web_folder . "/stats/index.html")) $app->system->unlink($data['new']['document_root']."/" . $web_folder . "/stats/index.html");
+                if(file_exists("/usr/local/ispconfig/server/conf-custom/goaccess_index.php.master")) {
+                        $app->system->copy("/usr/local/ispconfig/server/conf-custom/goaccess_index.php.master", $data['new']['document_root']."/" . $web_folder . "/stats/index.php");
+                } else {
+                        $app->system->copy("/usr/local/ispconfig/server/conf/goaccess_index.php.master", $data['new']['document_root']."/" . $web_folder . "/stats/index.php");
+                }
+        }
+
+        //* Delete the GoAccess configuration file
+        private function goaccess_delete ($data, $web_config) {
+                global $app;
+
+                $goaccess_conf = escapeshellcmd($data['new']['document_root'].'/log/goaccess.conf');
+
+                if ( @is_file($goaccess_conf) ) {
+                        $app->system->unlink($goaccess_conf);
+                        $app->log('Removed GoAccess config file: '.$goaccess_conf, LOGLEVEL_DEBUG);
+                }
+        }
+
+
 	//* Delete the awstats configuration file
 	private function awstats_delete ($data, $web_config) {
 		global $app;
-- 
GitLab