#!/usr/bin/env php
<?php
//	License for all code of this FreePBX module can be found in the license file inside the module directory
//	Copyright 2006-2014 Schmooze Com Inc.
//
/* --------WARNING---------
 * 
 * This script is auto-copied from an included module and will get overwritten.
 * If you modify it, you must change it to write only, in the agi-bin directory,
 * to keep it from getting changed.
 */

// set to 1 to say "zed" instead of "zee"
define("SAY_ZED",0);

/******************************************************************************/

define("DEBUG", 0);

define("DIR_LAST", 0);
define("DIR_FIRST", 1);
define("DIR_BOTH", 2);

define("NUM_DIGITS", 3);
define("MAX_REPEAT", 2); // how many times can we repeat the menu before hanging up?

// ordinal values of digits
define("D_0",48);
define("D_1",49);
define("D_2",50);
define("D_3",51);
define("D_4",52);
define("D_5",53);
define("D_6",54);
define("D_7",55);
define("D_8",56);
define("D_9",57);
define("D_POUND",35);
define("D_STAR",42);

include("phpagi.php");

function get_var( $agi, $value)
{
	$r = $agi->get_variable( $value );
	
	if ($r['result'] == 1)
	{
		$result = $r['data'];
		return $result;
	}
	else
		return '';
}

function output(&$var) {
	if (DEBUG) {
		global $logfile;
		
		if (!isset($logfile)) return false;
		
		ob_start();
		var_dump($var);
		$output = ob_get_contents();
		ob_end_clean();
		fwrite($logfile, $output);
	}
}


function parse_voicemailconf($filename, &$vmconf, &$section) {
	if (is_null($vmconf)) {
		$vmconf = array();
	}
	if (is_null($section)) {
		$section = "general";
	}
	
	if (file_exists($filename)) {
		$fd = fopen($filename, "r");
		while ($line = fgets($fd, 1024)) {
      if (preg_match("/^\s*(\d+)\s*=>\s*(\d*),(.*),(.*),(.*),(.*)\s*([;#].*)?/",$line,$matches)) {
				// "mailbox=>password,name,email,pager,options"
				// this is a voicemail line	
				$vmconf[$section][ $matches[1] ] = array("mailbox"=>$matches[1],
									"pwd"=>$matches[2],
									"name"=>$matches[3],
									"email"=>$matches[4],
									"pager"=>$matches[5],
									);
								
				// parse options
				foreach (explode("|",$matches[6]) as $opt) {
					$temp = explode("=",$opt);
					if (isset($temp[1])) {
						list($key,$value) = $temp;
						$vmconf[$section][ $matches[1] ]["options"][$key] = $value;
					}
				}
			} else if (preg_match("/^\s*#include\s+(.*)\s*([;#].*)?/",$line,$matches)) {
				// include another file
				
				if ($matches[1][0] == "/") {
					// absolute path
					$filename = $matches[1];
				} else {
					// relative path
					$filename =  dirname($filename)."/".$matches[1];
				}
				
				parse_voicemailconf($filename, $vmconf, $section);
				
			} else if (preg_match("/^\s*\[(.+)\]/",$line,$matches)) {
				// section name
				$section = strtolower($matches[1]);
			} else if (preg_match("/^\s*([a-zA-Z0-9-_]+)\s*=\s*(.*?)\s*([;#].*)?$/",$line,$matches)) {
				// name = value
				// option line
				$vmconf[$section][ $matches[1] ] = $matches[2];
			}
		}
	}
}


/** Play a bunch of files, optionally accepting input and looping
 * @param $files		The file/files to play (for multiple files, pass array)
 * @param $escape_digits	The DTMF tones that can be pressed & returned, ie, "123*"
 * @param $timeout		The timeout waiting for a digit, or 0 to not wait at all.
 * @param $max_digits		Maximum number of digits to get. **NOT IMPLEMENTED YET.. 1 only**
 * @param $loop			False for no loop, or an integer >0 being the number of times to loop. 
 * @param $loopreturn		Value to return when the loop expires
*/
function stream_multiple($files, $escape_digits = "", $timeout = 0, $max_digits = 1, $loop = false, $loopreturn = 0) {
	global $agi;
	
	if (!is_array($files)) {
		$files = array($files);
	}
	
	$i = 0;
	do {
		foreach ($files as $file) {
			$agi->verbose("-- Playing '".$file."' (language 'en')");
			$r = $agi->stream_file($file, $escape_digits);
			$agi->conlog("stream_multiple: $file returned ".$r["result"]);
			switch ($r["result"]) {
				case 0: // they did nothing
				break;
				case -1: // they hungup
					$agi->verbose("remote user hungup");
					return -1;
				break;
				default: // they pressed a key
					return $r["result"];
				break;
			}
		}
		
		if ($timeout > 0) {
			$r = $agi->wait_for_digit($timeout);
			if (($r["result"] != 0) || (!$loop)) {
				// return only if the reult is not 0 (timeout)
				// or we're not doing a loop
				return $r["result"];
			}
		}
		
		if ($loop && (++$i > $loop)) {
			return $loopreturn;
		}
	} while ($loop);
	
	return 0;
}

/** If $file.(wav|WAV|gsm|GSM) exists
 */
function sound_file_exists($file) {
	global $agi;
	
	foreach (array("gsm","GSM","wav","WAV") as $ext) {
		if (file_exists($file.".".$ext)) {
			$agi->verbose("Found ".$file.".".$ext, 2);
			return true;
		}
	}
	return false;
}

function string_to_digits($string) {
	$out = "";
	
	for($i=0; $i<strlen($string); $i++) {
		switch (strtoupper($string[$i])) {
			case '1':
				$out .= '1';
			break;
			case '2': case 'A': case 'B': case 'C':
				$out .= '2';
			break;
			case '3': case 'D': case 'E': case 'F':
				$out .= '3';
			break;
			case '4': case 'G': case 'H': case 'I':
				$out .= '4';
			break;
			case '5': case 'J': case 'K': case 'L':
				$out .= '5';
			break;
			case '6': case 'M': case 'N': case 'O':
				$out .= '6';
			break;
			case '7': case 'P': case 'Q': case 'R': case 'S':
				$out .= '7';
			break;
			case '8': case 'T': case 'U': case 'V':
				$out .= '8';
			break;
			case '9': case 'W': case 'X': case 'Y': case 'Z':
				$out .= '9';
			break;
		}
		
		if ($i+1 == NUM_DIGITS) break;
	}
	
	return $out;
}

function say_alpha($string,$escape_digits) {
	$string = strtolower($string);
	$files = array();
	
	for($i=0; $i<strlen($string); $i++) {
		if (('a' <= $string[$i]) && ($string[$i] <= 'z')) {
		
			if (($string[$i] == 'z') && SAY_ZED) {
				$files[] = "letters/zed";
			} else {
				$files[] = "letters/".$string[$i];
			}
			
		} else if (('0' <= $string[$i]) && ($string[$i] <= '9')) {
			$files[] = "digits/".$string[$i];
			
		} else {
			switch ($string[$i]) {
				case "@": $files[] = "letters/at"; break;
				case "-": $files[] = "letters/dash"; break;
				case "$": $files[] = "letters/dollar"; break;
				case ".": $files[] = "letters/dot"; break;
				case "=": $files[] = "letters/equals"; break;
				case "!": $files[] = "letters/exclaimation-point"; break;
				case "+": $files[] = "letters/plus"; break;
				case "/": case "\\": $files[] = "letters/slash"; break;
				case " ": $files[] = "letters/space"; break;
			}
		}
	}
	
	return stream_multiple($files,$escape_digits);
}

function do_directory($type, &$directory, $dial_context, $say_exten, $operator) {
	global $agi;
	global $voicemail_dir;
	
	$escape_digits = "1*";
	if ($operator) $escape_digits .= "0";
	
	switch ($type) {
		case DIR_FIRST: $intro = ($operator ? "dir-intro-fn-oper" : "dir-intro-fn"); break;
		case DIR_BOTH: $intro = ($operator ? "dir-intro-fnln-oper" : "dir-intro-fnln"); break;
		case DIR_LAST: default: $intro = ($operator ? "dir-intro-oper" : "dir-intro"); break;
	}
	
	$loop = 0;
	while ($loop < MAX_REPEAT) {
		$r = $agi->get_data($intro, 4000, NUM_DIGITS);
		
		if (($r["result"] == "0") && $operator) {
			// operator
			$agi->verbose("Dropping to operator");
			// switch to o,1 in the current context
			$agi->set_extension("o");
			$agi->set_priority("1");
			// exiting application immediately!
			exit(0);
		}
		$digits = $r["result"];
		
		usleep(500); // pause a bit, so digit presses get cleared
		
		if ($digits != "") {
			// they entered SOMETHING, reset our loop
			$loop = 0;
		}
		
		$i = 0;
		$digit = false;
		if (isset($directory[$digits]) && isset($directory[$digits][$i])) {
			$loop = 0; // reset loop counter
			do {
				$match = & $directory[$digits][$i];
				
				$maindirname = sprintf($voicemail_dir, $match["context"], $match["ext"]);

				if (sound_file_exists($maindirname."/greet")) {
					$r = $agi->stream_file($maindirname."/greet",$escape_digits);
					if ($r["result"] > 0) $digit = $r["result"];
				} else {
					$digit = say_alpha($match["name"],$escape_digits);
				}
				
				if (!$digit) {
					$digit = stream_multiple("dir-instr", $escape_digits, 3000);
				}
				
				switch ($digit) {
					case D_1: // dial this
						if ($say_exten) {
							$agi->stream_file("pls-hold-while-try");
							$agi->stream_file("to-extension");

							$agi->say_digits($match["ext"]);
						}
						$agi->conlog("Dial ".$match["ext"]);
						
						$agi->set_context($dial_context);
						$agi->set_extension($match["ext"]);
						$agi->set_priority("1");
						exit(0);
					break;
					case D_STAR: // not correct
						$i += 1;
					break;
					case D_0: // operator
						if ($operator) {
							$agi->verbose("Dropping to operator");
							// switch to o,1 in the current context
							$agi->set_extension("o");
							$agi->set_priority("1");
							// exiting application immediately!
							exit(0);
						}
					break;
					case -1: // hungup
						$agi->conlog("User hungup");
						exit(1);
					break;
					case 0: // no response
						$loop++;
					break;
					case -2: // loop timed out
						$agi->stream_file("goodbye");
						$agi->hangup();
						exit(0);
					break;
				}
				
				if ($digit != 0) {
					$loop = 0;
				}
				$digit = false;
			} while (isset($directory[$digits][$i]) && ($loop < MAX_REPEAT));
			
			if (!isset($directory[$digits][$i])) {
				$agi->stream_file("dir-nomore");
				$loop = 0; // reset our loop counter so it doesn't hangup
			}
		} else if (!empty($digits) || ($digits === "0")) {
			// strict type checking as they may have entered "0" (string) which is empty()
			$agi->stream_file("dir-nomatch");
		} // else, we timed out
		
		$loop++;
	}
}

/******************************************************************************/

$agi = new AGI;

$directory_file = get_var($agi, "ASTETCDIR")."/voicemail.conf";

// where is voicemail stored?
$voicemail_dir  = get_var($agi, "ASTSPOOLDIR")."/voicemail/%s/%d";

// where should we store logs? (fixes #1912)
$log_dir        = get_var($agi, "ASTLOGDIR");

if (DEBUG) $logfile = fopen( $log_dir . "/directory.log","w");

$vmconf = array();
$null = null;
parse_voicemailconf($directory_file, $vmconf, $null);


if (!$argv[1]) {
	$agi->verbose("Notice: vm-context not specified.  Using 'default'");
	$vm_context = "default";
} else {
	$vm_context = trim($argv[1]);
}

if (!isset($vmconf[$vm_context]) && ($vm_context != "general")) {
	// we make an exception for "general" context,as it just includes other contexts
	$agi->verbose("Cannot find context ".$vm_context." in ".$directory_file);
	exit(1);
}

if (isset($argv[2])) {
	$dial_context = trim($argv[2]);
} else {
	$dial_context = $vm_context;
}

$operator = false;
$dir_type = DIR_FIRST;
$say_exten = false;

if (isset($argv[3])) {
	for($i=0; $i<strlen($argv[3]); $i++) {
		switch ($argv[3][$i]) {
			case "f": case "F": 
				$dir_type = DIR_FIRST;
			break;
			case "l": case "L": 
				$dir_type = DIR_LAST;
			break;
			case "b": case "B": 
				$dir_type = DIR_BOTH;
			break;
			case "o": case "O": 
				$operator = true;
			break;
			case "e": case "E": 
				$say_exten = true;
			break;
		}
	}
}

if ($vm_context == "general") {
	$boxes = array();
	foreach ($vmconf as $context=>$arr) {
		// skip if it's general context -- this doesn't contain mailboxes
		if ($context == "general") continue;
		
		foreach ($arr as $key=>$box) {
			// we could do if !isset($boxes[$key]) to NOT override mailboxes
			
			// add in the context, otherwise we don't know what it is
			$box["context"] = $context;
			
			$boxes[$key] = $box;
		}
	}
} else {
	$boxes = &$vmconf[$vm_context];
}

$directory = array();
foreach ($boxes as $box) {
	if (!empty($box["name"])) {
		$name = explode(" ",$box["name"]);
		
		if (isset($box["context"])) {
			$context = $box["context"];
		} else {
			$context = $vm_context;
		}
		
		switch ($dir_type) {
			case DIR_FIRST: // first name only
				$digits = string_to_digits($name[0]);
				$directory[$digits][] = array("ext"=>$box["mailbox"], "name"=>$box["name"], "context"=>$context);
			break;
			case DIR_BOTH: // all names
				foreach ($name as $temp) {
					$digits = string_to_digits($temp);
					$directory[$digits][] = array("ext"=>$box["mailbox"], "name"=>$box["name"], "context"=>$context);
				}
			break;
			case DIR_LAST: default: // last name only
				$digits = string_to_digits(end($name));
				$directory[$digits][] = array("ext"=>$box["mailbox"], "name"=>$box["name"], "context"=>$context);
			break;
		}
	}
}

if (DEBUG) {
	output($argv);
	output($dir_type);
	output($directory);
}

do_directory($dir_type, $directory, $dial_context, $say_exten, $operator);
	
?>
