Skip to content
Snippets Groups Projects
actionlog.class.php 14.90 KiB
<?php
require_once 'user.class.php';

/**
 * class to log all project actions, ie. build, deploy etc.
 *
 * @author hahn
 */
class Actionlog {

    private $_aLoglevels = array("info", "warning", "error", "success"); // array of valid loglevels
    private $_sIP = false;
    private $_sUser = false;
    private $_sProject = false;
    private $_sCreate = '
        CREATE TABLE "logs" (
          `id` INTEGER PRIMARY KEY  AUTOINCREMENT  NOT NULL  UNIQUE ,
          `time` DATETIME,
          `loglevel` TEXT,
          `ip` TEXT,
          `user` TEXT,
          `project` TEXT,
          `action` TEXT,
          `message` TEXT
        )';
    /*
    private $_sCreateFUTURE = '
        CREATE TABLE "logs" (
          `id` INTEGER PRIMARY KEY  AUTOINCREMENT  NOT NULL  UNIQUE ,
          `time` DATETIME,
          `time-start` DATETIME,
          `loglevel` TEXT,
          `ip` TEXT,
          `user` TEXT,
          `project` TEXT,
          `action` TEXT,
          `message` TEXT
        )';
        */
    /**
     * constructor - sets internal environment variables and checks existence 
     * of the database
     * @global array $aConfig    settings
     * @param  string $sProject  project ID
     */
    public function __construct($sProject = false) {
        global $aConfig;
        if (!is_array($aConfig) || !array_key_exists("appRootDir", $aConfig)) {
            die(__CLASS__ . "::".__FUNCTION__." ERROR: configuration with \$aConfig was not loaded.");
        }
        $this->_dbfile = $aConfig['dataDir'] . '/database/logs.db';
        if (!file_exists($this->_dbfile)) {
            $this->_createDb();
            if (!file_exists($this->_dbfile)) {
                die("ERROR: unable to create sqlite database " . $this->_dbfile);
            }
        }
        $this->_sProject = $sProject;
        $oUser=new user();
        if ($oUser->getUsername()) {
            $this->_sIP = $_SERVER["REMOTE_ADDR"];
            $this->_sUser = $oUser->getUsername() . " (web)";
        } else {
            $this->_sIP = 'local';
            $aUser=posix_getpwuid(posix_geteuid());
            $this->_sUser = $aUser['name'] . " (system)";
        }
    }

    /**
     * create sqlite database - called in constructor if the file does not exist
     */
    private function _createDb() {
        echo "try to create file $this->_dbfile ...<br>\n";
        return $this->_makeQuery($this->_sCreate);
    }

    /**
     * execute a sql statement
     * @param string $sSql sql statement
     * @return database object
     */
    private function _makeQuery($sSql) {
        // $this->_log(__FUNCTION__."($sSql)");
        // echo "<pre>$sSql</pre>";
        $db = new PDO("sqlite:" . $this->_dbfile);
        $result = $db->query($sSql);
        /*
        if(!$result){
            echo "PDO ERROR " . print_r($db->errorInfo(), 1) ."<br>";
        }
         */
        $db = NULL;
        return $result;
    }

    /**
     * add a log message
     * @param string $sMessage   message
     * @param string $sAction    project action; i.e. build, deploy, ...
     * @param string $sLoglevel  loglevel
     * @return type
     */
    public function add($sMessage, $sAction = "", $sLoglevel = "info", $sTimeStart = false) {
        if (array_search($sLoglevel, $this->_aLoglevels) === false) {
            die(__class__ . ": loglevel $sLoglevel is invalid");
        }
        $sql = "INSERT INTO `logs` (`time`, `loglevel`, `ip`, `user`, `project` ,`action`, `message`)
          VALUES(
            '" . date("Y-m-d H:i:s") . "',
            '" . $sLoglevel . "',
            '" . $this->_sIP . "',
            '" . $this->_sUser . "',
            '" . $this->_sProject . "',
            '" . $sAction . "',
            '" . $sMessage . "'
          );
        ";
        /*
        $sqlFUTURE = "INSERT INTO `logs` (`time`, ".($sTimeStart ? "`time-start`, " : "" )." `loglevel`, `ip`, `user`, `project` ,`action`, `message`)
          VALUES(
            '" . date("Y-m-d H:i:s") . "',
            ".($sTimeStart ? "'" . $sTimeStart . "', " : "" ) ."
            '" . $sLoglevel . "',
            '" . $this->_sIP . "',
            '" . $this->_sUser . "',
            '" . $this->_sProject . "',
            '" . $sAction . "',
            '" . $sMessage . "'
          );
        ";
         * 
         */
        // echo $sql . "<br>";
        $oResult=$this->_makeQuery($sql);
        return $oResult;
    }

    /**
     * helper function to remove chars in a string
     * @param string  $sVal      user value
     * @param string  $sOKChars  good chars to keep
     * @return string
     */
    private function _filterAllowedChars($sVal, $sOKChars){
        return preg_replace('/[^'.$sOKChars. ']/i', '',$sVal);
    }
    
    /**
     * get log data
     * @param array $aFilter with the following keys:
     *   'project' - filter by project; will be mixed with where (see next key)
     *   'from   ' - time greater equal; time as string i.e. "2020-06-24" or "2020-06-24 11:00:00"
     *   'to'      - max time (see from)
     *   'order'   - order clausel - part behind "ORDER BY "; default is "id DESC" (order by newest entries)
     *   'limit'   - limit clausel - part behind "LIMIT "
     * @return array
     */
    public function getLogs($aFilter = array()) {
        // var_dump(R::findAll( 'log' ));
        $aReturn = array();

        $sSql = 'SELECT `id`,`time`,`loglevel`,`ip`,`user`,`project`,`action`,`message`  from logs ';
        $sWhere = false;
        
        $aWhere=array();
        if (array_key_exists("project", $aFilter) && $aFilter["project"]) {
            $aWhere[]='`project`="' . $this->_filterAllowedChars($aFilter["project"], '[a-z0-9\-\_]') . '"';
        }
        if (array_key_exists("from", $aFilter) && $aFilter["from"]) {
            $aWhere[]='`time`>="' . $this->_filterAllowedChars($aFilter["from"], '[0-9\-\ \:]') . '"';
        }
        if (array_key_exists("to", $aFilter) && $aFilter["to"]) {
            $aWhere[]='`time`<="' . $this->_filterAllowedChars($aFilter["to"], '[0-9\-\ \:]') . '"';
        }

        $sSql.=(count($aWhere) ? 'WHERE '. implode(' AND ', $aWhere) : '');
        
        if (array_key_exists("order", $aFilter) && $aFilter["order"]) {
            $sSql.=' ORDER BY ' . $this->_filterAllowedChars($aFilter["order"], '[a-z\`0-9\,\ ]');
        } else {
            $sSql.=' ORDER BY id DESC ';
        }
        if (array_key_exists("limit", $aFilter) && $aFilter["limit"]) {
            $sSql.=' LIMIT ' . $this->_filterAllowedChars($aFilter["limit"], '[0-9\,\ ]');
        }
        
        foreach ($this->_makeQuery($sSql) as $row) {
            for ($i = 0; $i <= 7; $i++) {
                unset($row[$i]);
            }
            $aReturn[] = $row;
        }
        return $aReturn;
    }

    /**
     * render html code for a table with logs. The filter will be sent to
     * getLogs method.
     * @param array $aFilter with the following keys:
     *   'project' - filter by project; will be mixed with where (see next key)
     *   'limit' - limit clausel - part behind "LIMIT "
     * @return string
     */
    public function renderLogs($aFilter = array(), $bIsFullsearch=false) {
        $sReturn = '';

        static $bWasShown;
        if ($bWasShown)
            return " ";

        $bWasShown = true;
        require_once 'formgen.class.php';

        // values for dropdowns - limit of lines; time
        
        $aLimits=array(
            '20'=>array('label'=>20),
            '50'=>array('label'=>50),
            '100'=>array('label'=>100),
            ''=>array('label'=>t("all")), 
        );
        $aTimes=array(
            date("Y-m-d", date("U"))                =>array('label'=>t("class-actionlog-time-today")),
            date("Y-m-d", date("U") - 60*60*24*1)   =>array('label'=>t("class-actionlog-time-since-yesterday")),
            date("Y-m-d", date("U") - 60*60*24*7)   =>array('label'=>t("class-actionlog-time-for-1-week")),
            date("Y-m-d", date("U") - 60*60*24*7*2) =>array('label'=>sprintf(t("class-actionlog-time-for-n-weeks"), "2")),
            date("Y-m-d", date("U") - 60*60*24*7*4) =>array('label'=>sprintf(t("class-actionlog-time-for-n-weeks"), "4")),
            ''=>array('label'=>t("all")), 
        );
        
        $aForms = array(
            'filter' => array(
                'meta' => array(
                    'method' => 'GET',
                    'action' => '?',
                    'class' => 'form-inline',
                ),
                'validate' => array(),
                'form' => array(),
            ));

        // generate filter for log table
        // $bIsFullsearch: true = show all inputs; false: no interactive search
        
        if ($bIsFullsearch){
            
            $aForms["filter"]["form"]['label'] = array(
                'type' => 'markup',
                'value' => '<h3>' . t("class-actionlog-title") . ' :: '.t("class-actionlog-filter").'</h3>',
            );
            
            // --- list of all projects in log
            $sSql='SELECT DISTINCT(project) from `logs` order by project asc';
            $aForms["filter"]["form"]['selectproject'] = array(
                    'type' => 'select',
                    'name' => 'selectproject',
                    'label' => '<i class="glyphicon glyphicon-tag"></i> ' . t('project'),
                    'class' => 'span2',
                    'onchange'=>'updateActionlog();',
                    'inline' => true,
            );
            $aForms["filter"]["form"]['selectproject']['options']['']=array('label'=>t("all"));
            foreach ($this->_makeQuery($sSql) as $row) {
                if ($row[0]){
                    $aForms["filter"]["form"]['selectproject']['options'][$row[0]]=array('label'=>$row[0]);
                }
            }
            $aForms["filter"]["form"]['selectfrom'] = array(
                    'type' => 'select',
                    'name' => 'selectWheretime',
                    'label' => '<i class="glyphicon glyphicon-calendar"></i> '.t("class-actionlog-time"),
                    'class' => 'span2',
                    'onchange'=>'updateActionlog();',
                    'options' => $aTimes,
                    'inline' => true,
            );
            $aForms["filter"]["form"]['selectlimit'] = array(
                    'type' => 'select',
                    'name' => 'selectlimit',
                    'label' => '<i class="glyphicon glyphicon-list"></i> '.t("class-actionlog-count"),
                    'class' => 'span1',
                    'onchange'=>'updateActionlog();',
                    'options' => $aLimits,
                    'inline' => true,
            );
            $aForms["filter"]["form"]['line'] = array(
                    'type' => 'markup',
                    'value' => '<hr>',
            );

            
        } else {
            
            // write filters as hidden fields
            if (array_key_exists("project", $aFilter)){
                $aForms["filter"]["form"]['selectproject'] = array(
                        'type' => 'hidden',
                        'value' => $aFilter["project"]
                );
            }
            if (array_key_exists("limit", $aFilter)){
                $aForms["filter"]["form"]['selectlimit'] = array(
                        'type' => 'hidden',
                        'value' => $aFilter["limit"]
                );
            }
        }
        $aForms["filter"]["form"]['efilterlogs'] = array(
                        'type' => 'text',
                        'name' => 'efilterlogs',
                        'label' => '<i class="glyphicon glyphicon-filter"></i>' . t("overview-textsearch"),
                        'inline'=> true,
                        'onkeyup' => 'filterLogTable();',
                        'onkeypress' => 'filterLogTable();',
                        'onchange' => 'filterLogTable();',
                        'size' => 20,
        );
        if (!$bIsFullsearch){
            $aForms["filter"]["form"]['btnalllogs'] = array(
                    'type' => 'button',
                    'value' => t('show all'),
                    'href'=>'/deployment/all/setup/actionlog/',
                    'onclick'=>'location.href=\'/deployment/all/setup/actionlog/\'; return false;',
            );
        }
        
        // generate html output
        
        $oForm = new formgen($aForms);
        
        $sReturn = '<div id="divActionlogs">'.$oForm->renderHtml("filter").'
                <div style="clear: both; margin-bottom: 1em;"></div>
                <div id="tableLogactions"></div>
                    <!--
                    <script src="/vendor/vis/4.21.0/vis.min.js"></script>
                    <link href="/vendor/vis/4.21.0/vis.min.css" rel="stylesheet" type="text/css" />
                    <div id="divTimeline"></div>
                    -->
                </div>
                <script>
                    var sMsgNolog="'.t("class-actionlog-nolog").'";
                </script>';

        if ($bIsFullsearch){
            $sReturn.='<script>
                        $(document).ready(function() {
                            updateActionlog();
                        });</script>';
        } else {

            $sReturn= '<strong>'
                . '<button onclick="setLogVisibility(\'block\');"  id="btnShowLogs" class="btn btn-default btnLogs"><b class="glyphicon glyphicon-chevron-right"></b> </button>'
                . '<button onclick="setLogVisibility(\'none\');"   id="btnHideLogs" class="btn btn-default btnLogs"><b class="glyphicon glyphicon-chevron-down"></b> </button>'
                . ' ' . t("class-actionlog-title") 
                . '</strong>'
                
                . $sReturn.'
                <script>
                function getLogVisibility(){
                    var sReturn=localStorage.getItem("bActionlogsVisible");
                    sReturn=(sReturn=="block")?"block":"none";
                    return sReturn;
                }

                function setLogVisibility(sVisibility){
                    localStorage.setItem("bActionlogsVisible", sVisibility);
                    $("#divActionlogs").css("display", sVisibility);
                    $(".btnLogs").css("display", "none");
                    if (sVisibility=="block"){
                        $("#btnHideLogs").css("display", "inline");
                        $(document).ready(function() {
                            updateActionlog();
                        });
                    } else {
                        $("#btnShowLogs").css("display", "inline");
                    }
                }
                setLogVisibility(getLogVisibility());
                </script>';
        }
        return $sReturn;
    }
    
}