source: trunk/include/functions.inc.php @ 1113

Last change on this file since 1113 was 1113, checked in by rvelices, 18 years ago

fix: image_order cookie path fixed for url rewriting

improve: add function access_denied called when check_status or
check_restrictions fail

fix: french language correction

fix: remove php warnings in clean_iptc_value

split search functions into include/functions_search.inc.php

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 21.5 KB
Line 
1<?php
2// +-----------------------------------------------------------------------+
3// | PhpWebGallery - a PHP based picture gallery                           |
4// | Copyright (C) 2002-2003 Pierrick LE GALL - pierrick@phpwebgallery.net |
5// | Copyright (C) 2003-2006 PhpWebGallery Team - http://phpwebgallery.net |
6// +-----------------------------------------------------------------------+
7// | branch        : BSF (Best So Far)
8// | file          : $Id: functions.inc.php 1113 2006-03-30 00:37:07Z rvelices $
9// | last update   : $Date: 2006-03-30 00:37:07 +0000 (Thu, 30 Mar 2006) $
10// | last modifier : $Author: rvelices $
11// | revision      : $Revision: 1113 $
12// +-----------------------------------------------------------------------+
13// | This program is free software; you can redistribute it and/or modify  |
14// | it under the terms of the GNU General Public License as published by  |
15// | the Free Software Foundation                                          |
16// |                                                                       |
17// | This program is distributed in the hope that it will be useful, but   |
18// | WITHOUT ANY WARRANTY; without even the implied warranty of            |
19// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      |
20// | General Public License for more details.                              |
21// |                                                                       |
22// | You should have received a copy of the GNU General Public License     |
23// | along with this program; if not, write to the Free Software           |
24// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
25// | USA.                                                                  |
26// +-----------------------------------------------------------------------+
27
28include_once( PHPWG_ROOT_PATH .'include/functions_user.inc.php' );
29include_once( PHPWG_ROOT_PATH .'include/functions_session.inc.php' );
30include_once( PHPWG_ROOT_PATH .'include/functions_category.inc.php' );
31include_once( PHPWG_ROOT_PATH .'include/functions_xml.inc.php' );
32include_once( PHPWG_ROOT_PATH .'include/functions_group.inc.php' );
33include_once( PHPWG_ROOT_PATH .'include/functions_html.inc.php' );
34include_once( PHPWG_ROOT_PATH .'include/functions_url.inc.php' );
35
36//----------------------------------------------------------- generic functions
37
38/**
39 * returns an array containing the possible values of an enum field
40 *
41 * @param string tablename
42 * @param string fieldname
43 */
44function get_enums($table, $field)
45{
46  // retrieving the properties of the table. Each line represents a field :
47  // columns are 'Field', 'Type'
48  $result = pwg_query('desc '.$table);
49  while ($row = mysql_fetch_array($result))
50  {
51    // we are only interested in the the field given in parameter for the
52    // function
53    if ($row['Field'] == $field)
54    {
55      // retrieving possible values of the enum field
56      // enum('blue','green','black')
57      $options = explode(',', substr($row['Type'], 5, -1));
58      foreach ($options as $i => $option)
59      {
60        $options[$i] = str_replace("'", '',$option);
61      }
62    }
63  }
64  mysql_free_result($result);
65  return $options;
66}
67
68// get_boolean transforms a string to a boolean value. If the string is
69// "false" (case insensitive), then the boolean value false is returned. In
70// any other case, true is returned.
71function get_boolean( $string )
72{
73  $boolean = true;
74  if ( preg_match( '/^false$/i', $string ) )
75  {
76    $boolean = false;
77  }
78  return $boolean;
79}
80
81/**
82 * returns boolean string 'true' or 'false' if the given var is boolean
83 *
84 * @param mixed $var
85 * @return mixed
86 */
87function boolean_to_string($var)
88{
89  if (is_bool($var))
90  {
91    if ($var)
92    {
93      return 'true';
94    }
95    else
96    {
97      return 'false';
98    }
99  }
100  else
101  {
102    return $var;
103  }
104}
105
106// The function get_moment returns a float value coresponding to the number
107// of seconds since the unix epoch (1st January 1970) and the microseconds
108// are precised : e.g. 1052343429.89276600
109function get_moment()
110{
111  $t1 = explode( ' ', microtime() );
112  $t2 = explode( '.', $t1[0] );
113  $t2 = $t1[1].'.'.$t2[1];
114  return $t2;
115}
116
117// The function get_elapsed_time returns the number of seconds (with 3
118// decimals precision) between the start time and the end time given.
119function get_elapsed_time( $start, $end )
120{
121  return number_format( $end - $start, 3, '.', ' ').' s';
122}
123
124// - The replace_space function replaces space and '-' characters
125//   by their HTML equivalent  &nbsb; and &minus;
126// - The function does not replace characters in HTML tags
127// - This function was created because IE5 does not respect the
128//   CSS "white-space: nowrap;" property unless space and minus
129//   characters are replaced like this function does.
130// - Example :
131//                 <div class="foo">My friend</div>
132//               ( 01234567891111111111222222222233 )
133//               (           0123456789012345678901 )
134// becomes :
135//             <div class="foo">My&nbsp;friend</div>
136function replace_space( $string )
137{
138  //return $string;
139  $return_string = '';
140  // $remaining is the rest of the string where to replace spaces characters
141  $remaining = $string;
142  // $start represents the position of the next '<' character
143  // $end   represents the position of the next '>' character
144  $start = 0;
145  $end = 0;
146  $start = strpos ( $remaining, '<' ); // -> 0
147  $end   = strpos ( $remaining, '>' ); // -> 16
148  // as long as a '<' and his friend '>' are found, we loop
149  while ( is_numeric( $start ) and is_numeric( $end ) )
150  {
151    // $treatment is the part of the string to treat
152    // In the first loop of our example, this variable is empty, but in the
153    // second loop, it equals 'My friend'
154    $treatment = substr ( $remaining, 0, $start );
155    // Replacement of ' ' by his equivalent '&nbsp;'
156    $treatment = str_replace( ' ', '&nbsp;', $treatment );
157    $treatment = str_replace( '-', '&minus;', $treatment );
158    // composing the string to return by adding the treated string and the
159    // following HTML tag -> 'My&nbsp;friend</div>'
160    $return_string.= $treatment.substr( $remaining, $start, $end-$start+1 );
161    // the remaining string is deplaced to the part after the '>' of this
162    // loop
163    $remaining = substr ( $remaining, $end + 1, strlen( $remaining ) );
164    $start = strpos ( $remaining, '<' );
165    $end   = strpos ( $remaining, '>' );
166  }
167  $treatment = str_replace( ' ', '&nbsp;', $remaining );
168  $treatment = str_replace( '-', '&minus;', $treatment );
169  $return_string.= $treatment;
170
171  return $return_string;
172}
173
174// get_extension returns the part of the string after the last "."
175function get_extension( $filename )
176{
177  return substr( strrchr( $filename, '.' ), 1, strlen ( $filename ) );
178}
179
180// get_filename_wo_extension returns the part of the string before the last
181// ".".
182// get_filename_wo_extension( 'test.tar.gz' ) -> 'test.tar'
183function get_filename_wo_extension( $filename )
184{
185  return substr( $filename, 0, strrpos( $filename, '.' ) );
186}
187
188/**
189 * returns an array contening sub-directories, excluding "CVS"
190 *
191 * @param string $dir
192 * @return array
193 */
194function get_dirs($directory)
195{
196  $sub_dirs = array();
197
198  if ($opendir = opendir($directory))
199  {
200    while ($file = readdir($opendir))
201    {
202      if ($file != '.'
203          and $file != '..'
204          and is_dir($directory.'/'.$file)
205          and $file != 'CVS'
206    and $file != '.svn')
207      {
208        array_push($sub_dirs, $file);
209      }
210    }
211  }
212  return $sub_dirs;
213}
214
215// The get_picture_size function return an array containing :
216//      - $picture_size[0] : final width
217//      - $picture_size[1] : final height
218// The final dimensions are calculated thanks to the original dimensions and
219// the maximum dimensions given in parameters.  get_picture_size respects
220// the width/height ratio
221function get_picture_size( $original_width, $original_height,
222                           $max_width, $max_height )
223{
224  $width = $original_width;
225  $height = $original_height;
226  $is_original_size = true;
227
228  if ( $max_width != "" )
229  {
230    if ( $original_width > $max_width )
231    {
232      $width = $max_width;
233      $height = floor( ( $width * $original_height ) / $original_width );
234    }
235  }
236  if ( $max_height != "" )
237  {
238    if ( $original_height > $max_height )
239    {
240      $height = $max_height;
241      $width = floor( ( $height * $original_width ) / $original_height );
242      $is_original_size = false;
243    }
244  }
245  if ( is_numeric( $max_width ) and is_numeric( $max_height )
246       and $max_width != 0 and $max_height != 0 )
247  {
248    $ratioWidth = $original_width / $max_width;
249    $ratioHeight = $original_height / $max_height;
250    if ( ( $ratioWidth > 1 ) or ( $ratioHeight > 1 ) )
251    {
252      if ( $ratioWidth < $ratioHeight )
253      {
254        $width = floor( $original_width / $ratioHeight );
255        $height = $max_height;
256      }
257      else
258      {
259        $width = $max_width;
260        $height = floor( $original_height / $ratioWidth );
261      }
262      $is_original_size = false;
263    }
264  }
265  $picture_size = array();
266  $picture_size[0] = $width;
267  $picture_size[1] = $height;
268  return $picture_size;
269}
270//-------------------------------------------- PhpWebGallery specific functions
271
272/**
273 * returns an array with a list of {language_code => language_name}
274 *
275 * @returns array
276 */
277function get_languages()
278{
279  $dir = opendir(PHPWG_ROOT_PATH.'language');
280  $languages = array();
281
282  while ($file = readdir($dir))
283  {
284    $path = PHPWG_ROOT_PATH.'language/'.$file;
285    if (is_dir($path) and !is_link($path) and file_exists($path.'/iso.txt'))
286    {
287      list($language_name) = @file($path.'/iso.txt');
288      $languages[$file] = $language_name;
289    }
290  }
291  closedir($dir);
292  @asort($languages);
293  @reset($languages);
294
295  return $languages;
296}
297
298/**
299 * replaces the $search into <span style="$style">$search</span> in the
300 * given $string.
301 *
302 * case insensitive replacements, does not replace characters in HTML tags
303 *
304 * @param string $string
305 * @param string $search
306 * @param string $style
307 * @return string
308 */
309function add_style( $string, $search, $style )
310{
311  //return $string;
312  $return_string = '';
313  $remaining = $string;
314
315  $start = 0;
316  $end = 0;
317  $start = strpos ( $remaining, '<' );
318  $end   = strpos ( $remaining, '>' );
319  while ( is_numeric( $start ) and is_numeric( $end ) )
320  {
321    $treatment = substr ( $remaining, 0, $start );
322    $treatment = preg_replace( '/('.$search.')/i',
323                               '<span style="'.$style.'">\\0</span>',
324                               $treatment );
325    $return_string.= $treatment.substr( $remaining, $start, $end-$start+1 );
326    $remaining = substr ( $remaining, $end + 1, strlen( $remaining ) );
327    $start = strpos ( $remaining, '<' );
328    $end   = strpos ( $remaining, '>' );
329  }
330  $treatment = preg_replace( '/('.$search.')/i',
331                             '<span style="'.$style.'">\\0</span>',
332                             $remaining );
333  $return_string.= $treatment;
334
335  return $return_string;
336}
337
338// replace_search replaces a searched words array string by the search in
339// another style for the given $string.
340function replace_search( $string, $search )
341{
342  // FIXME : with new advanced search, this function needs a rewrite
343  return $string;
344
345  $words = explode( ',', $search );
346  $style = 'background-color:white;color:red;';
347  foreach ( $words as $word ) {
348    $string = add_style( $string, $word, $style );
349  }
350  return $string;
351}
352
353function pwg_log( $file, $category, $picture = '' )
354{
355  global $conf, $user;
356
357  if ($conf['log'])
358  {
359   if ( ($conf['history_admin'] ) or  ( (! $conf['history_admin'])  and (!is_admin())  ) )
360    {
361    $login = ($user['id'] == $conf['guest_id'])
362      ? 'guest' : addslashes($user['username']);
363
364    $query = '
365INSERT INTO '.HISTORY_TABLE.'
366  (date,login,IP,file,category,picture)
367  VALUES
368  (NOW(),
369  \''.$login.'\',
370  \''.$_SERVER['REMOTE_ADDR'].'\',
371  \''.addslashes($file).'\',
372  \''.addslashes(strip_tags($category)).'\',
373  \''.addslashes($picture).'\')
374;';
375    pwg_query($query);
376  }
377  }
378}
379
380// format_date returns a formatted date for display. The date given in
381// argument can be a unixdate (number of seconds since the 01.01.1970) or an
382// american format (2003-09-15). By option, you can show the time. The
383// output is internationalized.
384//
385// format_date( "2003-09-15", 'us', true ) -> "Monday 15 September 2003 21:52"
386function format_date($date, $type = 'us', $show_time = false)
387{
388  global $lang;
389
390  list($year,$month,$day,$hour,$minute,$second) = array(0,0,0,0,0,0);
391
392  switch ( $type )
393  {
394    case 'us' :
395    {
396      list($year,$month,$day) = explode('-', $date);
397      break;
398    }
399    case 'unix' :
400    {
401      list($year,$month,$day,$hour,$minute) =
402        explode('.', date('Y.n.j.G.i', $date));
403      break;
404    }
405    case 'mysql_datetime' :
406    {
407      preg_match('/^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/',
408                 $date, $out);
409      list($year,$month,$day,$hour,$minute,$second) =
410        array($out[1],$out[2],$out[3],$out[4],$out[5],$out[6]);
411      break;
412    }
413  }
414  $formated_date = '';
415  // before 1970, Microsoft Windows can't mktime
416  if ($year >= 1970)
417  {
418    // we ask midday because Windows think it's prior to midnight with a
419    // zero and refuse to work
420    $formated_date.= $lang['day'][date('w', mktime(12,0,0,$month,$day,$year))];
421  }
422  $formated_date.= ' '.$day;
423  $formated_date.= ' '.$lang['month'][(int)$month];
424  $formated_date.= ' '.$year;
425  if ($show_time)
426  {
427    $formated_date.= ' '.$hour.':'.$minute;
428  }
429
430  return $formated_date;
431}
432
433function pwg_query($query)
434{
435  global $conf,$page,$debug,$t2;
436
437  $start = get_moment();
438  $result = mysql_query($query) or my_error($query."\n");
439
440  $time = get_moment() - $start;
441
442  if (!isset($page['count_queries']))
443  {
444    $page['count_queries'] = 0;
445    $page['queries_time'] = 0;
446  }
447
448  $page['count_queries']++;
449  $page['queries_time']+= $time;
450
451  if ($conf['show_queries'])
452  {
453    $output = '';
454    $output.= '<pre>['.$page['count_queries'].'] ';
455    $output.= "\n".$query;
456    $output.= "\n".'(this query time : ';
457    $output.= '<b>'.number_format($time, 3, '.', ' ').' s)</b>';
458    $output.= "\n".'(total SQL time  : ';
459    $output.= number_format($page['queries_time'], 3, '.', ' ').' s)';
460    $output.= "\n".'(total time      : ';
461    $output.= number_format( ($time+$start-$t2), 3, '.', ' ').' s)';
462    $output.= "</pre>\n";
463
464    $debug .= $output;
465  }
466
467  return $result;
468}
469
470function pwg_debug( $string )
471{
472  global $debug,$t2,$page;
473
474  $now = explode( ' ', microtime() );
475  $now2 = explode( '.', $now[0] );
476  $now2 = $now[1].'.'.$now2[1];
477  $time = number_format( $now2 - $t2, 3, '.', ' ').' s';
478  $debug .= '<p>';
479  $debug.= '['.$time.', ';
480  $debug.= $page['count_queries'].' queries] : '.$string;
481  $debug.= "</p>\n";
482}
483
484/**
485 * Redirects to the given URL
486 *
487 * Note : once this function called, the execution doesn't go further
488 * (presence of an exit() instruction.
489 *
490 * @param string $url
491 * @return void
492 */
493function redirect( $url )
494{
495  global $user, $template, $lang_info, $conf, $lang, $t2, $page, $debug;
496
497  // $refresh, $url_link and $title are required for creating an automated
498  // refresh page in header.tpl
499  $refresh = 0;
500  $url_link = $url;
501  $title = 'redirection';
502
503  include( PHPWG_ROOT_PATH.'include/page_header.php' );
504
505  $template->set_filenames( array( 'redirect' => 'redirect.tpl' ) );
506  $template->parse('redirect');
507
508  include( PHPWG_ROOT_PATH.'include/page_tail.php' );
509
510  exit();
511}
512
513/**
514 * returns $_SERVER['QUERY_STRING'] whitout keys given in parameters
515 *
516 * @param array $rejects
517 * @returns string
518 */
519function get_query_string_diff($rejects = array())
520{
521  $query_string = '';
522
523  $str = $_SERVER['QUERY_STRING'];
524  parse_str($str, $vars);
525
526  $is_first = true;
527  foreach ($vars as $key => $value)
528  {
529    if (!in_array($key, $rejects))
530    {
531      $query_string.= $is_first ? '?' : '&amp;';
532      $is_first = false;
533      $query_string.= $key.'='.$value;
534    }
535  }
536
537  return $query_string;
538}
539
540function url_is_remote($url)
541{
542  if (preg_match('/^https?:\/\/[~\/\.\w-]+$/', $url))
543  {
544    return true;
545  }
546  return false;
547}
548
549/**
550 * returns available template/theme
551 */
552function get_pwg_themes()
553{
554  $themes = array();
555
556  $template_dir = PHPWG_ROOT_PATH.'template';
557
558  foreach (get_dirs($template_dir) as $template)
559  {
560    foreach (get_dirs($template_dir.'/'.$template.'/theme') as $theme)
561    {
562      array_push($themes, $template.'/'.$theme);
563    }
564  }
565
566  return $themes;
567}
568
569/**
570 * returns thumbnail filepath (or distant URL if thumbnail is remote) for a
571 * given element
572 *
573 * the returned string can represente the filepath of the thumbnail or the
574 * filepath to the corresponding icon for non picture elements
575 *
576 * @param string path
577 * @param string tn_ext
578 * @param bool with_rewrite if true returned path can't be used from the script
579 * @return string
580 */
581function get_thumbnail_src($path, $tn_ext = '', $with_rewrite = true)
582{
583  global $conf, $user;
584
585  if ($tn_ext != '')
586  {
587    $src = substr_replace(
588      get_filename_wo_extension($path),
589      '/thumbnail/'.$conf['prefix_thumbnail'],
590      strrpos($path,'/'),
591      1
592      );
593    $src.= '.'.$tn_ext;
594    if ($with_rewrite==true and !url_is_remote($src) )
595    {
596      $src = get_root_url().$src;
597    }
598  }
599  else
600  {
601    $src = ($with_rewrite==true) ? get_root_url() : '';
602    $src .= get_themeconf('mime_icon_dir');
603    $src.= strtolower(get_extension($path)).'.png';
604  }
605
606  return $src;
607}
608
609// my_error returns (or send to standard output) the message concerning the
610// error occured for the last mysql query.
611function my_error($header)
612{
613  $error = '<pre>';
614  $error.= $header;
615  $error.= '[mysql error '.mysql_errno().'] ';
616  $error.= mysql_error();
617  $error.= '</pre>';
618  die ($error);
619}
620
621/**
622 * creates an array based on a query, this function is a very common pattern
623 * used here
624 *
625 * @param string $query
626 * @param string $fieldname
627 * @return array
628 */
629function array_from_query($query, $fieldname)
630{
631  $array = array();
632
633  $result = pwg_query($query);
634  while ($row = mysql_fetch_array($result))
635  {
636    array_push($array, $row[$fieldname]);
637  }
638
639  return $array;
640}
641
642/**
643 * instantiate number list for days in a template block
644 *
645 * @param string blockname
646 * @param string selection
647 */
648function get_day_list($blockname, $selection)
649{
650  global $template;
651
652  $template->assign_block_vars(
653    $blockname, array('SELECTED' => '', 'VALUE' => 0, 'OPTION' => '--'));
654
655  for ($i = 1; $i <= 31; $i++)
656  {
657    $selected = '';
658    if ($i == (int)$selection)
659    {
660      $selected = 'selected="selected"';
661    }
662    $template->assign_block_vars(
663      $blockname, array('SELECTED' => $selected,
664                        'VALUE' => $i,
665                        'OPTION' => str_pad($i, 2, '0', STR_PAD_LEFT)));
666  }
667}
668
669/**
670 * instantiate month list in a template block
671 *
672 * @param string blockname
673 * @param string selection
674 */
675function get_month_list($blockname, $selection)
676{
677  global $template, $lang;
678
679  $template->assign_block_vars(
680    $blockname, array('SELECTED' => '',
681                      'VALUE' => 0,
682                      'OPTION' => '------------'));
683
684  for ($i = 1; $i <= 12; $i++)
685  {
686    $selected = '';
687    if ($i == (int)$selection)
688    {
689      $selected = 'selected="selected"';
690    }
691    $template->assign_block_vars(
692      $blockname, array('SELECTED' => $selected,
693                        'VALUE' => $i,
694                        'OPTION' => $lang['month'][$i]));
695  }
696}
697
698/**
699 * fill the current user caddie with given elements, if not already in
700 * caddie
701 *
702 * @param array elements_id
703 */
704function fill_caddie($elements_id)
705{
706  global $user;
707
708  include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
709
710  $query = '
711SELECT element_id
712  FROM '.CADDIE_TABLE.'
713  WHERE user_id = '.$user['id'].'
714;';
715  $in_caddie = array_from_query($query, 'element_id');
716
717  $caddiables = array_diff($elements_id, $in_caddie);
718
719  $datas = array();
720
721  foreach ($caddiables as $caddiable)
722  {
723    array_push($datas, array('element_id' => $caddiable,
724                             'user_id' => $user['id']));
725  }
726
727  if (count($caddiables) > 0)
728  {
729    mass_inserts(CADDIE_TABLE, array('element_id','user_id'), $datas);
730  }
731}
732
733/**
734 * returns the element name from its filename
735 *
736 * @param string filename
737 * @return string name
738 */
739function get_name_from_file($filename)
740{
741  return str_replace('_',' ',get_filename_wo_extension($filename));
742}
743
744/**
745 * returns the corresponding value from $lang if existing. Else, the key is
746 * returned
747 *
748 * @param string key
749 * @return string
750 */
751function raw_l10n($key)
752{
753  global $lang, $conf;
754
755  if ($conf['debug_l10n'] and !isset($lang[$key]))
756  {
757    echo '[l10n] language key "'.$key.'" is not defined<br />';
758  }
759
760  return isset($lang[$key]) ? $lang[$key] : $key;
761}
762/**
763 * Like l10n but converts html entities
764 *
765 * @param string key
766 * @return string
767 */
768function l10n($key)
769{
770  return htmlentities(raw_l10n($key),ENT_QUOTES);
771}
772
773/**
774 * returns the corresponding value from $themeconf if existing. Else, the
775 * key is returned
776 *
777 * @param string key
778 * @return string
779 */
780function get_themeconf($key)
781{
782  global $themeconf;
783
784  return $themeconf[$key];
785}
786
787/**
788 * Returns webmaster mail address depending on $conf['webmaster_id']
789 *
790 * @return string
791 */
792function get_webmaster_mail_address()
793{
794  global $conf;
795
796  $query = '
797SELECT '.$conf['user_fields']['email'].'
798  FROM '.USERS_TABLE.'
799  WHERE '.$conf['user_fields']['id'].' = '.$conf['webmaster_id'].'
800;';
801  list($email) = mysql_fetch_array(pwg_query($query));
802
803  return $email;
804}
805
806/**
807 * which upgrades are available ?
808 *
809 * @return array
810 */
811function get_available_upgrade_ids()
812{
813  $upgrades_path = PHPWG_ROOT_PATH.'install/db';
814
815  $available_upgrade_ids = array();
816
817  if ($contents = opendir($upgrades_path))
818  {
819    while (($node = readdir($contents)) !== false)
820    {
821      if (is_file($upgrades_path.'/'.$node)
822          and preg_match('/^(.*?)-database\.php$/', $node, $match))
823      {
824        array_push($available_upgrade_ids, $match[1]);
825      }
826    }
827  }
828  natcasesort($available_upgrade_ids);
829
830  return $available_upgrade_ids;
831}
832
833?>
Note: See TracBrowser for help on using the repository browser.