#!/usr/bin/php -q


<?php

// Copyright (c) 2012, Intorrent Inc.
//
// This software is the copyrighted work of NeoNova BV.
// Use of the software is subject to the general business terms and conditions of NeoNova BV
// and its related transaction documents like license sheets or other written notices.
//
// Any reproduction or redistribution of the software not in accordance with the license terms
// is expressly prohibited by law, and may result in severe civil and criminally penalties.
// Violators will be prosecuted to the maximum extent possible.

// INFO:
// This script will monitor astiumd to see the status of queues.
// It will play a warning sound over the server's speakers if
// callers have been waiting in a particular queue over a certain
// time limit.
// It will play a "critical" sound if the wait time exceeds a second
// limit.  At the moment, I have two "beepbeep" variants, the "crit"
// version beepbeeps at twice the rate of the "warn" variant. Both
// play about 10 s. of sound; the refresh rate is initially set to 10.
// sox (play) is used to send out the sound. The average pbx server
// can be made to work its sound card (either on-the MB, or inserted,
// via system-config-soundcard, which will probably have to "yum install"-ed,
// and run once by hand where this is intended to be run.
// Therefore, this daemon is intended for in-premise boxes, whith some sort of
// sound connections. An init.d file should be installed in /etc/init.d/ and
// "chkconfig runq on" should be run once to make sure that warn-queue is run at boot time.

/* instructions
1. collect data from database and call manager
2. put in array $fields[$qid] [...]
3. print and skip exceptions based on url parameters and cookies
*/
error_reporting (E_ALL & ~E_NOTICE);


// include "/usr/local/astium/web/php/warnq-config.php";
include "/usr/local/astium/web/php/config.php";

$pid_file = '/var/run/warnq.pid';
$LOGFILE = "/usr/local/astium/web/html/log/warnq-error.log"; // so that log_msg() knows where to write to.

global $debug;
$options = getopt("hd:");
if (isset($options['h'])) {
  printf("usage: warn-queue [-h] [-d n]\n");
  exit(0);
}
$debug = $options["d"];


function sig_handler($signo) {

     global $running;
     switch($signo) {
         case SIGTERM:
             // handle shutdown tasks
             //$running = false;
             exit;
             break;
         case SIGHUP:
             // handle restart tasks
             break;
         case SIGUSR1:
              log_msg(__FILE__, __LINE__, "Caught SIGUSR1...");
             break;
         case SIGCHLD:
             while( pcntl_waitpid(-1,$status,WNOHANG)>0 ) {
             }
             break;
         case SIGINT:
       exit;
         default:
             // not implemented yet...
             break;
     }

}

function copy_if_changed($source, $destination)
{
  $sha_src = sha1_file($source);
  $sha_dst = sha1_file($destination);

  //printf("%s: %s\n", $sha_src, $source);
  //printf("%s: %s\n", $sha_dst, $destination);
  if ($sha_src != $sha_dst) {
    //system("diff -uNr $source $destination");
    copy($source, $destination);
    return true;
  }
  return false;
}

function become_daemon() {

   $child = pcntl_fork();
   if($child) {
       exit; // kill parent
   }
   posix_setsid(); // become session leader
   chdir("/");
   umask(0); // clear umask
   return posix_getpid();

}

function open_pid_file($file) {

   if(file_exists($file)) {
       $fp = fopen($file,"r");
       $pid = fgets($fp,1024);
       fclose($fp);
       if(posix_kill($pid,0)) {
           print "Server already running with PID: $pid\n";
           exit;
       }
       print "Removing PID file for defunct server process $pid\n";
       if(!unlink($file)) {
           print "Cannot unlink PID file $file\n";
           exit;
       }
   }
   if($fp = fopen($file,"w")) {
       return $fp;
   } else {
       print "Unable to open PID file $file for writing...\n";
       exit;
   }
}

function remove_pid_file($file) {

  if(file_exists($file)) {
    $fp = fopen($file,"r");
    $pid = fgets($fp,1024);
    fclose($fp);

    // Remove pid file.
    if(posix_getpid() == $pid) {
      unlink($file);
    }
  }
}

function change_identity($uid,$gid) {
   global $pid_file;
   if(!posix_setgid($gid)) {
       print "Unable to setgid to $gid!\n";
       unlink($pid_file);
       exit;
   }   
   if(!posix_setuid($uid)) {
       print "Unable to setuid to $uid!\n";
       unlink($pid_file);
       exit;
   }
}

pcntl_signal(SIGHUP,  "sig_handler");

if ($debug == 0) {
  // Write 
  print "Starting daemon\n";
  $fh = open_pid_file($pid_file);

  // Become a daemon process.
  $pid = become_daemon();
  fputs($fh,$pid);
  fclose($fh);
  define_syslog_variables();
  ini_set('error_log', 'syslog');
}

print "Daemon running...\n";
// These variables should probably be set from a config file somewhere!

$refresh = 10;
$company  = 3;      // company-id
$lastsound = 0;     // used to keep track of the last time a sound was played.
$resound  = 30;     // amount of time to separate the generated sounds
$extension = 971;   // extension of queue to track
$warn_time = 45;    // number of seconds to warn, that a caller is waiting in queue
$crit_time = 75;    // number of seconds that an incoming caller is waiting, before sounding the "crit" sound.
$warn_file = "/usr/local/astium/web/html/beepbeep-short.wav"; // the sound file to play for a warning
$crit_file = "/usr/local/astium/web/html/beepbeepbeepbeep.wav"; // the sound file to play for a critical wait time
$astiumserver = '127.0.0.1';
$theqid = 0; // the Q that matches the $extension 

  function play_file($type)
  {
    global $lastsound, $resound, $warn_file, $crit_file, $debug;
    if (time() - $lastsound >= $resound) {
      $sound = ($type == "warn" ? $warn_file : $crit_file);
      
      system("play $sound > /dev/null 2>&1 &");
      if ($debug > 1) echo "Time: " . time() . ": Playing the $type sound file $sound!\n";
      $lastsound = time();
    }
  } 

  function start_over()
  {
    global $astiumserver, $errno, $errstr, $fp, $response, $shortname, $username, $password;
    $fp = fsockopen ($astiumserver, 5655, $errno, $errstr, 10);
  
    if (!$fp) {
      echo "<p class='error'>" . _("Cannot connect") . "<p>\n";
      flush();
      exit;
    }

    /*
     * Authentication: logging in to the Call Manager
     *                 disabling the automatic stream of events
     */
  
    $response = fgets($fp);
    fwrite($fp, "Action: Login\r\nCompany: $shortname\r\nUsername: $username\r\nSecret: $password\r\n\r\n");
    $response = get_response($fp);
    $response = get_response($fp);

    if (!strstr($response, "Succes")) {
      echo "<p class='error'>". _("Cannot Login") . "</p>\n";
      flush();
      fclose($fp);
      exit;
    }
  }

  function get_response($fp)
  {
    $inside_command = false;
    $buffer = '';
    $count = 2000;
    while ($count > 0) {
      $line = fgets($fp);
      //echo "-$line-"; echo strlen($line);
      if (strstr($line, "--END COMMAND--")) {
        $inside_command = false;
      }
      if (strstr($line, "Response: Follows")) {
        $inside_command = true;
      }
      if (!$inside_command && $line == "\r\n") break;
      $buffer .= $line;
      $count--;
    }
    return($buffer);
  }

// START COLLECTING DATA 
  
  $cms = new DB_CMS;
  $tmp = new DB_CMS;

  if ($debug > 2) $cms->Debug = 1;
  if ($debug > 2) $tmp->Debug = 1;
  /*
   * Get username/password combination as well as company shortname,
   * to gain access to astiumd manager interface.
   * Any active person who is operator will do, because that person will get information
   * on queues of all active companies on this machine.
   */

  $q = "select username, password, shortname from contact, contact_company, company
         where contact.status != 'deleted' and isoperator = 'yes' and contact.id = contact_company.contact
           and contact_company.company = company.id and company.status != 'deleted' and contact.id > 2 limit 1";
  $cms->query($q);
  if ($cms->num_rows() > 0) {
    $cms->next_record();
    $username  = $cms->f('username');
    $password  = $cms->f('password');
    $shortname = $cms->f('shortname');
  } else {
    $q = "select username, password, shortname from contact, contact_company, company
           where contact.status != 'deleted' and isoperator = 'yes' and contact.id = contact_company.contact
             and contact_company.company = company.id and company.status != 'deleted' limit 1";
    $cms->query($q);
    $cms->next_record();
    $username  = $cms->f('username');
    $password  = $cms->f('password');
    $shortname = $cms->f('shortname');
  }

  /*
   * Connection: making the connection with Astiumd
   * Port is 5655, Switchboard software connects to same port.
   */
  
  start_over(); 

  $running = true;

  while ($running) {
      if ($debug>1) echo ".";

      $r1 = fwrite($fp, "Action: ListQueues\r\n\r\n");
      if (!$r1 || $r1 == 'false') {
        sleep(2);
        if ($fp) fclose($fp);
        start_over();
        continue;
      }

      $response = get_response($fp);

    /*
    [Company / 4]
    [Phoneline / 237]
    [Name / NN-Sales]
    [Completecaller / 1]
    [Completeagent / 0]
    [Agentdump / 0]
    [Agentconnect / 0]
    [Abandoned / 1]
    [Oldest / 1246959832]
    [Calls / 1]
    [Agents / 4]
    [Loggedon / 3]
    [Call-1 / 3754]
    */

      if (!strstr($response, "Succes")) {
        echo "<p class='error'>". _("Communication error") . "</p>\n";
        flush();
        fclose($fp);
        exit;
      }

      /*
       * Output of Action: ListQueues command is blocks of information about every queue.
       * First we put all data in the $fields array, keeping the variable names used in astiumd output.
       */

      do {
        $response = get_response($fp);
        if (strstr($response, "Event: ListComplete")) {
          break;
        }
        $response = get_response($fp);

        unset($qid);
        $data = explode("\n", $response);
        foreach($data as $line) {
          $line = trim($line);
          $data = explode(": ", $line);
          if (($data[0] == "Company") && isset($comp_ex_array) && (is_array($comp_ex_array))) {
            if (in_array($data[1], $comp_ex_array)) {
              break;
            }
          }
          if ($data[0] == "Id") {
            $qid = $data[1];
            $fields[$qid] = array();
            continue;
          }
          if (isset($qid) && isset($data[1])) {
            $fields[$qid][$data[0]] = $data[1];
          }
        }

      } while (!strstr($response, "Event: ListComplete"));

      /*
       * Using queue data just collected, copy values to variables with the correct names
       */

      $t = 0;
      foreach($fields as $qid => $data) {
        if ($qid == 1) continue;

        if (isset($fields[$qid]["Name"]))           $fields[$qid]["name"]            = $fields[$qid]["Name"];
        if (isset($fields[$qid]["Calls"]))          $fields[$qid]["callers"]         = $fields[$qid]["Calls"];
        $fields[$qid]["connectedtoday"] = 0;    
        if (isset($fields[$qid]["Completecaller"])) $fields[$qid]["connectedtoday"] += $fields[$qid]["Completecaller"];
        if (isset($fields[$qid]["Completeagent"]))  $fields[$qid]["connectedtoday"] += $fields[$qid]["Completeagent"];
        if (isset($fields[$qid]["Abandoned"]))      $fields[$qid]["abandonedtoday"]  = $fields[$qid]["Abandoned"];
        if (isset($fields[$qid]["Agents"]))         $fields[$qid]["agents"]          = $fields[$qid]["Agents"];

        if (isset($fields[$qid]["Phoneline"])) {
          $q = sprintf("select extension from phoneline where id='%s'", $fields[$qid]["Phoneline"]);
          $cms->query($q);
          $cms->next_record();
          $fields[$qid]["extension"]      = $cms->f('extension');
        
          if (isset($extension) && $extension != '' && $extension == $fields[$qid]["extension"]) {
            $theqid = $qid;
          }

        }

        $qid = $theqid;

        if (isset($fields[$qid]["Oldest"]) && $fields[$qid]["Oldest"] != '0') {
          // Oldest is date given in seconds. time() - Oldest = seconds waiting
      
          $seconds = time() - $fields[$qid]["Oldest"];
          $minutes = $seconds / 60;
          $hours = $minutes / 60;
          $fields[$qid]["longestwaiting"] = sprintf("%d:%02d.%02d", $hours, $minutes % 60, $seconds % 60);
        }

    
        // calculate percentage of abandoned calls:
        
        if (isset($fields[$qid]) && $fields[$qid]["connectedtoday"] > 0) {
          $percentabandoned = $fields[$qid]["abandonedtoday"] /
                             ($fields[$qid]["connectedtoday"] + $fields[$qid]["abandonedtoday"]) * 100;
        } else {
          $percentabandoned = 0;
        }
        $fields[$qid]["percentabandoned"] = number_format($percentabandoned, 0, '.', '') . "&nbsp;%";

        // collect information of current queue and place in array $fields
        if (!isset($fields[$qid]["longestwaiting"])) $fields[$qid]["longestwaiting"] = 0;


        // selecting amount of active agents in this queue:
        $q = sprintf("select contact.*
            from   queue_contact right join contact on contact.id = queue_contact.contact
                                                   and contact.pause = 'no'
                                                   and contact.phone > 1
                                                   and contact.status <> 'deleted'
            where  queue_contact.queue = '%s'", $qid);

        $tmp->query($q);

        while ($tmp->next_record()) {
          $fields[$qid]['agent_name'][] = $tmp->f('firstname') . " " . $tmp->f('lastname');
        }

        $t++;
      }
   
      if ($theqid == 0) {
            echo "We didn't find a Queue that has the extension $extension!\n";
            exit;
      }
      // $fields[$qid]["Oldest"] = time() - 70;
      if (isset($fields[$qid]["Oldest"])  && $fields[$qid]["Oldest"] != '0') {
          $seconds = time() - $fields[$qid]["Oldest"];

          if ($crit_time > 0 && $seconds >= $crit_time) {
            if ($debug > 1) echo "Time: " . time() . ": Conditions are right for playing the crit file!\n";
            play_file("crit");
          } else if ($warn_time > 0 && $seconds >= $warn_time) {
	    if ($debug > 1) echo "Time: " . time() . ": Conditions are good for playing the warn file!\n";
            play_file("warn");
          }
      }
      sleep($refresh); 
  } // end of while!

  /*
   * Disconnect from Asterisk Call Manager
   */
   
  fwrite($fp, "Action: logoff\r\n");
  fclose($fp);

  remove_pid_file($pid_file);
?>
