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

Revision 8400, 131.4 kB (checked in by mbrevda, 4 years ago)

re: #3905

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