root/freepbx/trunk/amp_conf/htdocs/admin/functions.inc.php

Revision 5026, 105.8 kB (checked in by p_lindheimer, 6 years ago)

change module admin tarball explodes so they are exploded in _cache
and then moved over to the module directory after removing the old
one. This provides an ability to remove old files. It does not yet
save the old module anywhere.

retrieve_conf has been changed where it does symlinks so that any
existing symlinks that point to a non-existant file are removed and
reported as file not existing if remove was successful.

  • Property svn:mime-type set to text/html
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1 <?php /* $id$ */
2 //Copyright (C) 2004 Coalescent Systems Inc. (info@coalescentsystems.ca)
3 //
4 //This program is free software; you can redistribute it and/or
5 //modify it under the terms of the GNU General Public License
6 //as published by the Free Software Foundation; either version 2
7 //of the License, or (at your option) any later version.
8 //
9 //This program is distributed in the hope that it will be useful,
10 //but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //GNU General Public License for more details.
13
14 require_once( (defined('AMP_BASE_INCLUDE_PATH') ? AMP_BASE_INCLUDE_PATH.'/' : '').'featurecodes.class.php');
15 require_once( (defined('AMP_BASE_INCLUDE_PATH') ? AMP_BASE_INCLUDE_PATH.'/' : '').'components.class.php');
16
17 define('MODULE_STATUS_NOTINSTALLED', 0);
18 define('MODULE_STATUS_DISABLED', 1);
19 define('MODULE_STATUS_ENABLED', 2);
20 define('MODULE_STATUS_NEEDUPGRADE', 3);
21 define('MODULE_STATUS_BROKEN', -1);
22
23 $amp_conf_defaults = array(
24   'AMPDBENGINE'    => array('std' , 'mysql'),
25   'AMPDBNAME'      => array('std' , 'asterisk'),
26   'AMPENGINE'      => array('std' , 'asterisk'),
27   'ASTMANAGERPORT' => array('std' , '5038'),
28   'AMPDBHOST'      => array('std' , 'localhost'),
29   'AMPDBUSER'      => array('std' , 'asteriskuser'),
30   'AMPDBPASS'      => array('std' , 'amp109'),
31   'AMPMGRUSER'     => array('std' , 'admin'),
32   'AMPMGRPASS'     => array('std' , 'amp111'),
33   'FOPPASSWORD'    => array('std' , 'passw0rd'),
34   'FOPSORT'        => array('std' , 'extension'),
35
36   'ASTETCDIR'      => array('dir' , '/etc/asterisk'),
37   'ASTMODDIR'      => array('dir' , '/usr/lib/asterisk/modules'),
38   'ASTVARLIBDIR'   => array('dir' , '/var/lib/asterisk'),
39   'ASTAGIDIR'      => array('dir' , '/var/lib/asterisk/agi-bin'),
40   'ASTSPOOLDIR'    => array('dir' , '/var/spool/asterisk/'),
41   'ASTRUNDIR'      => array('dir' , '/var/run/asterisk'),
42   'ASTLOGDIR'      => array('dir' , '/var/log/asterisk'),
43   'AMPBIN'         => array('dir' , '/var/lib/asterisk/bin'),
44   'AMPSBIN'        => array('dir' , '/usr/sbin'),
45   'AMPWEBROOT'     => array('dir' , '/var/www/html'),
46   'FOPWEBROOT'     => array('dir' , '/var/www/html/panel'),
47
48   'USECATEGORIES'  => array('bool' , true),
49   'ENABLECW'       => array('bool' , true),
50   'CWINUSEBUSY'    => array('bool' , true),
51   'FOPRUN'         => array('bool' , true),
52   'AMPBADNUMBER'   => array('bool' , true),
53   'DEVEL'          => array('bool' , false),
54   'DEVELRELOAD'    => array('bool' , false),
55 );
56
57 function parse_amportal_conf($filename) {
58   global $amp_conf_defaults;
59
60   /* defaults
61    * This defines defaults and formating to assure consistency across the system so that
62    * components don't have to keep being 'gun shy' about these variables.
63    *
64
65    */
66   $file = file($filename);
67   if (is_array($file)) {
68     foreach ($file as $line) {
69       if (preg_match("/^\s*([a-zA-Z0-9_]+)=([a-zA-Z0-9 .&-@=_<>\"\']+)\s*$/",$line,$matches)) {
70         $conf[ $matches[1] ] = $matches[2];
71       }
72     }
73   } else {
74     die_freepbx("<h1>".sprintf(_("Missing or unreadable config file (%s)...cannot continue"), $filename)."</h1>");
75   }
76  
77   // set defaults
78   foreach ($amp_conf_defaults as $key=>$arr) {
79
80     switch ($arr[0]) {
81       // for type dir, make sure there is no trailing '/' to keep consistent everwhere
82       //
83       case 'dir':
84         if (!isset($conf[$key]) || trim($conf[$key]) == '') {
85           $conf[$key] = $arr[1];
86         } else {
87           $conf[$key] = rtrim($conf[$key],'/');
88         }
89         break;
90       // booleans:
91       // "yes", "true", "on", true, 1 (case-insensitive) will be treated as true, everything else is false
92       //
93       case 'bool':
94         if (!isset($conf[$key])) {
95           $conf[$key] = $arr[1];
96         } else {
97           $conf[$key] = ($conf[$key] === true || strtolower($conf[$key]) == 'true' || $conf[$key] === 1 || $conf[$key] == '1'
98                                               || strtolower($conf[$key]) == 'yes' ||  strtolower($conf[$key]) == 'on');
99         }
100         break;
101       default:
102         if (!isset($conf[$key])) {
103           $conf[$key] = $arr[1];
104         } else {
105           $conf[$key] = trim($conf[$key]);
106         }
107     }
108   }
109
110 /*     
111   TODO: what was this, should the comment be removed?
112
113   if (($amp_conf["AMPDBENGINE"] == "sqlite") && (!isset($amp_conf["AMPDBENGINE"])))
114     $amp_conf["AMPDBFILE"] = "/var/lib/freepbx/freepbx.sqlite";
115 */
116
117   return $conf;
118 }
119
120 function parse_asterisk_conf($filename) {
121   //TODO: Should the correction of $amp_conf be passed by refernce and optional?
122   //
123   global $amp_conf;
124     
125   $convert = array(
126     'astetcdir'    => 'ASTETCDIR',
127     'astmoddir'    => 'ASTMODDIR',
128     'astvarlibdir' => 'ASTVARLIBDIR',
129     'astagidir'    => 'ASTAGIDIR',
130     'astspooldir'  => 'ASTSPOOLDIR',
131     'astrundir'    => 'ASTRUNDIR',
132     'astlogdir'    => 'ASTLOGDIR'
133   );
134
135   $file = file($filename);
136   foreach ($file as $line) {
137     if (preg_match("/^\s*([a-zA-Z0-9]+)\s* => \s*(.*)\s*([;#].*)?/",$line,$matches)) {
138       $conf[ $matches[1] ] = rtrim($matches[2],'/');
139     }
140   }
141
142   // Now that we parsed asterisk.conf, we need to make sure $amp_conf is consistent
143   // so just set it to what we found, since this is what asterisk will use anyhow.
144   //
145   foreach ($convert as $ast_conf_key => $amp_conf_key) {
146     if (isset($conf[$ast_conf_key])) {
147       $amp_conf[$amp_conf_key] = $conf[$ast_conf_key];
148     }
149   }
150   return $conf;
151 }
152
153
154 define("NOTIFICATION_TYPE_CRITICAL", 100);
155 define("NOTIFICATION_TYPE_SECURITY", 200);
156 define("NOTIFICATION_TYPE_UPDATE",  300);
157 define("NOTIFICATION_TYPE_ERROR",    400);
158 define("NOTIFICATION_TYPE_WARNING" , 500);
159 define("NOTIFICATION_TYPE_NOTICE",   600);
160
161 class notifications {
162
163   var $not_loaded = true;
164   var $notification_table = array();
165   var $_db;
166     
167   function &create(&$db) {
168     static $obj;
169     if (!isset($obj)) {
170       $obj = new notifications($db);
171     }
172     return $obj;
173   }
174
175   function notifications(&$db) {
176     $this->_db =& $db;
177   }
178
179
180   function add_critical($module, $id, $display_text, $extended_text="", $link="", $reset=true, $candelete=false) {
181     $this->_add_type(NOTIFICATION_TYPE_CRITICAL, $module, $id, $display_text, $extended_text, $link, $reset, $candelete);
182   }
183   function add_security($module, $id, $display_text, $extended_text="", $link="", $reset=true, $candelete=false) {
184     $this->_add_type(NOTIFICATION_TYPE_SECURITY, $module, $id, $display_text, $extended_text, $link, $reset, $candelete);
185   }
186   function add_update($module, $id, $display_text, $extended_text="", $link="", $reset=false, $candelete=false) {
187     $this->_add_type(NOTIFICATION_TYPE_UPDATE, $module, $id, $display_text, $extended_text, $link, $reset, $candelete);
188   }
189   function add_error($module, $id, $display_text, $extended_text="", $link="", $reset=false, $candelete=false) {
190     $this->_add_type(NOTIFICATION_TYPE_ERROR, $module, $id, $display_text, $extended_text, $link, $reset, $candelete);
191   }
192   function add_warning($module, $id, $display_text, $extended_text="", $link="", $reset=false, $candelete=false) {
193     $this->_add_type(NOTIFICATION_TYPE_WARNING, $module, $id, $display_text, $extended_text, $link, $reset, $candelete);
194   }
195   function add_notice($module, $id, $display_text, $extended_text="", $link="", $reset=false, $candelete=true) {
196     $this->_add_type(NOTIFICATION_TYPE_NOTICE, $module, $id, $display_text, $extended_text, $link, $reset, $candelete);
197   }
198
199
200   function list_critical($show_reset=false) {
201     return $this->_list(NOTIFICATION_TYPE_CRITICAL, $show_reset);
202   }
203   function list_security($show_reset=false) {
204     return $this->_list(NOTIFICATION_TYPE_SECURITY, $show_reset);
205   }
206   function list_update($show_reset=false) {
207     return $this->_list(NOTIFICATION_TYPE_UPDATE, $show_reset);
208   }
209   function list_error($show_reset=false) {
210     return $this->_list(NOTIFICATION_TYPE_ERROR, $show_reset);
211   }
212   function list_warning($show_reset=false) {
213     return $this->_list(NOTIFICATION_TYPE_WARNING, $show_reset);
214   }
215   function list_notice($show_reset=false) {
216     return $this->_list(NOTIFICATION_TYPE_NOTICE, $show_reset);
217   }
218   function list_all($show_reset=false) {
219     return $this->_list("", $show_reset);
220   }
221
222
223   function reset($module, $id) {
224     $module        = q($module);
225     $id            = q($id);
226
227     $sql = "UPDATE notifications SET reset = 1 WHERE module = $module AND id = $id";
228     sql($sql);
229   }
230
231   function delete($module, $id) {
232     $module        = q($module);
233     $id            = q($id);
234
235     $sql = "DELETE FROM notifications WHERE module = $module AND id = $id";
236     sql($sql);
237   }
238
239   function safe_delete($module, $id) {
240     $module        = q($module);
241     $id            = q($id);
242
243     $sql = "DELETE FROM notifications WHERE module = $module AND id = $id AND candelete = 1";
244     sql($sql);
245   }
246
247   /* Internal functions
248    */
249
250   function _add_type($level, $module, $id, $display_text, $extended_text="", $link="", $reset=false, $candelete=false) {
251     if ($this->not_loaded) {
252       $this->notification_table = $this->_list("",true);
253       $this->not_loaded = false;
254     }
255
256     $existing_row = false;
257     foreach ($this->notification_table as $row) {
258       if ($row['module'] == $module && $row['id'] == $id ) {
259         $existing_row = $row;
260         break;
261       }
262     }
263     // Found an existing row - check if anything changed or if we are suppose to reset it
264     //
265     $candelete = $candelete ? 1 : 0;
266     if ($existing_row) {
267
268       if (($reset && $existing_row['reset'] == 1) || $existing_row['level'] != $level || $existing_row['display_text'] != $display_text || $existing_row['extended_text'] != $extended_text || $existing_row['link'] != $link || $existing_row['candelete'] == $candelete) {
269
270         // If $reset is set to the special case of PASSIVE then the updates will not change it's value in an update
271         //
272         $reset_value = ($reset == 'PASSIVE') ? $existing_row['reset'] : 0;
273
274         $module        = q($module);
275         $id            = q($id);
276         $level         = q($level);
277         $display_text  = q($display_text);
278         $extended_text = q($extended_text);
279         $link          = q($link);
280         $now = time();
281         $sql = "UPDATE notifications SET
282           level = $level,
283           display_text = $display_text,
284           extended_text = $extended_text,
285           link = $link,
286           reset = $reset_value,
287           candelete = $candelete,
288           timestamp = $now
289           WHERE module = $module AND id = $id
290         ";
291         sql($sql);
292
293         // TODO: I should really just add this to the internal cache, but really
294         //       how often does this get called that if is a big deal.
295         $this->not_loaded = true;
296       }
297     } else {
298       // No existing row so insert this new one
299       //
300       $now           = time();
301       $module        = q($module);
302       $id            = q($id);
303       $level         = q($level);
304       $display_text  = q($display_text);
305       $extended_text = q($extended_text);
306       $link          = q($link);
307       $sql = "INSERT INTO notifications
308         (module, id, level, display_text, extended_text, link, reset, candelete, timestamp)
309         VALUES
310         ($module, $id, $level, $display_text, $extended_text, $link, 0, $candelete, $now)
311       ";
312       sql($sql);
313
314       // TODO: I should really just add this to the internal cache, but really
315       //       how often does this get called that if is a big deal.
316       $this->not_loaded = true;
317     }
318   }
319
320   function _list($level, $show_reset=false) {
321
322     $level = q($level);
323     $where = array();
324
325     if (!$show_reset) {
326       $where[] = "reset = 0";
327     }
328
329     switch ($level) {
330       case NOTIFICATION_TYPE_CRITICAL:
331       case NOTIFICATION_TYPE_SECURITY:
332       case NOTIFICATION_TYPE_UPDATE:
333       case NOTIFICATION_TYPE_ERROR:
334       case NOTIFICATION_TYPE_WARNING:
335       case NOTIFICATION_TYPE_NOTICE:
336         $where[] = "level = $level ";
337         break;
338       default:
339     }
340     $sql = "SELECT * FROM notifications ";
341     if (count($where)) {
342       $sql .= " WHERE ".implode(" AND ", $where);
343     }
344     $sql .= " ORDER BY level, module";
345
346     $list = sql($sql,"getAll",DB_FETCHMODE_ASSOC);
347     return $list;
348   }
349   /* Returns the number of active notifications
350    */
351   function get_num_active() {
352     $sql = "SELECT COUNT(id) FROM notifications WHERE reset = 0";
353     return sql($sql,'getOne');
354   }
355 }
356
357 class cronmanager {
358   /**
359    * note: time is the hour time of day a job should run, -1 indicates don't care
360    */
361
362   function &create(&$db) {
363     static $obj;
364     if (!isset($obj)) {
365       $obj = new cronmanager($db);
366     }
367     return $obj;
368   }
369
370   function cronmanager(&$db) {
371     $this->_db =& $db;
372   }
373
374   function save_email($address) {
375     $address = q($address);
376     sql("DELETE FROM admin WHERE variable = 'email'");
377     sql("INSERT INTO admin (variable, value) VALUES ('email', $address)");
378   }
379
380   function get_email() {
381     $sql = "SELECT value FROM admin WHERE variable = 'email'";
382     return sql($sql, 'getOne');
383   }
384
385   function save_hash($id, &$string) {
386     $hash = md5($string);
387     $id = q($id);
388     sql("DELETE FROM admin WHERE variable = $id");
389     sql("INSERT INTO admin (variable, value) VALUE ($id, '$hash')");
390   }
391
392   function check_hash($id, &$string) {
393     $id = q($id);
394     $sql = "SELECT value FROM admin WHERE variable = $id LIMIT 1";
395     $hash = sql($sql, "getOne");
396     return ($hash == md5($string));
397   }
398
399   function enable_updates($freq=24) {
400     global $amp_conf;
401
402     $night_time = array(19,20,21,22,23,0,1,2,3,4,5);
403     $run_time = $night_time[rand(0,10)];
404     $command = $amp_conf['AMPBIN']."/module_admin listonline";
405     $lasttime = 0;
406
407     $sql = "SELECT * FROM cronmanager WHERE module = 'module_admin' AND id = 'UPDATES'";
408     $result = sql($sql, "getAll",DB_FETCHMODE_ASSOC);
409     if (count($result)) {
410       $sql = "UPDATE cronmanager SET
411                 freq = '$freq',
412                 command = '$command'
413               WHERE
414                 module = 'module_admin' AND id = 'UPDATES' 
415              ";
416     } else {
417       $sql = "INSERT INTO cronmanager
418               (module, id, time, freq, lasttime, command)
419               VALUES
420               ('module_admin', 'UPDATES', '$run_time', $freq, 0, '$command')
421             ";
422     }
423     sql($sql);
424   }
425
426   function disable_updates() {
427     sql("DELETE FROM cronmanager WHERE module = 'module_admin' AND id = 'UPDATES'");
428   }
429
430   function updates_enabled() {
431     $results = sql("SELECT module, id FROM cronmanager WHERE module = 'module_admin' AND id = 'UPDATES'",'getAll');
432     return count($results);
433   }
434
435   /** run_jobs()
436    *  select all entries that need to be run now and run them, then update the times.
437    * 
438    *  1. select all entries
439    *  2. foreach entry, if its paramters indicate it should be run, then run it and
440    *     update it was run in the time stamp.
441    */
442   function run_jobs() {
443
444     $errors = 0;
445     $error_arr = array();
446
447     $now = time();
448     $jobs = sql("SELECT * FROM cronmanager","getAll", DB_FETCHMODE_ASSOC);
449     foreach ($jobs as $job) {
450       $nexttime = $job['lasttime'] + $job['freq']*3600;
451       if ($nexttime <= $now) {
452         if ($job['time'] >= 0 && $job['time'] < 24) {
453           $date_arr = getdate($now);
454           // Now if lasttime is 0, then we want this kicked off at the proper hour
455           // after wich the frequencey will set the pace for same time each night
456           //
457           if (($date_arr['hours'] != $job['time']) && !$job['lasttime']) {
458             continue;
459           }
460         }
461       } else {
462         // no need to run job, time is not up yet
463         continue;
464       }
465       // run the job
466       exec($job['command'],$job_out,$ret);
467       if ($ret) {
468         $errors++;
469         $error_arr[] = array($job['command'],$ret);
470
471         // If there where errors, let's print them out in case the script is being debugged or running
472         // from cron which will then put the errors out through the cron system.
473         //
474         foreach ($job_out as $line) {
475           echo $line."\n";
476         }
477       } else {
478         $module = $job['module'];
479         $id =     $job['id'];
480         $sql = "UPDATE cronmanager SET lasttime = $now WHERE module = '$module' AND id = '$id'";
481         sql($sql);
482       }
483     }
484     if ($errors) {
485       $nt =& notifications::create($db);
486       $text = sprintf(_("Cronmanager encountered %s Errors"),$errors);
487       $extext = _("The following commands failed with the listed error");
488       foreach ($error_arr as $item) {
489         $extext .= "<br>".$item[0]." (".$item[1].")";
490       }
491       $nt->add_error('cron_manager', 'EXECFAIL', $text, $extext, '', true, true);
492     }
493   }
494 }
495
496
497 /** Expands variables from amportal.conf
498  * Replaces any variables enclosed in percent (%) signs with their value
499  * eg, "%AMPWEBROOT%/admin/functions.inc.php"
500  */
501 function expand_variables($string) {
502   global $amp_conf;
503   $search = $replace = array();
504   foreach ($amp_conf as $key=>$value) {
505     $search[] = '%'.$key.'%';
506     $replace[] = $value;
507   }
508   return str_replace($search, $replace, $string);
509 }
510
511 function getAmpAdminUsers() {
512   global $db;
513
514   $sql = "SELECT username FROM ampusers WHERE sections='*'";
515   $results = $db->getAll($sql);
516   if(DB::IsError($results)) {
517      die_freepbx($results->getMessage());
518   }
519   return $results;
520 }
521
522 function getAmpUser($username) {
523   global $db;
524  
525   $sql = "SELECT username, password, extension_low, extension_high, deptname, sections FROM ampusers WHERE username = '".addslashes($username)."'";
526   $results = $db->getAll($sql);
527   if(DB::IsError($results)) {
528      die_freepbx($results->getMessage());
529   }
530  
531   if (count($results) > 0) {
532     $user = array();
533     $user["username"] = $results[0][0];
534     $user["password"] = $results[0][1];
535     $user["extension_low"] = $results[0][2];
536     $user["extension_high"] = $results[0][3];
537     $user["deptname"] = $results[0][4];
538     $user["sections"] = explode(";",$results[0][5]);
539     return $user;
540   } else {
541     return false;
542   }
543 }
544
545 class ampuser {
546   var $username;
547   var $_password;
548   var $_extension_high;
549   var $_extension_low;
550   var $_deptname;
551   var $_sections;
552  
553   function ampuser($username) {
554     $this->username = $username;
555     if ($user = getAmpUser($username)) {
556       $this->_password = $user["password"];
557       $this->_extension_high = $user["extension_high"];
558       $this->_extension_low = $user["extension_low"];
559       $this->_deptname = $user["deptname"];
560       $this->_sections = $user["sections"];
561     } else {
562       // user doesn't exist
563       $this->_password = false;
564       $this->_extension_high = "";
565       $this->_extension_low = "";
566       $this->_deptname = "";
567       $this->_sections = array();
568     }
569   }
570  
571   /** Give this user full admin access
572   */
573   function setAdmin() {
574     $this->_extension_high = "";
575     $this->_extension_low = "";
576     $this->_deptname = "";
577     $this->_sections = array("*");
578   }
579  
580   function checkPassword($password) {
581     // strict checking so false will never match
582     return ($this->_password === $password);
583   }
584  
585   function checkSection($section) {
586     // if they have * then it means all sections
587     return in_array("*", $this->_sections) || in_array($section, $this->_sections);
588   }
589 }
590
591 // returns true if extension is within allowed range
592 function checkRange($extension){
593   $low = isset($_SESSION["AMP_user"]->_extension_low)?$_SESSION["AMP_user"]->_extension_low:'';
594   $high = isset($_SESSION["AMP_user"]->_extension_high)?$_SESSION["AMP_user"]->_extension_high:'';
595  
596   if ((($extension >= $low) && ($extension <= $high)) || ($low == '' && $high == ''))
597     return true;
598   else
599     return false;
600 }
601
602 // returns true if department string matches dept for this user
603 function checkDept($dept){
604   $deptname = isset($_SESSION["AMP_user"])?$_SESSION["AMP_user"]->_deptname:null;
605  
606   if ( ($dept == null) || ($dept == $deptname) )
607     return true;
608   else
609     return false;
610 }
611
612 function engine_getinfo() {
613   global $amp_conf;
614   global $astman;
615
616   switch ($amp_conf['AMPENGINE']) {
617     case 'asterisk':
618       if (isset($astman) && $astman->connected()) {
619         //get version (1.4)
620         $response = $astman->send_request('Command', array('Command'=>'core show version'));
621         if (preg_match('/No such command/',$response['data'])) {
622           // get version (1.2)
623           $response = $astman->send_request('Command', array('Command'=>'show version'));
624         }
625         $verinfo = $response['data'];
626       } else {
627         // could not connect to asterisk manager, try console
628         $verinfo = exec('asterisk -V');
629       }
630       
631       if (preg_match('/Asterisk (\d+(\.\d+)*)(-?(\S*))/', $verinfo, $matches)) {
632         return array('engine'=>'asterisk', 'version' => $matches[1], 'additional' => $matches[4]);
633       } elseif (preg_match('/Asterisk SVN-(\d+(\.\d+)*)(-?(\S*))/', $verinfo, $matches)) {
634         return array('engine'=>'asterisk', 'version' => $matches[1], 'additional' => $matches[4]);
635       } elseif (preg_match('/Asterisk SVN-branch-(\d+(\.\d+)*)-r(-?(\S*))/', $verinfo, $matches)) {
636         return array('engine'=>'asterisk', 'version' => $matches[1].'.'.$matches[4], 'additional' => $matches[4]);
637       } elseif (preg_match('/Asterisk SVN-trunk-r(-?(\S*))/', $verinfo, $matches)) {
638         return array('engine'=>'asterisk', 'version' => '1.6', 'additional' => $matches[1]);
639       }
640
641       return array('engine'=>'ERROR-UNABLE-TO-CONNECT', 'version'=>'0', 'additional' => '0');
642     break;
643   }
644   return array('engine'=>'ERROR-UNSUPPORTED-ENGINE-'.$amp_conf['AMPENGINE'], 'version'=>'0', 'additional' => '0');
645 }
646
647
648 /* verison_compare that works with freePBX version numbers
649  */
650 function version_compare_freepbx($version1, $version2, $op = null) {
651         $version1 = str_replace("rc","RC", strtolower($version1));
652         $version2 = str_replace("rc","RC", strtolower($version2));
653     if (!is_null($op)) {
654       return version_compare($version1, $version2, $op);
655     } else {
656       return version_compare($version1, $version2);
657     }
658 }
659
660
661 /* queries database using PEAR.
662 *  $type can be query, getAll, getRow, getCol, getOne, etc
663 *  $fetchmode can be DB_FETCHMODE_ORDERED, DB_FETCHMODE_ASSOC, DB_FETCHMODE_OBJECT
664 *  returns array, unless using getOne
665 */
666 function sql($sql,$type="query",$fetchmode=null) {
667   global $db;
668   $results = $db->$type($sql,$fetchmode);
669   if(DB::IsError($results)) {
670     die_freepbx($results->getDebugInfo() . "SQL - <br> $sql" );
671   }
672   return $results;
673 }
674
675 /**  Format input so it can be safely used as a literal in a query.
676  * Literals are values such as strings or numbers which get utilized in places
677  * like WHERE, SET and VALUES clauses of SQL statements.
678  * The format returned depends on the PHP data type of input and the database
679  * type being used. This simply calls PEAR's DB::smartQuote() function
680  * @param  mixed  The value to go into the database
681  * @return string  A value that can be safely inserted into an SQL query
682  */
683 function q(&$value) {
684   global $db;
685   return $db->quoteSmart($value);
686 }
687
688 // sql text formatting -- couldn't see that one was available already
689 function sql_formattext($txt) {
690   if (isset($txt)) {
691     $fmt = str_replace("'", "''", $txt);
692     $fmt = "'" . $fmt . "'";
693   } else {
694     $fmt = 'null';
695   }
696
697   return $fmt;
698 }
699
700
701 function die_freepbx($text, $extended_text="", $type="FATAL") {
702   if (function_exists('fatal')) {
703     // "custom" error handler
704     // fatal may only take one param, so we suppress error messages because it doesn't really matter
705     @fatal($text, $extended_text, $type);
706   } else if (isset($_SERVER['REQUEST_METHOD'])) {
707     // running in webserver
708     echo "<h1>".$type." ERROR</h1>\n";
709     echo "<h3>".$text."</h3>\n";
710     if (!empty($extended_text)) {
711       echo "<p>".$extended_text."</p>\n";
712     }
713   } else {
714     // CLI
715     echo "[$type] ".$text." ".$extended_text."\n";
716   }
717
718   // always ensure we exit at this point
719   exit(1);
720 }
721
722 //tell application we need to reload asterisk
723 function needreload() {
724   global $db;
725   $sql = "UPDATE admin SET value = 'true' WHERE variable = 'need_reload'";
726   $result = $db->query($sql);
727   if(DB::IsError($result)) {     
728     die_freepbx($result->getMessage());
729   }
730 }
731
732 function check_reload_needed() {
733   global $db;
734   global $amp_conf;
735   $sql = "SELECT value FROM admin WHERE variable = 'need_reload'";
736   $row = $db->getRow($sql);
737   if(DB::IsError($row)) {
738     die_freepbx($row->getMessage());
739   }
740   return ($row[0] == 'true' || $amp_conf['DEVELRELOAD']);
741 }
742
743 function do_reload() {
744   global $amp_conf, $asterisk_conf, $db, $astman;
745  
746   $notify =& notifications::create($db);
747  
748   $return = array('num_errors'=>0,'test'=>'abc');
749   $exit_val = null;
750  
751   if (isset($amp_conf["PRE_RELOAD"]) && !empty($amp_conf['PRE_RELOAD']))  {
752     exec( $amp_conf["PRE_RELOAD"], $output, $exit_val );
753     
754     if ($exit_val != 0) {
755       $desc = sprintf(_("Exit code was %s and output was: %s"), $exit_val, "\n\n".implode("\n",$output));
756       $notify->add_error('freepbx','reload_pre_script', sprintf(_('Could not run %s script.'), $amp_conf['PRE_RELOAD']), $desc);
757       
758       $return['num_errors']++;
759     } else {
760       $notify->delete('freepbx', 'reload_pre_script');
761     }
762   }
763  
764   $retrieve = $amp_conf['AMPBIN'].'/retrieve_conf';
765   //exec($retrieve.'&>'.$asterisk_conf['astlogdir'].'/freepbx-retrieve.log', $output, $exit_val);
766   exec($retrieve, $output, $exit_val);
767  
768   // retrive_conf html output
769   $return['retrieve_conf'] = 'exit: '.$exit_val.'<br/>'.implode('<br/>',$output);
770  
771   if ($exit_val != 0) {
772     $return['status'] = false;
773     $return['message'] = sprintf(_('Reload failed because retrieve_conf encountered an error: %s'),$exit_val);
774     $return['num_errors']++;
775     $notify->add_critical('freepbx','RCONFFAIL', _("retrieve_conf failed, config not applied"), $return['message']);
776     return $return;
777   }
778  
779   if (!isset($astman) || !$astman) {
780     $return['status'] = false;
781     $return['message'] = _('Reload failed because FreePBX could not connect to the asterisk manager interface.');
782     $return['num_errors']++;
783     $notify->add_critical('freepbx','RCONFFAIL', _("retrieve_conf failed, config not applied"), $return['message']);
784     return $return;
785   }
786   $notify->delete('freepbx', 'RCONFFAIL');
787  
788   //reload MOH to get around 'reload' not actually doing that.
789   $astman->send_request('Command', array('Command'=>'moh reload'));
790  
791   //reload asterisk
792   $astman->send_request('Command', array('Command'=>'reload'));
793  
794   $return['status'] = true;
795   $return['message'] = _('Successfully reloaded');
796  
797  
798   if ($amp_conf['FOPRUN']) {
799     //bounce op_server.pl
800     $wOpBounce = $amp_conf['AMPBIN'].'/bounce_op.sh';
801     exec($wOpBounce.' &>'.$asterisk_conf['astlogdir'].'/freepbx-bounce_op.log', $output, $exit_val);
802     
803     if ($exit_val != 0) {
804       $desc = _('Could not reload the FOP operator panel server using the bounce_op.sh script. Configuration changes may not be reflected in the panel display.');
805       $notify->add_error('freepbx','reload_fop', _('Could not reload FOP server'), $desc);
806       
807       $return['num_errors']++;
808     } else {
809       $notify->delete('freepbx','reload_fop');
810     }
811   }
812  
813   //store asterisk reloaded status
814   $sql = "UPDATE admin SET value = 'false' WHERE variable = 'need_reload'";
815   $result = $db->query($sql);
816   if(DB::IsError($result)) {
817     $return['message'] = _('Successful reload, but could not clear reload flag due to a database error: ').$db->getMessage();
818     $return['num_errors']++;
819   }
820  
821   if (isset($amp_conf["POST_RELOAD"]) && !empty($amp_conf['POST_RELOAD']))  {
822     exec( $amp_conf["POST_RELOAD"], $output, $exit_val );
823     
824     if ($exit_val != 0) {
825       $desc = sprintf(_("Exit code was %s and output was: %s"), $exit_val, "\n\n".implode("\n",$output));
826       $notify->add_error('freepbx','reload_post_script', sprintf(_('Could not run %s script.'), 'POST_RELOAD'), $desc);
827       
828       $return['num_errors']++;
829     } else {
830       $notify->delete('freepbx', 'reload_post_script');
831     }
832   }
833  
834   return $return;
835 }
836
837 //get the version number
838 function getversion() {
839   global $db;
840   $sql = "SELECT value FROM admin WHERE variable = 'version'";
841   $results = $db->getRow($sql);
842   if(DB::IsError($results)) {
843     die_freepbx($results->getMessage());
844   }
845   return $results[0];
846 }
847
848 //get the version number
849 function get_framework_version() {
850   global $db;
851   $sql = "SELECT version FROM modules WHERE modulename = 'framework' AND enabled = 1";
852   $version = $db->getOne($sql);
853   if(DB::IsError($version)) {
854     die_freepbx($version->getMessage());
855   }
856   return $version;
857 }
858
859 // draw list for users and devices with paging
860 function drawListMenu($results, $skip, $type, $dispnum, $extdisplay, $description) {
861   // Dirty Fix to get rid of [NEXT/PREV] since I'm not sure what passing skip does and don't want to mess with it.
862   // When someone feels like looking closer at the below, probably should remove the code.
863   // I removed pagination cause of the new scroll box ticket #1415
864   $perpage=20000;
865  
866   $skipped = 0;
867   $index = 0;
868   if ($skip == "") $skip = 0;
869   echo "<ul>\n";
870   echo "\t<li><a ".($extdisplay=='' ? 'class="current"':'')." href=\"config.php?type=".$type."&display=".$dispnum."\">"._("Add")." ".$description."</a></li>\n";
871
872   if (isset($results)) {
873     foreach ($results AS $key=>$result) {
874       if ($index >= $perpage) {
875         $shownext= 1;
876         break;
877       }
878       
879       if ($skipped<$skip && $skip!= 0) {
880         $skipped= $skipped + 1;
881         continue;
882       }
883       
884       $index= $index + 1;
885       echo "\t<li><a".($extdisplay==$result[0] ? ' class="current"':''). " href=\"config.php?type=".$type."&amp;display=".$dispnum."&amp;extdisplay={$result[0]}&amp;skip={$skip}\">{$result[1]} &lt;{$result[0]}&gt;</a></li>\n";
886     }
887   }
888   
889   $prevtag = "";
890   $prevtag_pre = "";
891   if ($skip) {
892      $prevskip= $skip - $perpage;
893      if ($prevskip<0) $prevskip= 0;
894      $prevtag_pre= "<a href='?type=".$type."&amp;display=".$dispnum."&amp;skip=$prevskip'>" .
895       _("[PREVIOUS]") ."</a>";
896      print "\t<li><center>";
897      print "$prevtag_pre";
898      print "</center></li>\n";
899   }
900  
901   if (isset($shownext)) {
902     $nextskip= $skip + $index;
903     if ($prevtag_pre) $prevtag .= " | ";
904     print "\t<li><center>";
905     print "$prevtag <a href='?type=".$type."&amp;display=".$dispnum."&amp;skip=$nextskip'>" .
906       _("[NEXT]") . "</a>";
907     print "</center></li>\n";
908   }
909   echo "</ul>\n";
910 }
911
912 // this function returns true if $astman is defined and set to something (implying a current connection, false otherwise.
913 // this function no longer puts out an error message, it is up to the caller to handle the situation.
914 // Should probably be changed (at least name) to check if a connection is available to the current engine)
915 //
916 function checkAstMan() {
917   global $astman;
918
919   return ($astman)?true:false;
920 }
921
922 /* merge_ext_followme($dest) {
923  *
924  * The purpose of this function is to take a destination
925  * that was either a core extension OR a findmefollow-destination
926  * and convert it so that they are merged and handled just like
927  * direct-did routing
928  *
929  * Assuming an extension number of 222:
930  *
931  * The two formats that existed for findmefollow were:
932  *
933  * ext-findmefollow,222,1
934  * ext-findmefollow,FM222,1
935  *
936  * The one format that existed for core was:
937  *
938  * ext-local,222,1
939  *
940  * In all those cases they should be converted to:
941  *
942  * from-did-direct,222,1
943  *
944  */
945 function merge_ext_followme($dest) {
946
947   if (preg_match("/^\s*ext-findmefollow,(FM)?(\d+),(\d+)/",$dest,$matches) ||
948       preg_match("/^\s*ext-local,(FM)?(\d+),(\d+)/",$dest,$matches) ) {
949         // matches[2] => extn
950         // matches[3] => priority
951     return "from-did-direct,".$matches[2].",".$matches[3];
952   } else {
953     return $dest;
954   }
955 }
956
957
958 /** Recursively read voicemail.conf (and any included files)
959  * This function is called by getVoicemailConf()
960  */
961 function parse_voicemailconf($filename, &$vmconf, &$section) {
962   if (is_null($vmconf)) {
963     $vmconf = array();
964   }
965   if (is_null($section)) {
966     $section = "general";
967   }
968  
969   if (file_exists($filename)) {
970     $fd = fopen($filename, "r");
971     while ($line = fgets($fd, 1024)) {
972       if (preg_match("/^\s*(\d+)\s*=>\s*(\d*),(.*),(.*),(.*),(.*)\s*([;#].*)?/",$line,$matches)) {
973         // "mailbox=>password,name,email,pager,options"
974         // this is a voicemail line
975         $vmconf[$section][ $matches[1] ] = array("mailbox"=>$matches[1],
976                   "pwd"=>$matches[2],
977                   "name"=>$matches[3],
978                   "email"=>$matches[4],
979                   "pager"=>$matches[5],
980                   "options"=>array(),
981                   );
982                 
983         // parse options
984         //output($matches);
985         foreach (explode("|",$matches[6]) as $opt) {
986           $temp = explode("=",$opt);
987           //output($temp);
988           if (isset($temp[1])) {
989             list($key,$value) = $temp;
990             $vmconf[$section][ $matches[1] ]["options"][$key] = $value;
991           }
992         }
993       } else if (preg_match("/^\s*(\d+)\s*=>\s*dup,(.*)\s*([;#].*)?/",$line,$matches)) {
994         // "mailbox=>dup,name"
995         // duplace name line
996         $vmconf[$section][ $matches[1] ]["dups"][] = $matches[2];
997       } else if (preg_match("/^\s*#include\s+(.*)\s*([;#].*)?/",$line,$matches)) {
998         // include another file
999         
1000         if ($matches[1][0] == "/") {
1001           // absolute path
1002           $filename = $matches[1];
1003         } else {
1004           // relative path
1005           $filename =  dirname($filename)."/".$matches[1];
1006         }
1007         
1008         parse_voicemailconf($filename, $vmconf, $section);
1009         
1010       } else if (preg_match("/^\s*\[(.+)\]/",$line,$matches)) {
1011         // section name
1012         $section = strtolower($matches[1]);
1013       } else if (preg_match("/^\s*([a-zA-Z0-9-_]+)\s*=\s*(.*?)\s*([;#].*)?$/",$line,$matches)) {
1014         // name = value
1015         // option line
1016         $vmconf[$section][ $matches[1] ] = $matches[2];
1017       }
1018     }
1019     fclose($fd);
1020   }
1021 }
1022
1023 /** Write the voicemail.conf file
1024  * This is called by saveVoicemail()
1025  * It's important to make a copy of $vmconf before passing it. Since this is a recursive function, has to
1026  * pass by reference. At the same time, it removes entries as it writes them to the file, so if you don't have
1027  * a copy, by the time it's done $vmconf will be empty.
1028 */
1029 function write_voicemailconf($filename, &$vmconf, &$section, $iteration = 0) {
1030   global $amp_conf;
1031   if ($iteration == 0) {
1032     $section = null;
1033   }
1034  
1035   $output = array();
1036     
1037   // if the file does not, copy if from the template.
1038   // TODO: is this logical?
1039   // TODO: don't use hardcoded path...?
1040   if (!file_exists($filename)) {
1041     if (!copy( rtrim($amp_conf["ASTETCDIR"],"/")."/voicemail.conf.template", $filename )){
1042       return;
1043     }
1044   }
1045  
1046     $fd = fopen($filename, "r");
1047     while ($line = fgets($fd, 1024)) {
1048       if (preg_match("/^(\s*)(\d+)(\s*)=>(\s*)(\d*),(.*),(.*),(.*),(.*)(\s*[;#].*)?$/",$line,$matches)) {
1049         // "mailbox=>password,name,email,pager,options"
1050         // this is a voicemail line
1051         //DEBUG echo "\nmailbox";
1052         
1053         // make sure we have something as a comment
1054         if (!isset($matches[10])) {
1055           $matches[10] = "";
1056         }
1057         
1058         // $matches[1] [3] and [4] are to preserve indents/whitespace, we add these back in
1059         
1060         if (isset($vmconf[$section][ $matches[2] ])) { 
1061           // we have this one loaded
1062           // repopulate from our version
1063           $temp = & $vmconf[$section][ $matches[2] ];
1064           
1065           $options = array();
1066           foreach ($temp["options"] as $key=>$value) {
1067             $options[] = $key."=".$value;
1068           }
1069           
1070           $output[] = $matches[1].$temp["mailbox"].$matches[3]."=>".$matches[4].$temp["pwd"].",".$temp["name"].",".$temp["email"].",".$temp["pager"].",". implode("|",$options).$matches[10];
1071           
1072           // remove this one from $vmconf
1073           unset($vmconf[$section][ $matches[2] ]);
1074         } else {
1075           // we don't know about this mailbox, so it must be deleted
1076           // (and hopefully not JUST added since we did read_voiceamilconf)
1077           
1078           // do nothing
1079         }
1080         
1081       } else if (preg_match("/^(\s*)(\d+)(\s*)=>(\s*)dup,(.*)(\s*[;#].*)?$/",$line,$matches)) {
1082         // "mailbox=>dup,name"
1083         // duplace name line
1084         // leave it as-is (for now)
1085         //DEBUG echo "\ndup mailbox";
1086         $output[] = $line;
1087       } else if (preg_match("/^(\s*)#include(\s+)(.*)(\s*[;#].*)?$/",$line,$matches)) {
1088         // include another file
1089         //DEBUG echo "\ninclude ".$matches[3]."<blockquote>";
1090         
1091         // make sure we have something as a comment
1092         if (!isset($matches[4])) {
1093           $matches[4] = "";
1094         }
1095         
1096         if ($matches[3][0] == "/") {
1097           // absolute path
1098           $include_filename = $matches[3];
1099         } else {
1100           // relative path
1101           $include_filename =  dirname($filename)."/".$matches[3];
1102         }
1103         
1104         $output[] = $matches[1]."#include".$matches[2].$matches[3].$matches[4];
1105         write_voicemailconf($include_filename, $vmconf, $section, $iteration+1);
1106         
1107         //DEBUG echo "</blockquote>";
1108         
1109       } else if (preg_match("/^(\s*)\[(.+)\](\s*[;#].*)?$/",$line,$matches)) {
1110         // section name
1111         //DEBUG echo "\nsection";
1112         
1113         // make sure we have something as a comment
1114         if (!isset($matches[3])) {
1115           $matches[3] = "";
1116         }
1117         
1118         // check if this is the first run (section is null)
1119         if ($section !== null) {
1120           // we need to add any new entries here, before the section changes
1121           //DEBUG echo "<blockquote><i>";
1122           //DEBUG var_dump($vmconf[$section]);
1123           if (isset($vmconf[$section])){  //need this, or we get an error if we unset the last items in this section - should probably automatically remove the section/context from voicemail.conf
1124             foreach ($vmconf[$section] as $key=>$value) {
1125               if (is_array($value)) {
1126                 // mailbox line
1127                 
1128                 $temp = & $vmconf[$section][ $key ];
1129                 
1130                 $options = array();
1131                 foreach ($temp["options"] as $key1=>$value) {
1132                   $options[] = $key1."=".$value;
1133                 }
1134                 
1135                 $output[] = $temp["mailbox"]." => ".$temp["pwd"].",".$temp["name"].",".$temp["email"].",".$temp["pager"].",". implode("|",$options);
1136                 
1137                 // remove this one from $vmconf
1138                 unset($vmconf[$section][ $key ]);
1139                 
1140               } else {
1141                 // option line
1142                 
1143                 $output[] = $key."=".$vmconf[$section][ $key ];
1144                 
1145                 // remove this one from $vmconf
1146                 unset($vmconf[$section][ $key ]);
1147               }
1148             }
1149           }
1150           //DEBUG echo "</i></blockquote>";
1151         }
1152         
1153         $section = strtolower($matches[2]);
1154         $output[] = $matches[1]."[".$section."]".$matches[3];
1155         $existing_sections[] = $section; //remember that this section exists
1156
1157       } else if (preg_match("/^(\s*)([a-zA-Z0-9-_]+)(\s*)=(\s*)(.*?)(\s*[;#].*)?$/",$line,$matches)) {
1158         // name = value
1159         // option line
1160         //DEBUG echo "\noption line";
1161         
1162         
1163         // make sure we have something as a comment
1164         if (!isset($matches[6])) {
1165           $matches[6] = "";
1166         }
1167         
1168         if (isset($vmconf[$section][ $matches[2] ])) {
1169           $output[] = $matches[1].$matches[2].$matches[3]."=".$matches[4].$vmconf[$section][ $matches[2] ].$matches[6];
1170           
1171           // remove this one from $vmconf
1172           unset($vmconf[$section][ $matches[2] ]);
1173         }
1174         // else it's been deleted, so we don't write it in
1175         
1176       } else {
1177         // unknown other line -- probably a comment or whitespace
1178         //DEBUG echo "\nother: ".$line;
1179         
1180         $output[] = str_replace(array("\n","\r"),"",$line); // str_replace so we don't double-space
1181       }
1182     }
1183     
1184     if (($iteration == 0) && (is_array($vmconf))) {
1185       // we need to add any new entries here, since it's the end of the file
1186       //DEBUG echo "END OF FILE!! <blockquote><i>";
1187       //DEBUG var_dump($vmconf);
1188       foreach (array_keys($vmconf) as $section) {
1189         if (!in_array($section,$existing_sections))  // If this is a new section, write the context label
1190           $output[] = "[".$section."]";
1191         foreach ($vmconf[$section] as $key=>$value) {
1192           if (is_array($value)) {
1193             // mailbox line
1194             
1195             $temp = & $vmconf[$section][ $key ];
1196             
1197             $options = array();
1198             foreach ($temp["options"] as $key=>$value) {
1199               $options[] = $key."=".$value;
1200             }
1201             
1202             $output[] = $temp["mailbox"]." => ".$temp["pwd"].",".$temp["name"].",".$temp["email"].",".$temp["pager"].",". implode("|",$options);
1203             
1204             // remove this one from $vmconf
1205             unset($vmconf[$section][ $key ]);
1206             
1207           } else {
1208             // option line
1209             
1210             $output[] = $key."=".$vmconf[$section][ $key ];
1211             
1212             // remove this one from $vmconf
1213             unset($vmconf[$section][$key ]);
1214           }
1215         }
1216       }
1217       //DEBUG echo "</i></blockquote>";
1218     }
1219     
1220     fclose($fd);
1221     
1222     //DEBUG echo "\n\nwriting ".$filename;
1223     //DEBUG echo "\n-----------\n";
1224     //DEBUG echo implode("\n",$output);
1225     //DEBUG echo "\n-----------\n";
1226     
1227     // write this file back out
1228     
1229     if ($fd = fopen($filename, "w")) {
1230       fwrite($fd, implode("\n",$output)."\n");
1231       fclose($fd);
1232     }
1233     
1234 }
1235
1236
1237 // $goto is the current goto destination setting
1238 // $i is the destination set number (used when drawing multiple destination sets in a single form ie: digital receptionist)
1239 // esnure that any form that includes this calls the setDestinations() javascript function on submit.
1240 // ie: if the form name is "edit", and drawselects has been called with $i=2 then use onsubmit="setDestinations(edit,2)"
1241 function drawselects($goto,$i) { 
1242  
1243   /* --- MODULES BEGIN --- */
1244   global $active_modules;
1245  
1246   $all_destinations = array();
1247
1248   $selectHtml = '<tr><td colspan=2>';
1249  
1250   //check for module-specific destination functions
1251   foreach ($active_modules as $rawmod => $module) {
1252     $funct = strtolower($rawmod.'_destinations');
1253  
1254     //if the modulename_destinations() function exits, run it and display selections for it
1255     if (function_exists($funct)) {
1256       $destArray = $funct(); //returns an array with 'destination' and 'description', and optionally 'category'
1257       if (is_Array($destArray)) {
1258         foreach ($destArray as $dest) {
1259           $cat = (isset($dest['category']) ? $dest['category'] : $module['displayname']);
1260           $all_destinations[$cat][] = $dest;
1261         }
1262       }
1263     }
1264   }
1265 //  var_dump($all_destinations);
1266 //  var_dump($goto);
1267
1268   $foundone = false;
1269   foreach ($all_destinations as $cat=>$destination) {
1270     // create a select option for each destination
1271     $options = "";
1272     $checked = false;
1273     foreach ($destination as $dest) {
1274       $options .= '<option value="'.$dest['destination'].'" '.(strpos($goto,$dest['destination']) === false ? '' : 'SELECTED').'>'.($dest['description'] ? $dest['description'] : $dest['destination']);
1275
1276       // check to see if the currently selected goto matches one these destinations
1277       if($dest['destination'] == $goto) $checked = true;
1278     }
1279
1280     // make a unique id to be used for the HTML id
1281     // This allows us to have multiple drawselect() sets on the page without
1282     // conflicting with each other
1283     $radioid = uniqid("drawselect");
1284     //
1285     $cat_identifier = preg_replace('/[^a-zA-Z0-9]/','_', $cat);
1286       
1287     $selectHtml .=  '<input type="radio" id="'.$radioid.'" name="goto'.$i.'" value="'.$cat_identifier.'" '.
1288                     //'onclick="javascript:this.form.goto'.$i.'.value=\''.$cat.'\';" '.
1289                     //'onkeypress="javascript:if (event.keyCode == 0 || (document.all && event.keyCode == 13)) this.form.goto'.$i.'.value=\''.$cat.'\';" '.
1290                     ($checked? 'CHECKED=CHECKED' : '').' /> ';
1291     $selectHtml .= '<label for="'.$radioid.'">'._($cat).':</label> ';
1292     
1293     // set the
1294 //    if ($checked) { $gotomod = $cat; }
1295
1296     $selectHtml .=  '<select name="'.$cat_identifier.$i.'" onfocus="document.getElementById(\''.$radioid.'\').checked = true; this.form.goto'.$i.'.value=\''.$cat.'\';">';
1297     $selectHtml .= $options; 
1298     $selectHtml .=  "</select><br>\n";
1299
1300     if ($checked) $foundone = true;
1301   }
1302   /* --- MODULES END --- */
1303  
1304   // This is selected if $foundone is false (and goto is not blank) - basically, a fallback
1305   // The ONLY time no radio box is selected is if $goto is empty
1306   $custom_selected = !$foundone && !empty($goto);
1307  
1308   //display a custom goto field
1309   $radioid = uniqid("drawselect");
1310   $selectHtml .= '<input type="radio" id="'.$radioid.'" name="goto'.$i.'" value="custom" '.
1311                  //'onclick="javascript:this.form.goto'.$i.'.value=\'custom\';" '.
1312            //'onkeypress="javascript:if (event.keyCode == 0 || (document.all && event.keyCode == 13)) this.form.goto'.$i.'.value=\'custom\';" '.
1313            ($custom_selected ? 'CHECKED=CHECKED' : '').' />';
1314   $selectHtml .= '<a href="#" class="info"> '._("Custom App<span><br>ADVANCED USERS ONLY<br><br>Uses Goto() to send caller to a custom context.<br><br>The context name should start with \"custom-\", and be in the format custom-context,extension,priority. Example entry:<br><br><b>custom-myapp,s,1</b><br><br>The <b>[custom-myapp]</b> context would need to be created and included in extensions_custom.conf</span>").'</a>:';
1315   $selectHtml .= '<input type="text" size="15" name="custom'.$i.'" value="'.($custom_selected ? $goto : '').'" onfocus="document.getElementById(\''.$radioid.'\').checked = true;" />';
1316
1317   //close off our row
1318   $selectHtml .= '</td></tr>';
1319  
1320   return $selectHtml;
1321 }
1322
1323
1324 /* below are legacy functions required to allow pre 2.0 modules to function (ie: interact with 'extensions' table) */
1325
1326   //add to extensions table - used in callgroups.php
1327   function legacy_extensions_add($addarray) {
1328     global $db;
1329     $sql = "INSERT INTO extensions (context, extension, priority, application, args, descr, flags) VALUES ('".$addarray[0]."', '".$addarray[1]."', '".$addarray[2]."', '".$addarray[3]."', '".$addarray[4]."', '".$addarray[5]."' , '".$addarray[6]."')";
1330     $result = $db->query($sql);
1331     if(DB::IsError($result)) {
1332       die_freepbx($result->getMessage().$sql);
1333     }
1334     return $result;
1335   }
1336  
1337   //delete extension from extensions table
1338   function legacy_extensions_del($context,$exten) {
1339     global $db;
1340     $sql = "DELETE FROM extensions WHERE context = '".addslashes($context)."' AND `extension` = '".addslashes($exten)."'";
1341     $result = $db->query($sql);
1342     if(DB::IsError($result)) {
1343       die_freepbx($result->getMessage());
1344     }
1345     return $result;
1346   }
1347  
1348  
1349   //get args for specified exten and priority - primarily used to grab goto destination
1350   function legacy_args_get($exten,$priority,$context) {
1351     global $db;
1352     $sql = "SELECT args FROM extensions WHERE extension = '".addslashes($exten)."' AND priority = '".addslashes($priority)."' AND context = '".addslashes($context)."'";
1353     list($args) = $db->getRow($sql);
1354     return $args;
1355   }
1356
1357 /* end legacy functions */
1358
1359 /* Usage
1360 Grab some XML data, either from a file, URL, etc. however you want. Assume storage in $strYourXML;
1361
1362 $xml = new xml2Array($strYourXML);
1363 xml array is in $xml->data;
1364   This is basically an array version of the XML data (no attributes), striaght-up. If there are
1365   multiple items with the same name, they are split into a numeric sub-array,
1366   eg, <items><item test="123">foo</item><item>bar</item></items>
1367   becomes: array('item' => array(0=>array('item'=>'foo'), 1=>array('item'=>'foo'))
1368 attributes are in $xml->attributes;
1369   These are stored with xpath type paths, as $xml->attributes['/items/item/0']["test"] == "123"
1370  
1371
1372 Other way (still works, but not as nice):
1373
1374 $objXML = new xml2Array();
1375 $arrOutput = $objXML->parse($strYourXML);
1376 print_r($arrOutput); //print it out, or do whatever!
1377
1378 */
1379
1380 class xml2Array {
1381   var $arrOutput = array();
1382   var $resParser;
1383   var $strXmlData;
1384  
1385   var $attributes;
1386   var $data;
1387  
1388   function xml2Array($strInputXML = false) {
1389     if (!empty($strInputXML)) {
1390       $this->data = $this->parseAdvanced($strInputXML);
1391     }
1392   }
1393  
1394   function parse($strInputXML) {
1395  
1396       $this->resParser = xml_parser_create ();
1397       xml_set_object($this->resParser,$this);
1398       xml_set_element_handler($this->resParser, "tagOpen", "tagClosed");
1399       
1400       xml_set_character_data_handler($this->resParser, "tagData");
1401     
1402       $this->strXmlData = xml_parse($this->resParser,$strInputXML );
1403       if(!$this->strXmlData) {
1404         die_freepbx(sprintf("XML error: %s at line %d",
1405       xml_error_string(xml_get_error_code($this->resParser)),
1406       xml_get_current_line_number($this->resParser)));
1407       }
1408               
1409       xml_parser_free($this->resParser);
1410       
1411       return $this->arrOutput;
1412   }
1413   function tagOpen($parser, $name, $attrs) {
1414     $tag=array("name"=>$name,"attrs"=>$attrs);
1415     array_push($this->arrOutput,$tag);
1416   }
1417  
1418   function tagData($parser, $tagData) {
1419     if(trim($tagData)) {
1420       if(isset($this->arrOutput[count($this->arrOutput)-1]['tagData'])) {
1421         $this->arrOutput[count($this->arrOutput)-1]['tagData'] .= "\n".$tagData;
1422       }
1423       else {
1424         $this->arrOutput[count($this->arrOutput)-1]['tagData'] = $tagData;
1425       }
1426     }
1427   }
1428  
1429   function tagClosed($parser, $name) {
1430     @$this->arrOutput[count($this->arrOutput)-2]['children'][] = $this->arrOutput[count($this->arrOutput)-1];
1431     array_pop($this->arrOutput);
1432   }
1433  
1434   function recursive_parseLevel($items, &$attrs, $path = "") {
1435     $array = array();
1436     foreach (array_keys($items) as $idx) {
1437       @$items[$idx]['name'] = strtolower($items[$idx]['name']);
1438       
1439       $multi = false;
1440       if (isset($array[ $items[$idx]['name'] ])) {
1441         // this child is already set, so we're adding multiple items to an array
1442         
1443         if (!is_array($array[ $items[$idx]['name'] ]) || !isset($array[ $items[$idx]['name'] ][0])) {
1444           // hasn't already been made into a numerically-indexed array, so do that now
1445           // we're basically moving the current contents of this item into a 1-item array (at the
1446           // original location) so that we can add a second item in the code below
1447           $array[ $items[$idx]['name'] ] = array( $array[ $items[$idx]['name'] ] );
1448
1449           if (isset($attrs[ $path.'/'.$items[$idx]['name'] ])) {
1450             // move the attributes to /0
1451             $attrs[ $path.'/'.$items[$idx]['name'].'/0' ] = $attrs[ $path.'/'.$items[$idx]['name'] ];
1452             unset($attrs[ $path.'/'.$items[$idx]['name'] ]);
1453           }
1454         }
1455         $multi = true;
1456       }
1457       
1458       if ($multi) {
1459         $newitem = &$array[ $items[$idx]['name'] ][];
1460       } else {
1461         $newitem = &$array[ $items[$idx]['name'] ];
1462       }
1463       
1464       
1465       if (isset($items[$idx]['children']) && is_array($items[$idx]['children'])) {
1466         $newitem = $this->recursive_parseLevel($items[$idx]['children'], $attrs, $path.'/'.$items[$idx]['name']);
1467       } else if (isset($items[$idx]['tagData'])) {
1468         $newitem = $items[$idx]['tagData'];
1469       } else {
1470         $newitem = false;
1471       }
1472       
1473       if (isset($items[$idx]['attrs']) && is_array($items[$idx]['attrs']) && count($items[$idx]['attrs'])) {
1474         $attrpath = $path.'/'.$items[$idx]['name'];
1475         if ($multi) {
1476           $attrpath .= '/'.(count($array[ $items[$idx]['name'] ])-1);
1477         }
1478         foreach ($items[$idx]['attrs'] as $name=>$value) {
1479           $attrs[ $attrpath ][ strtolower($name) ] = $value;
1480         }
1481       }
1482     }
1483     return $array;
1484   }
1485  
1486   function parseAdvanced($strInputXML) {
1487     $array = $this->parse($strInputXML);
1488     $this->attributes = array();
1489     return $this->data = $this->recursive_parseLevel($array, $this->attributes);
1490   }
1491 }
1492
1493
1494 /*
1495   Return a much more manageable assoc array with module data.
1496 */
1497 class xml2ModuleArray extends xml2Array {
1498   function parseModulesXML($strInputXML) {
1499     $array = $this->parseAdvanced($strInputXML);
1500     if (isset($array['xml'])) {
1501       foreach ($array['xml'] as $key=>$module) {
1502         if ($key == 'module') {
1503           // copy the structure verbatim
1504           $modules[ $module['name'] ] = $module;
1505         }
1506       }
1507     }
1508     
1509     // if you are confused about what's happening below, uncomment this why we do it
1510     // echo "<pre>"; print_r($arrOutput); echo "</pre>";
1511     
1512     // ignore the regular xml garbage ([0]['children']) & loop through each module
1513     if(!is_array($arrOutput[0]['children'])) return false;
1514     foreach($arrOutput[0]['children'] as $module) {
1515       if(!is_array($module['children'])) return false;
1516       // loop through each modules's tags
1517       foreach($module['children'] as $modTags) {
1518           if(isset($modTags['children']) && is_array($modTags['children'])) {
1519             $$modTags['name'] = $modTags['children'];
1520             // loop if there are children (menuitems and requirements)
1521             foreach($modTags['children'] as $subTag) {
1522               $subTags[strtolower($subTag['name'])] = $subTag['tagData'];
1523             }
1524             $$modTags['name'] = $subTags;
1525             unset($subTags);
1526           } else {
1527             // create a variable for each tag we find
1528             $$modTags['name'] = $modTags['tagData'];
1529           }
1530
1531       }
1532       // now build our return array
1533       $arrModules[$RAWNAME]['rawname'] = $RAWNAME;    // This has to be set
1534       $arrModules[$RAWNAME]['displayName'] = $NAME;    // This has to be set
1535       $arrModules[$RAWNAME]['version'] = $VERSION;     // This has to be set
1536       $arrModules[$RAWNAME]['type'] = isset($TYPE)?$TYPE:'setup';
1537       $arrModules[$RAWNAME]['category'] = isset($CATEGORY)?$CATEGORY:'Unknown';
1538       $arrModules[$RAWNAME]['info'] = isset($INFO)?$INFO:'http://www.freepbx.org/wiki/'.$RAWNAME;
1539       $arrModules[$RAWNAME]['location'] = isset($LOCATION)?$LOCATION:'local';
1540       $arrModules[$RAWNAME]['items'] = isset($MENUITEMS)?$MENUITEMS:null;
1541       $arrModules[$RAWNAME]['requirements'] = isset($REQUIREMENTS)?$REQUIREMENTS:null;
1542       $arrModules[$RAWNAME]['md5sum'] = isset($MD5SUM)?$MD5SUM:null;
1543       //print_r($arrModules);
1544       //unset our variables
1545       unset($NAME);
1546       unset($VERSION);
1547       unset($TYPE);
1548       unset($CATEGORY);
1549       unset($AUTHOR);
1550       unset($EMAIL);
1551       unset($LOCATION);
1552       unset($MENUITEMS);
1553       unset($REQUIREMENTS);
1554       unset($MD5SUM);
1555     }
1556     //echo "<pre>"; print_r($arrModules); echo "</pre>";
1557
1558     return $arrModules;
1559   }
1560 }
1561
1562 function get_headers_assoc($url ) {
1563   $url_info=parse_url($url);
1564   if (isset($url_info['scheme']) && $url_info['scheme'] == 'https') {
1565     $port = isset($url_info['port']) ? $url_info['port'] : 443;
1566     @$fp=fsockopen('ssl://'.$url_info['host'], $port, $errno, $errstr, 10);
1567   } else {
1568     $port = isset($url_info['port']) ? $url_info['port'] : 80;
1569     @$fp=fsockopen($url_info['host'], $port, $errno, $errstr, 10);
1570   }
1571   if ($fp) {
1572     stream_set_timeout($fp, 10);
1573     $head = "HEAD ".@$url_info['path']."?".@$url_info['query'];
1574     $head .= " HTTP/1.0\r\nHost: ".@$url_info['host']."\r\n\r\n";
1575     fputs($fp, $head);
1576     while(!feof($fp)) {
1577       if($header=trim(fgets($fp, 1024))) {
1578         $sc_pos = strpos($header, ':');
1579         if ($sc_pos === false) {
1580           $headers['status'] = $header;
1581         } else {
1582           $label = substr( $header, 0, $sc_pos );
1583           $value = substr( $header, $sc_pos+1 );
1584           $headers[strtolower($label)] = trim($value);
1585         }
1586       }
1587     }
1588     return $headers;
1589   } else {
1590     return false;
1591   }
1592 }
1593
1594   
1595
1596 class moduleHook {
1597   var $hookHtml = '';
1598   var $arrHooks = array();
1599  
1600   function install_hooks($viewing_itemid,$target_module,$target_menuid = '') {
1601     global $active_modules;
1602     // loop through all active modules
1603     foreach($active_modules as $this_module) {
1604         // look for requested hooks for $module
1605         // ie: findme_hook_extensions()
1606         $funct = $this_module['rawname'] . '_hook_' . $target_module;
1607         if( function_exists( $funct ) ) {
1608           // execute the function, appending the
1609           // html output to that of other hooking modules
1610           if ($hookReturn = $funct($target_menuid, $viewing_itemid)) {
1611             $this->hookHtml .= $hookReturn;
1612           }
1613           // remember who installed hooks
1614           // we need to know this for processing form vars
1615           $this->arrHooks[] = $this_module['rawname'];
1616         }
1617     }
1618   }
1619  
1620   // process the request from the module we hooked
1621   function process_hooks($viewing_itemid, $target_module, $target_menuid, $request) {
1622     if(is_array($this->arrHooks)) {
1623       foreach($this->arrHooks as $hookingMod) {
1624         // check if there is a processing function
1625         $funct = $hookingMod . '_hookProcess_' . $target_module;
1626         if( function_exists( $funct ) ) {
1627           $funct($viewing_itemid, $request);
1628         }
1629       }
1630     }
1631   }
1632 }
1633
1634 function execSQL( $file )
1635 {
1636   global $db;
1637   $data = null;
1638  
1639   // run sql script
1640   $fd = fopen( $file ,"r" );
1641  
1642   while (!feof($fd)) {
1643     $data .= fread($fd, 1024);
1644   }
1645   fclose($fd);
1646  
1647   preg_match_all("/((SELECT|INSERT|UPDATE|DELETE|CREATE|DROP).*);\s*\n/Us", $data, $matches);
1648   foreach ($matches[1] as $sql) {
1649     $result = $db->query($sql);
1650     if(DB::IsError($result)) { return false; }
1651   }
1652 }
1653
1654 // Dragged this in from page.modules.php, so it can be used by install_amp.
1655 function runModuleSQL($moddir,$type){
1656   trigger_error("runModuleSQL() is depreciated - please use _module_runscripts(), or preferably module_install() or module_enable() instead", E_USER_WARNING);
1657   _module_runscripts($moddir, $type);
1658 }
1659
1660
1661 /** Replaces variables in a string with the values from ampconf
1662  * eg, "%AMPWEBROOT%/admin" => "/var/www/html/admin"
1663  */
1664 function ampconf_string_replace($string) {
1665   global $amp_conf;
1666  
1667   $target = array();
1668   $replace = array();
1669  
1670   foreach ($amp_conf as $key=>$value) {
1671     $target[] = '%'.$key.'%';
1672     $replace[] = $value;
1673   }
1674  
1675   return str_replace($target, $replace, $string);
1676 }
1677
1678 /***********************************************************************************************************
1679                                        Module functions
1680 ************************************************************************************************************/
1681  
1682 /** Get the latest module.xml file for this FreePBX version.
1683  * Caches in the database for 5 mintues.
1684  * If $module is specified, only returns the data for that module.
1685  * If the module is not found (or none are available for whatever reason),
1686  * then null is returned.
1687  *
1688  * Sets the global variable $module_getonlinexml_error to true if an error
1689  * occured getting the module from the repository, false if no error occured,
1690  * or null if the repository wasn't checked. Note that this may change in the
1691  * future if we decide we need to return more error codes, but as long as it's
1692  * a php zero-value (false, null, 0, etc) then no error happened.
1693  */
1694 function module_getonlinexml($module = false, $override_xml = false) { // was getModuleXml()
1695   global $amp_conf;
1696  
1697   global $module_getonlinexml_error;  // okay, yeah, this sucks, but there's no other good way to do it without breaking BC
1698   $module_getonlinexml_error = null;
1699   $got_new = false;
1700  
1701   //this should be in an upgrade file ... putting here for now.
1702   /*
1703   sql('CREATE TABLE IF NOT EXISTS module_xml (time INT NOT NULL , data BLOB NOT NULL) TYPE = MYISAM ;');
1704   */
1705  
1706   $result = sql('SELECT * FROM module_xml WHERE id = "xml"','getRow',DB_FETCHMODE_ASSOC);
1707   $data = $result['data'];
1708  
1709   // if the epoch in the db is more than 2 hours old, or the xml is less than 100 bytes, then regrab xml
1710   // Changed to 5 minutes while not in release. Change back for released version.
1711   //
1712   // used for debug, time set to 0 to always fall through
1713   // if((time() - $result['time']) > 0 || strlen($result['data']) < 100 ) {
1714   if((time() - $result['time']) > 300 || strlen($result['data']) < 100 ) {
1715     $version = getversion();
1716     // we need to know the freepbx major version we have running (ie: 2.1.2 is 2.1)
1717     preg_match('/(\d+\.\d+)/',$version,$matches);
1718     //echo "the result is ".$matches[1];
1719     if ($override_xml) {
1720       $fn = $override_xml."modules-".$matches[1].".xml";
1721     } else {
1722       $fn = "http://mirror.freepbx.org/modules-".$matches[1].".xml";
1723       // echo "(From default)"; //debug
1724     }
1725     //$fn = "/usr/src/freepbx-modules/modules.xml";
1726     $data = @ file_get_contents($fn);
1727     $module_getonlinexml_error = empty($data);
1728     
1729     $old_xml = array();
1730     $got_new = false;
1731     if (!empty($data)) {
1732       // Compare the download to our current XML to see if anything changed for the notification system.
1733       //
1734       $sql = 'SELECT data FROM module_xml WHERE id = "xml"';
1735       $old_xml = sql($sql, "getOne");
1736       $got_new = true;
1737       // remove the old xml
1738       sql('DELETE FROM module_xml WHERE id = "xml"');
1739       // update the db with the new xml
1740       $data4sql = addslashes($data);
1741       sql('INSERT INTO module_xml (id,time,data) VALUES ("xml",'.time().',"'.$data4sql.'")');
1742     }
1743   }
1744  
1745   if (empty($data)) {
1746     // no data, probably couldn't connect online, and nothing cached
1747     return null;
1748   }
1749  
1750   //echo time() - $result['time'];
1751   $parser = new xml2ModuleArray($data);
1752   $xmlarray = $parser->parseAdvanced($data);
1753   //$modules = $xmlarray['XML']['MODULE'];
1754  
1755   if ($got_new) {
1756     module_update_notifications($old_xml, $xmlarray, ($old_xml == $data4sql));
1757   }
1758
1759
1760   //echo "<hr>Raw XML Data<pre>"; print_r(htmlentities($data)); echo "</pre>";
1761   //echo "<hr>XML2ARRAY<pre>"; print_r($xmlarray); echo "</pre>";
1762  
1763  
1764   if (isset($xmlarray['xml']['module'])) {
1765  
1766     if ($module != false) {
1767       foreach ($xmlarray['xml']['module'] as $mod) {
1768         if ($module == $mod['rawname']) {
1769           return $mod;
1770         }
1771       }
1772       return null;
1773     } else {
1774     
1775     
1776       $modules = array();
1777       foreach ($xmlarray['xml']['module'] as $mod) {
1778         $modules[ $mod['rawname'] ] = $mod;
1779       }
1780       return $modules;
1781     }
1782   }
1783   return null;
1784 }
1785
1786 /**  Determines if there are updates we don't already know about and posts to notification
1787  *   server about those updates.
1788  *
1789  */
1790 function module_update_notifications(&$old_xml, &$xmlarray, $passive) {
1791   global $db;
1792
1793   $notifications =& notifications::create($db);
1794
1795   $reset_value = $passive ? 'PASSIVE' : false;
1796   $old_parser = new xml2ModuleArray($old_xml);
1797   $old_xmlarray = $old_parser->parseAdvanced($old_xml);
1798
1799   $new_modules = array();
1800   if (count($xmlarray)) {
1801     foreach ($xmlarray['xml']['module'] as $mod) {
1802       $new_modules[$mod['rawname']] = $mod;
1803     }
1804   }
1805   $old_modules = array();
1806   if (count($old_xmlarray)) {
1807     foreach ($old_xmlarray['xml']['module'] as $mod) {
1808       $old_modules[$mod['rawname']] = $mod;
1809     }
1810   }
1811
1812   // If keys (rawnames) are different then there are new modules, create a notification.
1813   // This will always be the case the first time it is run since the xml is empty.
1814   //
1815   // TODO: if old_modules is empty, should I populate it from getinfo to at find out what
1816   //       is installed or otherwise, just skip it since it is the first time?
1817   //
1818   $diff_modules = array_diff_assoc($new_modules, $old_modules);
1819   $cnt = count($diff_modules);
1820   if ($cnt) {
1821     $extext = _("The following new modules are available for download")."<br>";
1822     foreach ($diff_modules as $mod) {
1823       $extext .= $mod['rawname']." (".$mod['version'].")<br>";
1824     }
1825     $notifications->add_notice('freepbx', 'NEWMODS', sprintf(_('%s New modules are available'),$cnt), $extext, '', $reset_value, true);
1826   }
1827
1828   // Now check if any of the installed modules need updating
1829   //
1830   module_upgrade_notifications($new_modules, $reset_value);
1831 }
1832
1833 /** Compare installed (enabled or disabled) modules against the xml to generate or
1834  *  update the noticiation table of which modules have available updates. If the list
1835  *  is empty then delete the notification.
1836  */
1837 function module_upgrade_notifications(&$new_modules, $passive_value) {
1838   global $db;
1839   $notifications =& notifications::create($db);
1840
1841   $installed_status = array(MODULE_STATUS_ENABLED, MODULE_STATUS_DISABLED);
1842   $modules_local = module_getinfo(false, $installed_status);
1843
1844   $modules_upgradable = array();
1845   foreach (array_keys($modules_local) as $name) {
1846     if (isset($new_modules[$name])) {
1847       if (version_compare_freepbx($modules_local[$name]['version'], $new_modules[$name]['version']) < 0) {
1848         $modules_upgradable[] = array(
1849           'name' => $name,
1850           'local_version' => $modules_local[$name]['version'],
1851           'online_version' => $new_modules[$name]['version'],
1852         );
1853       }
1854     }
1855   }
1856   $cnt = count($modules_upgradable);
1857   if ($cnt) {
1858     if ($cnt == 1) {
1859       $text = _("There is 1 module available for online upgrade");
1860     } else {
1861       $text = sprintf(_("There are %s modules available for online upgrades"),$cnt);
1862     }
1863     $extext = "";
1864     foreach ($modules_upgradable as $mod) {
1865       $extext .= sprintf(_("%s (current: %s)"), $mod['name'].' '.$mod['online_version'], $mod['local_version'])."\n";
1866     }
1867     $notifications->add_update('freepbx', 'NEWUPDATES', $text, $extext, '', $passive_value);
1868   } else {
1869     $notifications->delete('freepbx', 'NEWUPDATES');
1870   }
1871 }
1872
1873 /** Looks through the modules directory and modules database and returns all available
1874  * information about one or all modules
1875  * @param string  (optional) The module name to query, or false for all module
1876  * @param mixed   (optional) The status(es) to show, using MODULE_STATUS_* constants. Can
1877  *                either be one value, or an array of values.
1878  */
1879 function module_getinfo($module = false, $status = false) {
1880   global $amp_conf, $db;
1881   $modules = array();
1882  
1883   if ($module) {
1884     // get info on only one module
1885     $xml = _module_readxml($module);
1886     if (!is_null($xml)) {
1887       $modules[$module] = $xml;
1888       // if status is anything else, it will be updated below when we read the db
1889       $modules[$module]['status'] = MODULE_STATUS_NOTINSTALLED;
1890     }
1891     
1892     // query to get just this one
1893     $sql = 'SELECT * FROM modules WHERE modulename = "'.$module.'"';
1894   } else {
1895     // get info on all modules
1896     $dir = opendir($amp_conf['AMPWEBROOT'].'/admin/modules');
1897     while ($file = readdir($dir)) {
1898       if (($file != ".") && ($file != "..") && ($file != "CVS") &&
1899           ($file != ".svn") && ($file != "_cache") &&
1900         is_dir($amp_conf['AMPWEBROOT'].'/admin/modules/'.$file)) {
1901         
1902         $xml = _module_readxml($file);
1903         if (!is_null($xml)) {
1904           $modules[$file] = $xml;
1905           // if status is anything else, it will be updated below when we read the db
1906           $modules[$file]['status'] = MODULE_STATUS_NOTINSTALLED;
1907         }
1908       }
1909     }
1910     
1911     // query to get everything
1912     $sql = 'SELECT * FROM modules';
1913   }
1914   // determine details about this module from database
1915   // modulename should match the directory name
1916  
1917   $results = $db->getAll($sql,DB_FETCHMODE_ASSOC);
1918   if(DB::IsError($results)) {
1919     die_freepbx($results->getMessage());
1920   }
1921  
1922   if (is_array($results)) {
1923     foreach($results as $row) {
1924       if (isset($modules[ $row['modulename'] ])) {
1925         if ($row['enabled'] != 0) {
1926           
1927           // check if file and registered versions are the same
1928           // version_compare returns 0 if no difference
1929           if (version_compare_freepbx($row['version'], $modules[ $row['modulename'] ]['version']) == 0) {
1930             $modules[ $row['modulename'] ]['status'] = MODULE_STATUS_ENABLED;
1931           } else {
1932             $modules[ $row['modulename'] ]['status'] = MODULE_STATUS_NEEDUPGRADE;
1933           }
1934           
1935         } else {
1936           $modules[ $row['modulename'] ]['status'] = MODULE_STATUS_DISABLED;
1937         }
1938       } else {
1939         // no directory for this db entry
1940         $modules[ $row['modulename'] ]['status'] = MODULE_STATUS_BROKEN;
1941       }
1942       $modules[ $row['modulename'] ]['dbversion'] = $row['version'];
1943     }
1944   }
1945  
1946   if ($status !== false) {
1947     if (!is_array($status)) {
1948       // make a one element array so we can use in_array below
1949       $status = array($status);
1950     }
1951     
1952     foreach (array_keys($modules) as $name) {
1953       if (!in_array($modules[$name]['status'], $status)) {
1954         // not found in the $status array, remove it
1955         unset($modules[$name]);
1956       }
1957     }
1958   }
1959  
1960   return $modules;
1961 }
1962
1963 /** Check if a module meets dependencies.
1964  * @param  mixed  The name of the module, or the modulexml Array
1965  * @return mixed  Returns true if dependencies are met, or an array
1966  *                containing a list of human-readable errors if not.
1967  *                NOTE: you must use strict type checking (===) to test
1968  *                for true, because  array() == true !
1969  */
1970 function module_checkdepends($modulename) {
1971  
1972   // check if we were passed a modulexml array, or a string (name)
1973   // ensure $modulexml is the modules array, and $modulename is the name (as a string)
1974   if (is_array($modulename)) {
1975     $modulexml = $modulename;
1976     $modulename = $modulename['rawname'];
1977   } else {
1978     $modulexml = module_getinfo($modulename);
1979   }
1980  
1981   $errors = array();
1982  
1983   // special handling for engine
1984   $engine_dependency = false; // if we've found ANY engine dependencies to check
1985   $engine_matched = false; // if an engine dependency has matched
1986   $engine_errors = array(); // the error strings for engines
1987  
1988   if (isset($modulexml['depends'])) {
1989     foreach ($modulexml['depends'] as $type => $requirements) {
1990       // if only a single item, make it an array so we can use the same code as for multiple items
1991       // this is because if there is  <module>a</module><module>b</module>  we will get array('module' => array('a','b'))
1992       if (!is_array($requirements)) {
1993         $requirements = array($requirements);
1994       }
1995       
1996       foreach ($requirements as $value) {
1997         switch ($type) {
1998           case 'version':
1999             if (preg_match('/^(lt|le|gt|ge|==|=|eq|!=|ne)?\s*(\d*[beta|alpha|rc|RC]?\d+(\.[^\.]+)*)$/i', $value, $matches)) {
2000               // matches[1] = operator, [2] = version
2001               $installed_ver = getversion();
2002               $operator = (!empty($matches[1]) ? $matches[1] : 'ge'); // default to >=
2003               $compare_ver = $matches[2];
2004               if (version_compare_freepbx($installed_ver, $compare_ver, $operator) ) {
2005                 // version is good
2006               } else {
2007                 $errors[] = _module_comparison_error_message('FreePBX', $compare_ver, $installed_ver, $operator);
2008               }
2009             }
2010           break;
2011           case 'module':
2012             // Modify to allow versions such as 2.3.0beta1.2
2013             if (preg_match('/^([a-z0-9_]+)(\s+(lt|le|gt|ge|==|=|eq|!=|ne)?\s*(\d+(\.\d*[beta|alpha|rc|RC]*\d+)+))?$/i', $value, $matches)) {
2014               // matches[1] = modulename, [3]=comparison operator, [4] = version
2015               $modules = module_getinfo($matches[1]);
2016               if (isset($modules[$matches[1]])) {
2017                 $needed_module = "<strong>".(isset($modules[$matches[1]]['name'])?$modules[$matches[1]]['name']:$matches[1])."</strong>";
2018                 switch ($modules[$matches[1]]['status'] ) {
2019                   case MODULE_STATUS_ENABLED:
2020                     if (!empty($matches[4])) {
2021                       // also doing version checking
2022                       $installed_ver = $modules[$matches[1]]['dbversion'];
2023                       $compare_ver = $matches[4];
2024                       $operator = (!empty($matches[3]) ? $matches[3] : 'ge'); // default to >=
2025                       
2026                       if (version_compare_freepbx($installed_ver, $compare_ver, $operator) ) {
2027                         // version is good
2028                       } else {
2029                         $errors[] = _module_comparison_error_message($needed_module.' module', $compare_ver, $installed_ver, $operator);
2030                       }
2031                     }
2032                   break;
2033                   case MODULE_STATUS_BROKEN:
2034                     $errors[] = sprintf(_('Module %s is required, but yours is broken. You should reinstall '.
2035                                           'it and try again.'), $needed_module);
2036                   break;
2037                   case MODULE_STATUS_DISABLED:
2038                     $errors[] = sprintf(_('Module %s is required, but yours is disabled.'), $needed_module);
2039                   break;
2040                   case MODULE_STATUS_NEEDUPGRADE:
2041                     $errors[] = sprintf(_('Module %s is required, but yours is disabled because it needs to '.
2042                                           'be upgraded. Please upgrade %s first, and then try again.'),
2043                               $needed_module, $needed_module);
2044                   break;
2045                   default:
2046                   case MODULE_STATUS_NOTINSTALLED:
2047                     $errors[] = sprintf(_('Module %s is required, yours is not installed.'), $needed_module);
2048                   break;
2049                 }
2050               } else {
2051                 $errors[] = sprintf(_('Module %s is required.'), $needed_module);
2052               }
2053             }
2054           break;
2055           case 'file': // file exists
2056             // replace embedded amp_conf %VARIABLES% in string
2057             $file = ampconf_string_replace($value);
2058             
2059             if (!file_exists( $file )) {
2060               $errors[] = sprintf(_('File %s must exist.'), $file);
2061             }
2062           break;
2063           case 'engine':
2064             /****************************
2065              *  NOTE: there is special handling for this check. We want to "OR" conditions, instead of
2066              *        "AND"ing like the rest of them.
2067              */
2068             
2069             // we found at least one engine, so mark that we're matching this
2070             $engine_dependency = true;
2071             
2072             if (preg_match('/^([a-z0-9_]+)(\s+(lt|le|gt|ge|==|=|eq|!=|ne)?\s*(\d+(\.[^\.]+)*))?$/i', $value, $matches)) {
2073               // matches[1] = engine, [3]=comparison operator, [4] = version
2074               $operator = (!empty($matches[3]) ? $matches[3] : 'ge'); // default to >=
2075               
2076               $engine = engine_getinfo();
2077               if (($engine['engine'] == $matches[1]) &&
2078                   (empty($matches[4]) || !version_compare($matches[4], $engine['version'], $operator))
2079                  ) {
2080                 
2081                 $engine_matched = true;
2082               } else {
2083                 // add it to the error messages
2084                 if ($matches[4]) {
2085                   // version specified
2086                   $operator_friendly = str_replace(array('gt','ge','lt','le','eq','ne'), array('>','>=','<','<=','=','not ='), $operator);
2087                   $engine_errors[] = $matches[1].' ('.$operator_friendly.' '.$matches[4].')';
2088                 } else {
2089                   // no version
2090                   $engine_errors[] = $matches[1];
2091                 }
2092               }
2093             }
2094           break;
2095         }
2096       }
2097     }
2098     
2099     // special handling for engine
2100     // if we've had at least one engine dependency check, and no engine dependencies matched, we have an error
2101     if ($engine_dependency && !$engine_matched) {
2102     
2103       $engineinfo = engine_getinfo();
2104       $yourengine = $engineinfo['engine'].' '.$engineinfo['version'];
2105       // print it nicely
2106       if (count($engine_errors) == 1) {
2107         $errors[] = sprintf(_('Requires engine %s, you have: %s'),$engine_errors[0],$yourengine);
2108       } else {
2109         $errors[] = sprintf(_('Requires one of the following engines: %s; you have: %s'),implode(', ', $engine_errors),$yourengine);
2110       }
2111     }
2112   }
2113  
2114   if (count($errors) > 0) {
2115     return $errors;
2116   } else {
2117     return true;
2118   }
2119 }
2120
2121 function _module_comparison_error_message($module, $reqversion, $version, $operator) {
2122   switch ($operator) {
2123     case 'lt': case '<':
2124       return sprintf(_('A %s version below %s is required, you have %s'), $module, $reqversion, $version);
2125     break;
2126     case 'le': case '<=';
2127       return sprintf(_('%s version %s or below is required, you have %s'), $module, $reqversion, $version);
2128     break;
2129     case 'gt': case '>';
2130       return sprintf(_('A %s version newer than %s required, you have %s'), $module, $reqversion, $version);
2131     break;
2132     case 'ne': case '!=': case '<>':
2133       return sprintf(_('Your %s version (%s) is incompatible.'), $version, $reqversion);
2134     break;
2135     case 'eq': case '==': case '=':
2136       return sprintf(_('Only %s version %s is compatible, you have %s'), $module, $reqversion, $version);
2137     break;
2138     default:
2139     case 'ge': case '>=':
2140       return sprintf(_('%s version %s or higher is required, you have %s'), $module, $reqversion, $version);
2141   }
2142 }
2143
2144 /** Finds all the enabled modules that depend on a given module
2145  * @param  mixed  The name of the module, or the modulexml Array
2146  * @return array  Array containing the list of modules, or false if no dependencies
2147  */
2148 function module_reversedepends($modulename) {
2149   // check if we were passed a modulexml array, or a string (name)
2150   // ensure $modulename is the name (as a string)
2151   if (is_array($modulename)) {
2152     $modulename = $modulename['rawname'];
2153   }
2154  
2155   $modules = module_getinfo(false, MODULE_STATUS_ENABLED);
2156  
2157   $depends = array();
2158  
2159   foreach (array_keys($modules) as $name) {
2160     if (isset($modules[$name]['depends'])) {
2161       foreach ($modules[$name]['depends'] as $type => $requirements) {
2162         if ($type == 'module') {
2163           // if only a single item, make it an array so we can use the same code as for multiple items
2164           // this is because if there is  <module>a</module><module>b</module>  we will get array('module' => array('a','b'))
2165           if (!is_array($requirements)) {
2166             $requirements = array($requirements);
2167           }
2168           
2169           foreach ($requirements as $value) {
2170             if (preg_match('/^([a-z0-9_]+)(\s+(>=|>|=|<|<=|!=)?\s*(\d(\.\d)*))?$/i', $value, $matches)) {
2171               // matches[1] = modulename, [3]=comparison operator, [4] = version
2172               
2173               // note, we're not checking version here. Normally this function is used when
2174               // uninstalling a module, so it doesn't really matter anyways, and version
2175               // dependency should have already been checked when the module was installed
2176               if ($matches[1] == $modulename) {
2177                 $depends[] = $name;
2178               }
2179             }
2180           }
2181         }
2182       }
2183     }
2184   }
2185  
2186   return (count($depends) > 0) ? $depends : false;
2187 }
2188
2189
2190 /** Enables a module
2191  * @param string    The name of the module to enable
2192  * @param bool      If true, skips status and dependency checks
2193  * @return  mixed   True if succesful, array of error messages if not succesful
2194  */
2195 function module_enable($modulename, $force = false) { // was enableModule
2196   $modules = module_getinfo($modulename);
2197  
2198   if ($modules[$modulename]['status'] == MODULE_STATUS_ENABLED) {
2199     return array(_("Module is already enabled"));
2200   }
2201  
2202   // doesn't make sense to skip this on $force - eg, we can't enable a non-installed or broken module
2203   if ($modules[$modulename]['status'] != MODULE_STATUS_DISABLED) {
2204     return array(_("Module cannot be enabled"));
2205   }
2206  
2207   if (!$force) {
2208     if (($errors = module_checkdepends($modules[$modulename])) !== true) {
2209       return $errors;
2210     }
2211   }
2212  
2213   // disabled (but doesn't needupgrade or need install), and meets dependencies
2214   _module_setenabled($modulename, true);
2215   needreload();
2216   return true;
2217 }
2218
2219 /** Downloads the latest version of a module
2220  * and extracts it to the directory
2221  * @param string    The name of the module to install
2222  * @param bool      If true, skips status and dependency checks
2223  * @param string    The name of a callback function to call with progress updates.
2224                     function($action, $params). Possible actions:
2225                       getinfo: while downloading modules.xml
2226                       downloading: while downloading file; params include 'read' and 'total'
2227                       untar: before untarring
2228                       done: when complete
2229  * @return  mixed   True if succesful, array of error messages if not succesful
2230  */
2231
2232 // was fetchModule
2233 function module_download($modulename, $force = false, $progress_callback = null, $override_svn = false, $override_xml = false) {
2234   global $amp_conf;
2235  
2236   // size of download blocks to fread()
2237   // basically, this controls how often progress_callback is called
2238   $download_chunk_size = 12*1024;
2239  
2240   // invoke progress callback
2241   if (function_exists($progress_callback)) {
2242     $progress_callback('getinfo', array('module'=>$modulename));
2243   }
2244       
2245   $res = module_getonlinexml($modulename, $override_xml);
2246   if ($res == null) {
2247     return array(_("Module not found in repository"));
2248   }
2249
2250   $file = basename($res['location']);
2251   $filename = $amp_conf['AMPWEBROOT']."/admin/modules/_cache/".$file;
2252   // if we're not forcing the download, and a file with the target name exists..
2253   if (!$force && file_exists($filename)) {
2254     // We might already have it! Let's check the MD5.
2255     $filedata = "";
2256     if ( $fh = @ fopen($filename, "r") ) {
2257       while (!feof($fh)) {
2258         $filedata .= fread($fh, 8192);
2259       }
2260       fclose($fh);
2261     }
2262     
2263     if (isset($res['md5sum']) && $res['md5sum'] == md5 ($filedata)) {
2264       // Note, if there's no MD5 information, it will redownload
2265       // every time. Otherwise theres no way to avoid a corrupt
2266       // download
2267       
2268       // invoke progress callback
2269       if (function_exists($progress_callback)) {
2270         $progress_callback('untar', array('module'=>$modulename, 'size'=>filesize($filename)));
2271       }
2272       
2273       /* We will explode the tarball in the cache directory and then once successful, remove the old module before before
2274        * moving the new one over. This way, things like removed files end up being removed instead of laying around
2275        *
2276        * TODO: save old module being replaced, if there is an old one.
2277        */
2278       exec("rm -rf ".$amp_conf['AMPWEBROOT']."/admin/modules/_cache/$modulename", $output, $exitcode);
2279       if ($exitcode != 0) {
2280         return array(sprintf(_('Could not remove %s to install new version'), $amp_conf['AMPWEBROOT'].'/admin/modules/_cache/'.$modulenam));
2281       }
2282       exec("tar zxf ".escapeshellarg($filename)." --directory=".escapeshellarg($amp_conf['AMPWEBROOT'].'/admin/modules/_cache/'), $output, $exitcode);
2283       if ($exitcode != 0) {
2284         return array(sprintf(_('Could not untar %s to %s'), $filename, $amp_conf['AMPWEBROOT'].'/admin/modules/_cache'));
2285       }
2286       exec("rm -rf ".$amp_conf['AMPWEBROOT']."/admin/modules/$modulename", $output, $exitcode);
2287       if ($exitcode != 0) {
2288         return array(sprintf(_('Could not remove old module %s to install new version'), $amp_conf['AMPWEBROOT'].'/admin/modules/'.$modulenam));
2289       }
2290       exec("mv ".$amp_conf['AMPWEBROOT']."/admin/modules/_cache/$modulename ".$amp_conf['AMPWEBROOT']."/admin/modules/$modulename", $output, $exitcode);
2291       if ($exitcode != 0) {
2292         return array(sprintf(_('Could not move %s to %s'), $amp_conf['AMPWEBROOT']."/admin/modules/_cache/$modulename", $amp_conf['AMPWEBROOT'].'/admin/modules/'));
2293       }
2294       
2295       // invoke progress_callback
2296       if (function_exists($progress_callback)) {
2297         $progress_callback('done', array('module'=>$modulename));
2298       }
2299       
2300       return true;
2301     } else {
2302       unlink($filename);
2303     }
2304   }
2305  
2306   if ($override_svn) {
2307     $url = $override_svn.$res['location'];
2308   } else {
2309     $url = "http://mirror.freepbx.org/modules/".$res['location'];
2310   }
2311  
2312   if (!($fp = @fopen($filename,"w"))) {
2313     return array(sprintf(_("Error opening %s for writing"), $filename));
2314   }
2315  
2316   $headers = get_headers_assoc($url);
2317  
2318   $totalread = 0;
2319   // invoke progress_callback
2320   if (function_exists($progress_callback)) {
2321     $progress_callback('downloading', array('module'=>$modulename, 'read'=>$totalread, 'total'=>$headers['content-length']));
2322   }
2323  
2324   if (!$dp = @fopen($url,'r')) {
2325     return array(sprintf(_("Error opening %s for reading"), $url));
2326   }
2327  
2328   $filedata = '';
2329   while (!feof($dp)) {
2330     $data = fread($dp, $download_chunk_size);
2331     $filedata .= $data;
2332     $totalread += strlen($data);
2333     if (function_exists($progress_callback)) {
2334       $progress_callback('downloading', array('module'=>$modulename, 'read'=>$totalread, 'total'=>$headers['content-length']));
2335     }
2336   }
2337   fwrite($fp,$filedata);
2338   fclose($dp);
2339   fclose($fp);
2340  
2341  
2342   if (is_readable($filename) !== TRUE ) {
2343     return array(sprintf(_('Unable to save %s'),$filename));
2344   }
2345  
2346   // Check the MD5 info against what's in the module's XML
2347   if (!isset($res['md5sum']) || empty($res['md5sum'])) {
2348     //echo "<div class=\"error\">"._("Unable to Locate Integrity information for")." {$filename} - "._("Continuing Anyway")."</div>";
2349   } else if ($res['md5sum'] != md5 ($filedata)) {
2350     unlink($filename);
2351     return array(sprintf(_('File Integrity failed for %s - aborting'), $filename));
2352   }
2353  
2354   // invoke progress callback
2355   if (function_exists($progress_callback)) {
2356     $progress_callback('untar', array('module'=>$modulename, 'size'=>filesize($filename)));
2357   }
2358
2359   /* We will explode the tarball in the cache directory and then once successful, remove the old module before before
2360    * moving the new one over. This way, things like removed files end up being removed instead of laying around
2361    *
2362    * TODO: save old module being replaced, if there is an old one.
2363    *
2364    */
2365   exec("rm -rf ".$amp_conf['AMPWEBROOT']."/admin/modules/_cache/$modulename", $output, $exitcode);
2366   if ($exitcode != 0) {
2367     return array(sprintf(_('Could not remove %s to install new version'), $amp_conf['AMPWEBROOT'].'/admin/modules/_cache/'.$modulenam));
2368   }
2369   exec("tar zxf ".escapeshellarg($filename)." --directory=".escapeshellarg($amp_conf['AMPWEBROOT'].'/admin/modules/_cache/'), $output, $exitcode);
2370   if ($exitcode != 0) {
2371     return array(sprintf(_('Could not untar %s to %s'), $filename, $amp_conf['AMPWEBROOT'].'/admin/modules/_cache'));
2372   }
2373   exec("rm -rf ".$amp_conf['AMPWEBROOT']."/admin/modules/$modulename", $output, $exitcode);
2374   if ($exitcode != 0) {
2375     return array(sprintf(_('Could not remove old module %s to install new version'), $amp_conf['AMPWEBROOT'].'/admin/modules/'.$modulenam));
2376   }
2377   exec("mv ".$amp_conf['AMPWEBROOT']."/admin/modules/_cache/$modulename ".$amp_conf['AMPWEBROOT']."/admin/modules/$modulename", $output, $exitcode);
2378   if ($exitcode != 0) {
2379     return array(sprintf(_('Could not move %s to %s'), $amp_conf['AMPWEBROOT']."/admin/modules/_cache/$modulename", $amp_conf['AMPWEBROOT'].'/admin/modules/'));
2380   }
2381
2382   // invoke progress_callback
2383   if (function_exists($progress_callback)) {
2384     $progress_callback('done', array('module'=>$modulename));
2385   }
2386
2387   return true;
2388 }
2389
2390
2391 function module_handleupload($uploaded_file) {
2392   global $amp_conf;
2393   $errors = array();
2394  
2395   if (!isset($uploaded_file['tmp_name']) || !file_exists($uploaded_file['tmp_name'])) {
2396     $errors[] = _("Error finding uploaded file - check your PHP and/or web server configuration");
2397     return $errors;
2398   }
2399  
2400   if (!preg_match('/\.(tar\.gz|tgz)$/', $uploaded_file['name'])) {
2401     $errors[] = _("File must be in tar+gzip (.tgz or .tar.gz) format");
2402     return $errors;
2403   }
2404  
2405   if (!preg_match('/^([A-Za-z][A-Za-z0-9_]+)\-([0-9a-z]+(\.[0-9a-z]+)*)\.(tar\.gz|tgz)$/', $uploaded_file['name'], $matches)) {
2406     $errors[] = _("Filename not in correct format: must be modulename-version.tar.gz (eg. custommodule-0.1.tar.gz)");
2407     return $errors;
2408   } else {
2409     $modulename = $matches[1];
2410     $moduleversion = $matches[2];
2411   }
2412  
2413   $temppath = $amp_conf['AMPWEBROOT'].'/admin/modules/_cache/'.uniqid("upload");
2414   if (! @mkdir($temppath) ) {
2415     return array(sprintf(_("Error creating temporary directory: %s"), $temppath));
2416   }
2417   $filename = $temppath.'/'.$uploaded_file['name'];
2418  
2419   move_uploaded_file($uploaded_file['tmp_name'], $filename);
2420  
2421   exec("tar ztf ".escapeshellarg($filename), $output, $exitcode);
2422   if ($exitcode != 0) {
2423     $errors[] = _("Error untaring uploaded file. Must be a tar+gzip file");
2424     return $errors;
2425   }
2426  
2427   foreach ($output as $line) {
2428     // make sure all lines start with "modulename/"
2429     if (!preg_match('/^'.$modulename.'\//', $line)) {
2430       $errors[] = 'File extracting to invalid location: '.$line;
2431     }
2432   }
2433   if (count($errors)) {
2434     return $errors;
2435   }
2436
2437   /* We will explode the tarball in the cache directory and then once successful, remove the old module before before
2438    * moving the new one over. This way, things like removed files end up being removed instead of laying around
2439    *
2440    * TODO: save old module being replaced, if there is an old one.
2441    *
2442    */
2443   exec("rm -rf ".$amp_conf['AMPWEBROOT']."/admin/modules/_cache/$modulename", $output, $exitcode);
2444   if ($exitcode != 0) {
2445     return array(sprintf(_('Could not remove %s to install new version'), $amp_conf['AMPWEBROOT'].'/admin/modules/_cache/'.$modulenam));
2446   }
2447   exec("tar zxf ".escapeshellarg($filename)." --directory=".escapeshellarg($amp_conf['AMPWEBROOT'].'/admin/modules/_cache/'), $output, $exitcode);
2448   if ($exitcode != 0) {
2449     return array(sprintf(_('Could not untar %s to %s'), $filename, $amp_conf['AMPWEBROOT'].'/admin/modules/_cache'));
2450   }
2451   exec("rm -rf ".$amp_conf['AMPWEBROOT']."/admin/modules/$modulename", $output, $exitcode);
2452   if ($exitcode != 0) {
2453     return array(sprintf(_('Could not remove old module %s to install new version'), $amp_conf['AMPWEBROOT'].'/admin/modules/'.$modulenam));
2454   }
2455   exec("mv ".$amp_conf['AMPWEBROOT']."/admin/modules/_cache/$modulename ".$amp_conf['AMPWEBROOT']."/admin/modules/$modulename", $output, $exitcode);
2456   if ($exitcode != 0) {
2457     return array(sprintf(_('Could not move %s to %s'), $amp_conf['AMPWEBROOT']."/admin/modules/_cache/$modulename", $amp_conf['AMPWEBROOT'].'/admin/modules/'));
2458   }
2459
2460   exec("rm -rf ".$temppath, $output, $exitcode);
2461   if ($exitcode != 0) {
2462     $errors[] = sprintf(_('Error removing temporary directory: %s'), $temppath);
2463   }
2464  
2465   if (count($errors)) {
2466     return $errors;
2467   }
2468  
2469   // finally, module installation is successful
2470   return true;
2471 }
2472
2473
2474 /** Installs or upgrades a module from it's directory
2475  * Checks dependencies, and enables
2476  * @param string   The name of the module to install
2477  * @param bool     If true, skips status and dependency checks
2478  * @return mixed   True if succesful, array of error messages if not succesful
2479  */
2480 function module_install($modulename, $force = false) {
2481   $modules = module_getinfo($modulename);
2482   global $db, $amp_conf;
2483  
2484   // make sure we have a directory, to begin with
2485   $dir = $amp_conf['AMPWEBROOT'].'/admin/modules/'.$modulename;
2486   if (!is_dir($dir)) {
2487     return array(_("Cannot find module"));
2488   }
2489  
2490   // read the module.xml file
2491   $modules = module_getinfo($modulename);
2492   if (!isset($modules[$modulename])) {
2493     return array(_("Could not read module.xml"));
2494   }
2495  
2496   // don't force this bit - we can't install a broken module (missing files)
2497   if ($modules[$modulename]['status'] == MODULE_STATUS_BROKEN) {
2498     return array(_("This module is broken and cannot be installed. You should try to download it again."));
2499   }
2500  
2501   if (!$force) {
2502  
2503     if (!in_array($modules[$modulename]['status'], array(MODULE_STATUS_NOTINSTALLED, MODULE_STATUS_NEEDUPGRADE))) {
2504       //return array(_("This module is already installed."));
2505       // This isn't really an error, we just exit
2506       return true;
2507     }
2508     
2509     // check dependencies
2510     if (is_array($errors = module_checkdepends($modules[$modulename]))) {
2511       return $errors;
2512     }
2513   }
2514  
2515   // run the scripts
2516   if (!_module_runscripts($modulename, 'install')) {
2517     return array(_("Failed to run installation scripts"));
2518   }
2519  
2520   if ($modules[$modulename]['status'] == MODULE_STATUS_NOTINSTALLED) {
2521     // customize INSERT query
2522     switch ($amp_conf["AMPDBENGINE"]) {
2523       case "sqlite":
2524         // to support sqlite2, we are not using autoincrement. we need to find the
2525         // max ID available, and then insert it
2526         $sql = "SELECT max(id) FROM modules;";
2527         $results = $db->getRow($sql);
2528         $new_id = $results[0];
2529         $new_id ++;
2530         $sql = "INSERT INTO modules (id,modulename, version,enabled) values ('".$new_id."','".addslashes($modules[$modulename]['rawname'])."','".addslashes($modules[$modulename]['version'])."','0' );";
2531         break;
2532       
2533       default:
2534         $sql = "INSERT INTO modules (modulename, version, enabled) values ('".addslashes($modules[$modulename]['rawname'])."','".addslashes($modules[$modulename]['version'])."', 1);";
2535       break;
2536     }
2537   } else {
2538     // just need to update the version
2539     $sql = "UPDATE modules SET version='".addslashes($modules[$modulename]['version'])."' WHERE modulename = '".addslashes($modules[$modulename]['rawname'])."'";
2540   }
2541  
2542   // run query
2543   $results = $db->query($sql);
2544   if(DB::IsError($results)) {
2545     return array(sprintf(_("Error updating database. Command was: %s; error was: %s "), $sql, $results->getMessage()));
2546   }
2547  
2548   // module is now installed & enabled
2549
2550   // edit the notification table to list any remaining upgrades available or clear
2551   // it if none are left. It requres a copy of the most recent module_xml to compare
2552   // against the installed modules.
2553   //
2554   $sql = 'SELECT data FROM module_xml WHERE id = "xml"';
2555   $data = sql($sql, "getOne");
2556   $parser = new xml2ModuleArray($data);
2557   $xmlarray = $parser->parseAdvanced($data);
2558   $new_modules = array();
2559   if (count($xmlarray)) {
2560     foreach ($xmlarray['xml']['module'] as $mod) {
2561       $new_modules[$mod['rawname']] = $mod;
2562     }
2563   }
2564   module_upgrade_notifications($new_modules, 'PASSIVE');
2565   needreload();
2566   return true;
2567 }
2568
2569 /** Disable a module, but reqmains installed
2570  * @param string   The name of the module to disable
2571  * @param bool     If true, skips status and dependency checks
2572  * @return mixed   True if succesful, array of error messages if not succesful
2573 */
2574 function module_disable($modulename, $force = false) { // was disableModule
2575   $modules = module_getinfo($modulename);
2576   if (!isset($modules[$modulename])) {
2577     return array(_("Specified module not found"));
2578   }
2579  
2580   if (!$force) {
2581     if ($modules[$modulename]['status'] != MODULE_STATUS_ENABLED) {
2582       return array(_("Module not enabled: cannot disable"));
2583     }
2584     
2585     if ( ($depmods = module_reversedepends($modulename)) !== false) {
2586       return array(_("Cannot disable: The following modules depend on this one: ").implode(',',$depmods));
2587     }
2588   }
2589  
2590   _module_setenabled($modulename, false);
2591   needreload();
2592   return true;
2593 }
2594
2595 /** Uninstall a module, but files remain
2596  * @param string   The name of the module to install
2597  * @param bool     If true, skips status and dependency checks
2598  * @return mixed   True if succesful, array of error messages if not succesful
2599  */
2600 function module_uninstall($modulename, $force = false) {
2601   global $db;
2602  
2603   $modules = module_getinfo($modulename);
2604   if (!isset($modules[$modulename])) {
2605     return array(_("Specified module not found"));
2606   }
2607  
2608   if (!$force) {
2609     if ($modules[$modulename]['status'] == MODULE_STATUS_NOTINSTALLED) {
2610       return array(_("Module not installed: cannot uninstall"));
2611     }
2612     
2613     if ( ($depmods = module_reversedepends($modulename)) !== false) {
2614       return array(_("Cannot disable: The following modules depend on this one: ").implode(',',$depmods));
2615     }
2616   }
2617  
2618   $sql = "DELETE FROM modules WHERE modulename = '".addslashes($modulename)."'";
2619   $results = $db->query($sql);
2620   if(DB::IsError($results)) {
2621     return array(_("Error updating database: ").$results->getMessage());
2622   }
2623  
2624   if (!_module_runscripts($modulename, 'uninstall')) {
2625     return array(_("Failed to run un-installation scripts"));
2626   }
2627  
2628   needreload();
2629   return true;
2630 }
2631
2632 /** Totally deletes a module
2633  * @param string   The name of the module to install
2634  * @param bool     If true, skips status and dependency checks
2635  * @return mixed   True if succesful, array of error messages if not succesful
2636  */
2637 function module_delete($modulename, $force = false) {
2638   global $amp_conf;
2639  
2640   $modules = module_getinfo($modulename);
2641   if (!isset($modules[$modulename])) {
2642     return array(_("Specified module not found"));
2643   }
2644  
2645   if ($modules[$modulename]['status'] != MODULE_STATUS_NOTINSTALLED) {
2646     if (is_array($errors = module_uninstall($modulename, $force))) {
2647       return $errors;
2648     }
2649   }
2650  
2651   // delete module directory
2652   //TODO : do this in pure php
2653   $dir = $amp_conf['AMPWEBROOT'].'/admin/modules/'.$modulename;
2654   if (!is_dir($dir)) {
2655     return array(sprintf(_("Cannot delete directory %s"), $dir));
2656   }
2657   if (strpos($dir,"..") !== false) {
2658     die_freepbx("Security problem, denying delete");
2659   }
2660   exec("rm -r ".escapeshellarg($dir),$output, $exitcode);
2661   if ($exitcode != 0) {
2662     return array(sprintf(_("Error deleting directory %s (code %d)"), $dir, $exitcode));
2663   }
2664  
2665   // uninstall will have called needreload() if necessary
2666   return true;
2667 }
2668
2669
2670 /** Internal use only */
2671 function _module_setenabled($modulename, $enabled) {
2672   global $db;
2673   $sql = 'UPDATE modules SET enabled = '.($enabled ? '1' : '0').' WHERE modulename = "'.addslashes($modulename).'"';
2674   $results = $db->query($sql);
2675   if(DB::IsError($results)) {
2676     die_freepbx($results->getMessage());
2677   }
2678 }
2679
2680 function _module_readxml($modulename) {
2681   global $amp_conf;
2682   $dir = $amp_conf['AMPWEBROOT'].'/admin/modules/'.$modulename;
2683   if (is_dir($dir) && file_exists($dir.'/module.xml')) {
2684     $data = file_get_contents($dir.'/module.xml');
2685     //$parser = new xml2ModuleArray($data);
2686     //$xmlarray = $parser->parseModulesXML($data);
2687     $parser = new xml2Array($data);
2688     $xmlarray = $parser->data;
2689     if (isset($xmlarray['module'])) {
2690       // add a couple fields first
2691       $xmlarray['module']['displayname'] = $xmlarray['module']['name'];
2692       if (isset($xmlarray['module']['menuitems'])) {
2693         
2694         foreach ($xmlarray['module']['menuitems'] as $item=>$displayname) {
2695           $path = '/module/menuitems/'.$item;
2696           
2697           // find category
2698           if (isset($parser->attributes[$path]['category'])) {
2699             $category = $parser->attributes[$path]['category'];
2700           } else if (isset($xmlarray['module']['category'])) {
2701             $category = $xmlarray['module']['category'];
2702           } else {
2703             $category = 'Basic';
2704           }
2705           
2706           // find type
2707           if (isset($parser->attributes[$path]['type'])) {
2708             $type = $parser->attributes[$path]['type'];
2709           } else if (isset($xmlarray['module']['type'])) {
2710             $type = $xmlarray['module']['type'];
2711           } else {
2712             $type = 'setup';
2713           }
2714           
2715           // sort priority
2716           if (isset($parser->attributes[$path]['sort'])) {
2717             // limit to -10 to 10
2718             if ($parser->attributes[$path]['sort'] > 10) {
2719               $sort = 10;
2720             } else if ($parser->attributes[$path]['sort'] < -10) {
2721               $sort = -10;
2722             } else {
2723               $sort = $parser->attributes[$path]['sort'];
2724             }
2725           } else {
2726             $sort = 0;
2727           }
2728
2729           // setup basic items array
2730           $xmlarray['module']['items'][$item] = array(
2731             'name' => $displayname,
2732             'type' => $type,
2733             'category' => $category,
2734             'sort' => $sort,
2735           );
2736           
2737           // add optional values:
2738           $optional_attribs = array(
2739             'href', // custom href
2740             'target', // custom target frame
2741             'display', // display= override
2742             'needsenginedb', // set to true if engine db access required (e.g. astman access)
2743             'needsenginerunning', // set to true if required to run
2744             'access', // set to all if all users should always have access
2745           );
2746           foreach ($optional_attribs as $attrib) {
2747             if (isset($parser->attributes[$path][ $attrib ])) {
2748               $xmlarray['module']['items'][$item][ $attrib ] = $parser->attributes[$path][ $attrib ];
2749             }
2750           }
2751           
2752         }
2753       }
2754       
2755       return $xmlarray['module'];
2756     }
2757   }
2758   return null;
2759 }
2760
2761 // Temporarily copied here, for people that haven't upgraded their
2762 // IVR module..
2763
2764 function modules_getversion($modname) {
2765   return _modules_getversion($modname);
2766 }
2767
2768 // This returns the version of a module
2769 function _modules_getversion($modname) {
2770   global $db;
2771
2772   $sql = "SELECT version FROM modules WHERE modulename = '".addslashes($modname)."'";
2773   $results = $db->getRow($sql,DB_FETCHMODE_ASSOC);
2774   if (isset($results['version']))
2775     return $results['version'];
2776   else
2777     return null;
2778 }
2779
2780 /** Updates the version field in the module table
2781  * Should only be called internally
2782  */
2783 function _modules_setversion($modname, $vers) {
2784   global $db;
2785
2786   return ;
2787 }
2788
2789 /** Run the module install/uninstall scripts
2790  * @param string  The name of the module
2791  * @param string  The action to perform, either 'install' or 'uninstall'
2792  * @return boolean  If the action was succesful
2793  */
2794 function _module_runscripts($modulename, $type) {
2795   global $amp_conf;
2796   $db_engine = $amp_conf["AMPDBENGINE"];
2797  
2798   $moduledir = $amp_conf["AMPWEBROOT"]."/admin/modules/".$modulename;
2799   if (!is_dir($moduledir)) {
2800     return false;
2801   }
2802  
2803   switch ($type) {
2804     case 'install':
2805       // install sql files
2806       if ($db_engine  == "sqlite") {
2807         $sqlfilename = "install.sqlite";
2808       } else {
2809         $sqlfilename = "install.sql";
2810       }
2811       
2812       if (is_file($moduledir.'/'.$sqlfilename)) {
2813         execSQL($moduledir.'/'.$sqlfilename);
2814       }
2815       
2816       // then run .php scripts
2817       _modules_doinclude($moduledir.'/install.php', $modulename);
2818     break;
2819     case 'uninstall':
2820       // run uninstall .php scripts first
2821       _modules_doinclude($moduledir.'/uninstall.php', $modulename);
2822       
2823       if ($db_engine  == "sqlite") {
2824         $sqlfilename = "uninstall.sqlite";
2825       } else {
2826         $sqlfilename = "uninstall.sql";
2827       }
2828       
2829       // then uninstall sql files
2830       if (is_file($moduledir.'/'.$sqlfilename)) {
2831         execSQL($moduledir.'/'.$sqlfilename);
2832       }
2833       
2834     break;
2835     default:
2836       return false;
2837   }
2838  
2839   return true;
2840 }
2841 function _modules_doinclude($filename, $modulename) {
2842   // we provide the following variables to the included file (as well as $filename and $modulename)
2843   global $db, $amp_conf, $asterisk_conf;
2844  
2845   if (file_exists($filename) && is_file($filename)) {
2846     include($filename);
2847   }
2848 }
2849  
2850
2851 /* module_get_annoucements()
2852
2853   Get's any annoucments, security warnings, etc. that may be related to the current freepbx version. Also
2854   transmits a uniqueid to help track the number of installations using the online module admin system.
2855   The uniqueid used is completely anonymous and not trackable.
2856 */
2857 function module_get_annoucements() {
2858   $firstinstall=false;
2859   $type=null;
2860
2861   $sql = "SELECT * FROM module_xml WHERE id = 'installid'";
2862   $result = sql($sql,'getRow',DB_FETCHMODE_ASSOC);
2863
2864   // if not set so this is a first time install
2865   // get a new hash to account for first time install
2866   //
2867   if (!isset($result['data']) || trim($result['data']) == "") {
2868
2869     $firstinstall=true;
2870     $install_hash = _module_generate_unique_id();
2871     $installid = $install_hash['uniqueid'];
2872     $type = $install_hash['type'];
2873
2874     // save the hash so we remeber this is a first time install
2875     //
2876     $data4sql = addslashes($installid);
2877     sql('INSERT INTO module_xml (id,time,data) VALUES ("installid",'.time().',"'.$data4sql.'")');
2878     $data4sql = addslashes($type);
2879     sql('INSERT INTO module_xml (id,time,data) VALUES ("type",'.time().',"'.$data4sql.'")');
2880
2881   // Not a first time so save the queried hash and check if there is a type set
2882   //
2883   } else {
2884     $installid=$result['data'];
2885     $sql = "SELECT * FROM module_xml WHERE id = 'type'";
2886     $result = sql($sql,'getRow',DB_FETCHMODE_ASSOC);
2887
2888     if (isset($result['data']) && trim($result['data']) != "") {
2889       $type=$result['data'];
2890     }
2891   }
2892
2893   // Now we have the id and know if this is a firstime install so we can get the announcement
2894   //
2895   $options = "?installid=".$installid;
2896
2897   if (trim($type) != "") {
2898     $options .= "&type=".$type;
2899   }
2900   if ($firstinstall) {
2901     $options .= "&firstinstall=yes";
2902   }
2903   $engver=engine_getinfo();
2904   if ($engver['engine'] == 'asterisk') {
2905     $options .="&astver=".$engver['version'];
2906   }
2907
2908   $announcement = @ file_get_contents("http://mirror.freepbx.org/version-".getversion().".html".$options);
2909   return $announcement;
2910 }
2911
2912
2913 /* Assumes properly formated input, which is ok since
2914    this is a private function and error checking is done
2915    through proper regex scanning above
2916
2917    Returns: random md5 hash
2918  */
2919 function _module_generate_random_id($type=null, $mac=null) {
2920
2921   if (trim($mac) == "") {
2922     $id['uniqueid'] = md5(mt_rand());
2923   } else {
2924     // MD5 hash of the MAC so it is not identifiable
2925     //
2926     $id['uniqueid'] = md5($mac);
2927   }
2928   $id['type'] = $type;
2929
2930   return $id;
2931 }
2932
2933 /* _module_generate_unique_id
2934
2935   The purpose of this function is to generate a unique id that will try
2936   and regenerate the same unique id on a system if called multiple
2937   times. The id is unique but is not in any way identifable so that
2938   privacy is not compromised.
2939
2940   Returns:
2941
2942   Array: ["uniqueid"] => unique_md5_hash
2943          ["type"]     => type_passed_in
2944  
2945 */
2946 function _module_generate_unique_id($type=null) {
2947
2948   // Array of macs that require identification so we know these are not
2949   // 'real' systems. Either home setups or test environments
2950   //
2951   $ids = array('000C29' => 'vmware',
2952                '000569' => 'vmware',
2953                '00163E' => 'xensource'
2954               );
2955   $mac_address = array();
2956   $chosen_mac = null;
2957
2958   // TODO: put proper path in places for ifconfig, try various locations where it may be if
2959   //       non-0 return code.
2960   //
2961   exec('/sbin/ifconfig',$output, $return);
2962
2963   if ($return != '0') {
2964
2965     // OK try another path
2966     //
2967     exec('ifconfig',$output, $return);
2968
2969     if ($return != '0') {
2970       // No seed available so return with random seed
2971       return _module_generate_random_id($type);
2972     }
2973   }
2974
2975   // parse the output of ifconfig to get list of MACs returned
2976   //
2977   foreach ($output as $str) {
2978     // make sure each line contains a valid MAC and IP address and then
2979     //
2980     if (preg_match("/([0-9A-Fa-f]{2}(:[0-9A-Fa-f]{2}){5})/", $str, $mac)) {
2981       $mac_address[] = strtoupper(preg_replace("/:/","",$mac[0]));
2982     }
2983   }
2984
2985   if (trim($type) == "") {
2986     foreach ($mac_address as $mac) {
2987       $id = substr($mac,0,6);
2988
2989       // If we care about this id, then choose it and set the type
2990       // we only choose the first one we see
2991       //
2992       if (array_key_exists($id,$ids)) {
2993         $chosen_mac = $mac;
2994         $type = $ids[$id];
2995         break;
2996       }
2997     }
2998   }
2999
3000   // Now either we have a chosen_mac, we will use the first mac, or if something went wrong
3001   // and there is nothing in the array (couldn't find a mac) then we will make it purely random
3002   //
3003   if ($type == "vmware" || $type == "xensource") {
3004     // vmware, xensource machines will have repeated macs so make random
3005     return _module_generate_random_id($type);
3006   } else if ($chosen_mac != "") {
3007     return _module_generate_random_id($type, $chosen_mac);
3008   } else if (isset($mac_address[0])) {
3009     return _module_generate_random_id($type, $mac_address[0]);
3010   } else {
3011     return _module_generate_random_id($type);
3012   }
3013 }
3014
3015 function module_run_notification_checks() {
3016   global $db;
3017   $modules_needup = module_getinfo(false, MODULE_STATUS_NEEDUPGRADE);
3018   $modules_broken = module_getinfo(false, MODULE_STATUS_BROKEN);
3019  
3020   $notifications =& notifications::create($db);
3021   if ($cnt = count($modules_needup)) {
3022     $text = (($cnt > 1) ? sprintf(_('You have %s disabled modules'), $cnt) : _('You have a disabled module'));
3023     $desc = _('The following modules are disabled because they need to be upgraded:')."\n".implode(", ",array_keys($modules_needup));
3024     $desc .= "\n\n"._('You should go to the module admin page to fix these.');
3025     $notifications->add_error('freepbx', 'modules_disabled', $text, $desc, '?type=tool&display=modules');
3026   } else {
3027     $notifications->delete('freepbx', 'modules_disabled');
3028   }
3029   if ($cnt = count($modules_broken)) {
3030     $text = (($cnt > 1) ? sprintf(_('You have %s broken modules'), $cnt) : _('You have a broken module'));
3031     $desc = _('The following modules are disabled because they are broken:')."\n".implode(", ",array_keys($modules_broken));
3032     $desc .= "\n\n"._('You should go to the module admin page to fix these.');
3033     $notifications->add_critical('freepbx', 'modules_broken', $text, $desc, '?type=tool&display=modules', false);
3034   } else {
3035     $notifications->delete('freepbx', 'modules_broken');
3036   }
3037 }
3038
3039 /** Log an error to the (database-based) log
3040  * @param  string   The section or script where the error occured
3041  * @param  string   The level/severity of the error. Valid levels: 'error', 'warning', 'debug', 'devel-debug'
3042  * @param  string   The error message
3043  */
3044 function freepbx_log($section, $level, $message) {
3045         global $db;
3046         global $debug; // This is used by retrieve_conf
3047         global $amp_conf;
3048         
3049         if (!isset($amp_conf['AMPDISABLELOG']) || ($amp_conf['AMPDISABLELOG'] != 'true')) {
3050             $sth = $db->prepare("INSERT INTO freepbx_log (time, section, level, message) VALUES (NOW(),?,?,?)");
3051             $db->execute($sth, array($section, $level, $message));
3052         }
3053         if (isset($debug) && ($debug != false))
3054                 print "[DEBUG-$section] ($level) $message\n";
3055 }
3056
3057 /** Abort all output, and redirect the browser's location.
3058  *
3059  * Useful for returning to the user to a GET location immediately after doing
3060  * a successful POST operation. This avoids the "this page was sent via POST, resubmit?"
3061  * message in the users browser, and also overwrites the POST page as a location in
3062  * the browser's URL history (eg, they can't press the back button and end up re-submitting
3063  * the page).
3064  *
3065  * @param string   The url to go to
3066  * @param bool     If execution should stop after the function. Defaults to true
3067  */
3068 function redirect($url, $stop_processing = true) {
3069   // TODO: If I don't call ob_end_clean() then is output buffering still on? Do I need to run it off still?
3070   //       (note ob_end_flush() results in the same php NOTICE so not sure how to turn it off. (?ob_implicit_flush(true)?)
3071   //
3072   @ob_end_clean();
3073   @header('Location: '.$url);
3074   if ($stop_processing) exit;
3075 }
3076
3077 /** Abort all output, and redirect the browser's location using standard
3078  * FreePBX user interface variables. By default, will take POST/GET variables
3079  * 'type' and 'display' and pass them along in the URL.
3080  * Also accepts a variable number of parameters, each being the name of a variable
3081  * to pass on.
3082  *
3083  * For example, calling redirect_standard('extdisplay','test'); will take $_REQUEST['type'],
3084  * $_REQUEST['display'], $_REQUEST['extdisplay'], and $_REQUEST['test'],
3085  * and if any are present, use them to build a GET string (eg, "config.php?type=setup&
3086  * display=somemodule&extdisplay=53&test=yes", which is then passed to redirect() to send the browser
3087  * there.
3088  *
3089  * redirect_standard_continue does exactly the same thing but does NOT abort processing. This
3090  * is used when you wish to do a redirect but there is a possibility of other hooks still needing
3091  * to continue processing. Note that this is used in core when in 'extensions' mode, as both the
3092  * users and devices modules need to hook into it together.
3093  *
3094  * @param string  (optional, variable number) The name of a variable from $_REQUEST to
3095  *                pass on to a GET URL.
3096  *
3097  */
3098 function redirect_standard( /* Note. Read the next line. Varaible No of Paramas */ ) {
3099   $args = func_get_Args();
3100
3101         foreach (array_merge(array('type','display'),$args) as $arg) {
3102                 if (isset($_REQUEST[$arg])) {
3103                         $urlopts[] = $arg.'='.urlencode($_REQUEST[$arg]);
3104                 }
3105         }
3106         $url = $_SERVER['PHP_SELF'].'?'.implode('&',$urlopts);
3107         redirect($url);
3108 }
3109
3110 function redirect_standard_continue( /* Note. Read the next line. Varaible No of Paramas */ ) {
3111   $args = func_get_Args();
3112
3113         foreach (array_merge(array('type','display'),$args) as $arg) {
3114                 if (isset($_REQUEST[$arg])) {
3115                         $urlopts[] = $arg.'='.urlencode($_REQUEST[$arg]);
3116                 }
3117         }
3118         $url = $_SERVER['PHP_SELF'].'?'.implode('&',$urlopts);
3119         redirect($url, false);
3120 }
3121
3122 //This function calls modulename_contexts()
3123 //expects a returned array which minimally includes 'context' => the actual context to include
3124 //can also define 'description' => the display for this context - if undefined will be set to 'context'
3125 //'module' => the display for the section this should be listed under defaults to modlue display (can be used to group subsets within one module)
3126 //'parent' => if including another context automatically includes this one, list the parent context
3127 //'priority' => default sort order for includes range -50 to +50, 0 is default
3128 //'enabled' => can be used to flag a context as disabled and it won't be included, but will not have its settings removed.
3129 //'extension' => can be used to tag with an extension for checkRange($extension)
3130 //'dept' => can be used to tag with a department for checkDept($dept)
3131 //  this defaults to false for disabled modules.
3132 function freepbx_get_contexts() {
3133   $modules = module_getinfo(false, array(MODULE_STATUS_ENABLED, MODULE_STATUS_DISABLED, MODULE_STATUS_NEEDUPGRADE));
3134  
3135   $contexts = array();
3136  
3137   foreach ($modules as $modname => $mod) {
3138                 $funct = strtolower($modname.'_contexts');
3139     if (function_exists($funct)) {
3140       // call the  modulename_contexts() function
3141       $contextArray = $funct();
3142       if (is_array($contextArray)) {
3143         foreach ($contextArray as $con) {
3144           if (isset($con['context'])) {
3145             if (!isset($con['description'])) {
3146               $con['description'] = $con['context'];
3147             }
3148             if (!isset($con['module'])) {
3149               $con['module'] = $mod['displayName'];
3150             }
3151             if (!isset($con['priority'])) {
3152               $con['priority'] = 0;
3153             }
3154             if (!isset($con['parent'])) {
3155               $con['parent'] = '';
3156             }
3157             if (!isset($con['extension'])) {
3158               $con['extension'] = null;
3159             }
3160             if (!isset($con['dept'])) {
3161               $con['dept'] = null;
3162             }
3163             if ($mod['status'] == MODULE_STATUS_ENABLED) {
3164               if (!isset($con['enabled'])) {
3165                 $con['enabled'] = true;
3166               }
3167             } else {
3168               $con['enabled'] = false;
3169             }
3170             $contexts[ $con['context'] ] = $con;
3171           }
3172         }
3173       }
3174     }
3175   }
3176   return $contexts;
3177 }
3178
3179 ?>
Note: See TracBrowser for help on using the browser.