root/freepbx/branches/2.3/amp_conf/htdocs/admin/functions.inc.php

Revision 4628, 105.3 kB (checked in by p_lindheimer, 6 years ago)

fix notices from ob_flush ob_clean call, fix some undefined index variables in xml library (which points at clear bugs that need to be addressed by adopting a new library), fix some undefined variable notices - in page.modules.php actually breaks javascripts and functionality when notices are turned up

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