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

Revision 3572, 75.0 kB (checked in by gregmac, 6 years ago)

Fix engine_getinfo() to do better checking for astman connection (see r3571), added fallback to using exec('asterisk -V')

  • 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 function parse_amportal_conf($filename) {
24   // defaults, if not specified in the file
25   $defaults = array(
26     'AMPDBENGINE' => 'mysql',
27     'AMPDBNAME' => 'asterisk',
28     'AMPENGINE' => 'asterisk',
29     'USECATEGORIES' => true,
30     );
31   // boolean values, will be converted to true/false
32   // "yes", "true", 1 (case-insensitive) will be treated as true, everything else is false
33   $booleans = array('USECATEGORIES');
34
35   $file = file($filename);
36   if (is_array($file)) {
37     foreach ($file as $line) {
38       if (preg_match("/^\s*([a-zA-Z0-9_]+)=([a-zA-Z0-9 .&-@=_<>\"\']+)\s*$/",$line,$matches)) {
39         $conf[ $matches[1] ] = $matches[2];
40       }
41     }
42   } else {
43     die("<h1>Missing or unreadable config file ($filename)...cannot continue</h1>");
44   }
45  
46   // set defaults
47   foreach ($defaults as $key=>$val) {
48     if (!isset($conf[$key]) || $conf[$key] == '') {
49       $conf[$key] = $val;
50     }
51   }
52
53   // evaluate boolean values
54   foreach ($booleans as $key) {
55     $conf[$key] = isset($conf[$key]) && ($conf[$key] === true || strtolower($conf[$key]) == 'true' || $conf[$key] === 1 || $conf[$key] == '1' || strtolower($conf[$key]) == 'yes');
56   }
57
58 /*     
59   if (($amp_conf["AMPDBENGINE"] == "sqlite") && (!isset($amp_conf["AMPDBENGINE"])))
60     $amp_conf["AMPDBFILE"] = "/var/lib/freepbx/freepbx.sqlite";
61 */
62  
63   return $conf;
64 }
65
66 function parse_asterisk_conf($filename) {
67   $file = file($filename);
68   foreach ($file as $line) {
69     if (preg_match("/^\s*([a-zA-Z0-9]+)\s* => \s*(.*)\s*([;#].*)?/",$line,$matches)) {
70       $conf[ $matches[1] ] = $matches[2];
71     }
72   }
73   return $conf;
74 }
75
76 function getAmpAdminUsers() {
77   global $db;
78
79   $sql = "SELECT username FROM ampusers WHERE sections='*'";
80   $results = $db->getAll($sql);
81   if(DB::IsError($results)) {
82      die($results->getMessage());
83   }
84   return $results;
85 }
86
87 function getAmpUser($username) {
88   global $db;
89  
90   $sql = "SELECT username, password, extension_low, extension_high, deptname, sections FROM ampusers WHERE username = '".addslashes($username)."'";
91   $results = $db->getAll($sql);
92   if(DB::IsError($results)) {
93      die($results->getMessage());
94   }
95  
96   if (count($results) > 0) {
97     $user = array();
98     $user["username"] = $results[0][0];
99     $user["password"] = $results[0][1];
100     $user["extension_low"] = $results[0][2];
101     $user["extension_high"] = $results[0][3];
102     $user["deptname"] = $results[0][4];
103     $user["sections"] = explode(";",$results[0][5]);
104     return $user;
105   } else {
106     return false;
107   }
108 }
109
110 class ampuser {
111   var $username;
112   var $_password;
113   var $_extension_high;
114   var $_extension_low;
115   var $_deptname;
116   var $_sections;
117  
118   function ampuser($username) {
119     $this->username = $username;
120     if ($user = getAmpUser($username)) {
121       $this->_password = $user["password"];
122       $this->_extension_high = $user["extension_high"];
123       $this->_extension_low = $user["extension_low"];
124       $this->_deptname = $user["deptname"];
125       $this->_sections = $user["sections"];
126     } else {
127       // user doesn't exist
128       $this->_password = false;
129       $this->_extension_high = "";
130       $this->_extension_low = "";
131       $this->_deptname = "";
132       $this->_sections = array();
133     }
134   }
135  
136   /** Give this user full admin access
137   */
138   function setAdmin() {
139     $this->_extension_high = "";
140     $this->_extension_low = "";
141     $this->_deptname = "";
142     $this->_sections = array("*");
143   }
144  
145   function checkPassword($password) {
146     // strict checking so false will never match
147     return ($this->_password === $password);
148   }
149  
150   function checkSection($section) {
151     // if they have * then it means all sections
152     return in_array("*", $this->_sections) || in_array($section, $this->_sections);
153   }
154 }
155
156 // returns true if extension is within allowed range
157 function checkRange($extension){
158   $low = isset($_SESSION["AMP_user"]->_extension_low)?$_SESSION["AMP_user"]->_extension_low:'';
159   $high = isset($_SESSION["AMP_user"]->_extension_high)?$_SESSION["AMP_user"]->_extension_high:'';
160  
161   if ((($extension >= $low) && ($extension <= $high)) || ($low == '' && $high == ''))
162     return true;
163   else
164     return false;
165 }
166
167 // returns true if department string matches dept for this user
168 function checkDept($dept){
169   $deptname = isset($_SESSION["AMP_user"])?$_SESSION["AMP_user"]->_deptname:null;
170  
171   if ( ($dept == null) || ($dept == $deptname) )
172     return true;
173   else
174     return false;
175 }
176
177 function engine_getinfo() {
178   global $amp_conf;
179   global $astman;
180
181   switch ($amp_conf['AMPENGINE']) {
182     case 'asterisk':
183       if (isset($astman) && $astman->connected()) {
184         //get version
185         $response = $astman->send_request('Command', array('Command'=>'show version'));
186         $verinfo = $response['data'];
187       } else {
188         // could not connect to asterisk manager, try console
189         $verinfo = exec('asterisk -V');
190       }
191       
192       if (preg_match('/Asterisk SVN.+/', $verinfo)) {
193         return array('engine'=>'asterisk', 'version' => '99', 'additional' => '99');
194       }
195       if (preg_match('/Asterisk (\d+(\.\d+)*)(-?(\S*))/', $verinfo, $matches)) {
196         return array('engine'=>'asterisk', 'version' => $matches[1], 'additional' => $matches[4]);
197       }
198
199       return array('engine'=>'ERROR-UNABLE-TO-CONNECT', 'version'=>'0', 'additional' => '0');
200     break;
201   }
202   return array('engine'=>'ERROR-UNSUPPORTED-ENGINE-'.$amp_conf['AMPENGINE'], 'version'=>'0', 'additional' => '0');
203 }
204
205 /* queries database using PEAR.
206 *  $type can be query, getAll, getRow, getCol, getOne, etc
207 *  $fetchmode can be DB_FETCHMODE_ORDERED, DB_FETCHMODE_ASSOC, DB_FETCHMODE_OBJECT
208 *  returns array, unless using getOne
209 */
210 function sql($sql,$type="query",$fetchmode=null) {
211   global $db;
212   $results = $db->$type($sql,$fetchmode);
213   if(DB::IsError($results)) {
214     die($results->getDebugInfo());
215   }
216   return $results;
217 }
218
219 // sql text formatting -- couldn't see that one was available already
220 function sql_formattext($txt) {
221   if (isset($txt)) {
222     $fmt = str_replace("'", "''", $txt);
223     $fmt = "'" . $fmt . "'";
224   } else {
225     $fmt = 'null';
226   }
227
228   return $fmt;
229 }
230
231 //tell application we need to reload asterisk
232 function needreload() {
233   global $db;
234   $sql = "UPDATE admin SET value = 'true' WHERE variable = 'need_reload'";
235   $result = $db->query($sql);
236   if(DB::IsError($result)) {     
237     die($result->getMessage());
238   }
239 }
240
241 function check_reload_needed() {
242   global $db;
243   $sql = "SELECT value FROM admin WHERE variable = 'need_reload'";
244   $row = $db->getRow($sql);
245   if(DB::IsError($row)) {
246     die($row->getMessage());
247   }
248   return ($row[0] == 'true');
249 }
250
251 //get the version number
252 function getversion() {
253   global $db;
254   $sql = "SELECT value FROM admin WHERE variable = 'version'";
255   $results = $db->getRow($sql);
256   if(DB::IsError($results)) {
257     die($results->getMessage());
258   }
259   return $results[0];
260 }
261
262 // draw list for users and devices with paging
263 function drawListMenu($results, $skip, $type, $dispnum, $extdisplay, $description) {
264   // Dirty Fix to get rid of [NEXT/PREV] since I'm not sure what passing skip does and don't want to mess with it.
265   // When someone feels like looking closer at the below, probably should remove the code.
266   // I removed pagination cause of the new scroll box ticket #1415
267   $perpage=20000;
268  
269   $skipped = 0;
270   $index = 0;
271   if ($skip == "") $skip = 0;
272   echo "<ul>\n";
273   echo "\t<li><a ".($extdisplay=='' ? 'class="current"':'')." href=\"config.php?type=".$type."&display=".$dispnum."\">"._("Add")." ".$description."</a></li>\n";
274
275   if (isset($results)) {
276     foreach ($results AS $key=>$result) {
277       if ($index >= $perpage) {
278         $shownext= 1;
279         break;
280       }
281       
282       if ($skipped<$skip && $skip!= 0) {
283         $skipped= $skipped + 1;
284         continue;
285       }
286       
287       $index= $index + 1;
288       echo "\t<li><a".($extdisplay==$result[0] ? ' class="current"':''). " href=\"config.php?type=".$type."&amp;display=".$dispnum."&amp;extdisplay={$result[0]}&amp;skip={$skip}\">{$result[1]} &lt;{$result[0]}&gt;</a></li>\n";
289     }
290   }
291   
292   $prevtag = "";
293   $prevtag_pre = "";
294   if ($skip) {
295      $prevskip= $skip - $perpage;
296      if ($prevskip<0) $prevskip= 0;
297      $prevtag_pre= "<a href='?type=".$type."&amp;display=".$dispnum."&amp;skip=$prevskip'>" .
298       _("[PREVIOUS]") ."</a>";
299      print "\t<li><center>";
300      print "$prevtag_pre";
301      print "</center></li>\n";
302   }
303  
304   if (isset($shownext)) {
305     $nextskip= $skip + $index;
306     if ($prevtag_pre) $prevtag .= " | ";
307     print "\t<li><center>";
308     print "$prevtag <a href='?type=".$type."&amp;display=".$dispnum."&amp;skip=$nextskip'>" .
309       _("[NEXT]") . "</a>";
310     print "</center></li>\n";
311   }
312   echo "</ul>\n";
313 }
314
315 // this function simply makes a connection to the asterisk manager, and should be called by modules that require it (ie: dbput/dbget)
316 function checkAstMan() {
317   global $amp_conf;
318   global $astman;
319
320   if ($astman) {
321 //    TODO old code was,
322 //    return $astman->disconnect();
323 //    is this correct...?
324     return true;
325   } else {
326     echo "<h3>Cannot connect to Asterisk Manager with ".$amp_conf["AMPMGRUSER"]."/".$amp_conf["AMPMGRPASS"]."</h3>This module requires access to the Asterisk Manager.  Please ensure Asterisk is running and access to the manager is available.</div>";
327     exit;
328   }
329 }
330
331
332 /** Recursively read voicemail.conf (and any included files)
333  * This function is called by getVoicemailConf()
334  */
335 function parse_voicemailconf($filename, &$vmconf, &$section) {
336   if (is_null($vmconf)) {
337     $vmconf = array();
338   }
339   if (is_null($section)) {
340     $section = "general";
341   }
342  
343   if (file_exists($filename)) {
344     $fd = fopen($filename, "r");
345     while ($line = fgets($fd, 1024)) {
346       if (preg_match("/^\s*(\d+)\s*=>\s*(\d*),(.*),(.*),(.*),(.*)\s*([;#].*)?/",$line,$matches)) {
347         // "mailbox=>password,name,email,pager,options"
348         // this is a voicemail line
349         $vmconf[$section][ $matches[1] ] = array("mailbox"=>$matches[1],
350                   "pwd"=>$matches[2],
351                   "name"=>$matches[3],
352                   "email"=>$matches[4],
353                   "pager"=>$matches[5],
354                   "options"=>array(),
355                   );
356                 
357         // parse options
358         //output($matches);
359         foreach (explode("|",$matches[6]) as $opt) {
360           $temp = explode("=",$opt);
361           //output($temp);
362           if (isset($temp[1])) {
363             list($key,$value) = $temp;
364             $vmconf[$section][ $matches[1] ]["options"][$key] = $value;
365           }
366         }
367       } else if (preg_match("/^\s*(\d+)\s*=>\s*dup,(.*)\s*([;#].*)?/",$line,$matches)) {
368         // "mailbox=>dup,name"
369         // duplace name line
370         $vmconf[$section][ $matches[1] ]["dups"][] = $matches[2];
371       } else if (preg_match("/^\s*#include\s+(.*)\s*([;#].*)?/",$line,$matches)) {
372         // include another file
373         
374         if ($matches[1][0] == "/") {
375           // absolute path
376           $filename = $matches[1];
377         } else {
378           // relative path
379           $filename =  dirname($filename)."/".$matches[1];
380         }
381         
382         parse_voicemailconf($filename, $vmconf, $section);
383         
384       } else if (preg_match("/^\s*\[(.+)\]/",$line,$matches)) {
385         // section name
386         $section = strtolower($matches[1]);
387       } else if (preg_match("/^\s*([a-zA-Z0-9-_]+)\s*=\s*(.*?)\s*([;#].*)?$/",$line,$matches)) {
388         // name = value
389         // option line
390         $vmconf[$section][ $matches[1] ] = $matches[2];
391       }
392     }
393     fclose($fd);
394   }
395 }
396
397 /** Write the voicemail.conf file
398  * This is called by saveVoicemail()
399  * It's important to make a copy of $vmconf before passing it. Since this is a recursive function, has to
400  * pass by reference. At the same time, it removes entries as it writes them to the file, so if you don't have
401  * a copy, by the time it's done $vmconf will be empty.
402 */
403 function write_voicemailconf($filename, &$vmconf, &$section, $iteration = 0) {
404   if ($iteration == 0) {
405     $section = null;
406   }
407  
408   $output = array();
409     
410   // if the file does not, copy if from the template.
411   // TODO: is this logical?
412   // TODO: don't use hardcoded path...?
413   if (!file_exists($filename)) {
414     if (!copy( "/etc/asterisk/voicemail.conf.template", $filename )){
415       return;
416     }
417   }
418  
419     $fd = fopen($filename, "r");
420     while ($line = fgets($fd, 1024)) {
421       if (preg_match("/^(\s*)(\d+)(\s*)=>(\s*)(\d*),(.*),(.*),(.*),(.*)(\s*[;#].*)?$/",$line,$matches)) {
422         // "mailbox=>password,name,email,pager,options"
423         // this is a voicemail line
424         //DEBUG echo "\nmailbox";
425         
426         // make sure we have something as a comment
427         if (!isset($matches[10])) {
428           $matches[10] = "";
429         }
430         
431         // $matches[1] [3] and [4] are to preserve indents/whitespace, we add these back in
432         
433         if (isset($vmconf[$section][ $matches[2] ])) { 
434           // we have this one loaded
435           // repopulate from our version
436           $temp = & $vmconf[$section][ $matches[2] ];
437           
438           $options = array();
439           foreach ($temp["options"] as $key=>$value) {
440             $options[] = $key."=".$value;
441           }
442           
443           $output[] = $matches[1].$temp["mailbox"].$matches[3]."=>".$matches[4].$temp["pwd"].",".$temp["name"].",".$temp["email"].",".$temp["pager"].",". implode("|",$options).$matches[10];
444           
445           // remove this one from $vmconf
446           unset($vmconf[$section][ $matches[2] ]);
447         } else {
448           // we don't know about this mailbox, so it must be deleted
449           // (and hopefully not JUST added since we did read_voiceamilconf)
450           
451           // do nothing
452         }
453         
454       } else if (preg_match("/^(\s*)(\d+)(\s*)=>(\s*)dup,(.*)(\s*[;#].*)?$/",$line,$matches)) {
455         // "mailbox=>dup,name"
456         // duplace name line
457         // leave it as-is (for now)
458         //DEBUG echo "\ndup mailbox";
459         $output[] = $line;
460       } else if (preg_match("/^(\s*)#include(\s+)(.*)(\s*[;#].*)?$/",$line,$matches)) {
461         // include another file
462         //DEBUG echo "\ninclude ".$matches[3]."<blockquote>";
463         
464         // make sure we have something as a comment
465         if (!isset($matches[4])) {
466           $matches[4] = "";
467         }
468         
469         if ($matches[3][0] == "/") {
470           // absolute path
471           $include_filename = $matches[3];
472         } else {
473           // relative path
474           $include_filename =  dirname($filename)."/".$matches[3];
475         }
476         
477         $output[] = $matches[1]."#include".$matches[2].$matches[3].$matches[4];
478         write_voicemailconf($include_filename, $vmconf, $section, $iteration+1);
479         
480         //DEBUG echo "</blockquote>";
481         
482       } else if (preg_match("/^(\s*)\[(.+)\](\s*[;#].*)?$/",$line,$matches)) {
483         // section name
484         //DEBUG echo "\nsection";
485         
486         // make sure we have something as a comment
487         if (!isset($matches[3])) {
488           $matches[3] = "";
489         }
490         
491         // check if this is the first run (section is null)
492         if ($section !== null) {
493           // we need to add any new entries here, before the section changes
494           //DEBUG echo "<blockquote><i>";
495           //DEBUG var_dump($vmconf[$section]);
496           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
497             foreach ($vmconf[$section] as $key=>$value) {
498               if (is_array($value)) {
499                 // mailbox line
500                 
501                 $temp = & $vmconf[$section][ $key ];
502                 
503                 $options = array();
504                 foreach ($temp["options"] as $key1=>$value) {
505                   $options[] = $key1."=".$value;
506                 }
507                 
508                 $output[] = $temp["mailbox"]." => ".$temp["pwd"].",".$temp["name"].",".$temp["email"].",".$temp["pager"].",". implode("|",$options);
509                 
510                 // remove this one from $vmconf
511                 unset($vmconf[$section][ $key ]);
512                 
513               } else {
514                 // option line
515                 
516                 $output[] = $key."=".$vmconf[$section][ $key ];
517                 
518                 // remove this one from $vmconf
519                 unset($vmconf[$section][ $key ]);
520               }
521             }
522           }
523           //DEBUG echo "</i></blockquote>";
524         }
525         
526         $section = strtolower($matches[2]);
527         $output[] = $matches[1]."[".$section."]".$matches[3];
528         $existing_sections[] = $section; //remember that this section exists
529
530       } else if (preg_match("/^(\s*)([a-zA-Z0-9-_]+)(\s*)=(\s*)(.*?)(\s*[;#].*)?$/",$line,$matches)) {
531         // name = value
532         // option line
533         //DEBUG echo "\noption line";
534         
535         
536         // make sure we have something as a comment
537         if (!isset($matches[6])) {
538           $matches[6] = "";
539         }
540         
541         if (isset($vmconf[$section][ $matches[2] ])) {
542           $output[] = $matches[1].$matches[2].$matches[3]."=".$matches[4].$vmconf[$section][ $matches[2] ].$matches[6];
543           
544           // remove this one from $vmconf
545           unset($vmconf[$section][ $matches[2] ]);
546         }
547         // else it's been deleted, so we don't write it in
548         
549       } else {
550         // unknown other line -- probably a comment or whitespace
551         //DEBUG echo "\nother: ".$line;
552         
553         $output[] = str_replace(array("\n","\r"),"",$line); // str_replace so we don't double-space
554       }
555     }
556     
557     if (($iteration == 0) && (is_array($vmconf))) {
558       // we need to add any new entries here, since it's the end of the file
559       //DEBUG echo "END OF FILE!! <blockquote><i>";
560       //DEBUG var_dump($vmconf);
561       foreach (array_keys($vmconf) as $section) {
562         if (!in_array($section,$existing_sections))  // If this is a new section, write the context label
563           $output[] = "[".$section."]";
564         foreach ($vmconf[$section] as $key=>$value) {
565           if (is_array($value)) {
566             // mailbox line
567             
568             $temp = & $vmconf[$section][ $key ];
569             
570             $options = array();
571             foreach ($temp["options"] as $key=>$value) {
572               $options[] = $key."=".$value;
573             }
574             
575             $output[] = $temp["mailbox"]." => ".$temp["pwd"].",".$temp["name"].",".$temp["email"].",".$temp["pager"].",". implode("|",$options);
576             
577             // remove this one from $vmconf
578             unset($vmconf[$section][ $key ]);
579             
580           } else {
581             // option line
582             
583             $output[] = $key."=".$vmconf[$section][ $key ];
584             
585             // remove this one from $vmconf
586             unset($vmconf[$section][$key ]);
587           }
588         }
589       }
590       //DEBUG echo "</i></blockquote>";
591     }
592     
593     fclose($fd);
594     
595     //DEBUG echo "\n\nwriting ".$filename;
596     //DEBUG echo "\n-----------\n";
597     //DEBUG echo implode("\n",$output);
598     //DEBUG echo "\n-----------\n";
599     
600     // write this file back out
601     
602     if ($fd = fopen($filename, "w")) {
603       fwrite($fd, implode("\n",$output)."\n");
604       fclose($fd);
605     }
606     
607 }
608
609
610 // $goto is the current goto destination setting
611 // $i is the destination set number (used when drawing multiple destination sets in a single form ie: digital receptionist)
612 // esnure that any form that includes this calls the setDestinations() javascript function on submit.
613 // ie: if the form name is "edit", and drawselects has been called with $i=2 then use onsubmit="setDestinations(edit,2)"
614 function drawselects($goto,$i) { 
615  
616   /* --- MODULES BEGIN --- */
617   global $active_modules;
618  
619   $all_destinations = array();
620
621   $selectHtml = '<tr><td colspan=2>';
622  
623   //check for module-specific destination functions
624   foreach ($active_modules as $rawmod => $module) {
625     $funct = strtolower($rawmod.'_destinations');
626  
627     //if the modulename_destinations() function exits, run it and display selections for it
628     if (function_exists($funct)) {
629       $destArray = $funct(); //returns an array with 'destination' and 'description', and optionally 'category'
630       if (is_Array($destArray)) {
631         foreach ($destArray as $dest) {
632           $cat = (isset($dest['category']) ? $dest['category'] : $module['displayname']);
633           $all_destinations[$cat][] = $dest;
634         }
635       }
636     }
637   }
638 //  var_dump($all_destinations);
639 //  var_dump($goto);
640
641   $foundone = false;
642   foreach ($all_destinations as $cat=>$destination) {
643     // create a select option for each destination
644     $options = "";
645     $checked = false;
646     foreach ($destination as $dest) {
647       $options .= '<option value="'.$dest['destination'].'" '.(strpos($goto,$dest['destination']) === false ? '' : 'SELECTED').'>'.($dest['description'] ? $dest['description'] : $dest['destination']);
648
649       // check to see if the currently selected goto matches one these destinations
650       if($dest['destination'] == $goto) $checked = true;
651     }
652
653     // make a unique id to be used for the HTML id
654     // This allows us to have multiple drawselect() sets on the page without
655     // conflicting with each other
656     $radioid = uniqid("drawselect");
657     //
658     $cat_identifier = preg_replace('/[^a-zA-Z0-9]/','_', $cat);
659       
660     $selectHtml .=  '<input type="radio" id="'.$radioid.'" name="goto'.$i.'" value="'.$cat_identifier.'" '.
661                     //'onclick="javascript:this.form.goto'.$i.'.value=\''.$cat.'\';" '.
662                     //'onkeypress="javascript:if (event.keyCode == 0 || (document.all && event.keyCode == 13)) this.form.goto'.$i.'.value=\''.$cat.'\';" '.
663                     ($checked? 'CHECKED=CHECKED' : '').' /> ';
664     $selectHtml .= '<label for="'.$radioid.'">'._($cat).':</label> ';
665     
666     // set the
667 //    if ($checked) { $gotomod = $cat; }
668
669     $selectHtml .=  '<select name="'.$cat_identifier.$i.'" onfocus="document.getElementById(\''.$radioid.'\').checked = true; this.form.goto'.$i.'.value=\''.$cat.'\';">';
670     $selectHtml .= $options; 
671     $selectHtml .=  "</select><br>\n";
672
673     if ($checked) $foundone = true;
674   }
675   /* --- MODULES END --- */
676  
677   // This is selected if $foundone is false (and goto is not blank) - basically, a fallback
678   // The ONLY time no radio box is selected is if $goto is empty
679   $custom_selected = !$foundone && !empty($goto);
680  
681   //display a custom goto field
682   $radioid = uniqid("drawselect");
683   $selectHtml .= '<input type="radio" id="'.$radioid.'" name="goto'.$i.'" value="custom" '.
684                  //'onclick="javascript:this.form.goto'.$i.'.value=\'custom\';" '.
685            //'onkeypress="javascript:if (event.keyCode == 0 || (document.all && event.keyCode == 13)) this.form.goto'.$i.'.value=\'custom\';" '.
686            ($custom_selected ? 'CHECKED=CHECKED' : '').' />';
687   $selectHtml .= '<a href="#" class="info"> '._("Custom App<span><br>ADVANCED USERS ONLY<br><br>Uses Goto() to send caller to a custom context.<br><br>The context name should start with \"custom-\", and be in the format custom-context,extension,priority. Example entry:<br><br><b>custom-myapp,s,1</b><br><br>The <b>[custom-myapp]</b> context would need to be created and included in extensions_custom.conf</span>").'</a>:';
688   $selectHtml .= '<input type="text" size="15" name="custom'.$i.'" value="'.($custom_selected ? $goto : '').'" onfocus="document.getElementById(\''.$radioid.'\').checked = true;" />';
689
690   //close off our row
691   $selectHtml .= '</td></tr>';
692  
693   return $selectHtml;
694 }
695
696
697 /* below are legacy functions required to allow pre 2.0 modules to function (ie: interact with 'extensions' table) */
698
699   //add to extensions table - used in callgroups.php
700   function legacy_extensions_add($addarray) {
701     global $db;
702     $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]."')";
703     $result = $db->query($sql);
704     if(DB::IsError($result)) {
705       die($result->getMessage().$sql);
706     }
707     return $result;
708   }
709  
710   //delete extension from extensions table
711   function legacy_extensions_del($context,$exten) {
712     global $db;
713     $sql = "DELETE FROM extensions WHERE context = '".addslashes($context)."' AND `extension` = '".addslashes($exten)."'";
714     $result = $db->query($sql);
715     if(DB::IsError($result)) {
716       die($result->getMessage());
717     }
718     return $result;
719   }
720  
721  
722   //get args for specified exten and priority - primarily used to grab goto destination
723   function legacy_args_get($exten,$priority,$context) {
724     global $db;
725     $sql = "SELECT args FROM extensions WHERE extension = '".addslashes($exten)."' AND priority = '".addslashes($priority)."' AND context = '".addslashes($context)."'";
726     list($args) = $db->getRow($sql);
727     return $args;
728   }
729
730 /* end legacy functions */
731
732 /* Usage
733 Grab some XML data, either from a file, URL, etc. however you want. Assume storage in $strYourXML;
734
735 $xml = new xml2Array($strYourXML);
736 xml array is in $xml->data;
737   This is basically an array version of the XML data (no attributes), striaght-up. If there are
738   multiple items with the same name, they are split into a numeric sub-array,
739   eg, <items><item test="123">foo</item><item>bar</item></items>
740   becomes: array('item' => array(0=>array('item'=>'foo'), 1=>array('item'=>'foo'))
741 attributes are in $xml->attributes;
742   These are stored with xpath type paths, as $xml->attributes['/items/item/0']["test"] == "123"
743  
744
745 Other way (still works, but not as nice):
746
747 $objXML = new xml2Array();
748 $arrOutput = $objXML->parse($strYourXML);
749 print_r($arrOutput); //print it out, or do whatever!
750
751 */
752
753 class xml2Array {
754   var $arrOutput = array();
755   var $resParser;
756   var $strXmlData;
757  
758   var $attributes;
759   var $data;
760  
761   function xml2Array($strInputXML = false) {
762     if (!empty($strInputXML)) {
763       $this->data = $this->parseAdvanced($strInputXML);
764     }
765   }
766  
767   function parse($strInputXML) {
768  
769       $this->resParser = xml_parser_create ();
770       xml_set_object($this->resParser,$this);
771       xml_set_element_handler($this->resParser, "tagOpen", "tagClosed");
772       
773       xml_set_character_data_handler($this->resParser, "tagData");
774     
775       $this->strXmlData = xml_parse($this->resParser,$strInputXML );
776       if(!$this->strXmlData) {
777         die(sprintf("XML error: %s at line %d",
778       xml_error_string(xml_get_error_code($this->resParser)),
779       xml_get_current_line_number($this->resParser)));
780       }
781               
782       xml_parser_free($this->resParser);
783       
784       return $this->arrOutput;
785   }
786   function tagOpen($parser, $name, $attrs) {
787     $tag=array("name"=>$name,"attrs"=>$attrs);
788     array_push($this->arrOutput,$tag);
789   }
790  
791   function tagData($parser, $tagData) {
792     if(trim($tagData)) {
793       if(isset($this->arrOutput[count($this->arrOutput)-1]['tagData'])) {
794         $this->arrOutput[count($this->arrOutput)-1]['tagData'] .= "\n".$tagData;
795       }
796       else {
797         $this->arrOutput[count($this->arrOutput)-1]['tagData'] = $tagData;
798       }
799     }
800   }
801  
802   function tagClosed($parser, $name) {
803     $this->arrOutput[count($this->arrOutput)-2]['children'][] = $this->arrOutput[count($this->arrOutput)-1];
804     array_pop($this->arrOutput);
805   }
806  
807   function recursive_parseLevel($items, &$attrs, $path = "") {
808     $array = array();
809     foreach (array_keys($items) as $idx) {
810       $items[$idx]['name'] = strtolower($items[$idx]['name']);
811       
812       $multi = false;
813       if (isset($array[ $items[$idx]['name'] ])) {
814         // this child is already set, so we're adding multiple items to an array
815         
816         if (!is_array($array[ $items[$idx]['name'] ]) || !isset($array[ $items[$idx]['name'] ][0])) {
817           // hasn't already been made into a numerically-indexed array, so do that now
818           // we're basically moving the current contents of this item into a 1-item array (at the
819           // original location) so that we can add a second item in the code below
820           $array[ $items[$idx]['name'] ] = array( $array[ $items[$idx]['name'] ] );
821
822           if (isset($attrs[ $path.'/'.$items[$idx]['name'] ])) {
823             // move the attributes to /0
824             $attrs[ $path.'/'.$items[$idx]['name'].'/0' ] = $attrs[ $path.'/'.$items[$idx]['name'] ];
825             unset($attrs[ $path.'/'.$items[$idx]['name'] ]);
826           }
827         }
828         $multi = true;
829       }
830       
831       if ($multi) {
832         $newitem = &$array[ $items[$idx]['name'] ][];
833       } else {
834         $newitem = &$array[ $items[$idx]['name'] ];
835       }
836       
837       
838       if (isset($items[$idx]['children']) && is_array($items[$idx]['children'])) {
839         $newitem = $this->recursive_parseLevel($items[$idx]['children'], $attrs, $path.'/'.$items[$idx]['name']);
840       } else if (isset($items[$idx]['tagData'])) {
841         $newitem = $items[$idx]['tagData'];
842       } else {
843         $newitem = false;
844       }
845       
846       if (isset($items[$idx]['attrs']) && is_array($items[$idx]['attrs']) && count($items[$idx]['attrs'])) {
847         $attrpath = $path.'/'.$items[$idx]['name'];
848         if ($multi) {
849           $attrpath .= '/'.(count($array[ $items[$idx]['name'] ])-1);
850         }
851         foreach ($items[$idx]['attrs'] as $name=>$value) {
852           $attrs[ $attrpath ][ strtolower($name) ] = $value;
853         }
854       }
855     }
856     return $array;
857   }
858  
859   function parseAdvanced($strInputXML) {
860     $array = $this->parse($strInputXML);
861     $this->attributes = array();
862     return $this->data = $this->recursive_parseLevel($array, $this->attributes);
863   }
864 }
865
866
867 /*
868   Return a much more manageable assoc array with module data.
869 */
870 class xml2ModuleArray extends xml2Array {
871   function parseModulesXML($strInputXML) {
872     $array = $this->parseAdvanced($strInputXML);
873     if (isset($array['xml'])) {
874       foreach ($array['xml'] as $key=>$module) {
875         if ($key == 'module') {
876           // copy the structure verbatim
877           $modules[ $module['name'] ] = $module;
878         }
879       }
880     }
881     
882     // if you are confused about what's happening below, uncomment this why we do it
883     // echo "<pre>"; print_r($arrOutput); echo "</pre>";
884     
885     // ignore the regular xml garbage ([0]['children']) & loop through each module
886     if(!is_array($arrOutput[0]['children'])) return false;
887     foreach($arrOutput[0]['children'] as $module) {
888       if(!is_array($module['children'])) return false;
889       // loop through each modules's tags
890       foreach($module['children'] as $modTags) {
891           if(isset($modTags['children']) && is_array($modTags['children'])) {
892             $$modTags['name'] = $modTags['children'];
893             // loop if there are children (menuitems and requirements)
894             foreach($modTags['children'] as $subTag) {
895               $subTags[strtolower($subTag['name'])] = $subTag['tagData'];
896             }
897             $$modTags['name'] = $subTags;
898             unset($subTags);
899           } else {
900             // create a variable for each tag we find
901             $$modTags['name'] = $modTags['tagData'];
902           }
903
904       }
905       // now build our return array
906       $arrModules[$RAWNAME]['rawname'] = $RAWNAME;    // This has to be set
907       $arrModules[$RAWNAME]['displayName'] = $NAME;    // This has to be set
908       $arrModules[$RAWNAME]['version'] = $VERSION;     // This has to be set
909       $arrModules[$RAWNAME]['type'] = isset($TYPE)?$TYPE:'setup';
910       $arrModules[$RAWNAME]['category'] = isset($CATEGORY)?$CATEGORY:'Unknown';
911       $arrModules[$RAWNAME]['info'] = isset($INFO)?$INFO:'http://www.freepbx.org/wiki/'.$RAWNAME;
912       $arrModules[$RAWNAME]['location'] = isset($LOCATION)?$LOCATION:'local';
913       $arrModules[$RAWNAME]['items'] = isset($MENUITEMS)?$MENUITEMS:null;
914       $arrModules[$RAWNAME]['requirements'] = isset($REQUIREMENTS)?$REQUIREMENTS:null;
915       $arrModules[$RAWNAME]['md5sum'] = isset($MD5SUM)?$MD5SUM:null;
916       //print_r($arrModules);
917       //unset our variables
918       unset($NAME);
919       unset($VERSION);
920       unset($TYPE);
921       unset($CATEGORY);
922       unset($AUTHOR);
923       unset($EMAIL);
924       unset($LOCATION);
925       unset($MENUITEMS);
926       unset($REQUIREMENTS);
927       unset($MD5SUM);
928     }
929     //echo "<pre>"; print_r($arrModules); echo "</pre>";
930
931     return $arrModules;
932   }
933 }
934
935 function get_headers_assoc($url ) {
936   $url_info=parse_url($url);
937   if (isset($url_info['scheme']) && $url_info['scheme'] == 'https') {
938     $port = isset($url_info['port']) ? $url_info['port'] : 443;
939     @$fp=fsockopen('ssl://'.$url_info['host'], $port, $errno, $errstr, 10);
940   } else {
941     $port = isset($url_info['port']) ? $url_info['port'] : 80;
942     @$fp=fsockopen($url_info['host'], $port, $errno, $errstr, 10);
943   }
944   if ($fp) {
945     stream_set_timeout($fp, 10);
946     $head = "HEAD ".@$url_info['path']."?".@$url_info['query'];
947     $head .= " HTTP/1.0\r\nHost: ".@$url_info['host']."\r\n\r\n";
948     fputs($fp, $head);
949     while(!feof($fp)) {
950       if($header=trim(fgets($fp, 1024))) {
951         $sc_pos = strpos($header, ':');
952         if ($sc_pos === false) {
953           $headers['status'] = $header;
954         } else {
955           $label = substr( $header, 0, $sc_pos );
956           $value = substr( $header, $sc_pos+1 );
957           $headers[strtolower($label)] = trim($value);
958         }
959       }
960     }
961     return $headers;
962   } else {
963     return false;
964   }
965 }
966
967   
968
969 class moduleHook {
970   var $hookHtml = '';
971   var $arrHooks = array();
972  
973   function install_hooks($viewing_itemid,$target_module,$target_menuid = '') {
974     global $active_modules;
975     // loop through all active modules
976     foreach($active_modules as $this_module) {
977         // look for requested hooks for $module
978         // ie: findme_hook_extensions()
979         $funct = $this_module['rawname'] . '_hook_' . $target_module;
980         if( function_exists( $funct ) ) {
981           // execute the function, appending the
982           // html output to that of other hooking modules
983           if ($hookReturn = $funct($viewing_itemid,$target_menuid))
984             $this->hookHtml .= $hookReturn;
985           // remember who installed hooks
986           // we need to know this for processing form vars
987           $this->arrHooks[] = $this_module['rawname'];
988         }
989     }
990   }
991  
992   // process the request from the module we hooked
993   function process_hooks($viewing_itemid, $target_module, $target_menuid, $request) {
994     if(is_array($this->arrHooks)) {
995       foreach($this->arrHooks as $hookingMod) {
996         // check if there is a processing function
997         $funct = $hookingMod . '_hookProcess_' . $target_module;
998         if( function_exists( $funct ) ) {
999           $funct($viewing_itemid, $request);
1000         }
1001       }
1002     }
1003   }
1004 }
1005
1006 function execSQL( $file )
1007 {
1008   global $db;
1009   $data = null;
1010  
1011   // run sql script
1012   $fd = fopen( $file ,"r" );
1013  
1014   while (!feof($fd)) {
1015     $data .= fread($fd, 1024);
1016   }
1017   fclose($fd);
1018  
1019   preg_match_all("/((SELECT|INSERT|UPDATE|DELETE|CREATE|DROP).*);\s*\n/Us", $data, $matches);
1020   foreach ($matches[1] as $sql) {
1021     $result = $db->query($sql);
1022     if(DB::IsError($result)) { return false; }
1023   }
1024 }
1025
1026 // Dragged this in from page.modules.php, so it can be used by install_amp.
1027 function runModuleSQL($moddir,$type){
1028   trigger_error("runModuleSQL() is depreciated - please use _module_runscripts(), or preferably module_install() or module_enable() instead", E_USER_WARNING);
1029   _module_runscripts($moddir, $type);
1030 }
1031
1032
1033
1034 /*
1035 // just for testing hooks, i'll delete it later
1036 function queues_hook_core($viewing_itemid, $target_menuid) {
1037   switch ($target_menuid) {
1038     case 'did':
1039       //get the current setting for this display (if any)
1040       $alertinfo = $viewing_itemid;
1041           return '
1042         <tr>
1043           <td><a href="#" class="info">'._("Alert Info").'<span>'._('ALERT_INFO can be used for distinctive ring with SIP devices.').'</span></a>:</td>
1044           <td><input type="text" name="alertinfo" size="10" value="'.(($alertinfo) ? $alertinfo : "") .'"></td>
1045         </tr>
1046       ';
1047     break;
1048     default:
1049       return false;
1050     break;
1051   }
1052 }
1053
1054 function queues_hookProcess_core($viewing_itemid, $request) {
1055   switch ($request['action']) {
1056     case 'edtIncoming':
1057       echo "<h1>HI</h1>";
1058           return '
1059         <tr>
1060           <td><a href="#" class="info">'._("Alert Info").'<span>'._('ALERT_INFO can be used for distinctive ring with SIP devices.').'</span></a>:</td>
1061           <td><input type="text" name="alertinfo" size="10" value="'.(($alertinfo) ? $alertinfo : "") .'"></td>
1062         </tr>
1063       ';
1064     break;
1065     default:
1066       return false;
1067     break;
1068   }
1069 }
1070 */
1071
1072 /** Replaces variables in a string with the values from ampconf
1073  * eg, "%AMPWEBROOT%/admin" => "/var/www/html/admin"
1074  */
1075 function ampconf_string_replace($string) {
1076   global $amp_conf;
1077  
1078   $target = array();
1079   $replace = array();
1080  
1081   foreach ($amp_conf as $key=>$value) {
1082     $target[] = '%'.$key.'%';
1083     $replace[] = $value;
1084   }
1085  
1086   return str_replace($target, $replace, $string);
1087 }
1088
1089 /***********************************************************************************************************
1090                                        Module functions
1091 ************************************************************************************************************/
1092  
1093 /** Get the latest module.xml file for this freePBX version.
1094  * Caches in the database for 5 mintues.
1095  * If $module is specified, only returns the data for that module.
1096  * If the module is not found (or none are available for whatever reason),
1097  * then null is returned.
1098  *
1099  * Sets the global variable $module_getonlinexml_error to true if an error
1100  * occured getting the module from the repository, false if no error occured,
1101  * or null if the repository wasn't checked. Note that this may change in the
1102  * future if we decide we need to return more error codes, but as long as it's
1103  * a php zero-value (false, null, 0, etc) then no error happened.
1104  */
1105 function module_getonlinexml($module = false) { // was getModuleXml()
1106   global $amp_conf;
1107  
1108   global $module_getonlinexml_error;  // okay, yeah, this sucks, but there's no other good way to do it without breaking BC
1109   $module_getonlinexml_error = null;
1110  
1111   //this should be in an upgrade file ... putting here for now.
1112   sql('CREATE TABLE IF NOT EXISTS module_xml (time INT NOT NULL , data BLOB NOT NULL) TYPE = MYISAM ;');
1113  
1114   $result = sql('SELECT * FROM module_xml','getRow',DB_FETCHMODE_ASSOC);
1115   $data = $result['data'];
1116  
1117   // if the epoch in the db is more than 2 hours old, or the xml is less than 100 bytes, then regrab xml
1118   // Changed to 5 minutes while not in release. Change back for released version.
1119   //
1120   // used for debug, time set to 0 to always fall through
1121   // if((time() - $result['time']) > 0 || strlen($result['data']) < 100 ) {
1122   if((time() - $result['time']) > 300 || strlen($result['data']) < 100 ) {
1123     $version = getversion();
1124     // we need to know the freepbx major version we have running (ie: 2.1.2 is 2.1)
1125     preg_match('/(\d+\.\d+)/',$version,$matches);
1126     //echo "the result is ".$matches[1];
1127     if (isset($amp_conf["AMPMODULEXML"])) {
1128       $fn = $amp_conf["AMPMODULEXML"]."modules-".$matches[1].".xml";
1129       // echo "(From amportal.conf)"; //debug
1130     } else {
1131       $fn = "http://mirror.freepbx.org/modules-".$matches[1].".xml";
1132       // echo "(From default)"; //debug
1133     }
1134     //$fn = "/usr/src/freepbx-modules/modules.xml";
1135     $data = @ file_get_contents($fn);
1136     $module_getonlinexml_error = empty($data);
1137     
1138     if (!empty($data)) {
1139       // remove the old xml
1140       sql('DELETE FROM module_xml');
1141       // update the db with the new xml
1142       $data4sql = addslashes($data);
1143       sql('INSERT INTO module_xml (time,data) VALUES ('.time().',"'.$data4sql.'")');
1144     }
1145   }
1146  
1147   if (empty($data)) {
1148     // no data, probably couldn't connect online, and nothing cached
1149     return null;
1150   }
1151  
1152   //echo time() - $result['time'];
1153   $parser = new xml2ModuleArray($data);
1154   $xmlarray = $parser->parseAdvanced($data);
1155   //$modules = $xmlarray['XML']['MODULE'];
1156  
1157   //echo "<hr>Raw XML Data<pre>"; print_r(htmlentities($data)); echo "</pre>";
1158   //echo "<hr>XML2ARRAY<pre>"; print_r($xmlarray); echo "</pre>";
1159  
1160  
1161   if (isset($xmlarray['xml']['module'])) {
1162  
1163     if ($module != false) {
1164       foreach ($xmlarray['xml']['module'] as $mod) {
1165         if ($module == $mod['rawname']) {
1166           return $mod;
1167         }
1168       }
1169       return null;
1170     } else {
1171     
1172     
1173       $modules = array();
1174       foreach ($xmlarray['xml']['module'] as $mod) {
1175         $modules[ $mod['rawname'] ] = $mod;
1176       }
1177       return $modules;
1178     }
1179   }
1180   return null;
1181 }
1182
1183 /** Looks through the modules directory and modules database and returns all available
1184  * information about one or all modules
1185  * @param string  (optional) The module name to query, or false for all module
1186  * @param mixed   (optional) The status(es) to show, using MODULE_STATUS_* constants. Can
1187  *                either be one value, or an array of values.
1188  */
1189 function module_getinfo($module = false, $status = false) {
1190   global $amp_conf, $db;
1191   $modules = array();
1192  
1193   if ($module) {
1194     // get info on only one module
1195     $xml = _module_readxml($module);
1196     if (!is_null($xml)) {
1197       $modules[$module] = $xml;
1198       // if status is anything else, it will be updated below when we read the db
1199       $modules[$module]['status'] = MODULE_STATUS_NOTINSTALLED;
1200     }
1201     
1202     // query to get just this one
1203     $sql = 'SELECT * FROM modules WHERE modulename = "'.$module.'"';
1204   } else {
1205     // get info on all modules
1206     $dir = opendir($amp_conf['AMPWEBROOT'].'/admin/modules');
1207     while ($file = readdir($dir)) {
1208       if (($file != ".") && ($file != "..") && ($file != "CVS") &&
1209           ($file != ".svn") && ($file != "_cache") &&
1210         is_dir($amp_conf['AMPWEBROOT'].'/admin/modules/'.$file)) {
1211         
1212         $xml = _module_readxml($file);
1213         if (!is_null($xml)) {
1214           $modules[$file] = $xml;
1215           // if status is anything else, it will be updated below when we read the db
1216           $modules[$file]['status'] = MODULE_STATUS_NOTINSTALLED;
1217         }
1218       }
1219     }
1220     
1221     // query to get everything
1222     $sql = 'SELECT * FROM modules';
1223   }
1224   // determine details about this module from database
1225   // modulename should match the directory name
1226  
1227   $results = $db->getAll($sql,DB_FETCHMODE_ASSOC);
1228   if(DB::IsError($results)) {
1229     die($results->getMessage());
1230   }
1231  
1232   if (is_array($results)) {
1233     foreach($results as $row) {
1234       if (isset($modules[ $row['modulename'] ])) {
1235         if ($row['enabled'] != 0) {
1236           
1237           // check if file and registered versions are the same
1238           // version_compare returns 0 if no difference
1239           if (version_compare($row['version'], $modules[ $row['modulename'] ]['version']) == 0) {
1240             $modules[ $row['modulename'] ]['status'] = MODULE_STATUS_ENABLED;
1241           } else {
1242             $modules[ $row['modulename'] ]['status'] = MODULE_STATUS_NEEDUPGRADE;
1243           }
1244           
1245         } else {
1246           $modules[ $row['modulename'] ]['status'] = MODULE_STATUS_DISABLED;
1247         }
1248       } else {
1249         // no directory for this db entry
1250         $modules[ $row['modulename'] ]['status'] = MODULE_STATUS_BROKEN;
1251       }
1252       $modules[ $row['modulename'] ]['dbversion'] = $row['version'];
1253     }
1254   }
1255  
1256   if ($status !== false) {
1257     if (!is_array($status)) {
1258       // make a one element array so we can use in_array below
1259       $status = array($status);
1260     }
1261     
1262     foreach (array_keys($modules) as $name) {
1263       if (!in_array($modules[$name]['status'], $status)) {
1264         // not found in the $status array, remove it
1265         unset($modules[$name]);
1266       }
1267     }
1268   }
1269  
1270   return $modules;
1271 }
1272
1273 /** Check if a module meets dependencies.
1274  * @param  mixed  The name of the module, or the modulexml Array
1275  * @return mixed  Returns true if dependencies are met, or an array
1276  *                containing a list of human-readable errors if not.
1277  *                NOTE: you must use strict type checking (===) to test
1278  *                for true, because  array() == true !
1279  */
1280 function module_checkdepends($modulename) {
1281  
1282   // check if we were passed a modulexml array, or a string (name)
1283   // ensure $modulexml is the modules array, and $modulename is the name (as a string)
1284   if (is_array($modulename)) {
1285     $modulexml = $modulename;
1286     $modulename = $modulename['rawname'];
1287   } else {
1288     $modulexml = module_getinfo($modulename);
1289   }
1290  
1291   $errors = array();
1292  
1293   // special handling for engine
1294   $engine_dependency = false; // if we've found ANY engine dependencies to check
1295   $engine_matched = false; // if an engine dependency has matched
1296   $engine_errors = array(); // the error strings for engines
1297  
1298   if (isset($modulexml['depends'])) {
1299     foreach ($modulexml['depends'] as $type => $requirements) {
1300       // if only a single item, make it an array so we can use the same code as for multiple items
1301       // this is because if there is  <module>a</module><module>b</module>  we will get array('module' => array('a','b'))
1302       if (!is_array($requirements)) {
1303         $requirements = array($requirements);
1304       }
1305       
1306       foreach ($requirements as $value) {
1307         switch ($type) {
1308           case 'version':
1309             if (preg_match('/^(lt|le|gt|ge|==|=|eq|!=|ne)?\s*(\d(\.\d)*)$/i', $value, $matches)) {
1310               // matches[1] = operator, [2] = version
1311               $ver = getversion();
1312               $operator = (!empty($matches[1]) ? $matches[1] : 'ge'); // default to >=
1313               if (version_compare($matches[2], $ver, $operator) ) {
1314                 $errors[] = _module_comparison_error_message('FreePBX', $matches[2], $ver, $operator);
1315               }
1316             }
1317           break;
1318           case 'module':
1319             if (preg_match('/^([a-z0-9_]+)(\s+(lt|le|gt|ge|==|=|eq|!=|ne)?\s*(\d(\.\d)*))?$/i', $value, $matches)) {
1320               // matches[1] = modulename, [3]=comparison operator, [4] = version
1321               $modules = module_getinfo($matches[1]);
1322               if (isset($modules[$matches[1]])) {
1323                 switch ($modules[$matches[1]]['status'] ) {
1324                   case MODULE_STATUS_ENABLED:
1325                     if (!empty($matches[4])) {
1326                       // also doing version checking
1327                       $operator = (!empty($matches[3]) ? $matches[3] : 'ge'); // default to >=
1328                       if (version_compare($matches[4], $modules[$matches[1]]['dbversion'], $operator) ) {
1329                         $errors[] = _module_comparison_error_message($matches[1].' module', $matches[4], $modules[$matches[1]]['dbversion'], $operator);
1330                       }
1331                     }
1332                   break;
1333                   case MODULE_STATUS_BROKEN:
1334                     $errors[] = sprintf(_('Module %s is required, but yours is broken. You should reinstall '.
1335                                           'it and try again.'), $matches[1]);
1336                   break;
1337                   case MODULE_STATUS_DISABLED:
1338                     $errors[] = sprintf(_('Module %s is required, but yours is disabled.'), $matches[1]);
1339                   break;
1340                   case MODULE_STATUS_NEEDUPGRADE:
1341                     $errors[] = sprintf(_('Module %s is required, but yours is disabled because it needs to '.
1342                                           'be upgraded. Please upgrade %s first, and then try again.'),
1343                               $matches[1], $matches[1]);
1344                   break;
1345                   default:
1346                   case MODULE_STATUS_NOTINSTALLED:
1347                     $errors[] = sprintf(_('Module %s is required, yours is not installed.'), $matches[1]);
1348                   break;
1349                 }
1350               } else {
1351                 $errors[] = sprintf(_('Module %s is required.'), $matches[1]);
1352               }
1353             }
1354           break;
1355           case 'file': // file exists
1356             // replace embedded amp_conf %VARIABLES% in string
1357             $file = ampconf_string_replace($value);
1358             
1359             if (!file_exists( $file )) {
1360               $errors[] = sprintf(_('File %s must exist.'), $file);
1361             }
1362           break;
1363           case 'engine':
1364             /****************************
1365              *  NOTE: there is special handling for this check. We want to "OR" conditions, instead of
1366              *        "AND"ing like the rest of them.
1367              */
1368             
1369             // we found at least one engine, so mark that we're matching this
1370             $engine_dependency = true;
1371             
1372             if (preg_match('/^([a-z0-9_]+)(\s+(lt|le|gt|ge|==|=|eq|!=|ne)?\s*(\d(\.\d)*))?$/i', $value, $matches)) {
1373               // matches[1] = engine, [3]=comparison operator, [4] = version
1374               $operator = (!empty($matches[3]) ? $matches[3] : 'ge'); // default to >=
1375               
1376               $engine = engine_getinfo();
1377               if (($engine['engine'] == $matches[1]) &&
1378                   (empty($matches[4]) || !version_compare($matches[4], $engine['version'], $operator))
1379                  ) {
1380                 
1381                 $engine_matched = true;
1382               } else {
1383                 // add it to the error messages
1384                 if ($matches[4]) {
1385                   // version specified
1386                   $operator_friendly = str_replace(array('gt','ge','lt','le','eq','ne'), array('>','>=','<','<=','=','not ='), $operator);
1387                   $engine_errors[] = $matches[1].' ('.$operator_friendly.' '.$matches[4].')';
1388                 } else {
1389                   // no version
1390                   $engine_errors[] = $matches[1];
1391                 }
1392               }
1393             }
1394           break;
1395         }
1396       }
1397     }
1398     
1399     // special handling for engine
1400     // if we've had at least one engine dependency check, and no engine dependencies matched, we have an error
1401     if ($engine_dependency && !$engine_matched) {
1402     
1403       $engineinfo = engine_getinfo();
1404       $yourengine = $engineinfo['engine'].' '.$engineinfo['version'];
1405       // print it nicely
1406       if (count($engine_errors) == 1) {
1407         $errors[] = sprintf(_('Requires engine %s, you have: %s'),$engine_errors[0],$yourengine);
1408       } else {
1409         $errors[] = sprintf(_('Requires one of the following engines: %s; you have: %s'),implode(', ', $engine_errors),$yourengine);
1410       }
1411     }
1412   }
1413  
1414   if (count($errors) > 0) {
1415     return $errors;
1416   } else {
1417     return true;
1418   }
1419 }
1420
1421 function _module_comparison_error_message($module, $reqversion, $version, $operator) {
1422   switch ($operator) {
1423     case 'lt': case '<':
1424       return sprintf(_('A %s version below %s is required, you have %s'), $module, $reqversion, $version);
1425     break;
1426     case 'le': case '<=';
1427       return sprintf(_('%s version %s or below is required, you have %s'), $module, $reqversion, $version);
1428     break;
1429     case 'gt': case '>';
1430       return sprintf(_('A %s version newer than %s required, you have %s'), $module, $reqversion, $version);
1431     break;
1432     case 'ne': case '!=': case '<>':
1433       return sprintf(_('Your %s version (%s) is incompatible.'), $version, $reqversion);
1434     break;
1435     case 'eq': case '==': case '=':
1436       return sprintf(_('Only %s version %s is compatible, you have %s'), $module, $reqversion, $version);
1437     break;
1438     default:
1439     case 'ge': case '>=':
1440       return sprintf(_('%s version %s or higher is required, you have %s'), $module, $reqversion, $version);
1441   }
1442 }
1443
1444 /** Finds all the enabled modules that depend on a given module
1445  * @param  mixed  The name of the module, or the modulexml Array
1446  * @return array  Array containing the list of modules, or false if no dependencies
1447  */
1448 function module_reversedepends($modulename) {
1449   // check if we were passed a modulexml array, or a string (name)
1450   // ensure $modulename is the name (as a string)
1451   if (is_array($modulename)) {
1452     $modulename = $modulename['rawname'];
1453   }
1454  
1455   $modules = module_getinfo(false, MODULE_STATUS_ENABLED);
1456  
1457   $depends = array();
1458  
1459   foreach (array_keys($modules) as $name) {
1460     if (isset($modules[$name]['depends'])) {
1461       foreach ($modules[$name]['depends'] as $type => $requirements) {
1462         if ($type == 'module') {
1463           // if only a single item, make it an array so we can use the same code as for multiple items
1464           // this is because if there is  <module>a</module><module>b</module>  we will get array('module' => array('a','b'))
1465           if (!is_array($requirements)) {
1466             $requirements = array($requirements);
1467           }
1468           
1469           foreach ($requirements as $value) {
1470             if (preg_match('/^([a-z0-9_]+)(\s+(>=|>|=|<|<=|!=)?\s*(\d(\.\d)*))?$/i', $value, $matches)) {
1471               // matches[1] = modulename, [3]=comparison operator, [4] = version
1472               
1473               // note, we're not checking version here. Normally this function is used when
1474               // uninstalling a module, so it doesn't really matter anyways, and version
1475               // dependency should have already been checked when the module was installed
1476               if ($matches[1] == $modulename) {
1477                 $depends[] = $name;
1478               }
1479             }
1480           }
1481         }
1482       }
1483     }
1484   }
1485  
1486   return (count($depends) > 0) ? $depends : false;
1487 }
1488
1489
1490 /** Enables a module
1491  * @param string    The name of the module to enable
1492  * @param bool      If true, skips status and dependency checks
1493  * @return  mixed   True if succesful, array of error messages if not succesful
1494  */
1495 function module_enable($modulename, $force = false) { // was enableModule
1496   $modules = module_getinfo($modulename);
1497  
1498   if ($modules[$modulename]['status'] == MODULE_STATUS_ENABLED) {
1499     return array(_("Module is already enabled"));
1500   }
1501  
1502   // doesn't make sense to skip this on $force - eg, we can't enable a non-installed or broken module
1503   if ($modules[$modulename]['status'] != MODULE_STATUS_DISABLED) {
1504     return array(_("Module cannot be enabled"));
1505   }
1506  
1507   if (!$force) {
1508     if (($errors = module_checkdepends($modules[$modulename])) !== true) {
1509       return $errors;
1510     }
1511   }
1512  
1513   // disabled (but doesn't needupgrade or need install), and meets dependencies
1514   _module_setenabled($modulename, true);
1515   needreload();
1516   return true;
1517 }
1518
1519 /** Downloads the latest version of a module
1520  * and extracts it to the directory
1521  * @param string    The name of the module to install
1522  * @param bool      If true, skips status and dependency checks
1523  * @param string    The name of a callback function to call with progress updates.
1524                     function($action, $params). Possible actions:
1525                       getinfo: while downloading modules.xml
1526                       downloading: while downloading file; params include 'read' and 'total'
1527                       untar: before untarring
1528                       done: when complete
1529  * @return  mixed   True if succesful, array of error messages if not succesful
1530  */
1531 function module_download($modulename, $force = false, $progress_callback = null) { // was fetchModule
1532   global $amp_conf;
1533  
1534   // size of download blocks to fread()
1535   // basically, this controls how often progress_callback is called
1536   $download_chunk_size = 12*1024;
1537  
1538   // invoke progress callback
1539   if (function_exists($progress_callback)) {
1540     $progress_callback('getinfo', array('module'=>$modulename));
1541   }
1542       
1543   $res = module_getonlinexml($modulename);
1544   if ($res == null) {
1545     return array(_("Module not found in repository"));
1546   }
1547  
1548   $file = basename($res['location']);
1549   $filename = $amp_conf['AMPWEBROOT']."/admin/modules/_cache/".$file;
1550   // if we're not forcing the download, and a file with the target name exists..
1551   if (!$force && file_exists($filename)) {
1552     // We might already have it! Let's check the MD5.
1553     $filedata = "";
1554     if ( $fh = @ fopen($filename, "r") ) {
1555       while (!feof($fh)) {
1556         $filedata .= fread($fh, 8192);
1557       }
1558       fclose($fh);
1559     }
1560     
1561     if (isset($res['md5sum']) && $res['md5sum'] == md5 ($filedata)) {
1562       // Note, if there's no MD5 information, it will redownload
1563       // every time. Otherwise theres no way to avoid a corrupt
1564       // download
1565       
1566       // invoke progress callback
1567       if (function_exists($progress_callback)) {
1568         $progress_callback('untar', array('module'=>$modulename, 'size'=>filesize($filename)));
1569       }
1570       
1571       exec("tar zxf ".escapeshellarg($filename)." --directory=".escapeshellarg($amp_conf['AMPWEBROOT'].'/admin/modules/'), $output, $exitcode);
1572       if ($exitcode != 0) {
1573         return array(sprintf(_('Could not untar %s to %s'), $filename, $amp_conf['AMPWEBROOT'].'/admin/modules/'));
1574       }
1575       
1576       // invoke progress_callback
1577       if (function_exists($progress_callback)) {
1578         $progress_callback('done', array('module'=>$modulename));
1579       }
1580       
1581       return true;
1582     } else {
1583       unlink($filename);
1584     }
1585   }
1586  
1587   if (isset($amp_conf['AMPMODULESVN'])) {
1588     $url = $amp_conf['AMPMODULESVN'].$res['location'];
1589     // echo "(From amportal.conf)"; // debug
1590   } else {
1591     $url = "http://mirror.freepbx.org/modules/".$res['location'];
1592     // echo "(From default)"; // debug
1593   }
1594  
1595   if (!($fp = @fopen($filename,"w"))) {
1596     return array(sprintf(_("Error opening %s for writing"), $filename));
1597   }
1598  
1599   $headers = get_headers_assoc($url);
1600  
1601   $totalread = 0;
1602   // invoke progress_callback
1603   if (function_exists($progress_callback)) {
1604     $progress_callback('downloading', array('module'=>$modulename, 'read'=>$totalread, 'total'=>$headers['content-length']));
1605   }
1606  
1607   if (!$dp = @fopen($url,'r')) {
1608     return array(sprintf(_("Error opening %s for reading"), $url));
1609   }
1610  
1611   $filedata = '';
1612   while (!feof($dp)) {
1613     $data = fread($dp, $download_chunk_size);
1614     $filedata .= $data;
1615     $totalread += strlen($data);
1616     if (function_exists($progress_callback)) {
1617       $progress_callback('downloading', array('module'=>$modulename, 'read'=>$totalread, 'total'=>$headers['content-length']));
1618     }
1619   }
1620   fwrite($fp,$filedata);
1621   fclose($dp);
1622   fclose($fp);
1623  
1624  
1625   if (is_readable($filename) !== TRUE ) {
1626     return array(sprintf(_('Unable to save %s'),$filename));
1627   }
1628  
1629   // Check the MD5 info against what's in the module's XML
1630   if (!isset($res['md5sum']) || empty($res['md5sum'])) {
1631     //echo "<div class=\"error\">"._("Unable to Locate Integrity information for")." {$filename} - "._("Continuing Anyway")."</div>";
1632   } else if ($res['md5sum'] != md5 ($filedata)) {
1633     unlink($filename);
1634     return array(sprintf(_('File Integrity failed for %s - aborting'), $filename));
1635   }
1636  
1637   // invoke progress callback
1638   if (function_exists($progress_callback)) {
1639     $progress_callback('untar', array('module'=>$modulename, 'size'=>filesize($filename)));
1640   }
1641
1642   exec("tar zxf ".escapeshellarg($filename)." --directory=".escapeshellarg($amp_conf['AMPWEBROOT'].'/admin/modules/'), $output, $exitcode);
1643   if ($exitcode != 0) {
1644     return array(sprintf(_('Could not untar %s to %s'), $filename, $amp_conf['AMPWEBROOT'].'/admin/modules/'));
1645   }
1646  
1647   // invoke progress_callback
1648   if (function_exists($progress_callback)) {
1649     $progress_callback('done', array('module'=>$modulename));
1650   }
1651
1652   return true;
1653 }
1654
1655
1656 function module_handleupload($uploaded_file) {
1657   global $amp_conf;
1658   $errors = array();
1659  
1660   if (!isset($uploaded_file['tmp_name']) || !file_exists($uploaded_file['tmp_name'])) {
1661     $errors[] = _("Error finding uploaded file - check your PHP and/or web server configuration");
1662     return $errors;
1663   }
1664  
1665   if (!preg_match('/\.(tar\.gz|tgz)$/', $uploaded_file['name'])) {
1666     $errors[] = _("File must be in tar+gzip (.tgz or .tar.gz) format");
1667     return $errors;
1668   }
1669  
1670   if (!preg_match('/^([A-Za-z][A-Za-z0-9_]+)\-([0-9a-z]+(\.[0-9a-z]+)*)\.(tar\.gz|tgz)$/', $uploaded_file['name'], $matches)) {
1671     $errors[] = _("Filename not in correct format: must be modulename-version.tar.gz (eg. custommodule-0.1.tar.gz)");
1672     return $errors;
1673   } else {
1674     $modulename = $matches[1];
1675     $moduleversion = $matches[2];
1676   }
1677  
1678   $temppath = $amp_conf['AMPWEBROOT'].'/admin/modules/_cache/'.uniqid("upload");
1679   if (! @mkdir($temppath) ) {
1680     return array(sprintf(_("Error creating temporary directory: %s"), $temppath));
1681   }
1682   $filename = $temppath.'/'.$uploaded_file['name'];
1683  
1684   move_uploaded_file($uploaded_file['tmp_name'], $filename);
1685  
1686   exec("tar ztf ".escapeshellarg($filename), $output, $exitcode);
1687   if ($exitcode != 0) {
1688     $errors[] = _("Error untaring uploaded file. Must be a tar+gzip file");
1689     return $errors;
1690   }
1691  
1692   foreach ($output as $line) {
1693     // make sure all lines start with "modulename/"
1694     if (!preg_match('/^'.$modulename.'\//', $line)) {
1695       $errors[] = 'File extracting to invalid location: '.$line;
1696     }
1697   }
1698   if (count($errors)) {
1699     return $errors;
1700   }
1701  
1702   exec("tar zxf ".escapeshellarg($filename)." --directory=".escapeshellarg($amp_conf['AMPWEBROOT'].'/admin/modules/'), $output, $exitcode);
1703   if ($exitcode != 0) {
1704     $errors[] = _('Error untaring to modules directory.');
1705   }
1706  
1707   exec("rm -rf ".$temppath, $output, $exitcode);
1708   if ($exitcode != 0) {
1709     $errors[] = sprintf(_('Error removing temporary directory: %s'), $temppath);
1710   }
1711  
1712   if (count($errors)) {
1713     return $errors;
1714   }
1715  
1716   // finally, module installation is successful
1717   return true;
1718 }
1719
1720
1721 /** Installs or upgrades a module from it's directory
1722  * Checks dependencies, and enables
1723  * @param string   The name of the module to install
1724  * @param bool     If true, skips status and dependency checks
1725  * @return mixed   True if succesful, array of error messages if not succesful
1726  */
1727 function module_install($modulename, $force = false) {
1728   $modules = module_getinfo($modulename);
1729   global $db, $amp_conf;
1730  
1731   // make sure we have a directory, to begin with
1732   $dir = $amp_conf['AMPWEBROOT'].'/admin/modules/'.$modulename;
1733   if (!is_dir($dir)) {
1734     return array(_("Cannot find module"));
1735   }
1736  
1737   // read the module.xml file
1738   $modules = module_getinfo($modulename);
1739   if (!isset($modules[$modulename])) {
1740     return array(_("Could not read module.xml"));
1741   }
1742  
1743   // don't force this bit - we can't install a broken module (missing files)
1744   if ($modules[$modulename]['status'] == MODULE_STATUS_BROKEN) {
1745     return array(_("This module is broken and cannot be installed. You should try to download it again."));
1746   }
1747  
1748   if (!$force) {
1749  
1750     if (!in_array($modules[$modulename]['status'], array(MODULE_STATUS_NOTINSTALLED, MODULE_STATUS_NEEDUPGRADE))) {
1751       //return array(_("This module is already installed."));
1752       // This isn't really an error, we just exit
1753       return true;
1754     }
1755     
1756     // check dependencies
1757     if (is_array($errors = module_checkdepends($modules[$modulename]))) {
1758       return $errors;
1759     }
1760   }
1761  
1762   // run the scripts
1763   if (!_module_runscripts($modulename, 'install')) {
1764     return array(_("Failed to run installation scripts"));
1765   }
1766  
1767   if ($modules[$modulename]['status'] == MODULE_STATUS_NOTINSTALLED) {
1768     // customize INSERT query
1769     switch ($amp_conf["AMPDBENGINE"]) {
1770       case "sqlite":
1771         // to support sqlite2, we are not using autoincrement. we need to find the
1772         // max ID available, and then insert it
1773         $sql = "SELECT max(id) FROM modules;";
1774         $results = $db->getRow($sql);
1775         $new_id = $results[0];
1776         $new_id ++;
1777         $sql = "INSERT INTO modules (id,modulename, version,enabled) values ('".$new_id."','".addslashes($modules[$modulename]['rawname'])."','".addslashes($modules[$modulename]['version'])."','0' );";
1778         break;
1779       
1780       default:
1781         $sql = "INSERT INTO modules (modulename, version, enabled) values ('".addslashes($modules[$modulename]['rawname'])."','".addslashes($modules[$modulename]['version'])."', 1);";
1782       break;
1783     }
1784   } else {
1785     // just need to update the version
1786     $sql = "UPDATE modules SET version='".addslashes($modules[$modulename]['version'])."' WHERE modulename = '".addslashes($modules[$modulename]['rawname'])."'";
1787   }
1788  
1789   // run query
1790   $results = $db->query($sql);
1791   if(DB::IsError($results)) {
1792     return array(sprintf(_("Error updating database. Command was: %s; error was: %s "), $sql, $results->getMessage()));
1793   }
1794  
1795   // module is now installed & enabled
1796   needreload();
1797   return true;
1798 }
1799
1800 /** Disable a module, but reqmains installed
1801  * @param string   The name of the module to disable
1802  * @param bool     If true, skips status and dependency checks
1803  * @return mixed   True if succesful, array of error messages if not succesful
1804 */
1805 function module_disable($modulename, $force = false) { // was disableModule
1806   $modules = module_getinfo($modulename);
1807   if (!isset($modules[$modulename])) {
1808     return array(_("Specified module not found"));
1809   }
1810  
1811   if (!$force) {
1812     if ($modules[$modulename]['status'] != MODULE_STATUS_ENABLED) {
1813       return array(_("Module not enabled: cannot disable"));
1814     }
1815     
1816     if ( ($depmods = module_reversedepends($modulename)) !== false) {
1817       return array(_("Cannot disable: The following modules depend on this one: ").implode(',',$depmods));
1818     }
1819   }
1820  
1821   _module_setenabled($modulename, false);
1822   needreload();
1823   return true;
1824 }
1825
1826 /** Uninstall a module, but files remain
1827  * @param string   The name of the module to install
1828  * @param bool     If true, skips status and dependency checks
1829  * @return mixed   True if succesful, array of error messages if not succesful
1830  */
1831 function module_uninstall($modulename, $force = false) {
1832   global $db;
1833  
1834   $modules = module_getinfo($modulename);
1835   if (!isset($modules[$modulename])) {
1836     return array(_("Specified module not found"));
1837   }
1838  
1839   if (!$force) {
1840     if ($modules[$modulename]['status'] == MODULE_STATUS_NOTINSTALLED) {
1841       return array(_("Module not installed: cannot uninstall"));
1842     }
1843     
1844     if ( ($depmods = module_reversedepends($modulename)) !== false) {
1845       return array(_("Cannot disable: The following modules depend on this one: ").implode(',',$depmods));
1846     }
1847   }
1848  
1849   $sql = "DELETE FROM modules WHERE modulename = '".addslashes($modulename)."'";
1850   $results = $db->query($sql);
1851   if(DB::IsError($results)) {
1852     return array(_("Error updating database: ").$results->getMessage());
1853   }
1854  
1855   if (!_module_runscripts($modulename, 'uninstall')) {
1856     return array(_("Failed to run un-installation scripts"));
1857   }
1858  
1859   needreload();
1860   return true;
1861 }
1862
1863 /** Totally deletes a module
1864  * @param string   The name of the module to install
1865  * @param bool     If true, skips status and dependency checks
1866  * @return mixed   True if succesful, array of error messages if not succesful
1867  */
1868 function module_delete($modulename, $force = false) {
1869   global $amp_conf;
1870  
1871   $modules = module_getinfo($modulename);
1872   if (!isset($modules[$modulename])) {
1873     return array(_("Specified module not found"));
1874   }
1875  
1876   if ($modules[$modulename]['status'] != MODULE_STATUS_NOTINSTALLED) {
1877     if (is_array($errors = module_uninstall($modulename, $force))) {
1878       return $errors;
1879     }
1880   }
1881  
1882   // delete module directory
1883   //TODO : do this in pure php
1884   $dir = $amp_conf['AMPWEBROOT'].'/admin/modules/'.$modulename;
1885   if (!is_dir($dir)) {
1886     return array(sprintf(_("Cannot delete directory %s"), $dir));
1887   }
1888   if (strpos($dir,"..") !== false) {
1889     die("Security problem, denying delete");
1890   }
1891   exec("rm -r ".escapeshellarg($dir),$output, $exitcode);
1892   if ($exitcode != 0) {
1893     return array(sprintf(_("Error deleting directory %s (code %d)"), $dir, $exitcode));
1894   }
1895  
1896   // uninstall will have called needreload() if necessary
1897   return true;
1898 }
1899
1900
1901 /** Internal use only */
1902 function _module_setenabled($modulename, $enabled) {
1903   global $db;
1904   $sql = 'UPDATE modules SET enabled = '.($enabled ? '1' : '0').' WHERE modulename = "'.addslashes($modulename).'"';
1905   $results = $db->query($sql);
1906   if(DB::IsError($results)) {
1907     die($results->getMessage());
1908   }
1909 }
1910
1911 function _module_readxml($modulename) {
1912   global $amp_conf;
1913   $dir = $amp_conf['AMPWEBROOT'].'/admin/modules/'.$modulename;
1914   if (is_dir($dir) && file_exists($dir.'/module.xml')) {
1915     $data = file_get_contents($dir.'/module.xml');
1916     //$parser = new xml2ModuleArray($data);
1917     //$xmlarray = $parser->parseModulesXML($data);
1918     $parser = new xml2Array($data);
1919     $xmlarray = $parser->data;
1920     if (isset($xmlarray['module'])) {
1921       // add a couple fields first
1922       $xmlarray['module']['displayname'] = $xmlarray['module']['name'];
1923       if (isset($xmlarray['module']['menuitems'])) {
1924         
1925         foreach ($xmlarray['module']['menuitems'] as $item=>$displayname) {
1926           $path = '/module/menuitems/'.$item;
1927           
1928           // find category
1929           if (isset($parser->attributes[$path]['category'])) {
1930             $category = $parser->attributes[$path]['category'];
1931           } else if (isset($xmlarray['module']['category'])) {
1932             $category = $xmlarray['module']['category'];
1933           } else {
1934             $category = 'Basic';
1935           }
1936           
1937           // find type
1938           if (isset($parser->attributes[$path]['type'])) {
1939             $type = $parser->attributes[$path]['type'];
1940           } else if (isset($xmlarray['module']['type'])) {
1941             $type = $xmlarray['module']['type'];
1942           } else {
1943             $type = 'setup';
1944           }
1945           
1946           // sort priority
1947           if (isset($parser->attributes[$path]['sort'])) {
1948             // limit to -10 to 10
1949             if ($parser->attributes[$path]['sort'] > 10) {
1950               $sort = 10;
1951             } else if ($parser->attributes[$path]['sort'] < -10) {
1952               $sort = -10;
1953             } else {
1954               $sort = $parser->attributes[$path]['sort'];
1955             }
1956           } else {
1957             $sort = 0;
1958           }
1959           
1960           // setup basic items array
1961           $xmlarray['module']['items'][$item] = array(
1962             'name' => $displayname,
1963             'type' => $type,
1964             'category' => $category,
1965             'sort' => $sort,
1966           );
1967           
1968           // add optional values:
1969           
1970           // custom href
1971           if (isset($parser->attributes[$path]['href'])) {
1972             $xmlarray['module']['items'][$item]['href'] = $parser->attributes[$path]['href'];
1973           }
1974           
1975           // custom target
1976           if (isset($parser->attributes[$path]['target'])) {
1977             $xmlarray['module']['items'][$item]['target'] = $parser->attributes[$path]['target'];
1978           }
1979           
1980         }
1981       }
1982       
1983       return $xmlarray['module'];
1984     }
1985   }
1986   return null;
1987 }
1988
1989 // Temporarily copied here, for people that haven't upgraded their
1990 // IVR module..
1991
1992 function modules_getversion($modname) {
1993   return _modules_getversion($modname);
1994 }
1995
1996 // This returns the version of a module
1997 function _modules_getversion($modname) {
1998   global $db;
1999
2000   $sql = "SELECT version FROM modules WHERE modulename = '".addslashes($modname)."'";
2001   $results = $db->getRow($sql,DB_FETCHMODE_ASSOC);
2002   if (isset($results['version']))
2003     return $results['version'];
2004   else
2005     return null;
2006 }
2007
2008 /** Updates the version field in the module table
2009  * Should only be called internally
2010  */
2011 function _modules_setversion($modname, $vers) {
2012   global $db;
2013
2014   return ;
2015 }
2016
2017 /** Run the module install/uninstall scripts
2018  * @param string  The name of the module
2019  * @param string  The action to perform, either 'install' or 'uninstall'
2020  * @return boolean  If the action was succesful
2021  */
2022 function _module_runscripts($modulename, $type) {
2023   global $amp_conf;
2024   $db_engine = $amp_conf["AMPDBENGINE"];
2025  
2026   $moduledir = $amp_conf["AMPWEBROOT"]."/admin/modules/".$modulename;
2027   if (!is_dir($moduledir)) {
2028     return false;
2029   }
2030  
2031   switch ($type) {
2032     case 'install':
2033       // install sql files
2034       if ($db_engine  == "sqlite") {
2035         $sqlfilename = "install.sqlite";
2036       } else {
2037         $sqlfilename = "install.sql";
2038       }
2039       
2040       if (is_file($moduledir.'/'.$sqlfilename)) {
2041         execSQL($moduledir.'/'.$sqlfilename);
2042       }
2043       
2044       // then run .php scripts
2045       _modules_doinclude($moduledir.'/install.php', $modulename);
2046     break;
2047     case 'uninstall':
2048       // run uninstall .php scripts first
2049       _modules_doinclude($moduledir.'/uninstall.php', $modulename);
2050       
2051       if ($db_engine  == "sqlite") {
2052         $sqlfilename = "uninstall.sqlite";
2053       } else {
2054         $sqlfilename = "uninstall.sql";
2055       }
2056       
2057       // then uninstall sql files
2058       if (is_file($moduledir.'/'.$sqlfilename)) {
2059         execSQL($moduledir.'/'.$sqlfilename);
2060       }
2061       
2062     break;
2063     default:
2064       return false;
2065   }
2066  
2067   return true;
2068 }
2069 function _modules_doinclude($filename, $modulename) {
2070   // we provide the following variables to the included file (as well as $filename and $modulename)
2071   global $db, $amp_conf, $asterisk_conf;
2072  
2073   if (file_exists($filename) && is_file($filename)) {
2074     include($filename);
2075   }
2076 }
2077  
2078
2079 /*
2080 function installModule($modname,$modversion)
2081 {
2082   global $db;
2083   global $amp_conf;
2084  
2085   switch ($amp_conf["AMPDBENGINE"])
2086   {
2087     case "sqlite":
2088       // to support sqlite2, we are not using autoincrement. we need to find the
2089       // max ID available, and then insert it
2090       $sql = "SELECT max(id) FROM modules;";
2091       $results = $db->getRow($sql);
2092       $new_id = $results[0];
2093       $new_id ++;
2094       $sql = "INSERT INTO modules (id,modulename, version,enabled) values ('{$new_id}','{$modname}','{$modversion}','0' );";
2095       break;
2096     
2097     default:
2098       $sql = "INSERT INTO modules (modulename, version) values ('{$modname}','{$modversion}');";
2099     break;
2100   }
2101
2102   $results = $db->query($sql);
2103   if(DB::IsError($results)) {
2104     die($results->getMessage());
2105   }
2106 }
2107
2108 function uninstallModule($modname) {
2109   global $db;
2110   $sql = "DELETE FROM modules WHERE modulename = '{$modname}'";
2111   $results = $db->query($sql);
2112   if(DB::IsError($results)) {
2113     die($results->getMessage());
2114   }
2115 }
2116
2117 /** downloads a module, and extracts it into the module dir
2118  * /
2119 function module_fetch($name) { // was fetchModule
2120   global $amp_conf;
2121   $res = module_getonlinexml($modulename);
2122   if (!isset($res)) {
2123     echo "<div class=\"error\">"._("Unaware of module")." {$name}</div>";
2124     return false;
2125   }
2126   $file = basename($res['location']);
2127   $filename = $amp_conf['AMPWEBROOT']."/admin/modules/_cache/".$file;
2128   if(file_exists($filename)) {
2129     // We might already have it! Let's check the MD5.
2130     $filedata = "";
2131     $fh = @fopen($filename, "r");
2132     while (!feof($fh)) {
2133       $filedata .= fread($fh, 8192);
2134     }
2135     if (isset($res['md5sum']) && $res['md5sum'] == md5 ($filedata)) {
2136       // Note, if there's no MD5 information, it will redownload
2137       // every time. Otherwise theres no way to avoid a corrupt
2138       // download
2139       
2140       return verifyAndInstall($filename);
2141     } else {
2142       unlink($filename);
2143     }
2144   }
2145   if (isset($amp_conf['AMPMODULESVN'])) {
2146     $url = $amp_conf['AMPMODULESVN'].$res['location'];
2147     // echo "(From amportal.conf)"; // debug
2148   } else {
2149   $url = "http://mirror.freepbx.org/modules/".$res['location'];
2150     // echo "(From default)"; // debug
2151   }
2152   $fp = @fopen($filename,"w");
2153   $filedata = file_get_contents($url);
2154   fwrite($fp,$filedata);
2155   fclose($fp);
2156   if (is_readable($filename) !== TRUE ) {
2157     echo "<div class=\"error\">"._("Unable to save")." {$filename} - Check file/directory permissions</div>";
2158     return false;
2159   }
2160   // Check the MD5 info against what's in the module's XML
2161   if (!isset($res['md5sum']) || empty($res['md5sum'])) {
2162     echo "<div class=\"error\">"._("Unable to Locate Integrity information for")." {$filename} - "._("Continuing Anyway")."</div>";
2163   } elseif ($res['md5sum'] != md5 ($filedata)) {
2164     echo "<div class=\"error\">"._("File Integrity FAILED for")." {$filename} - "._("Aborting")."</div>";
2165     unlink($filename);
2166     return false;
2167   }
2168   // verifyAndInstall does the untar, and will do the signed-package check.
2169   return verifyAndInstall($filename);
2170
2171 }
2172
2173 function upgradeModule($module, $allmods = NULL) {
2174   if($allmods === NULL)
2175     $allmods = find_allmodules();
2176   // the install.php can set this to false if the upgrade fails.
2177   $success = true;
2178   if(is_file("modules/$module/install.php"))
2179     include "modules/$module/install.php";
2180   if ($success) {
2181     sql('UPDATE modules SET version = "'.$allmods[$module]['version'].'" WHERE modulename = "'.$module.'"');
2182     needreload();
2183   }
2184 }
2185
2186 function rmModule($module) {
2187   global $amp_conf;
2188   if($module != 'core') {
2189     if (is_dir($amp_conf['AMPWEBROOT'].'/admin/modules/'.$module) && strstr($module, '.') === FALSE ) {
2190       exec('/bin/rm -rf '.$amp_conf['AMPWEBROOT'].'/admin/modules/'.$module);
2191     }
2192   } else {
2193     echo "<script language=\"Javascript\">alert('"._("You cannot delete the Core module")."');</script>";
2194   }
2195 }
2196
2197 */
2198
2199
2200 /** Log an error to the (database-based) log
2201  * @param  string   The section or script where the error occured
2202  * @param  string   The level/severity of the error. Valid levels: 'error', 'warning', 'debug', 'devel-debug'
2203  * @param  string   The error message
2204  */
2205 function freepbx_log($section, $level, $message) {
2206         global $db;
2207         global $debug; // This is used by retrieve_conf
2208         global $amp_conf;
2209         
2210         if (!isset($amp_conf['AMPDISABLELOG']) || ($amp_conf['AMPDISABLELOG'] != 'true')) {
2211             $sth = $db->prepare("INSERT INTO freepbx_log (time, section, level, message) VALUES (NOW(),?,?,?)");
2212             $db->execute($sth, array($section, $level, $message));
2213         }
2214         if (isset($debug) && ($debug != false))
2215                 print "[DEBUG-$section] ($level) $message\n";
2216 }
2217
2218 /** Abort all output, and redirect the browser's location.
2219  *
2220  * Useful for returning to the user to a GET location immediately after doing
2221  * a successful POST operation. This avoids the "this page was sent via POST, resubmit?"
2222  * message in the users browser, and also overwrites the POST page as a location in
2223  * the browser's URL history (eg, they can't press the back button and end up re-submitting
2224  * the page).
2225  *
2226  * @param string   The url to go to
2227  * @param bool     If execution should stop after the function. Defaults to true
2228  */
2229 function redirect($url, $stop_processing = true) {
2230   ob_end_clean();
2231   header('Location: '.$url);
2232   if ($stop_processing) exit;
2233 }
2234
2235 /** Abort all output, and redirect the browser's location using standard
2236  * freePBX user interface variables. By default, will take POST/GET variables
2237  * 'type' and 'display' and pass them along in the URL.
2238  * Also accepts a variable number of parameters, each being the name of a variable
2239  * to pass on.
2240  *
2241  * For example, calling redirect_standard('extdisplay','test'); will take $_REQUEST['type'],
2242  * $_REQUEST['display'], $_REQUEST['extdisplay'], and $_REQUEST['test'],
2243  * and if any are present, use them to build a GET string (eg, "config.php?type=setup&
2244  * display=somemodule&extdisplay=53&test=yes", which is then passed to redirect() to send the browser
2245  * there.
2246  *
2247  * redirect_standard_continue does exactly the same thing but does NOT abort processing. This
2248  * is used when you wish to do a redirect but there is a possibility of other hooks still needing
2249  * to continue processing. Note that this is used in core when in 'extensions' mode, as both the
2250  * users and devices modules need to hook into it together.
2251  *
2252  * @param string  (optional, variable number) The name of a variable from $_REQUEST to
2253  *                pass on to a GET URL.
2254  *
2255  */
2256 function redirect_standard( /* Note. Read the next line. Varaible No of Paramas */ ) {
2257   $args = func_get_Args();
2258
2259         foreach (array_merge(array('type','display'),$args) as $arg) {
2260                 if (isset($_REQUEST[$arg])) {
2261                         $urlopts[] = $arg.'='.urlencode($_REQUEST[$arg]);
2262                 }
2263         }
2264         $url = $_SERVER['PHP_SELF'].'?'.implode('&',$urlopts);
2265         redirect($url);
2266 }
2267
2268 function redirect_standard_continue( /* Note. Read the next line. Varaible No of Paramas */ ) {
2269   $args = func_get_Args();
2270
2271         foreach (array_merge(array('type','display'),$args) as $arg) {
2272                 if (isset($_REQUEST[$arg])) {
2273                         $urlopts[] = $arg.'='.urlencode($_REQUEST[$arg]);
2274                 }
2275         }
2276         $url = $_SERVER['PHP_SELF'].'?'.implode('&',$urlopts);
2277         redirect($url, false);
2278 }
2279
2280 function freepbx_get_contexts() {
2281   $modules = module_getinfo(false, MODULE_STATUS_ENABLED);
2282  
2283   $contexts = array();
2284  
2285   foreach ($modules as $modname => $mod) {
2286                 $funct = strtolower($modname.'_contexts');
2287     if (function_exists($funct)) {
2288       // call the  modulename_contexts() function
2289       $contextArray = $funct(); // returns array with 'context' and 'description'
2290       if (is_array($contextArray)) {
2291         foreach ($contextArray as $con) {
2292           if (isset($con['context'])) {
2293             if (!isset($con['description'])) {
2294               $con['description'] = $con['context'];
2295             }
2296             if (!isset($con['module'])) {
2297               $con['module'] = $modname;
2298             }
2299             $contexts[ $con['context'] ] = $con['description'];
2300           }
2301         }
2302       }
2303     }
2304   }
2305   return $contexts;
2306 }
2307
2308 ?>
Note: See TracBrowser for help on using the browser.