Open Source Training Seminar FreePBX Paid Support

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

Revision 6492, 123.8 kB (checked in by p_lindheimer, 2 days ago)

Merged revisions 6458-6459,6461-6490 via svnmerge from
http://svn.freepbx.org/freepbx/branches/2.5

........

r6462 | p_lindheimer | 2008-08-24 20:37:24 -0700 (Sun, 24 Aug 2008) | 1 line


Creating release 2.5.0rc2

........

r6474 | p_lindheimer | 2008-08-25 12:06:13 -0700 (Mon, 25 Aug 2008) | 1 line


fixes #3104 - looks like some of the code paths do extra urlencodes/decodes - this looks like it does it

........

r6477 | p_lindheimer | 2008-08-25 15:26:43 -0700 (Mon, 25 Aug 2008) | 1 line


fixes #3107 don't include module specific css/js files twice in some circumstances

........

r6478 | sasargen | 2008-08-26 05:30:34 -0700 (Tue, 26 Aug 2008) | 1 line


fixes #3104 - removes manual urlencode of checkbox values because they are form fields and are automatically urlencoded by the browser when the form is submitted

........

r6483 | p_lindheimer | 2008-08-26 12:15:04 -0700 (Tue, 26 Aug 2008) | 1 line


closes #3093 reset the execution time limit to the system configured limit before each module download and install so downloading many modules at once does not result in a timeout failure, this still counts on a reasonable php.ini setting for any given installation of which the default is typically adeqaute

........

r6484 | p_lindheimer | 2008-08-26 19:06:13 -0700 (Tue, 26 Aug 2008) | 1 line


closes #3113 and ref #3090 - puts error in notification panel if magic_quotes_gpc is enabled

........

r6486 | p_lindheimer | 2008-08-27 11:40:19 -0700 (Wed, 27 Aug 2008) | 1 line


improve the symlink failure message to provide feedback on what can be done to resolve the issue

........

r6490 | p_lindheimer | 2008-08-27 22:29:03 -0700 (Wed, 27 Aug 2008) | 1 line


updated CHANGES

........

  • Property svn:mime-type set to text/html
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
Line 
1 <?php /* $id$ */
2 //Copyright (C) 2004 Coalescent Systems Inc. (info@coalescentsystems.ca)
3 //
4 //This program is free software; you can redistribute it and/or
5 //modify it under the terms of the GNU General Public License
6 //as published by the Free Software Foundation; either version 2
7 //of the License, or (at your option) any later version.
8 //
9 //This program is distributed in the hope that it will be useful,
10 //but WITHOUT ANY WARRANTY; without even the implied warranty of
11 //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 //GNU General Public License for more details.
13
14 require_once( (defined('AMP_BASE_INCLUDE_PATH') ? AMP_BASE_INCLUDE_PATH.'/' : '').'featurecodes.class.php');
15 require_once( (defined('AMP_BASE_INCLUDE_PATH') ? AMP_BASE_INCLUDE_PATH.'/' : '').'components.class.php');
16
17 define('MODULE_STATUS_NOTINSTALLED', 0);
18 define('MODULE_STATUS_DISABLED', 1);
19 define('MODULE_STATUS_ENABLED', 2);
20 define('MODULE_STATUS_NEEDUPGRADE', 3);
21 define('MODULE_STATUS_BROKEN', -1);
22
23 $amp_conf_defaults = array(
24         'AMPDBENGINE'    => array('std' , 'mysql'),
25         'AMPDBNAME'      => array('std' , 'asterisk'),
26         'AMPENGINE'      => array('std' , 'asterisk'),
27         'ASTMANAGERPORT' => array('std' , '5038'),
28         'AMPDBHOST'      => array('std' , 'localhost'),
29         'AMPDBUSER'      => array('std' , 'asteriskuser'),
30         'AMPDBPASS'      => array('std' , 'amp109'),
31         'AMPMGRUSER'     => array('std' , 'admin'),
32         'AMPMGRPASS'     => array('std' , 'amp111'),
33         'FOPPASSWORD'    => array('std' , 'passw0rd'),
34         'FOPSORT'        => array('std' , 'extension'),
35         'AMPSYSLOGLEVEL '=> array('std' , 'LOG_ERR'),
36
37         'ASTETCDIR'      => array('dir' , '/etc/asterisk'),
38         'ASTMODDIR'      => array('dir' , '/usr/lib/asterisk/modules'),
39         'ASTVARLIBDIR'   => array('dir' , '/var/lib/asterisk'),
40         'ASTAGIDIR'      => array('dir' , '/var/lib/asterisk/agi-bin'),
41         'ASTSPOOLDIR'    => array('dir' , '/var/spool/asterisk/'),
42         'ASTRUNDIR'      => array('dir' , '/var/run/asterisk'),
43         'ASTLOGDIR'      => array('dir' , '/var/log/asterisk'),
44         'AMPBIN'         => array('dir' , '/var/lib/asterisk/bin'),
45         'AMPSBIN'        => array('dir' , '/usr/sbin'),
46         'AMPWEBROOT'     => array('dir' , '/var/www/html'),
47         'FOPWEBROOT'     => array('dir' , '/var/www/html/panel'),
48
49         'USECATEGORIES'  => array('bool' , true),
50         'ENABLECW'       => array('bool' , true),
51         'CWINUSEBUSY'    => array('bool' , true),
52         'FOPRUN'         => array('bool' , true),
53         'AMPBADNUMBER'   => array('bool' , true),
54         'DEVEL'          => array('bool' , false),
55         'DEVELRELOAD'    => array('bool' , false),
56         'CUSTOMASERROR'  => array('bool' , true),
57         'DYNAMICHINTS'   => array('bool' , false),
58         'BADDESTABORT'   => array('bool' , false),
59         'SERVERINTITLE'  => array('bool' , false),
60         'XTNCONFLICTABORT' => array('bool' , false),
61         'USEDEVSTATE'    => array('bool' , false),
62         'MODULEADMINWGET'=> array('bool' , false),
63         'AMPDISABLELOG'  => array('bool' , true),
64         'AMPENABLEDEVELDEBUG'=> array('bool' , false),
65         'AMPMPG123'      => array('bool' , true),
66 );
67
68 function parse_amportal_conf($filename) {
69         global $amp_conf_defaults;
70
71         /* defaults
72          * This defines defaults and formating to assure consistency across the system so that
73          * components don't have to keep being 'gun shy' about these variables.
74          *
75
76          */
77         $file = file($filename);
78         if (is_array($file)) {
79                 foreach ($file as $line) {
80                         if (preg_match("/^\s*([a-zA-Z0-9_]+)=([a-zA-Z0-9 .&-@=_<>\"\']+)\s*$/",$line,$matches)) {
81                                 $conf[ $matches[1] ] = $matches[2];
82                         }
83                 }
84         } else {
85                 die_freepbx("<h1>".sprintf(_("Missing or unreadable config file (%s)...cannot continue"), $filename)."</h1>");
86         }
87        
88         // set defaults
89         foreach ($amp_conf_defaults as $key=>$arr) {
90
91                 switch ($arr[0]) {
92                         // for type dir, make sure there is no trailing '/' to keep consistent everwhere
93                         //
94                         case 'dir':
95                                 if (!isset($conf[$key]) || trim($conf[$key]) == '') {
96                                         $conf[$key] = $arr[1];
97                                 } else {
98                                         $conf[$key] = rtrim($conf[$key],'/');
99                                 }
100                                 break;
101                         // booleans:
102                         // "yes", "true", "on", true, 1 (case-insensitive) will be treated as true, everything else is false
103                         //
104                         case 'bool':
105                                 if (!isset($conf[$key])) {
106                                         $conf[$key] = $arr[1];
107                                 } else {
108                                         $conf[$key] = ($conf[$key] === true || strtolower($conf[$key]) == 'true' || $conf[$key] === 1 || $conf[$key] == '1'
109                                                                             || strtolower($conf[$key]) == 'yes' ||  strtolower($conf[$key]) == 'on');
110                                 }
111                                 break;
112                         default:
113                                 if (!isset($conf[$key])) {
114                                         $conf[$key] = $arr[1];
115                                 } else {
116                                         $conf[$key] = trim($conf[$key]);
117                                 }
118                 }
119         }
120
121 /*                     
122   TODO: what was this, should the comment be removed?
123
124         if (($amp_conf["AMPDBENGINE"] == "sqlite") && (!isset($amp_conf["AMPDBENGINE"])))
125                 $amp_conf["AMPDBFILE"] = "/var/lib/freepbx/freepbx.sqlite";
126 */
127
128         return $conf;
129 }
130
131 function parse_asterisk_conf($filename) {
132         //TODO: Should the correction of $amp_conf be passed by refernce and optional?
133         //
134         global $amp_conf;
135                
136         $convert = array(
137                 'astetcdir'    => 'ASTETCDIR',
138                 'astmoddir'    => 'ASTMODDIR',
139                 'astvarlibdir' => 'ASTVARLIBDIR',
140                 'astagidir'    => 'ASTAGIDIR',
141                 'astspooldir'  => 'ASTSPOOLDIR',
142                 'astrundir'    => 'ASTRUNDIR',
143                 'astlogdir'    => 'ASTLOGDIR'
144         );
145
146         $file = file($filename);
147         foreach ($file as $line) {
148                 if (preg_match("/^\s*([a-zA-Z0-9]+)\s* => \s*(.*)\s*([;#].*)?/",$line,$matches)) {
149                         $conf[ $matches[1] ] = rtrim($matches[2],"/ \t");
150                 }
151         }
152
153         // Now that we parsed asterisk.conf, we need to make sure $amp_conf is consistent
154         // so just set it to what we found, since this is what asterisk will use anyhow.
155         //
156         foreach ($convert as $ast_conf_key => $amp_conf_key) {
157                 if (isset($conf[$ast_conf_key])) {
158                         $amp_conf[$amp_conf_key] = $conf[$ast_conf_key];
159                 }
160         }
161         return $conf;
162 }
163
164
165 define("NOTIFICATION_TYPE_CRITICAL", 100);
166 define("NOTIFICATION_TYPE_SECURITY", 200);
167 define("NOTIFICATION_TYPE_UPDATE",  300);
168 define("NOTIFICATION_TYPE_ERROR",    400);
169 define("NOTIFICATION_TYPE_WARNING" , 500);
170 define("NOTIFICATION_TYPE_NOTICE",   600);
171
172 class notifications {
173
174         var $not_loaded = true;
175         var $notification_table = array();
176         var $_db;
177                
178         function &create(&$db) {
179                 static $obj;
180                 if (!isset($obj)) {
181                         $obj = new notifications($db);
182                 }
183                 return $obj;
184         }
185
186         function notifications(&$db) {
187                 $this->_db =& $db;
188         }
189
190
191         function add_critical($module, $id, $display_text, $extended_text="", $link="", $reset=true, $candelete=false) {
192                 $this->_add_type(NOTIFICATION_TYPE_CRITICAL, $module, $id, $display_text, $extended_text, $link, $reset, $candelete);
193         }
194         function add_security($module, $id, $display_text, $extended_text="", $link="", $reset=true, $candelete=false) {
195                 $this->_add_type(NOTIFICATION_TYPE_SECURITY, $module, $id, $display_text, $extended_text, $link, $reset, $candelete);
196         }
197         function add_update($module, $id, $display_text, $extended_text="", $link="", $reset=false, $candelete=false) {
198                 $this->_add_type(NOTIFICATION_TYPE_UPDATE, $module, $id, $display_text, $extended_text, $link, $reset, $candelete);
199         }
200         function add_error($module, $id, $display_text, $extended_text="", $link="", $reset=false, $candelete=false) {
201                 $this->_add_type(NOTIFICATION_TYPE_ERROR, $module, $id, $display_text, $extended_text, $link, $reset, $candelete);
202         }
203         function add_warning($module, $id, $display_text, $extended_text="", $link="", $reset=false, $candelete=false) {
204                 $this->_add_type(NOTIFICATION_TYPE_WARNING, $module, $id, $display_text, $extended_text, $link, $reset, $candelete);
205         }
206         function add_notice($module, $id, $display_text, $extended_text="", $link="", $reset=false, $candelete=true) {
207                 $this->_add_type(NOTIFICATION_TYPE_NOTICE, $module, $id, $display_text, $extended_text, $link, $reset, $candelete);
208         }
209
210
211         function list_critical($show_reset=false) {
212                 return $this->_list(NOTIFICATION_TYPE_CRITICAL, $show_reset);
213         }
214         function list_security($show_reset=false) {
215                 return $this->_list(NOTIFICATION_TYPE_SECURITY, $show_reset);
216         }
217         function list_update($show_reset=false) {
218                 return $this->_list(NOTIFICATION_TYPE_UPDATE, $show_reset);
219         }
220         function list_error($show_reset=false) {
221                 return $this->_list(NOTIFICATION_TYPE_ERROR, $show_reset);
222         }
223         function list_warning($show_reset=false) {
224                 return $this->_list(NOTIFICATION_TYPE_WARNING, $show_reset);
225         }
226         function list_notice($show_reset=false) {
227                 return $this->_list(NOTIFICATION_TYPE_NOTICE, $show_reset);
228         }
229         function list_all($show_reset=false) {
230                 return $this->_list("", $show_reset);
231         }
232
233
234         function reset($module, $id) {
235                 $module        = q($module);
236                 $id            = q($id);
237
238                 $sql = "UPDATE notifications SET reset = 1 WHERE module = $module AND id = $id";
239                 sql($sql);
240         }
241
242         function delete($module, $id) {
243                 $module        = q($module);
244                 $id            = q($id);
245
246                 $sql = "DELETE FROM notifications WHERE module = $module AND id = $id";
247                 sql($sql);
248         }
249
250         function safe_delete($module, $id) {
251                 $module        = q($module);
252                 $id            = q($id);
253
254                 $sql = "DELETE FROM notifications WHERE module = $module AND id = $id AND candelete = 1";
255                 sql($sql);
256         }
257
258         /* Internal functions
259          */
260
261         function _add_type($level, $module, $id, $display_text, $extended_text="", $link="", $reset=false, $candelete=false) {
262                 if ($this->not_loaded) {
263                         $this->notification_table = $this->_list("",true);
264                         $this->not_loaded = false;
265                 }
266
267                 $existing_row = false;
268                 foreach ($this->notification_table as $row) {
269                         if ($row['module'] == $module && $row['id'] == $id ) {
270                                 $existing_row = $row;
271                                 break;
272                         }
273                 }
274                 // Found an existing row - check if anything changed or if we are suppose to reset it
275                 //
276                 $candelete = $candelete ? 1 : 0;
277                 if ($existing_row) {
278
279                         if (($reset && $existing_row['reset'] == 1) || $existing_row['level'] != $level || $existing_row['display_text'] != $display_text || $existing_row['extended_text'] != $extended_text || $existing_row['link'] != $link || $existing_row['candelete'] == $candelete) {
280
281                                 // If $reset is set to the special case of PASSIVE then the updates will not change it's value in an update
282                                 //
283                                 $reset_value = ($reset == 'PASSIVE') ? $existing_row['reset'] : 0;
284
285                                 $module        = q($module);
286                                 $id            = q($id);
287                                 $level         = q($level);
288                                 $display_text  = q($display_text);
289                                 $extended_text = q($extended_text);
290                                 $link          = q($link);
291                                 $now = time();
292                                 $sql = "UPDATE notifications SET
293                                         level = $level,
294                                         display_text = $display_text,
295                                         extended_text = $extended_text,
296                                         link = $link,
297                                         reset = $reset_value,
298                                         candelete = $candelete,
299                                         timestamp = $now
300                                         WHERE module = $module AND id = $id
301                                 ";
302                                 sql($sql);
303
304                                 // TODO: I should really just add this to the internal cache, but really
305                                 //       how often does this get called that if is a big deal.
306                                 $this->not_loaded = true;
307                         }
308                 } else {
309                         // No existing row so insert this new one
310                         //
311                         $now           = time();
312                         $module        = q($module);
313                         $id            = q($id);
314                         $level         = q($level);
315                         $display_text  = q($display_text);
316                         $extended_text = q($extended_text);
317                         $link          = q($link);
318                         $sql = "INSERT INTO notifications
319                                 (module, id, level, display_text, extended_text, link, reset, candelete, timestamp)
320                                 VALUES
321                                 ($module, $id, $level, $display_text, $extended_text, $link, 0, $candelete, $now)
322                         ";
323                         sql($sql);
324
325                         // TODO: I should really just add this to the internal cache, but really
326                         //       how often does this get called that if is a big deal.
327                         $this->not_loaded = true;
328                 }
329         }
330
331         function _list($level, $show_reset=false) {
332
333                 $level = q($level);
334                 $where = array();
335
336                 if (!$show_reset) {
337                         $where[] = "reset = 0";
338                 }
339
340                 switch ($level) {
341                         case NOTIFICATION_TYPE_CRITICAL:
342                         case NOTIFICATION_TYPE_SECURITY:
343                         case NOTIFICATION_TYPE_UPDATE:
344                         case NOTIFICATION_TYPE_ERROR:
345                         case NOTIFICATION_TYPE_WARNING:
346                         case NOTIFICATION_TYPE_NOTICE:
347                                 $where[] = "level = $level ";
348                                 break;
349                         default:
350                 }
351                 $sql = "SELECT * FROM notifications ";
352                 if (count($where)) {
353                         $sql .= " WHERE ".implode(" AND ", $where);
354                 }
355                 $sql .= " ORDER BY level, module";
356
357                 $list = sql($sql,"getAll",DB_FETCHMODE_ASSOC);
358                 return $list;
359         }
360         /* Returns the number of active notifications
361          */
362         function get_num_active() {
363                 $sql = "SELECT COUNT(id) FROM notifications WHERE reset = 0";
364                 return sql($sql,'getOne');
365         }
366 }
367
368 class cronmanager {
369         /**
370          * note: time is the hour time of day a job should run, -1 indicates don't care
371          */
372
373         function &create(&$db) {
374                 static $obj;
375                 if (!isset($obj)) {
376                         $obj = new cronmanager($db);
377                 }
378                 return $obj;
379         }
380
381         function cronmanager(&$db) {
382                 $this->_db =& $db;
383         }
384
385         function save_email($address) {
386                 $address = q($address);
387                 sql("DELETE FROM admin WHERE variable = 'email'");
388                 sql("INSERT INTO admin (variable, value) VALUES ('email', $address)");
389         }
390
391         function get_email() {
392                 $sql = "SELECT value FROM admin WHERE variable = 'email'";
393                 return sql($sql, 'getOne');
394         }
395
396         function save_hash($id, &$string) {
397                 $hash = md5($string);
398                 $id = q($id);
399                 sql("DELETE FROM admin WHERE variable = $id");
400                 sql("INSERT INTO admin (variable, value) VALUE ($id, '$hash')");
401         }
402
403         function check_hash($id, &$string) {
404                 $id = q($id);
405                 $sql = "SELECT value FROM admin WHERE variable = $id LIMIT 1";
406                 $hash = sql($sql, "getOne");
407                 return ($hash == md5($string));
408         }
409
410         function enable_updates($freq=24) {
411                 global $amp_conf;
412
413                 $night_time = array(19,20,21,22,23,0,1,2,3,4,5);
414                 $run_time = $night_time[rand(0,10)];
415                 $command = $amp_conf['AMPBIN']."/module_admin listonline";
416                 $lasttime = 0;
417
418                 $sql = "SELECT * FROM cronmanager WHERE module = 'module_admin' AND id = 'UPDATES'";
419                 $result = sql($sql, "getAll",DB_FETCHMODE_ASSOC);
420                 if (count($result)) {
421                         $sql = "UPDATE cronmanager SET
422                                   freq = '$freq',
423                                                           command = '$command'
424                                                   WHERE
425                                                     module = 'module_admin' AND id = 'UPDATES' 
426                                ";
427                 } else {
428                         $sql = "INSERT INTO cronmanager
429                                 (module, id, time, freq, lasttime, command)
430                                                         VALUES
431                                                         ('module_admin', 'UPDATES', '$run_time', $freq, 0, '$command')
432                                                 ";
433                 }
434                 sql($sql);
435         }
436
437         function disable_updates() {
438                 sql("DELETE FROM cronmanager WHERE module = 'module_admin' AND id = 'UPDATES'");
439         }
440
441         function updates_enabled() {
442                 $results = sql("SELECT module, id FROM cronmanager WHERE module = 'module_admin' AND id = 'UPDATES'",'getAll');
443                 return count($results);
444         }
445
446         /** run_jobs()
447          *  select all entries that need to be run now and run them, then update the times.
448          * 
449          *  1. select all entries
450          *  2. foreach entry, if its paramters indicate it should be run, then run it and
451          *     update it was run in the time stamp.
452          */
453         function run_jobs() {
454
455                 $errors = 0;
456                 $error_arr = array();
457
458                 $now = time();
459                 $jobs = sql("SELECT * FROM cronmanager","getAll", DB_FETCHMODE_ASSOC);
460                 foreach ($jobs as $job) {
461                         $nexttime = $job['lasttime'] + $job['freq']*3600;
462                         if ($nexttime <= $now) {
463                                 if ($job['time'] >= 0 && $job['time'] < 24) {
464                                         $date_arr = getdate($now);
465                                         // Now if lasttime is 0, then we want this kicked off at the proper hour
466                                         // after wich the frequencey will set the pace for same time each night
467                                         //
468                                         if (($date_arr['hours'] != $job['time']) && !$job['lasttime']) {
469                                                 continue;
470                                         }
471                                 }
472                         } else {
473                                 // no need to run job, time is not up yet
474                                 continue;
475                         }
476                         // run the job
477                         exec($job['command'],$job_out,$ret);
478                         if ($ret) {
479                                 $errors++;
480                                 $error_arr[] = array($job['command'],$ret);
481
482                                 // If there where errors, let's print them out in case the script is being debugged or running
483                                 // from cron which will then put the errors out through the cron system.
484                                 //
485                                 foreach ($job_out as $line) {
486                                         echo $line."\n";
487                                 }
488                         } else {
489                                 $module = $job['module'];
490                                 $id =     $job['id'];
491                                 $sql = "UPDATE cronmanager SET lasttime = $now WHERE module = '$module' AND id = '$id'";
492                                 sql($sql);
493                         }
494                 }
495                 if ($errors) {
496                         $nt =& notifications::create($db);
497                         $text = sprintf(_("Cronmanager encountered %s Errors"),$errors);
498                         $extext = _("The following commands failed with the listed error");
499                         foreach ($error_arr as $item) {
500                                 $extext .= "<br>".$item[0]." (".$item[1].")";
501                         }
502                         $nt->add_error('cron_manager', 'EXECFAIL', $text, $extext, '', true, true);
503                 }
504         }
505 }
506
507 /** check if a specific extension is being used, or get a list of all extensions that are being used
508  * @param mixed     an array of extension numbers to check against, or if boolean true then return list of all extensions
509  * @param array     a hash of module names to search for callbacks, otherwise global $active_modules is used
510  * @return array    returns an empty array if exten not in use, or any array with usage info, or of all usage
511  *                  if exten is boolean true
512  * @description     Upon passing in an array of extension numbers, this api will query all modules to determine if any
513  *                  are using those extension numbers. If so, it will return an array with the usage information
514  *                  as described below, otherwise an empty array. If passed boolean true, it will return an array
515  *                  of the same format with all extensions on the system that are being used.
516  *
517  *                  $exten_usage[$module][$exten]['description'] // description of the extension
518  *                                               ['edit_url']    // a url that could be invoked to edit extension
519  *                                               ['status']      // Status: INUSE, RESERVED, RESTRICTED
520  */
521 function framework_check_extension_usage($exten=true, $module_hash=false) {
522         global $active_modules;
523         $exten_usage = array();
524
525         if (!is_array($module_hash)) {
526                 $module_hash = $active_modules;
527         }
528
529         if (!is_array($exten) && $exten !== true) {
530                 $exten = array($exten);
531         }
532         foreach(array_keys($module_hash) as $mod) {
533                 $function = $mod."_check_extensions";
534                 if (function_exists($function)) {
535                         $module_usage = $function($exten);
536                         if (!empty($module_usage)) {
537                                 $exten_usage[$mod] = $module_usage;
538                         }
539                 }
540         }
541         if ($exten === true) {
542                 return $exten_usage;
543         } else {
544                 foreach (array_keys($exten_usage) as $mod) {
545                         foreach ($exten as $test_exten) {
546                                 if (isset($exten_usage[$mod][$test_exten])) {
547                                         $exten_matches[$mod][$test_exten] = $exten_usage[$mod][$test_exten];
548                                 }
549                         }
550                 }
551         }
552         return $exten_matches;
553 }
554
555 /** check if a specific destination is being used, or get a list of all destinations that are being used
556  * @param mixed     an array of destinations to check against, or if boolean true then return list of all destinations in use
557  * @param array     a hash of module names to search for callbacks, otherwise global $active_modules is used
558  * @return array    returns an empty array if destination not in use, or any array with usage info, or of all usage
559  *                  if dest is boolean true
560  * @description     Upon passing in an array of destinations, this api will query all modules to determine if any
561  *                  are using that destination. If so, it will return an array with the usage information
562  *                  as described below, otherwise an empty array. If passed boolean true, it will return an array
563  *                  of the same format with all destinations on the system that are being used.
564  *
565  *                  $dest_usage[$module][]['dest']        // The destination being used
566  *                                        ['description'] // Description of who is using it
567  *                                        ['edit_url']    // a url that could be invoked to edit the using entity
568  *                                               
569  */
570 function framework_check_destination_usage($dest=true, $module_hash=false) {
571         global $active_modules;
572         $dest_usage = array();
573         $dest_matches = array();
574
575         if (!is_array($module_hash)) {
576                 $module_hash = $active_modules;
577         }
578
579         if (!is_array($dest) && $dest !== true) {
580                 $dest = array($dest);
581         }
582         foreach(array_keys($module_hash) as $mod) {
583                 $function = $mod."_check_destinations";
584                 if (function_exists($function)) {
585                         $module_usage = $function($dest);
586                         if (!empty($module_usage)) {
587                                 $dest_usage[$mod] = $module_usage;
588                         }
589                 }
590         }
591         if ($dest === true) {
592                 return $dest_usage;
593         } else {
594                 /*
595                 $destlist[] = array(
596                         'dest' => $thisdest,
597                         'description' => 'Annoucement: '.$result['description'],
598                         'edit_url' => 'config.php?display=announcement&type='.$type.'&extdisplay='.urlencode($thisid),
599                 );
600                 */
601                 foreach (array_keys($dest_usage) as $mod) {
602                         foreach ($dest as $test_dest) {
603                                 foreach ($dest_usage[$mod] as $dest_item) {
604                                         if ($dest_item['dest'] == $test_dest) {
605                                                 $dest_matches[$mod][] = $dest_item;
606                                         }
607                                 }
608                         }
609                 }
610         }
611         return $dest_matches;
612 }
613
614 /** provide optional alert() box and formatted url info for extension conflicts
615  * @param array     an array of extensions that are in conflict obtained from framework_check_extension_usage
616  * @param boolean   default false. True if url and descriptions should be split, false to combine (see return)
617  * @param boolean   default true. True to echo an alert() box, false to bypass the alert box
618  * @return array    returns an array of formatted URLs with descriptions. If $split is true, retuns an array
619  *                  of the URLs with each element an array in the format of array('label' => 'description, 'url' => 'a url')
620  * @description     This is used upon detecting conflicting extension numbers to provide an optional alert box of the issue
621  *                  by a module which should abort the attempt to create the extension. It also returns an array of
622  *                  URLs that can be displayed by the module to show the conflicting extension(s) and links to edit
623  *                  them or further interogate. The resulting URLs are returned in an array either formatted for immediate
624  *                  display or split into a description and the raw URL to provide more fine grained control (or use with guielements).
625  */
626 function framework_display_extension_usage_alert($usage_arr=array(),$split=false,$alert=true) {
627         $url = array();
628         if (!empty($usage_arr)) {
629                 $conflicts=0;
630                 foreach($usage_arr as $rawmodule => $properties) {
631                         foreach($properties as $exten => $details) {
632                                 $conflicts++;
633                                 if ($conflicts == 1) {
634                                         switch ($details['status']) {
635                                                 case 'INUSE':
636                                                         $str = "Extension $exten not available, it is currently used by ".htmlspecialchars($details['description']).".";
637                                                         if ($split) {
638                                                                 $url[] =  array('label' => "Edit: ".htmlspecialchars($details['description']),
639                                                                                  'url'  =>  $details['edit_url'],
640                                                                                );
641                                                         } else {
642                                                                 $url[] =  "<a href='".$details['edit_url']."'>Edit: ".htmlspecialchars($details['description'])."</a>";
643                                                         }
644                                                         break;
645                                                 default:
646                                                 $str = "This extension is not available: ".htmlspecialchars($details['description']).".";
647                                         }
648                                 } else {
649                                         if ($split) {
650                                                 $url[] =  array('label' => "Edit: ".htmlspecialchars($details['description']),
651                                                                  'url'  =>  $details['edit_url'],
652                                                                                                          );
653                                         } else {
654                                                 $url[] =  "<a href='".$details['edit_url']."'>Edit: ".htmlspecialchars($details['description'])."</a>";
655                                         }
656                                 }
657                         }
658                 }
659                 if ($conflicts > 1) {
660                         $str .= sprintf(" There are %s additonal conflicts not listed",$conflicts-1);
661                 }
662         }
663         if ($alert) {
664                 echo "<script>javascript:alert('$str')</script>";
665         }
666         return($url);
667 }
668
669 /** check if a specific destination is being used, or get a list of all destinations that are being used
670  * @param mixed     an array of destinations to check against
671  * @param array     a hash of module names to search for callbacks, otherwise global $active_modules is used
672  * @return array    array with a message and tooltip to display usage of this destination
673  * @description     This is called to generate a label and tooltip which summarized the usage of this
674  *                  destination and a tooltip listing all the places that use it
675  *
676  */
677 function framework_display_destination_usage($dest, $module_hash=false) {
678
679         if (!is_array($dest)) {
680                 $dest = array($dest);
681         }
682         $usage_list = framework_check_destination_usage($dest, $module_hash);
683         if (!empty($usage_list)) {
684                 $usage_count = 0;
685                 $str = null;
686                 foreach ($usage_list as $mod_list) {
687                         foreach ($mod_list as $details) {
688                                 $usage_count++;
689                                 $str .= $details['description']."<br />";
690                         }
691                 }
692                 $object = $usage_count > 1 ? "Objects":"Object";
693                 return array('text' => '&nbsp;'.sprintf(_("Used as Destination by %s %s"),$usage_count, $object),
694                              'tooltip' => $str,
695                                                                 );
696         } else {
697                 return array();
698         }
699 }
700
701 /** determines which module a list of destinations belongs to, and if the destination object exists
702  * @param mixed     an array of destinations to check against
703  * @param array     a hash of module names to search for callbacks, otherwise global $active_modules is used
704  * @return array    an array structure with informaiton about the destinations (see code)
705  * @description     Mainly used by framework_list_problem_destinations. This function will find the module
706  *                  that a destination belongs to and determine if the object still exits. This allow it to
707  *                  either obtain the identify, identify it as an object that has been deleted, or identify
708  *                  it as an unknown destination, usually a custom destination.
709  *
710  */
711 function framework_identify_destinations($dest, $module_hash=false) {
712         global $active_modules;
713         static $dest_cache = array();
714
715         $dest_results = array();
716
717         $dest_usage = array();
718         $dest_matches = array();
719
720         if (!is_array($module_hash)) {
721                 $module_hash = $active_modules;
722         }
723
724         if (!is_array($dest)) {
725                 $dest = array($dest);
726         }
727
728         foreach ($dest as $target) {
729                 if (isset($dest_cache[$target])) {
730                         $dest_results[$target] = $dest_cache[$target];
731                 } else {
732
733                         $found_owner = false;
734                         foreach(array_keys($module_hash) as $mod) {
735                                 $function = $mod."_getdestinfo";
736                                 if (function_exists($function)) {
737                                         $check_module = $function($target);
738                                         if ($check_module !== false) {
739                                                 $found_owner = true;
740                                                 $dest_cache[$target] = array($mod => $check_module);
741                                                 $dest_results[$target] = $dest_cache[$target];
742                                                 break;
743                                         }
744                                 }
745                         }
746                         if (! $found_owner) {
747                                 //echo "Not Found: $target\n";
748                                 $dest_cache[$target] = false;
749                                 $dest_results[$target] = $dest_cache[$target];
750                         }
751                 }
752         }
753         return $dest_results;
754 }
755
756 /** create a comprehensive list of all destinations that are problematic
757  * @param array     an array of destinations to check against
758  * @param bool      set to true if custome (unknown) destinations should be reported
759  * @return array    an array of the destinations that are empty, orphaned or custom
760  * @description     This function will scan the entire system and identify destinations
761  *                  that are problematic. Either empty, orphaned or an unknow custom
762  *                  destinations. An orphaned destination is one that should belong
763  *                  to a module but the object it would have pointed to does not exist
764  *                  because it was probably deleted.
765  */
766 function framework_list_problem_destinations($module_hash=false, $ignore_custom=false) {
767         global $active_modules;
768
769         if (!is_array($module_hash)) {
770                 $module_hash = $active_modules;
771         }
772
773         $my_dest_arr = array();
774         $problem_dests = array();
775
776         $all_dests = framework_check_destination_usage(true, $module_hash);
777
778         foreach ($all_dests as $dests) {
779                 foreach ($dests as $adest) {
780                         if (!empty($adest['dest'])) {
781                                 $my_dest_arr[] = $adest['dest'];
782                         }
783                 }
784         }
785         $my_dest_arr = array_unique($my_dest_arr);
786
787         $identities = framework_identify_destinations($my_dest_arr, $module_hash);
788
789         foreach ($all_dests as $dests) {
790                 foreach ($dests as $adest) {
791                         if (empty($adest['dest'])) {
792                                 $problem_dests[] = array('status' => 'EMPTY',
793                                                                'dest' => $adest['dest'],
794                                 &nbs