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

Revision 11747, 124.8 kB (checked in by p_lindheimer, 1 year ago)

Merged revisions 11746 via svnmerge from
http://www.freepbx.org/v2/svn/freepbx/branches/2.5

................

r11746 | p_lindheimer | 2011-03-08 17:20:03 -0800 (Tue, 08 Mar 2011) | 23 lines


Merged revisions 11745 via svnmerge from
http://www.freepbx.org/v2/svn/freepbx/branches/2.6


................

r11745 | p_lindheimer | 2011-03-08 17:17:27 -0800 (Tue, 08 Mar 2011) | 16 lines


Merged revisions 11744 via svnmerge from
http://www.freepbx.org/v2/svn/freepbx/branches/2.7


................

r11744 | p_lindheimer | 2011-03-08 17:16:29 -0800 (Tue, 08 Mar 2011) | 9 lines


Merged revisions 11743 via svnmerge from
http://www.freepbx.org/v2/svn/freepbx/branches/2.8


........

r11743 | p_lindheimer | 2011-03-08 17:15:03 -0800 (Tue, 08 Mar 2011) | 1 line


distro info from 2.9

........

................

................

................

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