root/freepbx/branches/2.10/amp_conf/htdocs/admin/libraries/module.functions.php

Revision 14060, 62.5 kB (checked in by p_lindheimer, 1 year ago)

revert accidental checkin of part of r13972 re #5812

Line 
1 <?php
2
3 /***********************************************************************************************************
4                                        Module functions
5 ************************************************************************************************************/
6  
7 /** Get the latest module.xml file for this FreePBX version.
8  * Caches in the database for 5 mintues.
9  * If $module is specified, only returns the data for that module.
10  * If the module is not found (or none are available for whatever reason),
11  * then null is returned.
12  *
13  * Sets the global variable $module_getonlinexml_error to true if an error
14  * occurred getting the module from the repository, false if no error occurred,
15  * or null if the repository wasn't checked. Note that this may change in the
16  * future if we decide we need to return more error codes, but as long as it's
17  * a php zero-value (false, null, 0, etc) then no error happened.
18  */
19 function module_getonlinexml($module = false, $override_xml = false) { // was getModuleXml()
20     global $amp_conf, $db, $module_getonlinexml_error// okay, yeah, this sucks, but there's no other good way to do it without breaking BC
21     $module_getonlinexml_error = null;
22     $got_new = false;
23     $skip_cache = false;
24     
25     $result = sql("SELECT * FROM module_xml WHERE id = 'xml'",'getRow',DB_FETCHMODE_ASSOC);
26     $data = $result['data'];
27
28     // Check if the cached module xml is for the same repo as being requested
29     // if not, then we get it anyhow
30     //
31     $repo_url = ($override_xml === false) ? $amp_conf['MODULE_REPO'] : $override_xml;
32     $result2 = sql("SELECT * FROM module_xml WHERE id = 'module_repo'",'getRow',DB_FETCHMODE_ASSOC);
33     $last_repo = $result2['data'];
34     if ($last_repo !== $repo_url) {
35         sql("DELETE FROM module_xml WHERE id = 'module_repo'");
36         $data4sql = $db->escapeSimple($repo_url);
37         sql("INSERT INTO module_xml (id,time,data) VALUES ('module_repo',".time().",'".$data4sql."')");
38         $skip_cache = true;
39     }
40
41     // if the epoch in the db is more than 2 hours old, or the xml is less than 100 bytes, then regrab xml
42     // Changed to 5 minutes while not in release. Change back for released version.
43     //
44     // used for debug, time set to 0 to always fall through
45     // if((time() - $result['time']) > 0 || strlen($result['data']) < 100 ) {
46   $skip_cache |= $amp_conf['MODULEADMIN_SKIP_CACHE'];
47     $version = getversion();
48     // we need to know the freepbx major version we have running (ie: 2.1.2 is 2.1)
49     preg_match('/(\d+\.\d+)/',$version,$matches);
50     $base_version = $matches[1];
51     if((time() - $result['time']) > 300 || $skip_cache || strlen($data) < 100 ) {
52         if ($override_xml) {
53             $fn = $override_xml."/modules-" . $base_version . ".xml";
54         } else {
55             $fn = generate_module_repo_url("/modules-" . $base_version . ".xml");
56             // echo "(From default)"; //debug
57         }
58         //$fn = "/usr/src/freepbx-modules/modules.xml";
59
60
61     $data = file_get_contents_url($fn);
62     $module_getonlinexml_error = ($data === false)?true:false;
63
64         
65         $old_xml = array();
66         $got_new = false;
67         if (!empty($data)) {
68             // Compare the download to our current XML to see if anything changed for the notification system.
69             //
70             $sql = "SELECT data FROM module_xml WHERE id = 'xml'";
71             $old_xml = sql($sql, "getOne");
72             $got_new = true;
73             // remove the old xml
74             sql("DELETE FROM module_xml WHERE id = 'xml'");
75             // update the db with the new xml
76             $data4sql = $db->escapeSimple($data);
77             sql("INSERT INTO module_xml (id,time,data) VALUES ('xml',".time().",'".$data4sql."')");
78         }
79     }
80     
81     if (empty($data)) {
82         // no data, probably couldn't connect online, and nothing cached
83         return null;
84     }
85     
86     $parser = new xml2ModuleArray($data);
87     $xmlarray = $parser->parseAdvanced($data);
88     
89     if ($got_new) {
90         module_update_notifications($old_xml, $xmlarray, ($old_xml == $data4sql));
91     }
92
93     $exposures = module_get_security($xmlarray, $base_version);
94
95     //dbug("done here is exposure analysis", $exposures);
96     foreach($exposures as $m => $vinfo) {
97         dbug("module $m has these vulnerabilities and version " . $vinfo['minver'] . " is needed you have " . $vinfo['curver'], $vinfo['vul']);
98     }
99     // module_update_security_notifications($exposures);
100
101     if (isset($xmlarray['xml']['module'])) {
102     
103         if ($module != false) {
104             foreach ($xmlarray['xml']['module'] as $mod) {
105                 if ($module == $mod['rawname']) {
106                     return $mod;
107                 }
108             }
109             return null;
110         } else {
111             $modules = array();
112             foreach ($xmlarray['xml']['module'] as $mod) {
113                 $modules[$mod['rawname']] = $mod;
114                 if (isset($exposures[$mod['rawname']])) {
115                     $modules[$mod['rawname']]['vulnerabilities'] = $exposures[$mod['rawname']];
116                 }
117             }
118             return $modules;
119         }
120     }
121     return null;
122 }
123
124 function module_get_security($xmlarray, $base_version=null) {
125
126     if ($base_version === null) {
127         $version = getversion();
128         // we need to know the freepbx major version we have running (ie: 2.1.2 is 2.1)
129         preg_match('/(\d+\.\d+)/',$version,$matches);
130         $base_version = $matches[1];
131     }
132
133     if (!empty($xmlarray['xml']['security'])) {
134         $exposures = array();
135         $modinfo = module_getinfo();
136
137         //foreach ($xmlarray['xml']['security'] as $vul => $sinfo) {
138         foreach ($xmlarray['xml']['security']['issue'] as $sinfo) {
139             $vul = $sinfo['id'];
140             if (!empty($sinfo['versions']['v' . $base_version])) {
141                 //dbug("vulnerability info for $base_version: ", $sinfo['versions']['v' . $base_version]);
142                 //dbug("is vulnerable?:: " . $sinfo['versions']['v' . $base_version]['vulnerable']);
143                 // TODO: if vulnerable or maybe, and no fixes listed need to post something, mostly around the unknown
144                 if (strtolower($sinfo['versions']['v' . $base_version]['vulnerable']) == 'yes' && !empty($sinfo['versions']['v' . $base_version]['fixes'])) foreach ($sinfo['versions']['v' . $base_version]['fixes'] as $rmod => $mver) {
145                     dbug("checking $rmod");
146                     $rmod = trim($rmod);
147                     $mver = trim($mver);
148                     if (!empty($modinfo[trim($rmod)])) {
149                         //dbug("Vulnerability: $vul, module: " . $rmod . ", version: " . $mver);
150                         if (!isset($modinfo[$rmod]['dbversion'])) {
151                             //dbug("dbversion isn't set for $rmod so Locally Available but NOT installed, report on it");
152                         } else {
153                             if (version_compare_freepbx($modinfo[$rmod]['dbversion'], $mver, 'lt')) {
154                                 if (!isset($exposures[$rmod])) {
155                                     //dbug("$rmod not set so setting min ver to $mver");
156                                     $exposures[$rmod] = array('vul' => array($vul), 'minver' => $mver, 'curver' => $modinfo[$rmod]['dbversion']);
157                                 } else {
158                                     $exposures[$rmod]['vul'][] = $vul;
159                                     //dbug("$rmod IS set so setting so check $mver against current minver: " . $exposures[$rmod]['minver']);
160                                     if (version_compare_freepbx($mver, $exposures[$rmod]['minver'], 'gt')) {
161                                         //dbug("since the new $mver is greater, we are setting $rmod up to it because of $vul");
162                                         $exposures[$rmod]['minver'] = $mver;
163                                     }
164                                 }
165                             } else {
166                                 //dbug($modinfo[$rmod]['dbversion'] . " is at least $mver");
167                             }
168                         }
169                     } else {
170                         //dbug("module $rmod not there or");
171                     }
172                 }
173             }
174         }
175         return $exposures;
176     }
177 }
178
179
180 /**  Determines if there are updates we don't already know about and posts to notification
181  *   server about those updates.
182  *
183  */
184 function module_update_notifications(&$old_xml, &$xmlarray, $passive) {
185     global $db;
186
187     $notifications =& notifications::create($db);
188
189     $reset_value = $passive ? 'PASSIVE' : false;
190     $old_parser = new xml2ModuleArray($old_xml);
191     $old_xmlarray = $old_parser->parseAdvanced($old_xml);
192
193     $new_modules = array();
194     if (count($xmlarray)) {
195         foreach ($xmlarray['xml']['module'] as $mod) {
196       $new_modules[$mod['rawname']] = $mod;
197         }
198     }
199     $old_modules = array();
200     if (count($old_xmlarray)) {
201         foreach ($old_xmlarray['xml']['module'] as $mod) {
202             $old_modules[$mod['rawname']] = $mod;
203         }
204     }
205
206     // If keys (rawnames) are different then there are new modules, create a notification.
207     // This will always be the case the first time it is run since the xml is empty.
208     //
209     $diff_modules = array_diff_assoc($new_modules, $old_modules);
210     $cnt = count($diff_modules);
211     if ($cnt) {
212     $active_repos = module_get_active_repos();
213         $extext = _("The following new modules are available for download. Click delete icon on the right to remove this notice.")."<br />";
214         foreach ($diff_modules as $mod) {
215       // If it's a new module in a repo we are not interested in, then don't send a notification.
216       if (isset($active_repos[$mod['repo']]) && $active_repos[$mod['repo']]) {
217         $extext .= $mod['rawname']." (".$mod['version'].")<br />";
218       } else {
219         $cnt--;
220       }
221         }
222     if ($cnt) {
223           $notifications->add_notice('freepbx', 'NEWMODS', sprintf(_('%s New modules are available'),$cnt), $extext, '', $reset_value, true);
224     }
225     }
226
227     // Now check if any of the installed modules need updating
228     //
229     module_upgrade_notifications($new_modules, $reset_value);
230 }
231
232 /** Compare installed (enabled or disabled) modules against the xml to generate or
233  *  update the noticiation table of which modules have available updates. If the list
234  *  is empty then delete the notification.
235  */
236 function module_upgrade_notifications(&$new_modules, $passive_value) {
237     global $db;
238     $notifications =& notifications::create($db);
239
240     $installed_status = array(MODULE_STATUS_ENABLED, MODULE_STATUS_DISABLED);
241     $modules_local = module_getinfo(false, $installed_status);
242
243     $modules_upgradable = array();
244     foreach (array_keys($modules_local) as $name) {
245         if (isset($new_modules[$name])) {
246             if (version_compare_freepbx($modules_local[$name]['version'], $new_modules[$name]['version']) < 0) {
247                 $modules_upgradable[] = array(
248                     'name' => $name,
249                     'local_version' => $modules_local[$name]['version'],
250                     'online_version' => $new_modules[$name]['version'],
251                 );
252             }
253         }
254     }
255     $cnt = count($modules_upgradable);
256     if ($cnt) {
257         if ($cnt == 1) {
258             $text = _("There is 1 module available for online upgrade");
259         } else {
260             $text = sprintf(_("There are %s modules available for online upgrades"),$cnt);
261         }
262         $extext = "";
263         foreach ($modules_upgradable as $mod) {
264             $extext .= sprintf(_("%s (current: %s)"), $mod['name'].' '.$mod['online_version'], $mod['local_version'])."\n";
265         }
266         $notifications->add_update('freepbx', 'NEWUPDATES', $text, $extext, '', $passive_value);
267     } else {
268         $notifications->delete('freepbx', 'NEWUPDATES');
269     }
270 }
271
272 function module_get_active_repos() {
273   global $active_repos;
274   if (!isset($active_repos) || !$active_repos) {
275     $repos_serialized = sql("SELECT `data` FROM `module_xml` WHERE `id` = 'repos_serialized'","getOne");
276     if (isset($repos_serialized) && $repos_serialized) {
277       $active_repos = unserialize($repos_serialized);
278     } else {
279       $active_repos = array('standard' => 1);
280       module_set_active_repos($active_repos);
281     }
282     return $active_repos;
283   } else {
284     return $active_repos;
285   }
286 }
287
288 function module_set_active_repos($repos) {
289   global $active_repos;
290   global $db;
291   $active_repos = $repos;
292   $repos_serialized = $db->escapeSimple(serialize($repos));
293   sql("REPLACE INTO `module_xml` (`id`, `time`, `data`) VALUES ('repos_serialized', '".time()."','".$repos_serialized."')");
294 }
295
296 /** Looks through the modules directory and modules database and returns all available
297  * information about one or all modules
298  * @param string  (optional) The module name to query, or false for all module
299  * @param mixed   (optional) The status(es) to show, using MODULE_STATUS_* constants. Can
300  *                either be one value, or an array of values.
301  */
302 function module_getinfo($module = false, $status = false, $forceload = false) {
303
304     global $amp_conf, $db;
305     $modules = array();
306     
307     if ($module) {
308         // get info on only one module
309         $xml = _module_readxml($module);
310         if (!is_null($xml)) {
311             $modules[$module] = $xml;
312             // if status is anything else, it will be updated below when we read the db
313             $modules[$module]['status'] = MODULE_STATUS_NOTINSTALLED;
314         }
315         
316         // query to get just this one
317         $sql = 'SELECT * FROM modules WHERE modulename = "'.$module.'"';
318     } else {
319         // create the modulelist so it is static and does not need to be recreated
320         // in subsequent calls
321         //
322         $modulelist =& modulelist::create($db);
323         if ($forceload) {
324             $modulelist->invalidate();
325         }
326         if (!$modulelist->is_loaded()) {
327             // initialize list with "builtin" module
328             $module_list = array('builtin');
329
330             // read modules dir for module names
331             $dir = opendir($amp_conf['AMPWEBROOT'].'/admin/modules');
332             while ($file = readdir($dir)) {
333                 if (($file != ".") && ($file != "..") && ($file != "CVS") &&
334                     ($file != ".svn") && ($file != "_cache") &&
335                     is_dir($amp_conf['AMPWEBROOT'].'/admin/modules/'.$file)) {
336                     $module_list[] = $file;
337                 }
338             }
339
340             // read the xml for each
341             foreach ($module_list as $file) {
342                 $xml = _module_readxml($file);
343                 if (!is_null($xml)) {
344                     $modules[$file] = $xml;
345                     // if status is anything else, it will be updated below when we read the db
346                     $modules[$file]['status'] = MODULE_STATUS_NOTINSTALLED;
347           // I think this is the source of reading every module from a file. The assumption is all modules
348           // from the online repo will always have a repo defined but local ones may not so we need to define
349           // them here.
350
351           //TODO: should we have a master list of supported repos and validate against that, or do it dynamically
352           //      we do with other stuff
353           if (!isset($modules[$file]['repo']) || !$modules[$file]['repo']) {
354             $modules[$file]['repo'] = 'local';
355           }
356                 }
357             }
358             closedir($dir);
359
360             // query to get everything
361             $sql = 'SELECT * FROM modules';
362         }
363     }
364     // determine details about this module from database
365     // modulename should match the directory name
366     
367     if ($module || !$modulelist->is_loaded()) {
368         $results = $db->getAll($sql,DB_FETCHMODE_ASSOC);
369         if(DB::IsError($results)) {
370             die_freepbx($sql."<br>\n".$results->getMessage());
371         }
372     
373         if (is_array($results)) {
374             foreach($results as $row) {
375                 if (isset($modules[ $row['modulename'] ])) {
376                     if ($row['enabled'] != 0) {
377                     
378                         // check if file and registered versions are the same
379                         // version_compare returns 0 if no difference
380                         if (version_compare_freepbx($row['version'], $modules[ $row['modulename'] ]['version']) == 0) {
381                             $modules[ $row['modulename'] ]['status'] = MODULE_STATUS_ENABLED;
382                         } else {
383                             $modules[ $row['modulename'] ]['status'] = MODULE_STATUS_NEEDUPGRADE;
384                         }
385                     
386                     } else {
387                         $modules[ $row['modulename'] ]['status'] = MODULE_STATUS_DISABLED;
388                     }
389                 } else {
390                     // no directory for this db entry
391                     $modules[ $row['modulename'] ]['status'] = MODULE_STATUS_BROKEN;
392                     $modules[ $row['modulename'] ]['repo'] = 'broken';
393                 }
394                 $modules[ $row['modulename'] ]['dbversion'] = $row['version'];
395             }
396         }
397
398         // "builtin" module is always enabled
399         $modules['builtin']['status'] = MODULE_STATUS_ENABLED;
400     }
401     if (!$module && !$modulelist->is_loaded()) {
402         $modulelist->initialize($modules);
403     }
404
405     if ($status === false) {
406         if (!$module) {
407             return $modulelist->module_array;
408         } else {
409             return $modules;
410         }
411     } else {
412         if (!$module) {
413             $modules $modulelist->module_array;
414         }
415         if (!is_array($status)) {
416             // make a one element array so we can use in_array below
417             $status = array($status);
418         }
419         foreach (array_keys($modules) as $name) {
420             if (!in_array($modules[$name]['status'], $status)) {
421                 // not found in the $status array, remove it
422                 unset($modules[$name]);
423             }
424         }
425         return $modules;
426     }
427 }
428
429 /** Check if a module meets dependencies.
430  * @param  mixed  The name of the module, or the modulexml Array
431  * @return mixed  Returns true if dependencies are met, or an array
432  *                containing a list of human-readable errors if not.
433  *                NOTE: you must use strict type checking (===) to test
434  *                for true, because  array() == true !
435  */
436 function module_checkdepends($modulename) {
437     
438     // check if we were passed a modulexml array, or a string (name)
439     // ensure $modulexml is the modules array, and $modulename is the name (as a string)
440     if (is_array($modulename)) {
441         $modulexml = $modulename;
442         $modulename = $modulename['rawname'];
443     } else {
444         $modulexml = module_getinfo($modulename);
445     }
446     
447     $errors = array();
448     
449     // special handling for engine
450     $engine_dependency = false; // if we've found ANY engine dependencies to check
451     $engine_matched = false; // if an engine dependency has matched
452     $engine_errors = array(); // the error strings for engines
453     
454     if (isset($modulexml['depends'])) {
455         foreach ($modulexml['depends'] as $type => $requirements) {
456             // if only a single item, make it an array so we can use the same code as for multiple items
457             // this is because if there is  <module>a</module><module>b</module>  we will get array('module' => array('a','b'))
458             if (!is_array($requirements)) {
459                 $requirements = array($requirements);
460             }
461             
462             foreach ($requirements as $value) {
463                 switch ($type) {
464                     case 'version':
465                         if (preg_match('/^(lt|le|gt|ge|==|=|eq|!=|ne)?\s*(\d*[beta|alpha|rc|RC]?\d+(\.[^\.]+)*)$/i', $value, $matches)) {
466                             // matches[1] = operator, [2] = version
467                             $installed_ver = getversion();
468                             $operator = (!empty($matches[1]) ? $matches[1] : 'ge'); // default to >=
469                             $compare_ver = $matches[2];
470                             if (version_compare_freepbx($installed_ver, $compare_ver, $operator) ) {
471                                 // version is good
472                             } else {
473                                 $errors[] = _module_comparison_error_message('FreePBX', $compare_ver, $installed_ver, $operator);
474                             }
475                         }
476                     break;
477                     case 'phpversion':
478                         /* accepted formats
479                            <depends>
480                                <phpversion>5.1.0<phpversion>       TRUE: if php is >= 5.1.0
481                                  <phpversion>gt 5.1.0<phpversion>    TRUE: if php is > 5.1.0
482                             </depends>
483                         */
484                         if (preg_match('/^(lt|le|gt|ge|==|=|eq|!=|ne)?\s*(\d*[beta|alpha|rc|RC]?\d+(\.[^\.]+)*)$/i', $value, $matches)) {
485                             // matches[1] = operator, [2] = version
486                             $installed_ver = phpversion();
487                             $operator = (!empty($matches[1]) ? $matches[1] : 'ge'); // default to >=
488                             $compare_ver = $matches[2];
489                             if (version_compare($installed_ver, $compare_ver, $operator) ) {
490                                 // php version is good
491                             } else {
492                                 $errors[] = _module_comparison_error_message('PHP', $compare_ver, $installed_ver, $operator);
493                             }
494                         }
495                     break;
496                     case 'phpcomponent':
497                         /* accepted formats
498                            <depends>
499                                <phpcomponent>zlib<phpversion>        TRUE: if extension zlib is loaded
500                                <phpcomponent>zlib 1.2<phpversion>    TRUE: if extension zlib is loaded and >= 1.2
501                                 <phpcomponent>zlib gt 1.2<phpversion> TRUE: if extension zlib is loaded and > 1.2   
502                            </depends>
503                         */
504                         $phpcomponents = explode('||',$value);
505                         $newerrors = array();
506                         foreach($phpcomponents as $value) {
507                             if (preg_match('/^([a-z0-9_]+|Zend (Optimizer|Guard Loader))(\s+(lt|le|gt|ge|==|=|eq|!=|ne)?\s*(\d+(\.\d*[beta|alpha|rc|RC]*\d+)+))?$/i', $value, $matches)) {
508                                 // matches[1] = extension name, [3]=comparison operator, [4] = version
509                                 $compare_ver = isset($matches[4]) ? $matches[4] : '';
510                                 if (extension_loaded($matches[1])) {
511                                     if (empty($compare_ver)) {
512                                         // extension is loaded and no version specified
513                                     } else {
514                                         if (($installed_ver = phpversion($matches[1])) != '') {
515                                             $operator = (!empty($matches[3]) ? $matches[3] : 'ge'); // default to >=
516                                             if (version_compare($installed_ver, $compare_ver, $operator) ) {
517                                                 // version is good
518                                             } else {
519                                                 $newerrors[] = _module_comparison_error_message("PHP Component ".$matches[1], $compare_ver, $installed_ver, $operator);
520                                             }
521                                         } else {
522                                             $newerrors[] = _module_comparison_error_message("PHP Component ".$matches[1], $compare_ver, "<no version info>", $operator);
523                                         }
524                                     }
525                                 } else {
526                                     if ($compare_version == '') {
527                                         $newerrors[] = sprintf(_('PHP Component %s is required but missing from you PHP installation.'), $matches[1]);
528                                     } else {
529                                         $newerrors[] = sprintf(_('PHP Component %s version %s is required but missing from you PHP installation.'), $matches[1], $compare_version);
530                                     }
531                                 }
532                             }   
533                         }
534                         if (count($newerrors) == count($phpcomponents)) {
535                             $errors = array_merge($errors,$newerrors);
536                         }
537                     break;
538                     case 'module':
539                         // Modify to allow versions such as 2.3.0beta1.2
540                         if (preg_match('/^([a-z0-9_]+)(\s+(lt|le|gt|ge|==|=|eq|!=|ne)?\s*(\d+(\.\d*[beta|alpha|rc|RC]*\d+)+))?$/i', $value, $matches)) {
541                             // matches[1] = modulename, [3]=comparison operator, [4] = version
542                             $modules = module_getinfo($matches[1]);
543                             if (isset($modules[$matches[1]])) {
544                                 $needed_module = "<strong>".(isset($modules[$matches[1]]['name'])?$modules[$matches[1]]['name']:$matches[1])."</strong>";
545                                 switch ($modules[$matches[1]]['status'] ) {
546                                     case MODULE_STATUS_ENABLED:
547                                         if (!empty($matches[4])) {
548                                             // also doing version checking
549                                             $installed_ver = $modules[$matches[1]]['dbversion'];
550                                             $compare_ver = $matches[4];
551                                             $operator = (!empty($matches[3]) ? $matches[3] : 'ge'); // default to >=
552                                             
553                                             if (version_compare_freepbx($installed_ver, $compare_ver, $operator) ) {
554                                                 // version is good
555                                             } else {
556                                                 $errors[] = _module_comparison_error_message($needed_module.' module', $compare_ver, $installed_ver, $operator);
557                                             }
558                                         }
559                                     break;
560                                     case MODULE_STATUS_BROKEN:
561                                         $errors[] = sprintf(_('Module %s is required, but yours is broken. You should reinstall '.
562                                                               'it and try again.'), $needed_module);
563                                     break;
564                                     case MODULE_STATUS_DISABLED:
565                                         $errors[] = sprintf(_('Module %s is required, but yours is disabled.'), $needed_module);
566                                     break;
567                                     case MODULE_STATUS_NEEDUPGRADE:
568                                         $errors[] = sprintf(_('Module %s is required, but yours is disabled because it needs to '.
569                                                               'be upgraded. Please upgrade %s first, and then try again.'),
570                                                             $needed_module, $needed_module);
571                                     break;
572                                     default:
573                                     case MODULE_STATUS_NOTINSTALLED:
574                                         $errors[] = sprintf(_('Module %s is required, yours is not installed.'), $needed_module);
575                                     break;
576                                 }
577                             } else {
578                                 $errors[] = sprintf(_('Module %s is required.'), $matches[1]);
579                             }
580                         }
581                     break;
582                     case 'file': // file exists
583                         // replace embedded amp_conf %VARIABLES% in string
584
585
586
587
588
589                         $file = _module_ampconf_string_replace($value);
590                         
591                         if (!file_exists( $file )) {
592                             $errors[] = sprintf(_('File %s must exist.'), $file);
593                         }
594                     break;
595                     case 'engine':
596                         /****************************
597                          *  NOTE: there is special handling for this check. We want to "OR" conditions, instead of
598                          *        "AND"ing like the rest of them.
599                          */
600                         
601                         // we found at least one engine, so mark that we're matching this
602                         $engine_dependency = true;
603                         
604                         if (preg_match('/^([a-z0-9_]+)(\s+(lt|le|gt|ge|==|=|eq|!=|ne)?\s*(\d+(\.[^\.]+)*))?$/i', $value, $matches)) {
605                             // matches[1] = engine, [3]=comparison operator, [4] = version
606                             $operator = (!empty($matches[3]) ? $matches[3] : 'ge'); // default to >=
607                             
608                             $engine = engine_getinfo();
609                             if (($engine['engine'] == $matches[1]) &&
610                 (empty($matches[4]) || version_compare($engine['version'], $matches[4], $operator))
611                                ) {
612                               
613                                 $engine_matched = true;
614                             } else {
615                                 // add it to the error messages
616                                 if ($matches[4]) {
617                                     // version specified
618                                     $operator_friendly = str_replace(array('gt','ge','lt','le','eq','ne'), array('>','>=','<','<=','=','not ='), $operator);
619                                     $engine_errors[] = $matches[1].' ('.$operator_friendly.' '.$matches[4].')';
620                                 } else {
621                                     // no version
622                                     $engine_errors[] = $matches[1];
623                                 }
624                             }
625                         }
626                     break;
627                 }
628             }
629         }
630         
631         // special handling for engine
632         // if we've had at least one engine dependency check, and no engine dependencies matched, we have an error
633         if ($engine_dependency && !$engine_matched) {
634         
635             $engineinfo = engine_getinfo();
636             $yourengine = $engineinfo['engine'].' '.$engineinfo['version'];
637             // print it nicely
638             if (count($engine_errors) == 1) {
639                 $errors[] = sprintf(_('Requires engine %s, you have: %s'),$engine_errors[0],$yourengine);
640             } else {
641                 $errors[] = sprintf(_('Requires one of the following engines: %s; you have: %s'),implode(', ', $engine_errors),$yourengine);
642             }
643         }
644     }
645     
646     if (count($errors) > 0) {
647         return $errors;
648     } else {
649         return true;
650     }
651 }
652
653 function _module_comparison_error_message($module, $reqversion, $version, $operator) {
654     switch ($operator) {
655         case 'lt': case '<':
656             return sprintf(_('A %s version below %s is required, you have %s'), $module, $reqversion, $version);
657         break;
658         case 'le': case '<=';
659             return sprintf(_('%s version %s or below is required, you have %s'), $module, $reqversion, $version);
660         break;
661         case 'gt': case '>';
662             return sprintf(_('A %s version newer than %s required, you have %s'), $module, $reqversion, $version);
663         break;
664         case 'ne': case '!=': case '<>':
665             return sprintf(_('Your %s version (%s) is incompatible.'), $version, $reqversion);
666         break;
667         case 'eq': case '==': case '=':
668             return sprintf(_('Only %s version %s is compatible, you have %s'), $module, $reqversion, $version);
669         break;
670         default:
671         case 'ge': case '>=':
672             return sprintf(_('%s version %s or higher is required, you have %s'), $module, $reqversion, $version);
673     }
674 }
675
676 /** Finds all the enabled modules that depend on a given module
677  * @param  mixed  The name of the module, or the modulexml Array
678  * @return array  Array containing the list of modules, or false if no dependencies
679  */
680 function module_reversedepends($modulename) {
681     // check if we were passed a modulexml array, or a string (name)
682     // ensure $modulename is the name (as a string)
683     if (is_array($modulename)) {
684         $modulename = $modulename['rawname'];
685     }
686     
687     $modules = module_getinfo(false, MODULE_STATUS_ENABLED);
688     
689     $depends = array();
690     
691     foreach (array_keys($modules) as $name) {
692         if (isset($modules[$name]['depends'])) {
693             foreach ($modules[$name]['depends'] as $type => $requirements) {
694                 if ($type == 'module') {
695                     // if only a single item, make it an array so we can use the same code as for multiple items
696                     // this is because if there is  <module>a</module><module>b</module>  we will get array('module' => array('a','b'))
697                     if (!is_array($requirements)) {
698                         $requirements = array($requirements);
699                     }
700                     
701                     foreach ($requirements as $value) {
702                         if (preg_match('/^([a-z0-9_]+)(\s+(>=|>|=|<|<=|!=)?\s*(\d(\.\d)*))?$/i', $value, $matches)) {
703                             // matches[1] = modulename, [3]=comparison operator, [4] = version
704                             
705                             // note, we're not checking version here. Normally this function is used when
706                             // uninstalling a module, so it doesn't really matter anyways, and version
707                             // dependency should have already been checked when the module was installed
708                             if ($matches[1] == $modulename) {
709                                 $depends[] = $name;
710                             }
711                         }
712                     }
713                 }
714             }
715         }
716     }
717     
718     return (count($depends) > 0) ? $depends : false;
719 }
720
721 /** Enables a module
722  * @param string    The name of the module to enable
723  * @param bool      If true, skips status and dependency checks
724  * @return  mixed   True if succesful, array of error messages if not succesful
725  */
726 function module_enable($modulename, $force = false) { // was enableModule
727     $modules = module_getinfo($modulename);
728     
729     if ($modules[$modulename]['status'] == MODULE_STATUS_ENABLED) {
730         return array(_("Module ".$modulename." is already enabled"));
731     }
732     
733     // doesn't make sense to skip this on $force - eg, we can't enable a non-installed or broken module
734     if ($modules[$modulename]['status'] != MODULE_STATUS_DISABLED) {
735         return array(_("Module ".$modulename." cannot be enabled"));
736     }
737     
738     if (!$force) {
739         if (($errors = module_checkdepends($modules[$modulename])) !== true) {
740             return $errors;
741         }
742     }
743     
744     // disabled (but doesn't needupgrade or need install), and meets dependencies
745     _module_setenabled($modulename, true);
746     needreload();
747     return true;
748 }
749
750 /** Downloads the latest version of a module
751  * and extracts it to the directory
752  * @param string    The name of the module to install
753  * @param bool      If true, skips status and dependency checks
754  * @param string    The name of a callback function to call with progress updates.
755                     function($action, $params). Possible actions:
756                       getinfo: while downloading modules.xml
757                       downloading: while downloading file; params include 'read' and 'total'
758                       untar: before untarring
759                       done: when complete
760  * @return  mixed   True if succesful, array of error messages if not succesful
761  */
762
763 // was fetchModule
764 function module_download($modulename, $force = false, $progress_callback = null, $override_svn = false, $override_xml = false) {
765     global $amp_conf;
766
767     if ($time_limit = ini_get('max_execution_time')) {
768         set_time_limit($time_limit);
769     }
770     
771     // size of download blocks to fread()
772     // basically, this controls how often progress_callback is called
773     $download_chunk_size = 12*1024;
774     
775     // invoke progress callback
776     if (function_exists($progress_callback)) {
777         $progress_callback('getinfo', array('module'=>$modulename));
778     }
779             
780     $res = module_getonlinexml($modulename, $override_xml);
781     if ($res == null) {
782         return array(_("Module not found in repository"));
783     }
784     
785     $file = basename($res['location']);
786     $filename = $amp_conf['AMPWEBROOT']."/admin/modules/_cache/".$file;
787     // if we're not forcing the download, and a file with the target name exists..
788     if (!$force && file_exists($filename)) {
789         // We might already have it! Let's check the MD5.
790         $filedata = "";
791         if ( $fh = @ fopen($filename, "r") ) {
792             while (!feof($fh)) {
793                 $filedata .= fread($fh, 8192);
794             }
795             fclose($fh);
796         }
797         
798         if (isset($res['md5sum']) && $res['md5sum'] == md5 ($filedata)) {
799             // Note, if there's no MD5 information, it will redownload
800             // every time. Otherwise theres no way to avoid a corrupt
801             // download
802             
803             // invoke progress callback
804             if (function_exists($progress_callback)) {
805                 $progress_callback('untar', array('module'=>$modulename, 'size'=>filesize($filename)));
806             }
807             
808             /* We will explode the tarball in the cache directory and then once successful, remove the old module before before
809              * moving the new one over. This way, things like removed files end up being removed instead of laying around
810              *
811              * TODO: save old module being replaced, if there is an old one.
812              */
813             exec("rm -rf ".$amp_conf['AMPWEBROOT']."/admin/modules/_cache/$modulename", $output, $exitcode);
814             if ($exitcode != 0) {
815                 return array(sprintf(_('Could not remove %s to install new version'), $amp_conf['AMPWEBROOT'].'/admin/modules/_cache/'.$modulenam));
816             }
817             exec("tar zxf ".escapeshellarg($filename)." -C ".escapeshellarg($amp_conf['AMPWEBROOT'].'/admin/modules/_cache/'), $output, $exitcode);
818             if ($exitcode != 0) {
819         freepbx_log(FPBX_LOG_ERROR,sprintf(_("failed to open %s module archive into _cache directory."),$filename));
820                 return array(sprintf(_('Could not untar %s to %s'), $filename, $amp_conf['AMPWEBROOT'].'/admin/modules/_cache'));
821       } else {
822         // since untarring was successful, remvove the tarball so they do not accumulate
823         if (unlink($filename) === false) {
824           freepbx_log(FPBX_LOG_WARNING,sprintf(_("failed to delete %s from cache directory after opening module archive."),$filename));
825         }
826       }
827             exec("rm -rf ".$amp_conf['AMPWEBROOT']."/admin/modules/$modulename", $output, $exitcode);
828             if ($exitcode != 0) {
829                 return array(sprintf(_('Could not remove old module %s to install new version'), $amp_conf['AMPWEBROOT'].'/admin/modules/'.$modulename));
830             }
831             exec("mv ".$amp_conf['AMPWEBROOT']."/admin/modules/_cache/$modulename ".$amp_conf['AMPWEBROOT']."/admin/modules/$modulename", $output, $exitcode);
832             if ($exitcode != 0) {
833                 return array(sprintf(_('Could not move %s to %s'), $amp_conf['AMPWEBROOT']."/admin/modules/_cache/$modulename", $amp_conf['AMPWEBROOT'].'/admin/modules/'));
834             }
835             
836             // invoke progress_callback
837             if (function_exists($progress_callback)) {
838                 $progress_callback('done', array('module'=>$modulename));
839             }
840             
841             return true;
842         } else {
843             unlink($filename);
844         }
845     }
846     
847     if (!($fp = @fopen($filename,"w"))) {
848         return array(sprintf(_("Error opening %s for writing"), $filename));
849     }
850
851     if ($override_svn) {
852         $url_list = array($override_svn.$res['location']);
853     } else {
854         $url_list = generate_module_repo_url("/modules/".$res['location']);
855     }
856     
857     // Check each URL until get_headers_assoc() returns something intelligible. We then use
858     // that URL and hope the file is there, we won't check others.
859     //
860     $headers = false;
861     foreach ($url_list as $u) {
862         $headers = get_headers_assoc($u);
863         if (!empty($headers)) {
864             $url = $u;
865             break;
866         }
867         freepbx_log(FPBX_LOG_ERROR,sprintf(_('Failed download module tarball from %s, server may be down'),$u));
868     }
869     if (!$headers || !$url) {
870         return array(sprintf(_("Unable to connect to servers from URLs provided: %s"), implode(',',$url_list)));
871     }
872     
873     // TODO: do we want to make more robust past this point:
874     // At this point we have settled on a specific URL that we can reach, if the file isn't there we won't try
875     // other servers to check for it. The assumption is that no backup server will have it either at this point
876     // If we wanted to make this more robust we could go back and try other servers. This code is a bit tangled
877     // so some better factoring might help.
878     //
879     $totalread = 0;
880     // invoke progress_callback
881     if (function_exists($progress_callback)) {
882         $progress_callback('downloading', array('module'=>$modulename, 'read'=>$totalread, 'total'=>$headers['content-length']));
883     }
884     
885     // Check MODULEADMINWGET first so we don't execute the fopen() if set
886     //
887     if ($amp_conf['MODULEADMINWGET'] || !$dp = @fopen($url,'r')) {
888         exec("wget --tries=1 --timeout=600 -O $filename $url 2> /dev/null", $filedata, $retcode);
889         if ($retcode != 0) {
890             return array(sprintf(_("Error opening %s for reading"), $url));
891         } else {
892             if (!$dp = @fopen($filename,'r')) {
893                 return array(sprintf(_("Error opening %s for reading"), $url));
894             }
895         }
896     }
897     
898     $filedata = '';
899     while (!feof($dp)) {
900         $data = fread($dp, $download_chunk_size);
901         $filedata .= $data;
902         $totalread += strlen($data);
903         if (function_exists($progress_callback)) {
904             $progress_callback('downloading', array('module'=>$modulename, 'read'=>$totalread, 'total'=>$headers['content-length']));
905         }
906     }
907     fwrite($fp,$filedata);
908     fclose($dp);
909     fclose($fp);
910     
911     
912     if (is_readable($filename) !== TRUE ) {
913         return array(sprintf(_('Unable to save %s'),$filename));
914     }
915     
916     // Check the MD5 info against what's in the module's XML
917     if (!isset($res['md5sum']) || empty($res['md5sum'])) {
918         //echo "<div class=\"error\">"._("Unable to Locate Integrity information for")." {$filename} - "._("Continuing Anyway")."</div>";
919     } else if ($res['md5sum'] != md5 ($filedata)) {
920         unlink($filename);
921         return array(sprintf(_('File Integrity failed for %s - aborting'), $filename));
922     }
923     
924     // invoke progress callback
925     if (function_exists($progress_callback)) {
926         $progress_callback('untar', array('module'=>$modulename, 'size'=>filesize($filename)));
927     }
928
929     /* We will explode the tarball in the cache directory and then once successful, remove the old module before before
930      * moving the new one over. This way, things like removed files end up being removed instead of laying around
931      *
932      * TODO: save old module being replaced, if there is an old one.
933      *
934      */
935     exec("rm -rf ".$amp_conf['AMPWEBROOT']."/admin/modules/_cache/$modulename", $output, $exitcode);
936     if ($exitcode != 0) {
937         return array(sprintf(_('Could not remove %s to install new version'), $amp_conf['AMPWEBROOT'].'/admin/modules/_cache/'.$modulenam));
938     }
939     exec("tar zxf ".escapeshellarg($filename)." -C ".escapeshellarg($amp_conf['AMPWEBROOT'].'/admin/modules/_cache/'), $output, $exitcode);
940     if ($exitcode != 0) {
941     freepbx_log(FPBX_LOG_ERROR,sprintf(_("failed to open %s module archive into _cache directory."),$filename));
942         return array(sprintf(_('Could not untar %s to %s'), $filename, $amp_conf['AMPWEBROOT'].'/admin/modules/_cache'));
943     } else {
944     // since untarring was successful, remvove the tarball so they do not accumulate
945     if (unlink($filename) === false) {
946       freepbx_log(FPBX_LOG_WARNING,sprintf(_("failed to delete %s from cache directory after opening module archive."),$filename));
947     }
948   }
949     exec("rm -rf ".$amp_conf['AMPWEBROOT']."/admin/modules/$modulename", $output, $exitcode);
950     if ($exitcode != 0) {
951         return array(sprintf(_('Could not remove old module %s to install new version'), $amp_conf['AMPWEBROOT'].'/admin/modules/'.$modulename));
952     }
953     exec("mv ".$amp_conf['AMPWEBROOT']."/admin/modules/_cache/$modulename ".$amp_conf['AMPWEBROOT']."/admin/modules/$modulename", $output, $exitcode);
954     if ($exitcode != 0) {
955         return array(sprintf(_('Could not move %s to %s'), $amp_conf['AMPWEBROOT']."/admin/modules/_cache/$modulename", $amp_conf['AMPWEBROOT'].'/admin/modules/'));
956     }
957
958     // invoke progress_callback
959     if (function_exists($progress_callback)) {
960         $progress_callback('done', array('module'=>$modulename));
961     }
962
963     return true;
964 }
965
966
967 function module_handleupload($uploaded_file) {
968     global $amp_conf;
969     $errors = array();
970     
971     if (!isset($uploaded_file['tmp_name']) || !file_exists($uploaded_file['tmp_name'])) {
972         $errors[] = _("Error finding uploaded file - check your PHP and/or web server configuration");
973         return $errors;
974     }
975     
976     if (!preg_match('/\.(tar\.gz|tgz|tar)$/', $uploaded_file['name'])) {
977         $errors[] = _("File must be in tar or tar+gzip (.tar, .tgz or .tar.gz) format");
978         return $errors;
979     }
980     
981     if (!preg_match('/^([A-Za-z][A-Za-z0-9_]+)\-([0-9a-zA-Z]+(\.[0-9a-zA-Z]+)*)\.(tar\.gz|tgz|tar)$/', $uploaded_file['name'], $matches)) {
982         $errors[] = _("Filename not in correct format: must be modulename-version.[tar|tar.gz|tgz] (eg. custommodule-0.1.tgz)");
983         return $errors;
984     } else {
985         $modulename = $matches[1];
986         $moduleversion = $matches[2];
987     }
988     
989     $temppath = $amp_conf['AMPWEBROOT'].'/admin/modules/_cache/'.uniqid("upload");
990     if (! @mkdir($temppath) ) {
991         return array(sprintf(_("Error creating temporary directory: %s"), $temppath));
992     }
993     $filename = $temppath.'/'.$uploaded_file['name'];
994     
995     move_uploaded_file($uploaded_file['tmp_name'], $filename);
996     
997   $tar_z_arg = substr($uploaded_file['name'],-4) == '.tar' ? '' : 'z';
998     exec("tar ".$tar_z_arg."tf ".escapeshellarg($filename), $output, $exitcode);
999     if ($exitcode != 0) {
1000         $errors[] = _("Error untaring uploaded file. Must be a tar or tar+gzip file");
1001         return $errors;
1002     }
1003     
1004     foreach ($output as $line) {
1005         // make sure all lines start with "modulename/"
1006         if (!preg_match('/^'.$modulename.'\//', $line)) {
1007             $errors[] = 'File extracting to invalid location: '.$line;
1008         }
1009     }
1010     if (count($errors)) {
1011         return $errors;
1012     }
1013
1014     /* We will explode the tarball in the cache directory and then once successful, remove the old module before before
1015      * moving the new one over. This way, things like removed files end up being removed instead of laying around
1016      *
1017      * TODO: save old module being replaced, if there is an old one.
1018      *
1019      */
1020     exec("rm -rf ".$amp_conf['AMPWEBROOT']."/admin/modules/_cache/$modulename", $output, $exitcode);
1021     if ($exitcode != 0) {
1022         return array(sprintf(_('Could not remove %s to install new version'), $amp_conf['AMPWEBROOT'].'/admin/modules/_cache/'.$modulenam));
1023     }
1024     exec("tar ".$tar_z_arg."xf ".escapeshellarg($filename)." -C ".escapeshellarg($amp_conf['AMPWEBROOT'].'/admin/modules/_cache/'), $output, $exitcode);
1025     if ($exitcode != 0) {
1026     freepbx_log(FPBX_LOG_ERROR,sprintf(_("failed to open %s module archive into _cache directory."),$filename));
1027         return array(sprintf(_('Could not untar %s to %s'), $filename, $amp_conf['AMPWEBROOT'].'/admin/modules/_cache'));
1028     } else {
1029     // since untarring was successful, remvove the tarball so they do not accumulate
1030     if (unlink($filename) === false) {
1031       freepbx_log(FPBX_LOG_WARNING,sprintf(_("failed to delete %s from cache directory after opening module archive."),$filename));
1032     }
1033     }
1034     exec("rm -rf ".$amp_conf['AMPWEBROOT']."/admin/modules/$modulename", $output, $exitcode);
1035     if ($exitcode != 0) {
1036         return array(sprintf(_('Could not remove old module %s to install new version'), $amp_conf['AMPWEBROOT'].'/admin/modules/'.$modulename));
1037     }
1038     exec("mv ".$amp_conf['AMPWEBROOT']."/admin/modules/_cache/$modulename ".$amp_conf['AMPWEBROOT']."/admin/modules/$modulename", $output, $exitcode);
1039     if ($exitcode != 0) {
1040         return array(sprintf(_('Could not move %s to %s'), $amp_conf['AMPWEBROOT']."/admin/modules/_cache/$modulename", $amp_conf['AMPWEBROOT'].'/admin/modules/'));
1041     }
1042
1043     exec("rm -rf ".$temppath, $output, $exitcode);
1044     if ($exitcode != 0) {
1045         $errors[] = sprintf(_('Error removing temporary directory: %s'), $temppath);
1046     }
1047     
1048     if (count($errors)) {
1049         return $errors;
1050     }
1051     
1052     // finally, module installation is successful
1053     return true;
1054 }
1055
1056 /** Installs or upgrades a module from it's directory
1057  * Checks dependencies, and enables
1058  * @param string   The name of the module to install
1059  * @param bool     If true, skips status and dependency checks
1060  * @return mixed   True if succesful, array of error messages if not succesful
1061  */
1062 function module_install($modulename, $force = false) {
1063     global $db, $amp_conf;
1064
1065     if ($time_limit = ini_get('max_execution_time')) {
1066         set_time_limit($time_limit);
1067     }
1068
1069     $modules = module_getinfo($modulename);
1070     
1071     // make sure we have a directory, to begin with
1072     $dir = $amp_conf['AMPWEBROOT'].'/admin/modules/'.$modulename;
1073     if (!is_dir($dir)) {
1074         return array(_("Cannot find module"));
1075     }
1076     
1077     // read the module.xml file
1078     $modules = module_getinfo($modulename);
1079     if (!isset($modules[$modulename])) {
1080         return array(_("Could not read module.xml"));
1081     }
1082     
1083     // don't force this bit - we can't install a broken module (missing files)
1084     if ($modules[$modulename]['status'] == MODULE_STATUS_BROKEN) {
1085         return array(_("Module ".$modules[$modulename]['rawname']." is broken and cannot be installed. You should try to download it again."));
1086     }
1087     
1088     if (!$force) {
1089     
1090         if (!in_array($modules[$modulename]['status'], array(MODULE_STATUS_NOTINSTALLED, MODULE_STATUS_NEEDUPGRADE))) {
1091             //return array(_("This module is already installed."));
1092             // This isn't really an error, we just exit
1093             return true;
1094         }
1095         
1096         // check dependencies
1097         if (is_array($errors = module_checkdepends($modules[$modulename]))) {
1098             return $errors;
1099         }
1100     }
1101     
1102     // run the scripts
1103     if (!_module_runscripts($modulename, 'install')) {
1104         return array(_("Failed to run installation scripts"));
1105     }
1106     
1107     if ($modules[$modulename]['status'] == MODULE_STATUS_NOTINSTALLED) {
1108         // customize INSERT query
1109         $sql = "INSERT INTO modules (modulename, version, enabled) values ('".$db->escapeSimple($modules[$modulename]['rawname'])."','".$db->escapeSimple($modules[$modulename]['version'])."', 1);";
1110     } else {
1111         // just need to update the version
1112         $sql = "UPDATE modules SET version='".$db->escapeSimple($modules[$modulename]['version'])."' WHERE modulename = '".$db->escapeSimple($modules[$modulename]['rawname'])."'";
1113     }
1114     
1115     // run query
1116     $results = $db->query($sql);
1117     if(DB::IsError($results)) {
1118         return array(sprintf(_("Error updating database. Command was: %s; error was: %s "), $sql, $results->getMessage()));
1119     }
1120     
1121     // module is now installed & enabled, invalidate the modulelist class since it is now stale
1122     $modulelist =& modulelist::create($db);
1123     $modulelist->invalidate();
1124
1125     // edit the notification table to list any remaining upgrades available or clear
1126     // it if none are left. It requres a copy of the most recent module_xml to compare
1127     // against the installed modules.
1128     //
1129     $sql = 'SELECT data FROM module_xml WHERE id = "xml"';
1130     $data = sql($sql, "getOne");
1131     $parser = new xml2ModuleArray($data);
1132     $xmlarray = $parser->parseAdvanced($data);
1133     $new_modules = array();
1134     if (count($xmlarray)) {
1135         foreach ($xmlarray['xml']['module'] as $mod) {
1136             $new_modules[$mod['rawname']] = $mod;
1137         }
1138     }
1139     module_upgrade_notifications($new_modules, 'PASSIVE');
1140     needreload();
1141     
1142     return true;
1143 }
1144
1145 /** Disable a module, but reqmains installed
1146  * @param string   The name of the module to disable
1147  * @param bool     If true, skips status and dependency checks
1148  * @return mixed   True if succesful, array of error messages if not succesful
1149 */
1150 function module_disable($modulename, $force = false) { // was disableModule
1151     $modules = module_getinfo($modulename);
1152     if (!isset($modules[$modulename])) {
1153         return array(_("Specified module not found"));
1154     }
1155     
1156     if (!$force) {
1157         if ($modules[$modulename]['status'] != MODULE_STATUS_ENABLED) {
1158             return array(_("Module not enabled: cannot disable"));
1159         }
1160         
1161         if ( ($depmods = module_reversedepends($modulename)) !== false) {
1162             return array(_("Cannot disable: The following modules depend on this one: ").implode(',',$depmods));
1163         }
1164     }
1165     
1166     _module_setenabled($modulename, false);
1167     needreload();
1168     return true;
1169 }
1170
1171 /** Uninstall a module, but files remain
1172  * @param string   The name of the module to install
1173  * @param bool     If true, skips status and dependency checks
1174  * @return mixed   True if succesful, array of error messages if not succesful
1175  */
1176 function module_uninstall($modulename, $force = false) {
1177     global $db;
1178     global $amp_conf;
1179     
1180     $modules = module_getinfo($modulename);
1181     if (!isset($modules[$modulename])) {
1182         return array(_("Specified module not found"));
1183     }
1184     
1185     if (!$force) {
1186         if ($modules[$modulename]['status'] == MODULE_STATUS_NOTINSTALLED) {
1187             return array(_("Module not installed: cannot uninstall"));
1188         }
1189         
1190         if ( ($depmods = module_reversedepends($modulename)) !== false) {
1191             return array(_("Cannot disable: The following modules depend on this one: ").implode(',',$depmods));
1192         }
1193     }
1194     
1195     $sql = "DELETE FROM modules WHERE modulename = '".$db->escapeSimple($modulename)."'";
1196     $results = $db->query($sql);
1197     if(DB::IsError($results)) {
1198         return array(_("Error updating database: ").$results->getMessage());
1199     }
1200     
1201     if (!_module_runscripts($modulename, 'uninstall')) {
1202         return array(_("Failed to run un-installation scripts"));
1203     }
1204
1205   // Now make sure all feature codes are uninstalled in case the module has not already done it
1206   //
1207   require_once(dirname(__FILE__) . '/featurecodes.class.php'); //TODO: do we need this, now that we have bootstrap? -MB
1208   featurecodes_delModuleFeatures($modulename);
1209
1210   $freepbx_conf =& freepbx_conf::create();
1211   $freepbx_conf->remove_module_settings($modulename);
1212   $mod_asset_dir = $amp_conf['AMPWEBROOT'] . "/admin/assets/" . $modulename;
1213   if (is_link($mod_asset_dir)) {
1214     @unlink($mod_asset_dir);
1215   }
1216     
1217     needreload();
1218     return true;
1219 }
1220
1221 /** Totally deletes a module
1222  * @param string   The name of the module to install
1223  * @param bool     If true, skips status and dependency checks
1224  * @return mixed   True if succesful, array of error messages if not succesful
1225  */
1226 function module_delete($modulename, $force = false) {
1227     global $amp_conf;
1228     
1229     $modules = module_getinfo($modulename);
1230     if (!isset($modules[$modulename])) {
1231         return array(_("Specified module not found"));
1232     }
1233     
1234     if ($modules[$modulename]['status'] != MODULE_STATUS_NOTINSTALLED) {
1235         if (is_array($errors = module_uninstall($modulename, $force))) {
1236             return $errors;
1237         }
1238     }
1239     
1240     // delete module directory
1241     //TODO : do this in pure php
1242     $dir = $amp_conf['AMPWEBROOT'].'/admin/modules/'.$modulename;
1243     if (!is_dir($dir)) {
1244         return array(sprintf(_("Cannot delete directory %s"), $dir));
1245     }
1246     if (strpos($dir,"..") !== false) {
1247         die_freepbx("Security problem, denying delete");
1248     }
1249     exec("rm -r ".escapeshellarg($dir),$output, $exitcode);
1250     if ($exitcode != 0) {
1251         return array(sprintf(_("Error deleting directory %s (code %d)"), $dir, $exitcode));
1252     }
1253     
1254     // uninstall will have called needreload() if necessary
1255     return true;
1256 }
1257
1258 /** Internal use only */
1259 function _module_setenabled($modulename, $enabled) {
1260     global $db;
1261     $sql = 'UPDATE modules SET enabled = '.($enabled ? '1' : '0').' WHERE modulename = "'.$db->escapeSimple($modulename).'"';
1262     $results = $db->query($sql);
1263     if(DB::IsError($results)) {
1264         die_freepbx($sql."<br>\n".$results->getMessage());
1265     }
1266     $modulelist =& modulelist::create($db);
1267     $modulelist->invalidate();
1268 }
1269
1270 function _module_readxml($modulename) {
1271     global $amp_conf;
1272     switch ($modulename) {
1273         case 'builtin': // special handling
1274             $dir = $amp_conf['AMPWEBROOT'];
1275             $xmlfile = $dir.'/admin/module-builtin.xml';
1276         break;
1277         default:
1278             $dir = $amp_conf['AMPWEBROOT'].'/admin/modules/'.$modulename;
1279             $xmlfile = $dir.'/module.xml';
1280         break;
1281     }
1282
1283     if (file_exists($xmlfile)) {
1284     ini_set('user_agent','Wget/1.10.2 (Red Hat modified)');
1285         $data = file_get_contents($xmlfile);
1286         //$parser = new xml2ModuleArray($data);
1287         //$xmlarray = $parser->parseModulesXML($data);
1288         $parser = new xml2Array($data);
1289         $xmlarray = $parser->data;
1290         if (isset($xmlarray['module'])) {
1291             // add a couple fields first
1292             $xmlarray['module']['name'] = str_replace("\n&\n","&",$xmlarray['module']['name']);
1293             $xmlarray['module']['displayname'] = $xmlarray['module']['name'];
1294             if (isset($xmlarray['module']['description'])) {
1295                 $xmlarray['module']['description'] = trim(str_replace("\n","",$xmlarray['module']['description']));
1296             }
1297             if (isset($xmlarray['module']['menuitems'])) {
1298                 
1299                 foreach ($xmlarray['module']['menuitems'] as $item=>$displayname) {
1300                     $displayname = str_replace("\n&\n","&",$displayname);
1301                     $xmlarray['module']['menuitems'][$item] = $displayname;
1302                     $path = '/module/menuitems/'.$item;
1303                     
1304                     // find category
1305                     if (isset($parser->attributes[$path]['category'])) {
1306                         $category = str_replace("\n&\n","&",$parser->attributes[$path]['category']);
1307                     } else if (isset($xmlarray['module']['category'])) {
1308                         $category = str_replace("\n&\n","&",$xmlarray['module']['category']);
1309                     } else {
1310                         $category = 'Basic';
1311                     }
1312                     
1313                     // find type
1314                     if (isset($parser->attributes[$path]['type'])) {
1315                         $type = $parser->attributes[$path]['type'];
1316                     } else if (isset($xmlarray['module']['type'])) {
1317                         $type = $xmlarray['module']['type'];
1318                     } else {
1319                         $type = 'setup';
1320                     }
1321                     
1322                     // sort priority
1323                     if (isset($parser->attributes[$path]['sort'])) {
1324                         // limit to -10 to 10
1325                         if ($parser->attributes[$path]['sort'] > 10) {
1326                             $sort = 10;
1327                         } else if ($parser->attributes[$path]['sort'] < -10) {
1328                             $sort = -10;
1329                         } else {
1330                             $sort = $parser->attributes[$path]['sort'];
1331                         }
1332                     } else {
1333                         $sort = 0;
1334                     }
1335
1336                     // setup basic items array
1337                     $xmlarray['module']['items'][$item] = array(
1338                         'name' => $displayname,
1339                         'type' => $type,
1340                         'category' => $category,
1341                         'sort' => $sort,
1342                     );
1343                     
1344                     // add optional values:
1345                     $optional_attribs = array(
1346                         'href', // custom href
1347                         'target', // custom target frame
1348                         'display', // display= override
1349                         'needsenginedb', // set to true if engine db access required (e.g. astman access)
1350                         'needsenginerunning', // set to true if required to run
1351                         'access', // set to all if all users should always have access
1352                         'hidden', //keep hidden from the gui at all times - but accesable if you kknow how...
1353                     );
1354                     foreach ($optional_attribs as $attrib) {
1355                         if (isset($parser->attributes[$path][ $attrib ])) {
1356                             $xmlarray['module']['items'][$item][ $attrib ] = $parser->attributes[$path][ $attrib ];
1357                         }
1358                     }
1359                     
1360                 }
1361             }
1362             return $xmlarray['module'];
1363         }
1364     }
1365     return null;
1366 }
1367
1368 // Temporarily copied here, for people that haven't upgraded their
1369 // IVR module..
1370
1371 function modules_getversion($modname) {
1372     return _modules_getversion($modname);
1373 }
1374
1375 // This returns the version of a module
1376 function _modules_getversion($modname) {
1377     global $db;
1378
1379     $sql = "SELECT version FROM modules WHERE modulename = '".$db->escapeSimple($modname)."'";
1380     $results = $db->getRow($sql,DB_FETCHMODE_ASSOC);
1381     if (isset($results['version']))
1382         return $results['version'];
1383     else
1384         return null;
1385 }
1386
1387 /** Updates the version field in the module table
1388  * Should only be called internally
1389  */
1390 function _modules_setversion($modname, $vers) {
1391     global $db;
1392
1393     return ;
1394 }
1395
1396 /** Run the module install/uninstall scripts
1397  * @param string  The name of the module
1398  * @param string  The action to perform, either 'install' or 'uninstall'
1399  * @return boolean  If the action was succesful
1400  */
1401 function _module_runscripts($modulename, $type) {
1402     global $amp_conf;
1403     $db_engine = $amp_conf["AMPDBENGINE"];
1404     
1405     $moduledir = $amp_conf["AMPWEBROOT"]."/admin/modules/".$modulename;
1406     if (!is_dir($moduledir)) {
1407         return false;
1408     }
1409     
1410     switch ($type) {
1411         case 'install':
1412             // install sql files
1413             $sqlfilename = "install.sql";
1414       $rc = true;
1415             
1416             if (is_file($moduledir.'/'.$sqlfilename)) {
1417                 $rc = execSQL($moduledir.'/'.$sqlfilename);
1418             }
1419             
1420             // then run .php scripts
1421             return (_modules_doinclude($moduledir.'/install.php', $modulename) && $rc);
1422         break;
1423         case 'uninstall':
1424             // run uninstall .php scripts first
1425             $rc = _modules_doinclude($moduledir.'/uninstall.php', $modulename);
1426             
1427             $sqlfilename = "uninstall.sql";
1428             
1429             // then uninstall sql files
1430             if (is_file($moduledir.'/'.$sqlfilename)) {
1431                 return ($rc && execSQL($moduledir.'/'.$sqlfilename));
1432             } else {
1433         return $rc;
1434       }
1435             
1436         break;
1437         default:
1438             return false;
1439     }
1440     
1441     return true;
1442 }
1443
1444 function _modules_doinclude($filename, $modulename) {
1445     // we provide the following variables to the included file (as well as $filename and $modulename)
1446     global $db, $amp_conf, $asterisk_conf;
1447     
1448     if (file_exists($filename) && is_file($filename)) {
1449         return include_once($filename);
1450     } else {
1451     return true;
1452   }
1453 }
1454
1455 /* module_get_annoucements()
1456
1457     Get's any annoucments, security warnings, etc. that may be related to the current freepbx version. Also
1458     transmits a uniqueid to help track the number of installations using the online module admin system.
1459     The uniqueid used is completely anonymous and not trackable.
1460 */
1461 function module_get_annoucements() {
1462     global $db;
1463     global $amp_conf;
1464     $firstinstall=false;
1465     $type=null;
1466
1467     $sql = "SELECT * FROM module_xml WHERE id = 'installid'";
1468     $result = sql($sql,'getRow',DB_FETCHMODE_ASSOC);
1469
1470     // if not set so this is a first time install
1471     // get a new hash to account for first time install
1472     //
1473     if (!isset($result['data']) || trim($result['data']) == "") {
1474
1475         $firstinstall=true;
1476         $install_hash = _module_generate_unique_id();
1477         $installid = $install_hash['uniqueid'];
1478         $type = $install_hash['type'];
1479
1480         // save the hash so we remeber this is a first time install
1481         //
1482         $data4sql = $db->escapeSimple($installid);
1483         sql("INSERT INTO module_xml (id,time,data) VALUES ('installid',".time().",'".$data4sql."')");
1484         $data4sql = $db->escapeSimple($type);
1485         sql("INSERT INTO module_xml (id,time,data) VALUES ('type',".time().",'".$data4sql."')");
1486
1487     // Not a first time so save the queried hash and check if there is a type set
1488     //
1489     } else {
1490         $installid=$result['data'];
1491         $sql = "SELECT * FROM module_xml WHERE id = 'type'";
1492         $result = sql($sql,'getRow',DB_FETCHMODE_ASSOC);
1493
1494         if (isset($result['data']) && trim($result['data']) != "") {
1495             $type=$result['data'];
1496         }
1497     }
1498
1499     // Now we have the id and know if this is a firstime install so we can get the announcement
1500     //
1501     $options = "?installid=".urlencode($installid);
1502
1503     if (trim($type) != "") {
1504         $options .= "&type=".urlencode($type);
1505     }
1506     if ($firstinstall) {
1507         $options .= "&firstinstall=yes";
1508     }
1509     $engver=engine_getinfo();
1510     if ($engver['engine'] == 'asterisk' && trim($engver['engine']) != "") {
1511         $options .="&astver=".urlencode($engver['version']);
1512     } else {
1513         $options .="&astver=".urlencode($engver['raw']);
1514     }
1515   $options .= "&phpver=".urlencode(phpversion());
1516
1517   $distro_info = _module_distro_id();
1518   $options .= "&distro=".urlencode($distro_info['pbx_type']);
1519   $options .= "&distrover=".urlencode($distro_info['pbx_version']);
1520   if (function_exists('core_users_list')) {
1521     $options .= "&ucount=".urlencode(count(core_users_list()));   
1522 }
1523     $fn = generate_module_repo_url("/version-".getversion().".html".$options);
1524   $announcement = file_get_contents_url($fn);
1525     return $announcement;
1526 }
1527
1528 /* Assumes properly formated input, which is ok since
1529    this is a private function and error checking is done
1530      through proper regex scanning above
1531
1532      Returns: random md5 hash
1533  */
1534 function _module_generate_random_id($type=null, $mac=null) {
1535
1536     if (trim($mac) == "") {
1537         $id['uniqueid'] = md5(mt_rand());
1538     } else {
1539         // MD5 hash of the MAC so it is not identifiable
1540         //
1541         $id['uniqueid'] = md5($mac);
1542     }
1543     $id['type'] = $type;
1544
1545     return $id;
1546 }
1547
1548 /* _module_generate_unique_id
1549
1550     The purpose of this function is to generate a unique id that will try
1551     and regenerate the same unique id on a system if called multiple
1552     times. The id is unique but is not in any way identifable so that
1553     privacy is not compromised.
1554
1555     Returns:
1556
1557     Array: ["uniqueid"] => unique_md5_hash
1558            ["type"]     => type_passed_in
1559  
1560 */
1561 function _module_generate_unique_id($type=null) {
1562
1563     // Array of macs that require identification so we know these are not
1564     // 'real' systems. Either home setups or test environments
1565     //
1566     $ids = array('000C29' => 'vmware',
1567                  '000569' => 'vmware',
1568                  '00163E' => 'xensource'
1569                 );
1570     $mac_address = array();
1571     $chosen_mac = null;
1572
1573     // TODO: put proper path in places for ifconfig, try various locations where it may be if
1574     //       non-0 return code.
1575     //
1576     exec('/sbin/ifconfig',$output, $return);
1577
1578     if ($return != '0') {
1579
1580         // OK try another path
1581         //
1582         exec('ifconfig',$output, $return);
1583
1584         if ($return != '0') {
1585             // No seed available so return with random seed
1586             return _module_generate_random_id($type);
1587         }
1588     }
1589
1590     // parse the output of ifconfig to get list of MACs returned
1591     //
1592     foreach ($output as $str) {
1593         // make sure each line contains a valid MAC and IP address and then
1594         //
1595         if (preg_match("/([0-9A-Fa-f]{2}(:[0-9A-Fa-f]{2}){5})/", $str, $mac)) {
1596             $mac_address[] = strtoupper(preg_replace("/:/","",$mac[0]));
1597         }
1598     }
1599
1600     if (trim($type) == "") {
1601         foreach ($mac_address as $mac) {
1602             $id = substr($mac,0,6);
1603
1604             // If we care about this id, then choose it and set the type
1605             // we only choose the first one we see
1606             //
1607             if (array_key_exists($id,$ids)) {
1608                 $chosen_mac = $mac;
1609                 $type = $ids[$id];
1610                 break;
1611             }
1612         }
1613     }
1614
1615     // Now either we have a chosen_mac, we will use the first mac, or if something went wrong
1616     // and there is nothing in the array (couldn't find a mac) then we will make it purely random
1617     //
1618   if ($type == "vmware" || $type == "xensource") {
1619         // vmware, xensource machines will have repeated macs so make random
1620         return _module_generate_random_id($type);
1621     } else if ($chosen_mac != "") {
1622         return _module_generate_random_id($type, $chosen_mac);
1623     } else if (isset($mac_address[0])) {
1624         return _module_generate_random_id($type, $mac_address[0]);
1625     } else {
1626         return _module_generate_random_id($type);
1627     }
1628 }
1629
1630 function module_run_notification_checks() {
1631     global $db;
1632     $modules_needup = module_getinfo(false, MODULE_STATUS_NEEDUPGRADE);
1633     $modules_broken = module_getinfo(false, MODULE_STATUS_BROKEN);
1634     
1635     $notifications =& notifications::create($db);
1636     if ($cnt = count($modules_needup)) {
1637         $text = (($cnt > 1) ? sprintf(_('You have %s disabled modules'), $cnt) : _('You have a disabled module'));
1638         $desc = _('The following modules are disabled because they need to be upgraded:')."\n".implode(", ",array_keys($modules_needup));
1639         $desc .= "\n\n"._('You should go to the module admin page to fix these.');
1640         $notifications->add_error('freepbx', 'modules_disabled', $text, $desc, '?type=tool&display=modules');
1641     } else {
1642         $notifications->delete('freepbx', 'modules_disabled');
1643     }
1644     if ($cnt = count($modules_broken)) {
1645         $text = (($cnt > 1) ? sprintf(_('You have %s broken modules'), $cnt) : _('You have a broken module'));
1646         $desc = _('The following modules are disabled because they are broken:')."\n".implode(", ",array_keys($modules_broken));
1647         $desc .= "\n\n"._('You should go to the module admin page to fix these.');
1648         $notifications->add_critical('freepbx', 'modules_broken', $text, $desc, '?type=tool&display=modules', false);
1649     } else {
1650         $notifications->delete('freepbx', 'modules_broken');
1651     }
1652 }
1653
1654 /** Replaces variables in a string with the values from ampconf
1655  * eg, "%AMPWEBROOT%/admin" => "/var/www/html/admin"
1656  */
1657 function _module_ampconf_string_replace($string) {
1658     $freepbx_conf =& freepbx_conf::create();
1659     
1660     $target = array();
1661     $replace = array();
1662     
1663     foreach ($freepbx_conf->conf as $key=>$value) {
1664         $target[] = '%'.$key.'%';
1665         $replace[] = $value;
1666     }
1667     
1668     return str_replace($target, $replace, $string);
1669 }
1670
1671 function _module_distro_id() {
1672   static $pbx_type;
1673   static $pbx_version;
1674
1675   if (isset($pbx_type)) {
1676     return array('pbx_type' => $pbx_type, 'pbx_version' => $pbx_version);
1677   }
1678
1679   // FreePBX Distro
1680   if (file_exists('/etc/asterisk/freepbxdistro-version')) {
1681     $pbx_type = 'freepbxdistro';
1682     $pbx_version = trim(file_get_contents('/etc/asterisk/freepbxdistro-version'));
1683
1684   // Trixbox
1685   } elseif (file_exists('/etc/trixbox/trixbox-version')) {
1686     $pbx_type = 'trixbox';
1687     $pbx_version = trim(file_get_contents('/etc/trixbox/trixbox-version'));
1688
1689   // AsteriskNOW
1690   } elseif (file_exists('/etc/asterisknow-version')) {
1691     $pbx_type = 'asterisknow';
1692     $pbx_version = trim(file_get_contents('/etc/asterisknow-version'));
1693  
1694   // Elastix
1695   } elseif (is_dir('/usr/share/elastix') || file_exists('/usr/share/elastix/pre_elastix_version.info')) {
1696     $pbx_type = 'elastix';
1697     $pbx_version = '';
1698     if (class_exists('PDO') && file_exists('/var/www/db/settings.db')) {
1699       $elastix_db = new PDO('sqlite:/var/www/db/settings.db');
1700       $result = $elastix_db->query("SELECT value FROM settings WHERE key='elastix_version_release'");
1701       if ($result !== false) foreach ($result as $row) {
1702         if (isset($row['value'])) {
1703           $pbx_version = $row['value'];
1704           break;
1705         }
1706       }
1707     }
1708     if (!$pbx_version && file_exists('/usr/share/elastix/pre_elastix_version.info')) {
1709       $pbx_version = trim(file_get_contents('/usr/share/elastix/pre_elastix_version.info'));
1710     }
1711     if (!$pbx_version) {
1712       $pbx_version = '2.X+';
1713     }
1714
1715   // PIAF
1716   } elseif (file_exists('/etc/pbx/.version') || file_exists('/etc/pbx/.color')) {
1717     $pbx_type = 'piaf';
1718     $pbx_version = '';
1719     if (file_exists('/etc/pbx/.version')) {
1720       $pbx_version = trim(file_get_contents('/etc/pbx/.version'));
1721     }
1722     if (file_exists('/etc/pbx/.color')) {
1723       $pbx_version .= '.' . trim(file_get_contents('/etc/pbx/.color'));
1724     }
1725     if (!$pbx_version) {
1726       if (file_exists('/etc/pbx/ISO-Version')) {
1727         $pbx_ver_raw = trim(file_get_contents('/etc/pbx/ISO-Version'));
1728         $pbx_arr = explode('=',$pbx_ver_raw);
1729         $pbx_version = $pbx_arr[count($pbx_arr)-1];
1730       } else {
1731         $pbx_version = 'unknown';
1732       }
1733     }
1734
1735   // Old PIAF or Fonica
1736   } elseif (file_exists('/etc/pbx/version') || file_exists('/etc/pbx/ISO-Version')) {
1737     $pbx_type = 'fonica';
1738     if (file_exists('/etc/pbx/ISO-Version')) {
1739       $pbx_ver_raw = trim(file_get_contents('/etc/pbx/ISO-Version'));
1740       $pbx_arr = explode('=',$pbx_ver_raw);
1741       $pbx_version = $pbx_arr[count($pbx_arr)-1];
1742       if (stristr($pbx_arr[0],'foncordiax') !== false) {
1743         $pbx_version .= '.pro';
1744       } else {
1745         $pbx_version = str_replace(' ','.',$pbx_version);
1746         if ($pbx_version != '1.0.standard') {
1747           $pbx_type = 'piaf';
1748         }
1749       }
1750     } else {
1751       $pbx_version = 'unknown';
1752     }
1753   } else {
1754     $pbx_type = 'unknown';
1755     $pbx_version = 'unknown';
1756   }
1757   return array('pbx_type' => $pbx_type, 'pbx_version' => $pbx_version);
1758 }
1759
Note: See TracBrowser for help on using the browser.