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

Revision 9261, 133.4 kB (checked in by mickecarlsson, 3 years ago)

Small spelling error

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