source: trunk/admin/include/functions.php @ 25018

Last change on this file since 25018 was 25018, checked in by mistic100, 11 years ago

remove all array_push (50% slower than []) + some changes missing for feature:2978

  • Property svn:eol-style set to LF
File size: 58.3 KB
Line 
1<?php
2// +-----------------------------------------------------------------------+
3// | Piwigo - a PHP based photo gallery                                    |
4// +-----------------------------------------------------------------------+
5// | Copyright(C) 2008-2013 Piwigo Team                  http://piwigo.org |
6// | Copyright(C) 2003-2008 PhpWebGallery Team    http://phpwebgallery.net |
7// | Copyright(C) 2002-2003 Pierrick LE GALL   http://le-gall.net/pierrick |
8// +-----------------------------------------------------------------------+
9// | This program is free software; you can redistribute it and/or modify  |
10// | it under the terms of the GNU General Public License as published by  |
11// | the Free Software Foundation                                          |
12// |                                                                       |
13// | This program is distributed in the hope that it will be useful, but   |
14// | WITHOUT ANY WARRANTY; without even the implied warranty of            |
15// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      |
16// | General Public License for more details.                              |
17// |                                                                       |
18// | You should have received a copy of the GNU General Public License     |
19// | along with this program; if not, write to the Free Software           |
20// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
21// | USA.                                                                  |
22// +-----------------------------------------------------------------------+
23
24include(PHPWG_ROOT_PATH.'admin/include/functions_metadata.php');
25
26// The function delete_site deletes a site and call the function
27// delete_categories for each primary category of the site
28function delete_site( $id )
29{
30  // destruction of the categories of the site
31  $query = '
32SELECT id
33  FROM '.CATEGORIES_TABLE.'
34  WHERE site_id = '.$id.'
35;';
36  $category_ids = array_from_query($query, 'id');
37  delete_categories($category_ids);
38
39  // destruction of the site
40  $query = '
41DELETE FROM '.SITES_TABLE.'
42  WHERE id = '.$id.'
43;';
44  pwg_query($query);
45}
46
47
48// The function delete_categories deletes the categories identified by the
49// (numeric) key of the array $ids. It also deletes (in the database) :
50//    - all the elements physically linked to the category (delete_elements, see further)
51//    - all the links between elements and this category
52//    - all the restrictions linked to the category
53// The function works recursively.
54//
55// the $photo_deletion_mode is for photos virtually linked to the categorty
56//   * no_delete : delete no photo, may create orphans
57//   * delete_orphans : delete photos that are no longer linked to any category
58//   * force_delete : delete photos even if they are linked to another category
59function delete_categories($ids, $photo_deletion_mode='no_delete')
60{
61  if (count($ids) == 0)
62  {
63    return;
64  }
65
66  // add sub-category ids to the given ids : if a category is deleted, all
67  // sub-categories must be so
68  $ids = get_subcat_ids($ids);
69
70  // destruction of all photos physically linked to the category
71  $query = '
72SELECT id
73  FROM '.IMAGES_TABLE.'
74  WHERE storage_category_id IN (
75'.wordwrap(implode(', ', $ids), 80, "\n").')
76;';
77  $element_ids = array_from_query($query, 'id');
78  delete_elements($element_ids);
79
80  // now, should we delete photos that are virtually linked to the category?
81  if ('delete_orphans' == $photo_deletion_mode or 'force_delete' == $photo_deletion_mode)
82  {
83    $query = '
84SELECT
85    DISTINCT(image_id)
86  FROM '.IMAGE_CATEGORY_TABLE.'
87  WHERE category_id IN ('.implode(',', $ids).')
88;';
89    $image_ids_linked = array_from_query($query, 'image_id');
90
91    if (count($image_ids_linked) > 0)
92    {
93      if ('delete_orphans' == $photo_deletion_mode)
94      {
95        $query = '
96SELECT
97    DISTINCT(image_id)
98  FROM '.IMAGE_CATEGORY_TABLE.'
99  WHERE image_id IN ('.implode(',', $image_ids_linked).')
100    AND category_id NOT IN ('.implode(',', $ids).')
101;';
102        $image_ids_not_orphans = array_from_query($query, 'image_id');
103        $image_ids_to_delete = array_diff($image_ids_linked, $image_ids_not_orphans);
104      }
105
106      if ('force_delete' == $photo_deletion_mode)
107      {
108        $image_ids_to_delete = $image_ids_linked;
109      }
110
111      delete_elements($image_ids_to_delete, true);
112    }
113  }
114
115  // destruction of the links between images and this category
116  $query = '
117DELETE FROM '.IMAGE_CATEGORY_TABLE.'
118  WHERE category_id IN (
119'.wordwrap(implode(', ', $ids), 80, "\n").')
120;';
121  pwg_query($query);
122
123  // destruction of the access linked to the category
124  $query = '
125DELETE FROM '.USER_ACCESS_TABLE.'
126  WHERE cat_id IN (
127'.wordwrap(implode(', ', $ids), 80, "\n").')
128;';
129  pwg_query($query);
130
131  $query = '
132DELETE FROM '.GROUP_ACCESS_TABLE.'
133  WHERE cat_id IN (
134'.wordwrap(implode(', ', $ids), 80, "\n").')
135;';
136  pwg_query($query);
137
138  // destruction of the category
139  $query = '
140DELETE FROM '.CATEGORIES_TABLE.'
141  WHERE id IN (
142'.wordwrap(implode(', ', $ids), 80, "\n").')
143;';
144  pwg_query($query);
145
146  $query='
147DELETE FROM '.OLD_PERMALINKS_TABLE.'
148  WHERE cat_id IN ('.implode(',',$ids).')';
149  pwg_query($query);
150
151  $query='
152DELETE FROM '.USER_CACHE_CATEGORIES_TABLE.'
153  WHERE cat_id IN ('.implode(',',$ids).')';
154  pwg_query($query);
155
156  trigger_action('delete_categories', $ids);
157}
158
159// Deletes all files (on disk) related to given image ids
160// @return image ids where files are deleted successfully
161function delete_element_files($ids)
162{
163  global $conf;
164  if (count($ids) == 0)
165  {
166    return 0;
167  }
168
169  $new_ids = array();
170
171  $query = '
172SELECT
173    id,
174    path,
175    representative_ext
176  FROM '.IMAGES_TABLE.'
177  WHERE id IN ('.implode(',', $ids).')
178;';
179  $result = pwg_query($query);
180  while ($row = pwg_db_fetch_assoc($result))
181  {
182    if (url_is_remote($row['path']))
183    {
184      continue;
185    }
186
187    $files = array();
188    $files[] = get_element_path($row);
189
190    if (!empty($row['representative_ext']))
191    {
192      $files[] = original_to_representative( $files[0], $row['representative_ext']);
193    }
194
195    $ok = true;
196    if (!isset($conf['never_delete_originals']))
197    {
198      foreach ($files as $path)
199      {
200        if (is_file($path) and !unlink($path))
201        {
202          $ok = false;
203          trigger_error('"'.$path.'" cannot be removed', E_USER_WARNING);
204          break;
205        }
206      }
207    }
208
209    if ($ok)
210    {
211      delete_element_derivatives($row);
212      $new_ids[] = $row['id'];
213    }
214    else
215    {
216      break;
217    }
218  }
219  return $new_ids;
220}
221
222// The function delete_elements deletes the elements identified by the
223// (numeric) values of the array $ids. It also deletes (in the database) :
224//    - all the comments related to elements
225//    - all the links between categories and elements
226//    - all the favorites associated to elements
227// @return number of deleted elements
228function delete_elements($ids, $physical_deletion=false)
229{
230  if (count($ids) == 0)
231  {
232    return 0;
233  }
234  trigger_action('begin_delete_elements', $ids);
235
236  if ($physical_deletion)
237  {
238    $ids = delete_element_files($ids);
239    if (count($ids)==0)
240    {
241      return 0;
242    }
243  }
244
245  // destruction of the comments on the image
246  $query = '
247DELETE FROM '.COMMENTS_TABLE.'
248  WHERE image_id IN (
249'.wordwrap(implode(', ', $ids), 80, "\n").')
250;';
251  pwg_query($query);
252
253  // destruction of the links between images and this category
254  $query = '
255DELETE FROM '.IMAGE_CATEGORY_TABLE.'
256  WHERE image_id IN (
257'.wordwrap(implode(', ', $ids), 80, "\n").')
258;';
259  pwg_query($query);
260
261  // destruction of the links between images and tags
262  $query = '
263DELETE FROM '.IMAGE_TAG_TABLE.'
264  WHERE image_id IN (
265'.wordwrap(implode(', ', $ids), 80, "\n").')
266;';
267  pwg_query($query);
268
269  // destruction of the favorites associated with the picture
270  $query = '
271DELETE FROM '.FAVORITES_TABLE.'
272  WHERE image_id IN (
273'.wordwrap(implode(', ', $ids), 80, "\n").')
274;';
275  pwg_query($query);
276
277  // destruction of the rates associated to this element
278  $query = '
279DELETE FROM '.RATE_TABLE.'
280  WHERE element_id IN (
281'.wordwrap(implode(', ', $ids), 80, "\n").')
282;';
283  pwg_query($query);
284
285  // destruction of the rates associated to this element
286  $query = '
287DELETE FROM '.CADDIE_TABLE.'
288  WHERE element_id IN (
289'.wordwrap(implode(', ', $ids), 80, "\n").')
290;';
291  pwg_query($query);
292
293  // destruction of the image
294  $query = '
295DELETE FROM '.IMAGES_TABLE.'
296  WHERE id IN (
297'.wordwrap(implode(', ', $ids), 80, "\n").')
298;';
299  pwg_query($query);
300
301  // are the photo used as category representant?
302  $query = '
303SELECT
304    id
305  FROM '.CATEGORIES_TABLE.'
306  WHERE representative_picture_id IN (
307'.wordwrap(implode(', ', $ids), 80, "\n").')
308;';
309  $category_ids = array_from_query($query, 'id');
310  if (count($category_ids) > 0)
311  {
312    update_category($category_ids);
313  }
314
315  trigger_action('delete_elements', $ids);
316  return count($ids);
317}
318
319// The delete_user function delete a user identified by the $user_id
320// It also deletes :
321//     - all the access linked to this user
322//     - all the links to any group
323//     - all the favorites linked to this user
324//     - calculated permissions linked to the user
325//     - all datas about notifications for the user
326function delete_user($user_id)
327{
328  global $conf;
329  $tables = array(
330    // destruction of the access linked to the user
331    USER_ACCESS_TABLE,
332    // destruction of data notification by mail for this user
333    USER_MAIL_NOTIFICATION_TABLE,
334    // destruction of data RSS notification for this user
335    USER_FEED_TABLE,
336    // deletion of calculated permissions linked to the user
337    USER_CACHE_TABLE,
338    // deletion of computed cache data linked to the user
339    USER_CACHE_CATEGORIES_TABLE,
340    // destruction of the group links for this user
341    USER_GROUP_TABLE,
342    // destruction of the favorites associated with the user
343    FAVORITES_TABLE,
344    // destruction of the caddie associated with the user
345    CADDIE_TABLE,
346    // deletion of piwigo specific informations
347    USER_INFOS_TABLE,
348    );
349
350  foreach ($tables as $table)
351  {
352    $query = '
353DELETE FROM '.$table.'
354  WHERE user_id = '.$user_id.'
355;';
356    pwg_query($query);
357  }
358
359  // destruction of the user
360  $query = '
361DELETE FROM '.SESSIONS_TABLE.'
362  WHERE data LIKE \'pwg_uid|i:'.(int)$user_id.';%\'
363;';
364  pwg_query($query);
365
366  // destruction of the user
367  $query = '
368DELETE FROM '.USERS_TABLE.'
369  WHERE '.$conf['user_fields']['id'].' = '.$user_id.'
370;';
371  pwg_query($query);
372
373  trigger_action('delete_user', $user_id);
374}
375
376/**
377 * Deletes all tags linked to no photo
378 */
379function delete_orphan_tags()
380{
381  $orphan_tags = get_orphan_tags();
382
383  if (count($orphan_tags) > 0)
384  {
385    $orphan_tag_ids = array();
386    foreach ($orphan_tags as $tag)
387    {
388      $orphan_tag_ids[] = $tag['id'];
389    }
390
391    $query = '
392DELETE
393  FROM '.TAGS_TABLE.'
394  WHERE id IN ('.implode(',', $orphan_tag_ids).')
395;';
396    pwg_query($query);
397  }
398}
399
400/**
401 * Get all tags (id + name) linked to no photo
402 */
403function get_orphan_tags()
404{
405  $query = '
406SELECT
407    id,
408    name
409  FROM '.TAGS_TABLE.'
410    LEFT JOIN '.IMAGE_TAG_TABLE.' ON id = tag_id
411  WHERE tag_id IS NULL
412;';
413  return array_from_query($query);
414}
415
416/**
417 * Verifies that the representative picture really exists in the db and
418 * picks up a random represantive if possible and based on config.
419 *
420 * @param mixed category id
421 * @returns void
422 */
423function update_category($ids = 'all')
424{
425  global $conf;
426
427  if ($ids=='all')
428  {
429    $where_cats = '1=1';
430  }
431  elseif ( !is_array($ids) )
432  {
433    $where_cats = '%s='.$ids;
434  }
435  else
436  {
437    if (count($ids) == 0)
438    {
439      return false;
440    }
441    $where_cats = '%s IN('.wordwrap(implode(', ', $ids), 120, "\n").')';
442  }
443
444  // find all categories where the setted representative is not possible :
445  // the picture does not exist
446  $query = '
447SELECT DISTINCT c.id
448  FROM '.CATEGORIES_TABLE.' AS c LEFT JOIN '.IMAGES_TABLE.' AS i
449    ON c.representative_picture_id = i.id
450  WHERE representative_picture_id IS NOT NULL
451    AND '.sprintf($where_cats, 'c.id').'
452    AND i.id IS NULL
453;';
454  $wrong_representant = array_from_query($query, 'id');
455
456  if (count($wrong_representant) > 0)
457  {
458    $query = '
459UPDATE '.CATEGORIES_TABLE.'
460  SET representative_picture_id = NULL
461  WHERE id IN ('.wordwrap(implode(', ', $wrong_representant), 120, "\n").')
462;';
463    pwg_query($query);
464  }
465
466  if (!$conf['allow_random_representative'])
467  {
468    // If the random representant is not allowed, we need to find
469    // categories with elements and with no representant. Those categories
470    // must be added to the list of categories to set to a random
471    // representant.
472    $query = '
473SELECT DISTINCT id
474  FROM '.CATEGORIES_TABLE.' INNER JOIN '.IMAGE_CATEGORY_TABLE.'
475    ON id = category_id
476  WHERE representative_picture_id IS NULL
477    AND '.sprintf($where_cats, 'category_id').'
478;';
479    $to_rand = array_from_query($query, 'id');
480    if (count($to_rand) > 0)
481    {
482      set_random_representant($to_rand);
483    }
484  }
485}
486
487/**
488 * check and repair images integrity
489 *
490 * TODO see delete_elements function to check all linked tables
491 */
492function images_integrity()
493{
494  $query = '
495SELECT
496    image_id
497  FROM '.IMAGE_CATEGORY_TABLE.'
498    LEFT JOIN '.IMAGES_TABLE.' ON id = image_id
499  WHERE id IS NULL
500;';
501  $result = pwg_query($query);
502  $orphan_image_ids = array_from_query($query, 'image_id');
503
504  if (count($orphan_image_ids) > 0)
505  {
506    $query = '
507DELETE
508  FROM '.IMAGE_CATEGORY_TABLE.'
509  WHERE image_id IN ('.implode(',', $orphan_image_ids).')
510;';
511    pwg_query($query);
512  }
513}
514
515/**
516 * returns an array containing sub-directories which can be a category,
517 * recursive by default
518 *
519 * directories nammed "thumbnail", "pwg_high" or "pwg_representative" are
520 * omitted
521 *
522 * @param string $basedir
523 * @return array
524 */
525function get_fs_directories($path, $recursive = true)
526{
527  $dirs = array();
528
529  if (is_dir($path))
530  {
531    if ($contents = opendir($path))
532    {
533      while (($node = readdir($contents)) !== false)
534      {
535        if ($node != '.'
536            and $node != '..'
537            and $node != '.svn'
538            and $node != 'thumbnail'
539            and $node != 'pwg_high'
540            and $node != 'pwg_representative'
541            and is_dir($path.'/'.$node))
542        {
543          $dirs[] = $path.'/'.$node;
544          if ($recursive)
545          {
546            $dirs = array_merge($dirs, get_fs_directories($path.'/'.$node));
547          }
548        }
549      }
550      closedir($contents);
551    }
552  }
553
554  return $dirs;
555}
556
557/**
558 * order categories (update categories.rank and global_rank database fields)
559 * so that rank field are consecutive integers starting at 1 for each child
560 * @return void
561 */
562function update_global_rank()
563{
564  $query = '
565SELECT id, id_uppercat, uppercats, rank, global_rank
566  FROM '.CATEGORIES_TABLE.'
567  ORDER BY id_uppercat,rank,name';
568
569  $cat_map = array();
570
571  $current_rank = 0;
572  $current_uppercat = '';
573
574  $result = pwg_query($query);
575  while ($row = pwg_db_fetch_assoc($result))
576  {
577    if ($row['id_uppercat'] != $current_uppercat)
578    {
579      $current_rank = 0;
580      $current_uppercat = $row['id_uppercat'];
581    }
582    ++$current_rank;
583    $cat =
584      array(
585        'rank' =>        $current_rank,
586        'rank_changed' =>$current_rank!=$row['rank'],
587        'global_rank' => $row['global_rank'],
588        'uppercats' =>   $row['uppercats'],
589        );
590    $cat_map[ $row['id'] ] = $cat;
591  }
592
593  $datas = array();
594
595  foreach( $cat_map as $id=>$cat )
596  {
597    $new_global_rank = preg_replace(
598          '/(\d+)/e',
599          "\$cat_map['$1']['rank']",
600          str_replace(',', '.', $cat['uppercats'] )
601          );
602    if ( $cat['rank_changed']
603      or $new_global_rank!=$cat['global_rank']
604      )
605    {
606      $datas[] = array(
607          'id' => $id,
608          'rank' => $cat['rank'],
609          'global_rank' => $new_global_rank,
610        );
611    }
612  }
613
614  mass_updates(
615    CATEGORIES_TABLE,
616    array(
617      'primary' => array('id'),
618      'update'  => array('rank', 'global_rank')
619      ),
620    $datas
621    );
622  return count($datas);
623}
624
625/**
626 * change the visible property on a set of categories
627 *
628 * @param array categories
629 * @param string value
630 * @return void
631 */
632function set_cat_visible($categories, $value)
633{
634  if (!in_array($value, array('true', 'false')))
635  {
636    trigger_error("set_cat_visible invalid param $value", E_USER_WARNING);
637    return false;
638  }
639
640  // unlocking a category => all its parent categories become unlocked
641  if ($value == 'true')
642  {
643    $uppercats = get_uppercat_ids($categories);
644    $query = '
645UPDATE '.CATEGORIES_TABLE.'
646  SET visible = \'true\'
647  WHERE id IN ('.implode(',', $uppercats).')';
648    pwg_query($query);
649  }
650  // locking a category   => all its child categories become locked
651  if ($value == 'false')
652  {
653    $subcats = get_subcat_ids($categories);
654    $query = '
655UPDATE '.CATEGORIES_TABLE.'
656  SET visible = \'false\'
657  WHERE id IN ('.implode(',', $subcats).')';
658    pwg_query($query);
659  }
660}
661
662/**
663 * change the status property on a set of categories : private or public
664 *
665 * @param array categories
666 * @param string value
667 * @return void
668 */
669function set_cat_status($categories, $value)
670{
671  if (!in_array($value, array('public', 'private')))
672  {
673    trigger_error("set_cat_status invalid param $value", E_USER_WARNING);
674    return false;
675  }
676
677  // make public a category => all its parent categories become public
678  if ($value == 'public')
679  {
680    $uppercats = get_uppercat_ids($categories);
681    $query = '
682UPDATE '.CATEGORIES_TABLE.'
683  SET status = \'public\'
684  WHERE id IN ('.implode(',', $uppercats).')
685;';
686    pwg_query($query);
687  }
688  // make a category private => all its child categories become private
689  if ($value == 'private')
690  {
691    $subcats = get_subcat_ids($categories);
692    $query = '
693UPDATE '.CATEGORIES_TABLE.'
694  SET status = \'private\'
695  WHERE id IN ('.implode(',', $subcats).')';
696    pwg_query($query);
697  }
698}
699
700/**
701 * returns all uppercats category ids of the given category ids
702 *
703 * @param array cat_ids
704 * @return array
705 */
706function get_uppercat_ids($cat_ids)
707{
708  if (!is_array($cat_ids) or count($cat_ids) < 1)
709  {
710    return array();
711  }
712
713  $uppercats = array();
714
715  $query = '
716SELECT uppercats
717  FROM '.CATEGORIES_TABLE.'
718  WHERE id IN ('.implode(',', $cat_ids).')
719;';
720  $result = pwg_query($query);
721  while ($row = pwg_db_fetch_assoc($result))
722  {
723    $uppercats = array_merge($uppercats,
724                             explode(',', $row['uppercats']));
725  }
726  $uppercats = array_unique($uppercats);
727
728  return $uppercats;
729}
730
731/**
732 * set a new random representant to the categories
733 *
734 * @param array categories
735 */
736function set_random_representant($categories)
737{
738  $datas = array();
739  foreach ($categories as $category_id)
740  {
741    $query = '
742SELECT image_id
743  FROM '.IMAGE_CATEGORY_TABLE.'
744  WHERE category_id = '.$category_id.'
745  ORDER BY '.DB_RANDOM_FUNCTION.'()
746  LIMIT 1
747;';
748    list($representative) = pwg_db_fetch_row(pwg_query($query));
749
750    $datas[] = array(
751      'id' => $category_id,
752      'representative_picture_id' => $representative,
753      );
754  }
755
756  mass_updates(
757    CATEGORIES_TABLE,
758    array(
759      'primary' => array('id'),
760      'update' => array('representative_picture_id')
761      ),
762    $datas
763    );
764}
765
766/**
767 * returns the fulldir for each given category id
768 *
769 * @param array cat_ids
770 * @return array
771 */
772function get_fulldirs($cat_ids)
773{
774  if (count($cat_ids) == 0)
775  {
776    return array();
777  }
778
779  // caching directories of existing categories
780  $query = '
781SELECT id, dir
782  FROM '.CATEGORIES_TABLE.'
783  WHERE dir IS NOT NULL
784;';
785  $cat_dirs = simple_hash_from_query($query, 'id', 'dir');
786
787  // caching galleries_url
788  $query = '
789SELECT id, galleries_url
790  FROM '.SITES_TABLE.'
791;';
792  $galleries_url = simple_hash_from_query($query, 'id', 'galleries_url');
793
794  // categories : id, site_id, uppercats
795  $query = '
796SELECT id, uppercats, site_id
797  FROM '.CATEGORIES_TABLE.'
798  WHERE dir IS NOT NULL
799    AND id IN (
800'.wordwrap(implode(', ', $cat_ids), 80, "\n").')
801;';
802  $categories = array_from_query($query);
803
804  // filling $cat_fulldirs
805  $cat_fulldirs = array();
806  foreach ($categories as $category)
807  {
808    $uppercats = str_replace(',', '/', $category['uppercats']);
809    $cat_fulldirs[$category['id']] = $galleries_url[$category['site_id']];
810    $cat_fulldirs[$category['id']].= preg_replace('/(\d+)/e',
811                                                  "\$cat_dirs['$1']",
812                                                  $uppercats);
813  }
814
815  return $cat_fulldirs;
816}
817
818/**
819 * returns an array with all file system files according to
820 * $conf['file_ext']
821 *
822 * @param string $path
823 * @param bool recursive
824 * @return array
825 */
826function get_fs($path, $recursive = true)
827{
828  global $conf;
829
830  // because isset is faster than in_array...
831  if (!isset($conf['flip_picture_ext']))
832  {
833    $conf['flip_picture_ext'] = array_flip($conf['picture_ext']);
834  }
835  if (!isset($conf['flip_file_ext']))
836  {
837    $conf['flip_file_ext'] = array_flip($conf['file_ext']);
838  }
839
840  $fs['elements'] = array();
841  $fs['thumbnails'] = array();
842  $fs['representatives'] = array();
843  $subdirs = array();
844
845  if (is_dir($path))
846  {
847    if ($contents = opendir($path))
848    {
849      while (($node = readdir($contents)) !== false)
850      {
851        if ($node == '.' or $node == '..') continue;
852
853        if (is_file($path.'/'.$node))
854        {
855          $extension = get_extension($node);
856
857//          if (in_array($extension, $conf['picture_ext']))
858          if (isset($conf['flip_picture_ext'][$extension]))
859          {
860            if (basename($path) == 'thumbnail')
861            {
862              $fs['thumbnails'][] = $path.'/'.$node;
863            }
864            else if (basename($path) == 'pwg_representative')
865            {
866              $fs['representatives'][] = $path.'/'.$node;
867            }
868            else
869            {
870              $fs['elements'][] = $path.'/'.$node;
871            }
872          }
873//          else if (in_array($extension, $conf['file_ext']))
874          else if (isset($conf['flip_file_ext'][$extension]))
875          {
876            $fs['elements'][] = $path.'/'.$node;
877          }
878        }
879        else if (is_dir($path.'/'.$node) and $node != 'pwg_high' and $recursive)
880        {
881          $subdirs[] = $node;
882        }
883      }
884    }
885    closedir($contents);
886
887    foreach ($subdirs as $subdir)
888    {
889      $tmp_fs = get_fs($path.'/'.$subdir);
890
891      $fs['elements']        = array_merge($fs['elements'],
892                                           $tmp_fs['elements']);
893
894      $fs['thumbnails']      = array_merge($fs['thumbnails'],
895                                           $tmp_fs['thumbnails']);
896
897      $fs['representatives'] = array_merge($fs['representatives'],
898                                           $tmp_fs['representatives']);
899    }
900  }
901  return $fs;
902}
903
904/**
905 * synchronize base users list and related users list
906 *
907 * compares and synchronizes base users table (USERS_TABLE) with its child
908 * tables (USER_INFOS_TABLE, USER_ACCESS, USER_CACHE, USER_GROUP) : each
909 * base user must be present in child tables, users in child tables not
910 * present in base table must be deleted.
911 *
912 * @return void
913 */
914function sync_users()
915{
916  global $conf;
917
918  $query = '
919SELECT '.$conf['user_fields']['id'].' AS id
920  FROM '.USERS_TABLE.'
921;';
922  $base_users = array_from_query($query, 'id');
923
924  $query = '
925SELECT user_id
926  FROM '.USER_INFOS_TABLE.'
927;';
928  $infos_users = array_from_query($query, 'user_id');
929
930  // users present in $base_users and not in $infos_users must be added
931  $to_create = array_diff($base_users, $infos_users);
932
933  if (count($to_create) > 0)
934  {
935    create_user_infos($to_create);
936  }
937
938  // users present in user related tables must be present in the base user
939  // table
940  $tables = array(
941    USER_MAIL_NOTIFICATION_TABLE,
942    USER_FEED_TABLE,
943    USER_INFOS_TABLE,
944    USER_ACCESS_TABLE,
945    USER_CACHE_TABLE,
946    USER_CACHE_CATEGORIES_TABLE,
947    USER_GROUP_TABLE
948    );
949
950  foreach ($tables as $table)
951  {
952    $query = '
953SELECT DISTINCT user_id
954  FROM '.$table.'
955;';
956    $to_delete = array_diff(
957      array_from_query($query, 'user_id'),
958      $base_users
959      );
960
961    if (count($to_delete) > 0)
962    {
963      $query = '
964DELETE
965  FROM '.$table.'
966  WHERE user_id in ('.implode(',', $to_delete).')
967;';
968      pwg_query($query);
969    }
970  }
971}
972
973/**
974 * updates categories.uppercats field based on categories.id +
975 * categories.id_uppercat
976 *
977 * @return void
978 */
979function update_uppercats()
980{
981  $query = '
982SELECT id, id_uppercat, uppercats
983  FROM '.CATEGORIES_TABLE.'
984;';
985  $cat_map = hash_from_query($query, 'id');
986
987  $datas = array();
988  foreach ($cat_map as $id => $cat)
989  {
990    $upper_list = array();
991
992    $uppercat = $id;
993    while ($uppercat)
994    {
995      $upper_list[] = $uppercat;
996      $uppercat = $cat_map[$uppercat]['id_uppercat'];
997    }
998
999    $new_uppercats = implode(',', array_reverse($upper_list));
1000    if ($new_uppercats != $cat['uppercats'])
1001    {
1002      $datas[] = array(
1003        'id' => $id,
1004        'uppercats' => $new_uppercats
1005        );
1006    }
1007  }
1008  $fields = array('primary' => array('id'), 'update' => array('uppercats'));
1009  mass_updates(CATEGORIES_TABLE, $fields, $datas);
1010}
1011
1012/**
1013 * update images.path field
1014 *
1015 * @return void
1016 */
1017function update_path()
1018{
1019  $query = '
1020SELECT DISTINCT(storage_category_id)
1021  FROM '.IMAGES_TABLE.'
1022  WHERE storage_category_id IS NOT NULL
1023;';
1024  $cat_ids = array_from_query($query, 'storage_category_id');
1025  $fulldirs = get_fulldirs($cat_ids);
1026
1027  foreach ($cat_ids as $cat_id)
1028  {
1029    $query = '
1030UPDATE '.IMAGES_TABLE.'
1031  SET path = '.pwg_db_concat(array("'".$fulldirs[$cat_id]."/'",'file')).'
1032  WHERE storage_category_id = '.$cat_id.'
1033;';
1034    pwg_query($query);
1035  }
1036}
1037
1038/**
1039 * change the parent category of the given categories. The categories are
1040 * supposed virtual.
1041 *
1042 * @param array category identifiers
1043 * @param int parent category identifier
1044 * @return void
1045 */
1046function move_categories($category_ids, $new_parent = -1)
1047{
1048  global $page;
1049
1050  if (count($category_ids) == 0)
1051  {
1052    return;
1053  }
1054
1055  $new_parent = $new_parent < 1 ? 'NULL' : $new_parent;
1056
1057  $categories = array();
1058
1059  $query = '
1060SELECT id, id_uppercat, status, uppercats
1061  FROM '.CATEGORIES_TABLE.'
1062  WHERE id IN ('.implode(',', $category_ids).')
1063;';
1064  $result = pwg_query($query);
1065  while ($row = pwg_db_fetch_assoc($result))
1066  {
1067    $categories[$row['id']] =
1068      array(
1069        'parent' => empty($row['id_uppercat']) ? 'NULL' : $row['id_uppercat'],
1070        'status' => $row['status'],
1071        'uppercats' => $row['uppercats']
1072        );
1073  }
1074
1075  // is the movement possible? The movement is impossible if you try to move
1076  // a category in a sub-category or itself
1077  if ('NULL' != $new_parent)
1078  {
1079    $query = '
1080SELECT uppercats
1081  FROM '.CATEGORIES_TABLE.'
1082  WHERE id = '.$new_parent.'
1083;';
1084    list($new_parent_uppercats) = pwg_db_fetch_row(pwg_query($query));
1085
1086    foreach ($categories as $category)
1087    {
1088      // technically, you can't move a category with uppercats 12,125,13,14
1089      // into a new parent category with uppercats 12,125,13,14,24
1090      if (preg_match('/^'.$category['uppercats'].'(,|$)/', $new_parent_uppercats))
1091      {
1092        $page['errors'][] = l10n('You cannot move an album in its own sub album');
1093        return;
1094      }
1095    }
1096  }
1097
1098  $tables =
1099    array(
1100      USER_ACCESS_TABLE => 'user_id',
1101      GROUP_ACCESS_TABLE => 'group_id'
1102      );
1103
1104  $query = '
1105UPDATE '.CATEGORIES_TABLE.'
1106  SET id_uppercat = '.$new_parent.'
1107  WHERE id IN ('.implode(',', $category_ids).')
1108;';
1109  pwg_query($query);
1110
1111  update_uppercats();
1112  update_global_rank();
1113
1114  // status and related permissions management
1115  if ('NULL' == $new_parent)
1116  {
1117    $parent_status = 'public';
1118  }
1119  else
1120  {
1121    $query = '
1122SELECT status
1123  FROM '.CATEGORIES_TABLE.'
1124  WHERE id = '.$new_parent.'
1125;';
1126    list($parent_status) = pwg_db_fetch_row(pwg_query($query));
1127  }
1128
1129  if ('private' == $parent_status)
1130  {
1131    foreach ($categories as $cat_id => $category)
1132    {
1133      if ('public' == $category['status'])
1134      {
1135        set_cat_status(array($cat_id), 'private');
1136      }
1137     
1138      $subcats = get_subcat_ids(array($cat_id));
1139
1140      foreach ($tables as $table => $field)
1141      {
1142        $query = '
1143SELECT '.$field.'
1144  FROM '.$table.'
1145  WHERE cat_id = '.$cat_id.'
1146;';
1147        $category_access = array_from_query($query, $field);
1148
1149        $query = '
1150SELECT '.$field.'
1151  FROM '.$table.'
1152  WHERE cat_id = '.$new_parent.'
1153;';
1154        $parent_access = array_from_query($query, $field);
1155
1156        $to_delete = array_diff($category_access, $parent_access);
1157
1158        if (count($to_delete) > 0)
1159        {
1160          $query = '
1161DELETE FROM '.$table.'
1162  WHERE '.$field.' IN ('.implode(',', $to_delete).')
1163    AND cat_id IN ('.implode(',', $subcats).')
1164;';
1165          pwg_query($query);
1166        }
1167      }
1168    }
1169  }
1170
1171  $page['infos'][] = l10n_dec(
1172    '%d album moved', '%d albums moved',
1173    count($categories)
1174    );
1175}
1176
1177/**
1178 * create a virtual category
1179 *
1180 * @param string category name
1181 * @param int parent category id
1182 * @return array with ('info' and 'id') or ('error') key
1183 */
1184function create_virtual_category($category_name, $parent_id=null, $options=array())
1185{
1186  global $conf, $user;
1187
1188  // is the given category name only containing blank spaces ?
1189  if (preg_match('/^\s*$/', $category_name))
1190  {
1191    return array('error' => l10n('The name of an album must not be empty'));
1192  }
1193
1194  $insert = array(
1195    'name' => $category_name,
1196    'rank' => 0,
1197    'global_rank' => 0,
1198    );
1199
1200  // is the album commentable?
1201  if (isset($options['commentable']) and is_bool($options['commentable']))
1202  {
1203    $insert['commentable'] = $options['commentable'];
1204  }
1205  else
1206  {
1207    $insert['commentable'] = $conf['newcat_default_commentable'];
1208  }
1209  $insert['commentable'] = boolean_to_string($insert['commentable']);
1210
1211  // is the album temporarily locked? (only visible by administrators,
1212  // whatever permissions) (may be overwritten if parent album is not
1213  // visible)
1214  if (isset($options['visible']) and is_bool($options['visible']))
1215  {
1216    $insert['visible'] = $options['visible'];
1217  }
1218  else
1219  {
1220    $insert['visible'] = $conf['newcat_default_visible'];
1221  }
1222  $insert['visible'] = boolean_to_string($insert['visible']);
1223
1224  // is the album private? (may be overwritten if parent album is private)
1225  if (isset($options['status']) and 'private' == $options['status'])
1226  {
1227    $insert['status'] = 'private';
1228  }
1229  else
1230  {
1231    $insert['status'] = $conf['newcat_default_status'];
1232  }
1233
1234  // any description for this album?
1235  if (isset($options['comment']))
1236  {
1237    $insert['comment'] = $conf['allow_html_descriptions'] ? $options['comment'] : strip_tags($options['comment']);
1238  }
1239
1240  if (!empty($parent_id) and is_numeric($parent_id))
1241  {
1242    $query = '
1243SELECT id, uppercats, global_rank, visible, status
1244  FROM '.CATEGORIES_TABLE.'
1245  WHERE id = '.$parent_id.'
1246;';
1247    $parent = pwg_db_fetch_assoc(pwg_query($query));
1248
1249    $insert['id_uppercat'] = $parent['id'];
1250    $insert['global_rank'] = $parent['global_rank'].'.'.$insert['rank'];
1251
1252    // at creation, must a category be visible or not ? Warning : if the
1253    // parent category is invisible, the category is automatically create
1254    // invisible. (invisible = locked)
1255    if ('false' == $parent['visible'])
1256    {
1257      $insert['visible'] = 'false';
1258    }
1259
1260    // at creation, must a category be public or private ? Warning : if the
1261    // parent category is private, the category is automatically create
1262    // private.
1263    if ('private' == $parent['status'])
1264    {
1265      $insert['status'] = 'private';
1266    }
1267
1268    $uppercats_prefix = $parent['uppercats'].',';
1269  }
1270  else
1271  {
1272    $uppercats_prefix = '';
1273  }
1274
1275  // we have then to add the virtual category
1276  single_insert(CATEGORIES_TABLE, $insert);
1277  $inserted_id = pwg_db_insert_id(CATEGORIES_TABLE);
1278
1279  single_update(
1280    CATEGORIES_TABLE,
1281    array('uppercats' => $uppercats_prefix.$inserted_id),
1282    array('id' => $inserted_id)
1283    );
1284
1285  update_global_rank();
1286
1287  if ('private' == $insert['status'] and !empty($insert['id_uppercat']) and ((isset($options['inherit']) and $options['inherit']) or $conf['inheritance_by_default']) )
1288  {
1289    $query = '
1290      SELECT group_id
1291      FROM '.GROUP_ACCESS_TABLE.'
1292      WHERE cat_id = '.$insert['id_uppercat'].'
1293    ;';
1294    $granted_grps =  array_from_query($query, 'group_id');
1295    $inserts = array();
1296    foreach ($granted_grps as $granted_grp)
1297    {
1298      $inserts[] = array(
1299        'group_id' => $granted_grp,
1300        'cat_id' => $inserted_id
1301        );
1302    }
1303    mass_inserts(GROUP_ACCESS_TABLE, array('group_id','cat_id'), $inserts);
1304
1305    $query = '
1306      SELECT user_id
1307      FROM '.USER_ACCESS_TABLE.'
1308      WHERE cat_id = '.$insert['id_uppercat'].'
1309    ;';
1310    $granted_users =  array_from_query($query, 'user_id');
1311    add_permission_on_category($inserted_id, array_unique(array_merge(get_admins(), array($user['id']), $granted_users)));
1312  }
1313  else if ('private' == $insert['status'])
1314  {
1315    add_permission_on_category($inserted_id, array_unique(array_merge(get_admins(), array($user['id']))));
1316  }
1317
1318  return array(
1319    'info' => l10n('Virtual album added'),
1320    'id'   => $inserted_id,
1321    );
1322}
1323
1324/**
1325 * Set tags to an image. Warning: given tags are all tags associated to the
1326 * image, not additionnal tags.
1327 *
1328 * @param array tag ids
1329 * @param int image id
1330 * @return void
1331 */
1332function set_tags($tags, $image_id)
1333{
1334  set_tags_of( array($image_id=>$tags) );
1335}
1336
1337/**
1338 * Add new tags to a set of images.
1339 *
1340 * @param array tag ids
1341 * @param array image ids
1342 * @return void
1343 */
1344function add_tags($tags, $images)
1345{
1346  if (count($tags) == 0 or count($images) == 0)
1347  {
1348    return;
1349  }
1350
1351  // we can't insert twice the same {image_id,tag_id} so we must first
1352  // delete lines we'll insert later
1353  $query = '
1354DELETE
1355  FROM '.IMAGE_TAG_TABLE.'
1356  WHERE image_id IN ('.implode(',', $images).')
1357    AND tag_id IN ('.implode(',', $tags).')
1358;';
1359  pwg_query($query);
1360
1361  $inserts = array();
1362  foreach ($images as $image_id)
1363  {
1364    foreach ( array_unique($tags) as $tag_id)
1365    {
1366      $inserts[] = array(
1367          'image_id' => $image_id,
1368          'tag_id' => $tag_id,
1369        );
1370    }
1371  }
1372  mass_inserts(
1373    IMAGE_TAG_TABLE,
1374    array_keys($inserts[0]),
1375    $inserts
1376    );
1377  invalidate_user_cache_nb_tags();
1378}
1379
1380/**
1381 *
1382 */
1383function delete_tags($tag_ids)
1384{
1385  if (is_numeric($tag_ids))
1386  {
1387    $tag_ids = array($tag_ids);
1388  }
1389
1390  if (!is_array($tag_ids))
1391  {
1392    return false;
1393  }
1394
1395  $query = '
1396DELETE
1397  FROM '.IMAGE_TAG_TABLE.'
1398  WHERE tag_id IN ('.implode(',', $tag_ids).')
1399;';
1400  pwg_query($query);
1401
1402  $query = '
1403DELETE
1404  FROM '.TAGS_TABLE.'
1405  WHERE id IN ('.implode(',', $tag_ids).')
1406;';
1407  pwg_query($query);
1408
1409  invalidate_user_cache_nb_tags();
1410}
1411
1412function tag_id_from_tag_name($tag_name)
1413{
1414  global $page;
1415
1416  $tag_name = trim($tag_name);
1417  if (isset($page['tag_id_from_tag_name_cache'][$tag_name]))
1418  {
1419    return $page['tag_id_from_tag_name_cache'][$tag_name];
1420  }
1421
1422  // search existing by exact name
1423  $query = '
1424SELECT id
1425  FROM '.TAGS_TABLE.'
1426  WHERE name = \''.$tag_name.'\'
1427;';
1428  if (count($existing_tags = array_from_query($query, 'id')) == 0)
1429  {
1430    // search existing by case insensitive name
1431    $query = '
1432SELECT id
1433  FROM '.TAGS_TABLE.'
1434  WHERE CONVERT(name, CHAR) = \''.$tag_name.'\'
1435;';
1436    if (count($existing_tags = array_from_query($query, 'id')) == 0)
1437    {
1438      $url_name = trigger_event('render_tag_url', $tag_name);
1439      // search existing by url name
1440      $query = '
1441SELECT id
1442  FROM '.TAGS_TABLE.'
1443  WHERE url_name = \''.$url_name.'\'
1444;';
1445      if (count($existing_tags = array_from_query($query, 'id')) == 0)
1446      {
1447        mass_inserts(
1448          TAGS_TABLE,
1449          array('name', 'url_name'),
1450          array(
1451            array(
1452              'name' => $tag_name,
1453              'url_name' => $url_name,
1454              )
1455            )
1456          );
1457        $page['tag_id_from_tag_name_cache'][$tag_name] = pwg_db_insert_id(TAGS_TABLE);
1458        return $page['tag_id_from_tag_name_cache'][$tag_name];
1459      }
1460    }
1461  }
1462
1463  $page['tag_id_from_tag_name_cache'][$tag_name] = $existing_tags[0];
1464  return $page['tag_id_from_tag_name_cache'][$tag_name];
1465}
1466
1467function set_tags_of($tags_of)
1468{
1469  if (count($tags_of) > 0)
1470  {
1471    $query = '
1472DELETE
1473  FROM '.IMAGE_TAG_TABLE.'
1474  WHERE image_id IN ('.implode(',', array_keys($tags_of)).')
1475;';
1476    pwg_query($query);
1477
1478    $inserts = array();
1479
1480    foreach ($tags_of as $image_id => $tag_ids)
1481    {
1482      foreach (array_unique($tag_ids) as $tag_id)
1483      {
1484        $inserts[] = array(
1485            'image_id' => $image_id,
1486            'tag_id' => $tag_id,
1487          );
1488      }
1489    }
1490
1491    if (count($inserts))
1492    {
1493      mass_inserts(
1494        IMAGE_TAG_TABLE,
1495        array_keys($inserts[0]),
1496        $inserts
1497        );
1498    }
1499
1500    invalidate_user_cache_nb_tags();
1501  }
1502}
1503
1504/**
1505 * Associate a list of images to a list of categories.
1506 *
1507 * The function will not duplicate links and will preserve ranks
1508 *
1509 * @param array images
1510 * @param array categories
1511 * @return void
1512 */
1513function associate_images_to_categories($images, $categories)
1514{
1515  if (count($images) == 0
1516      or count($categories) == 0)
1517  {
1518    return false;
1519  }
1520
1521  // get existing associations
1522  $query = '
1523SELECT
1524    image_id,
1525    category_id
1526  FROM '.IMAGE_CATEGORY_TABLE.'
1527  WHERE image_id IN ('.implode(',', $images).')
1528    AND category_id IN ('.implode(',', $categories).')
1529;';
1530  $result = pwg_query($query);
1531
1532  $existing = array();
1533  while ($row = pwg_db_fetch_assoc($result))
1534  {
1535    $existing[ $row['category_id'] ][] = $row['image_id'];
1536  }
1537
1538  // get max rank of each categories
1539  $query = '
1540SELECT
1541    category_id,
1542    MAX(rank) AS max_rank
1543  FROM '.IMAGE_CATEGORY_TABLE.'
1544  WHERE rank IS NOT NULL
1545    AND category_id IN ('.implode(',', $categories).')
1546  GROUP BY category_id
1547;';
1548
1549  $current_rank_of = simple_hash_from_query(
1550    $query,
1551    'category_id',
1552    'max_rank'
1553    );
1554
1555  // associate only not already associated images
1556  $inserts = array();
1557  foreach ($categories as $category_id)
1558  {
1559    if (!isset($current_rank_of[$category_id]))
1560    {
1561      $current_rank_of[$category_id] = 0;
1562    }
1563    if (!isset($existing[$category_id]))
1564    {
1565      $existing[$category_id] = array();
1566    }
1567
1568    foreach ($images as $image_id)
1569    {
1570      if (!in_array($image_id, $existing[$category_id]))
1571      {
1572        $rank = ++$current_rank_of[$category_id];
1573
1574        $inserts[] = array(
1575          'image_id' => $image_id,
1576          'category_id' => $category_id,
1577          'rank' => $rank,
1578          );
1579      }
1580    }
1581  }
1582
1583  if (count($inserts))
1584  {
1585    mass_inserts(
1586      IMAGE_CATEGORY_TABLE,
1587      array_keys($inserts[0]),
1588      $inserts
1589      );
1590
1591    update_category($categories);
1592  }
1593}
1594
1595/**
1596 * Dissociate images from all old categories except their storage category and
1597 * associate to new categories.
1598 *
1599 * This function will preserve ranks
1600 *
1601 * @param array images
1602 * @param array categories
1603 * @return void
1604 */
1605function move_images_to_categories($images, $categories)
1606{
1607  if (count($images) == 0)
1608  {
1609    return false;
1610  }
1611
1612  // let's first break links with all old albums but their "storage album"
1613  $query = '
1614DELETE '.IMAGE_CATEGORY_TABLE.'.*
1615  FROM '.IMAGE_CATEGORY_TABLE.'
1616    JOIN '.IMAGES_TABLE.' ON image_id=id
1617  WHERE id IN ('.implode(',', $images).')
1618';
1619
1620  if (is_array($categories) and count($categories) > 0)
1621  {
1622    $query.= '
1623    AND category_id NOT IN ('.implode(',', $categories).')
1624';
1625  }
1626
1627  $query.= '
1628    AND (storage_category_id IS NULL OR storage_category_id != category_id)
1629;';
1630  pwg_query($query);
1631
1632  if (is_array($categories) and count($categories) > 0)
1633  {
1634    associate_images_to_categories($images, $categories);
1635  }
1636}
1637
1638/**
1639 * Associate images associated to a list of source categories to a list of
1640 * destination categories.
1641 *
1642 * @param array sources
1643 * @param array destinations
1644 * @return void
1645 */
1646function associate_categories_to_categories($sources, $destinations)
1647{
1648  if (count($sources) == 0)
1649  {
1650    return false;
1651  }
1652
1653  $query = '
1654SELECT image_id
1655  FROM '.IMAGE_CATEGORY_TABLE.'
1656  WHERE category_id IN ('.implode(',', $sources).')
1657;';
1658  $images = array_from_query($query, 'image_id');
1659
1660  associate_images_to_categories($images, $destinations);
1661}
1662
1663/**
1664 * Refer main Piwigo URLs (currently PHPWG_DOMAIN domain)
1665 *
1666 * @param void
1667 * @return array like $conf['links']
1668 */
1669function pwg_URL()
1670{
1671  $urls = array(
1672    'HOME'       => PHPWG_URL,
1673    'WIKI'       => PHPWG_URL.'/doc',
1674    'DEMO'       => PHPWG_URL.'/demo',
1675    'FORUM'      => PHPWG_URL.'/forum',
1676    'BUGS'       => PHPWG_URL.'/bugs',
1677    'EXTENSIONS' => PHPWG_URL.'/ext',
1678    );
1679  return $urls;
1680}
1681
1682/**
1683 * Invalidates cahed data (permissions and category counts) for all users.
1684 */
1685function invalidate_user_cache($full = true)
1686{
1687  if ($full)
1688  {
1689    $query = '
1690TRUNCATE TABLE '.USER_CACHE_CATEGORIES_TABLE.';';
1691    pwg_query($query);
1692    $query = '
1693TRUNCATE TABLE '.USER_CACHE_TABLE.';';
1694    pwg_query($query);
1695  }
1696  else
1697  {
1698    $query = '
1699UPDATE '.USER_CACHE_TABLE.'
1700  SET need_update = \'true\';';
1701    pwg_query($query);
1702  }
1703  trigger_action('invalidate_user_cache', $full);
1704}
1705
1706
1707function invalidate_user_cache_nb_tags()
1708{
1709  global $user;
1710  unset($user['nb_available_tags']);
1711  $query = '
1712UPDATE '.USER_CACHE_TABLE.'
1713  SET nb_available_tags = NULL';
1714  pwg_query($query);
1715}
1716
1717/**
1718 * adds the caracter set to a create table sql query.
1719 * all CREATE TABLE queries must call this function
1720 * @param string query - the sql query
1721 */
1722function create_table_add_character_set($query)
1723{
1724  defined('DB_CHARSET') or fatal_error('create_table_add_character_set DB_CHARSET undefined');
1725  if ('DB_CHARSET'!='')
1726  {
1727    if ( version_compare(pwg_get_db_version(), '4.1.0', '<') )
1728    {
1729      return $query;
1730    }
1731    $charset_collate = " DEFAULT CHARACTER SET ".DB_CHARSET;
1732    if (DB_COLLATE!='')
1733    {
1734      $charset_collate .= " COLLATE ".DB_COLLATE;
1735    }
1736    if ( is_array($query) )
1737    {
1738      foreach( $query as $id=>$q)
1739      {
1740        $q=trim($q);
1741        $q=trim($q, ';');
1742        if (preg_match('/^CREATE\s+TABLE/i',$q))
1743        {
1744          $q.=$charset_collate;
1745        }
1746        $q .= ';';
1747        $query[$id] = $q;
1748      }
1749    }
1750    else
1751    {
1752      $query=trim($query);
1753      $query=trim($query, ';');
1754      if (preg_match('/^CREATE\s+TABLE/i',$query))
1755      {
1756        $query.=$charset_collate;
1757      }
1758      $query .= ';';
1759    }
1760  }
1761  return $query;
1762}
1763
1764/**
1765 * Returns array use on template with html_options method
1766 * @param Min and Max access to use
1767 * @return array of user access level
1768 */
1769function get_user_access_level_html_options($MinLevelAccess = ACCESS_FREE, $MaxLevelAccess = ACCESS_CLOSED)
1770{
1771  $tpl_options = array();
1772  for ($level = $MinLevelAccess; $level <= $MaxLevelAccess; $level++)
1773  {
1774    $tpl_options[$level] = l10n(sprintf('ACCESS_%d', $level));
1775  }
1776  return $tpl_options;
1777}
1778
1779/**
1780 * returns a list of templates currently available in template-extension
1781 * Each .tpl file is extracted from template-extension.
1782 * @return array
1783 */
1784function get_extents($start='')
1785{
1786  if ($start == '') { $start = './template-extension'; }
1787  $dir = opendir($start);
1788  $extents = array();
1789
1790  while (($file = readdir($dir)) !== false)
1791  {
1792    if ( $file == '.' or $file == '..' or $file == '.svn') continue;
1793    $path = $start . '/' . $file;
1794    if (is_dir($path))
1795    {
1796      $extents = array_merge($extents, get_extents($path));
1797    }
1798    elseif ( !is_link($path) and file_exists($path)
1799            and get_extension($path) == 'tpl' )
1800    {
1801      $extents[] = substr($path, 21);
1802    }
1803  }
1804  return $extents;
1805}
1806
1807function create_tag($tag_name)
1808{
1809  // does the tag already exists?
1810  $query = '
1811SELECT id
1812  FROM '.TAGS_TABLE.'
1813  WHERE name = \''.$tag_name.'\'
1814;';
1815  $existing_tags = array_from_query($query, 'id');
1816
1817  if (count($existing_tags) == 0)
1818  {
1819    mass_inserts(
1820      TAGS_TABLE,
1821      array('name', 'url_name'),
1822      array(
1823        array(
1824          'name' => $tag_name,
1825          'url_name' => trigger_event('render_tag_url', $tag_name),
1826          )
1827        )
1828      );
1829
1830    $inserted_id = pwg_db_insert_id(TAGS_TABLE);
1831
1832    return array(
1833      'info' => sprintf(
1834        l10n('Tag "%s" was added'),
1835        stripslashes($tag_name)
1836        ),
1837      'id' => $inserted_id,
1838      );
1839  }
1840  else
1841  {
1842    return array(
1843      'error' => sprintf(
1844        l10n('Tag "%s" already exists'),
1845        stripslashes($tag_name)
1846        )
1847      );
1848  }
1849}
1850
1851/**
1852 * Is the category accessible to the (Admin) user ?
1853 *
1854 * Note : if the user is not authorized to see this category, category jump
1855 * will be replaced by admin cat_modify page
1856 *
1857 * @param int category id to verify
1858 * @return bool
1859 */
1860function cat_admin_access($category_id)
1861{
1862  global $user;
1863
1864  // $filter['visible_categories'] and $filter['visible_images']
1865  // are not used because it's not necessary (filter <> restriction)
1866  if (in_array($category_id, explode(',', $user['forbidden_categories'])))
1867  {
1868    return false;
1869  }
1870  return true;
1871}
1872
1873/**
1874 * Retrieve data from external URL
1875 *
1876 * @param string $src: URL
1877 * @param global $dest: can be a file ressource or string
1878 * @return bool
1879 */
1880function fetchRemote($src, &$dest, $get_data=array(), $post_data=array(), $user_agent='Piwigo', $step=0)
1881{
1882  // Try to retrieve data from local file?
1883  if (!url_is_remote($src))
1884  {
1885    $content = @file_get_contents($src);
1886    if ($content !== false)
1887    {
1888      is_resource($dest) ? @fwrite($dest, $content) : $dest = $content;
1889      return true;
1890    }
1891    else
1892    {
1893      return false;
1894    }
1895  }
1896
1897  // After 3 redirections, return false
1898  if ($step > 3) return false;
1899
1900  // Initialization
1901  $method  = empty($post_data) ? 'GET' : 'POST';
1902  $request = empty($post_data) ? '' : http_build_query($post_data, '', '&');
1903  if (!empty($get_data))
1904  {
1905    $src .= strpos($src, '?') === false ? '?' : '&';
1906    $src .= http_build_query($get_data, '', '&');
1907  }
1908
1909  // Initialize $dest
1910  is_resource($dest) or $dest = '';
1911
1912  // Try curl to read remote file
1913  if (function_exists('curl_init'))
1914  {
1915    $ch = @curl_init();
1916    @curl_setopt($ch, CURLOPT_URL, $src);
1917    @curl_setopt($ch, CURLOPT_HEADER, 1);
1918    @curl_setopt($ch, CURLOPT_USERAGENT, $user_agent);
1919    @curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
1920    if ($method == 'POST')
1921    {
1922      @curl_setopt($ch, CURLOPT_POST, 1);
1923      @curl_setopt($ch, CURLOPT_POSTFIELDS, $request);
1924    }
1925    $content = @curl_exec($ch);
1926    $header_length = @curl_getinfo($ch, CURLINFO_HEADER_SIZE);
1927    $status = @curl_getinfo($ch, CURLINFO_HTTP_CODE);
1928    @curl_close($ch);
1929    if ($content !== false and $status >= 200 and $status < 400)
1930    {
1931      if (preg_match('/Location:\s+?(.+)/', substr($content, 0, $header_length), $m))
1932      {
1933        return fetchRemote($m[1], $dest, array(), array(), $user_agent, $step+1);
1934      }
1935      $content = substr($content, $header_length);
1936      is_resource($dest) ? @fwrite($dest, $content) : $dest = $content;
1937      return true;
1938    }
1939  }
1940
1941  // Try file_get_contents to read remote file
1942  if (ini_get('allow_url_fopen'))
1943  {
1944    $opts = array(
1945      'http' => array(
1946        'method' => $method,
1947        'user_agent' => $user_agent,
1948      )
1949    );
1950    if ($method == 'POST')
1951    {
1952      $opts['http']['content'] = $request;
1953    }
1954    $context = @stream_context_create($opts);
1955    $content = @file_get_contents($src, false, $context);
1956    if ($content !== false)
1957    {
1958      is_resource($dest) ? @fwrite($dest, $content) : $dest = $content;
1959      return true;
1960    }
1961  }
1962
1963  // Try fsockopen to read remote file
1964  $src = parse_url($src);
1965  $host = $src['host'];
1966  $path = isset($src['path']) ? $src['path'] : '/';
1967  $path .= isset($src['query']) ? '?'.$src['query'] : '';
1968
1969  if (($s = @fsockopen($host,80,$errno,$errstr,5)) === false)
1970  {
1971    return false;
1972  }
1973
1974  $http_request  = $method." ".$path." HTTP/1.0\r\n";
1975  $http_request .= "Host: ".$host."\r\n";
1976  if ($method == 'POST')
1977  {
1978    $http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n";
1979    $http_request .= "Content-Length: ".strlen($request)."\r\n";
1980  }
1981  $http_request .= "User-Agent: ".$user_agent."\r\n";
1982  $http_request .= "Accept: */*\r\n";
1983  $http_request .= "\r\n";
1984  $http_request .= $request;
1985
1986  fwrite($s, $http_request);
1987
1988  $i = 0;
1989  $in_content = false;
1990  while (!feof($s))
1991  {
1992    $line = fgets($s);
1993
1994    if (rtrim($line,"\r\n") == '' && !$in_content)
1995    {
1996      $in_content = true;
1997      $i++;
1998      continue;
1999    }
2000    if ($i == 0)
2001    {
2002      if (!preg_match('/HTTP\/(\\d\\.\\d)\\s*(\\d+)\\s*(.*)/',rtrim($line,"\r\n"), $m))
2003      {
2004        fclose($s);
2005        return false;
2006      }
2007      $status = (integer) $m[2];
2008      if ($status < 200 || $status >= 400)
2009      {
2010        fclose($s);
2011        return false;
2012      }
2013    }
2014    if (!$in_content)
2015    {
2016      if (preg_match('/Location:\s+?(.+)$/',rtrim($line,"\r\n"),$m))
2017      {
2018        fclose($s);
2019        return fetchRemote(trim($m[1]),$dest,array(),array(),$user_agent,$step+1);
2020      }
2021      $i++;
2022      continue;
2023    }
2024    is_resource($dest) ? @fwrite($dest, $line) : $dest .= $line;
2025    $i++;
2026  }
2027  fclose($s);
2028  return true;
2029}
2030
2031
2032/**
2033 * returns the groupname corresponding to the given group identifier if
2034 * exists
2035 *
2036 * @param int group_id
2037 * @return mixed
2038 */
2039function get_groupname($group_id)
2040{
2041  $query = '
2042SELECT name
2043  FROM '.GROUPS_TABLE.'
2044  WHERE id = '.intval($group_id).'
2045;';
2046  $result = pwg_query($query);
2047  if (pwg_db_num_rows($result) > 0)
2048  {
2049    list($groupname) = pwg_db_fetch_row($result);
2050  }
2051  else
2052  {
2053    return false;
2054  }
2055
2056  return $groupname;
2057}
2058
2059/**
2060 * returns the username corresponding to the given user identifier if exists
2061 *
2062 * @param int user_id
2063 * @return mixed
2064 */
2065function get_username($user_id)
2066{
2067  global $conf;
2068
2069  $query = '
2070SELECT '.$conf['user_fields']['username'].'
2071  FROM '.USERS_TABLE.'
2072  WHERE '.$conf['user_fields']['id'].' = '.intval($user_id).'
2073;';
2074  $result = pwg_query($query);
2075  if (pwg_db_num_rows($result) > 0)
2076  {
2077    list($username) = pwg_db_fetch_row($result);
2078  }
2079  else
2080  {
2081    return false;
2082  }
2083
2084  return stripslashes($username);
2085}
2086
2087function get_newsletter_subscribe_base_url($language) {
2088  return PHPWG_URL.'/announcement/subscribe/';
2089}
2090
2091/**
2092 * Accordion menus need to know which section to open by default when
2093 * loading the page
2094 */
2095function get_active_menu($menu_page)
2096{
2097  global $page;
2098
2099  if (isset($page['active_menu']))
2100  {
2101    return $page['active_menu'];
2102  }
2103
2104  switch ($menu_page)
2105  {
2106    case 'photo':
2107    case 'photos_add':
2108    case 'rating':
2109    case 'tags':
2110    case 'batch_manager':
2111      return 0;
2112
2113    case 'album':
2114    case 'cat_list':
2115    case 'cat_move':
2116    case 'cat_options':
2117    case 'permalinks':
2118      return 1;
2119
2120    case 'user_list':
2121    case 'user_perm':
2122    case 'group_list':
2123    case 'group_perm':
2124    case 'notification_by_mail':
2125      return 2;
2126
2127    case 'plugins':
2128    case 'plugin':
2129      return 3;
2130
2131    case 'site_manager':
2132    case 'site_update':
2133    case 'stats':
2134    case 'history':
2135    case 'maintenance':
2136    case 'comments':
2137    case 'updates':
2138      return 4;
2139
2140    case 'configuration':
2141    case 'derivatives':
2142    case 'extend_for_templates':
2143    case 'menubar':
2144    case 'themes':
2145    case 'theme':
2146    case 'languages':
2147      return 5;
2148  }
2149  return 0;
2150}
2151
2152/*
2153 * get tags list from SQL query (ids are surrounded by ~~, for get_tag_ids())
2154 * @param string: query
2155 * @param boolean: only_user_language, if true, only local name is returned for multilingual tags
2156 */
2157function get_taglist($query, $only_user_language=true)
2158{
2159  $result = pwg_query($query);
2160
2161  $taglist = array();
2162  $altlist = array();
2163  while ($row = pwg_db_fetch_assoc($result))
2164  {
2165    $raw_name = $row['name'];
2166    $name = trigger_event('render_tag_name', $raw_name);
2167
2168    $taglist[] =  array(
2169        'name' => $name,
2170        'id' => '~~'.$row['id'].'~~',
2171      );
2172
2173    if (!$only_user_language)
2174    {
2175      $alt_names = trigger_event('get_tag_alt_names', array(), $raw_name);
2176
2177      foreach( array_diff( array_unique($alt_names), array($name) ) as $alt)
2178      {
2179        $altlist[] =  array(
2180            'name' => $alt,
2181            'id' => '~~'.$row['id'].'~~',
2182          );
2183      }
2184    }
2185  }
2186
2187  usort($taglist, 'tag_alpha_compare');
2188  if (count($altlist))
2189  {
2190    usort($altlist, 'tag_alpha_compare');
2191    $taglist = array_merge($taglist, $altlist);
2192  }
2193
2194  return $taglist;
2195}
2196
2197/*
2198 * get tags ids from a list of raw tags (existing tags or new tags)
2199 * @param mixed: raw_tags, array or comma separated string
2200 * @param boolean: allow_create
2201 */
2202function get_tag_ids($raw_tags, $allow_create=true)
2203{
2204  // In $raw_tags we receive something like array('~~6~~', '~~59~~', 'New
2205  // tag', 'Another new tag') The ~~34~~ means that it is an existing
2206  // tag. I've added the surrounding ~~ to permit creation of tags like "10"
2207  // or "1234" (numeric characters only)
2208
2209  $tag_ids = array();
2210  if (!is_array($raw_tags))
2211  {
2212    $raw_tags = explode(',',$raw_tags);
2213  }
2214
2215  foreach ($raw_tags as $raw_tag)
2216  {
2217    if (preg_match('/^~~(\d+)~~$/', $raw_tag, $matches))
2218    {
2219      $tag_ids[] = $matches[1];
2220    }
2221    elseif ($allow_create)
2222    {
2223      // we have to create a new tag
2224      $tag_ids[] = tag_id_from_tag_name($raw_tag);
2225    }
2226  }
2227
2228  return $tag_ids;
2229}
2230
2231/** returns the argument_ids array with new sequenced keys based on related
2232 * names. Sequence is not case sensitive.
2233 * Warning: By definition, this function breaks original keys
2234 */
2235function order_by_name($element_ids,$name)
2236{
2237  $ordered_element_ids = array();
2238  foreach ($element_ids as $k_id => $element_id)
2239  {
2240    $key = strtolower($name[$element_id]) .'-'. $name[$element_id] .'-'. $k_id;
2241    $ordered_element_ids[$key] = $element_id;
2242  }
2243  ksort($ordered_element_ids);
2244  return $ordered_element_ids;
2245}
2246
2247function add_permission_on_category($category_ids, $user_ids)
2248{
2249  // array-ify categories and users
2250  if (!is_array($category_ids))
2251  {
2252    $category_ids = array($category_ids);
2253  }
2254
2255  if (!is_array($user_ids))
2256  {
2257    $user_ids = array($user_ids);
2258  }
2259
2260  // check for emptiness
2261  if (count($category_ids) == 0 or count($user_ids) == 0)
2262  {
2263    return;
2264  }
2265
2266  // make sure categories are private and select uppercats or subcats
2267  $cat_ids = (isset($_POST['apply_on_sub'])) ? implode(',', get_subcat_ids($category_ids)).",".implode(',', get_uppercat_ids($category_ids)) : implode(',', get_uppercat_ids($category_ids));
2268  $query = '
2269SELECT
2270    id
2271  FROM '.CATEGORIES_TABLE.'
2272  WHERE id IN ('.$cat_ids.')
2273    AND status = \'private\'
2274;';
2275  $private_cats = array_from_query($query, 'id');
2276
2277  if (count($private_cats) == 0)
2278  {
2279    return;
2280  }
2281
2282  // We must not reinsert already existing lines in user_access table
2283  $granteds = array();
2284  foreach ($private_cats as $cat_id)
2285  {
2286    $granteds[$cat_id] = array();
2287  }
2288
2289  $query = '
2290SELECT
2291    user_id,
2292    cat_id
2293  FROM '.USER_ACCESS_TABLE.'
2294  WHERE cat_id IN ('.implode(',', $private_cats).')
2295    AND user_id IN ('.implode(',', $user_ids).')
2296;';
2297  $result = pwg_query($query);
2298  while ($row = pwg_db_fetch_assoc($result))
2299  {
2300    $granteds[ $row['cat_id'] ][] = $row['user_id'];
2301  }
2302
2303  $inserts = array();
2304
2305  foreach ($private_cats as $cat_id)
2306  {
2307    $grant_to_users = array_diff($user_ids, $granteds[$cat_id]);
2308
2309    foreach ($grant_to_users as $user_id)
2310    {
2311      $inserts[] = array(
2312        'user_id' => $user_id,
2313        'cat_id' => $cat_id
2314        );
2315    }
2316  }
2317
2318  if (count($inserts) > 0)
2319  {
2320    mass_inserts(USER_ACCESS_TABLE, array_keys($inserts[0]), $inserts);
2321  }
2322}
2323
2324
2325function get_admins($include_webmaster=true)
2326{
2327  $status_list = array('admin');
2328
2329  if ($include_webmaster)
2330  {
2331    $status_list[] = 'webmaster';
2332  }
2333
2334  $query = '
2335SELECT
2336    user_id
2337  FROM '.USER_INFOS_TABLE.'
2338  WHERE status in (\''.implode("','", $status_list).'\')
2339;';
2340
2341  return array_from_query($query, 'user_id');
2342}
2343
2344/** delete all derivative files for one or several types */
2345function clear_derivative_cache($types='all')
2346{
2347  if ($types === 'all')
2348  {
2349    $types = ImageStdParams::get_all_types();
2350    $types[] = IMG_CUSTOM;
2351  }
2352  elseif (!is_array($types))
2353  {
2354    $types = array($types);
2355  }
2356
2357  for ($i=0; $i<count($types); $i++)
2358  {
2359    $type = $types[$i];
2360    if ($type == IMG_CUSTOM)
2361    {
2362      $type = derivative_to_url($type).'[a-zA-Z0-9]+';
2363    }
2364    elseif (in_array($type, ImageStdParams::get_all_types()))
2365    {
2366      $type = derivative_to_url($type);
2367    }
2368    else
2369    {//assume a custom type
2370      $type = derivative_to_url(IMG_CUSTOM).'_'.$type;
2371    }
2372    $types[$i] = $type;
2373  }
2374
2375  $pattern='#.*-';
2376  if (count($types)>1)
2377  {
2378    $pattern .= '(' . implode('|',$types) . ')';
2379  }
2380  else
2381  {
2382    $pattern .= $types[0];
2383  }
2384  $pattern.='\.[a-zA-Z0-9]{3,4}$#';
2385
2386  if ($contents = @opendir(PHPWG_ROOT_PATH.PWG_DERIVATIVE_DIR))
2387  {
2388    while (($node = readdir($contents)) !== false)
2389    {
2390      if ($node != '.'
2391          and $node != '..'
2392          and is_dir(PHPWG_ROOT_PATH.PWG_DERIVATIVE_DIR.$node))
2393      {
2394        clear_derivative_cache_rec(PHPWG_ROOT_PATH.PWG_DERIVATIVE_DIR.$node, $pattern);
2395      }
2396    }
2397    closedir($contents);
2398  }
2399}
2400
2401function clear_derivative_cache_rec($path, $pattern)
2402{
2403  $rmdir = true;
2404  $rm_index = false;
2405
2406  if ($contents = opendir($path))
2407  {
2408    while (($node = readdir($contents)) !== false)
2409    {
2410      if ($node == '.' or $node == '..')
2411        continue;
2412      if (is_dir($path.'/'.$node))
2413      {
2414        $rmdir &= clear_derivative_cache_rec($path.'/'.$node, $pattern);
2415      }
2416      else
2417      {
2418        if (preg_match($pattern, $node))
2419        {
2420          unlink($path.'/'.$node);
2421        }
2422        elseif ($node=='index.htm')
2423        {
2424          $rm_index = true;
2425        }
2426        else
2427        {
2428          $rmdir = false;
2429        }
2430      }
2431    }
2432    closedir($contents);
2433
2434    if ($rmdir)
2435    {
2436      if ($rm_index)
2437      {
2438        unlink($path.'/index.htm');
2439      }
2440      clearstatcache();
2441      @rmdir($path);
2442    }
2443    return $rmdir;
2444  }
2445}
2446
2447function delete_element_derivatives($infos, $type='all')
2448{
2449  $path = $infos['path'];
2450  if (!empty($infos['representative_ext']))
2451  {
2452    $path = original_to_representative( $path, $infos['representative_ext']);
2453  }
2454  if (substr_compare($path, '../', 0, 3)==0)
2455  {
2456    $path = substr($path, 3);
2457  }
2458  $dot = strrpos($path, '.');
2459  if ($type=='all')
2460  {
2461    $pattern = '-*';
2462  }
2463  else
2464  {
2465    $pattern = '-'.derivative_to_url($type).'*';
2466  }
2467  $path = substr_replace($path, $pattern, $dot, 0);
2468  if ( ($glob=glob(PHPWG_ROOT_PATH.PWG_DERIVATIVE_DIR.$path)) !== false)
2469  {
2470    foreach( $glob as $file)
2471    {
2472      @unlink($file);
2473    }
2474  }
2475}
2476
2477/**
2478 * returns an array contening sub-directories, excluding ".svn"
2479 *
2480 * @param string $dir
2481 * @return array
2482 */
2483function get_dirs($directory)
2484{
2485  $sub_dirs = array();
2486  if ($opendir = opendir($directory))
2487  {
2488    while ($file = readdir($opendir))
2489    {
2490      if ($file != '.'
2491          and $file != '..'
2492          and is_dir($directory.'/'.$file)
2493          and $file != '.svn')
2494      {
2495        $sub_dirs[] = $file;
2496      }
2497    }
2498    closedir($opendir);
2499  }
2500  return $sub_dirs;
2501}
2502
2503/**
2504 * recursively delete a directory
2505 * @param string $path
2506 * @param string $trash_path, try to move the directory to this path if it cannot be delete
2507 */
2508function deltree($path, $trash_path=null)
2509{
2510  if (is_dir($path))
2511  {
2512    $fh = opendir($path);
2513    while ($file = readdir($fh))
2514    {
2515      if ($file != '.' and $file != '..')
2516      {
2517        $pathfile = $path . '/' . $file;
2518        if (is_dir($pathfile))
2519        {
2520          deltree($pathfile, $trash_path);
2521        }
2522        else
2523        {
2524          @unlink($pathfile);
2525        }
2526      }
2527    }
2528    closedir($fh);
2529   
2530    if (@rmdir($path))
2531    {
2532      return true;
2533    }
2534    elseif (!empty($trash_path))
2535    {
2536      if (!is_dir($trash_path))
2537      {
2538        @mkgetdir($trash_path, MKGETDIR_RECURSIVE|MKGETDIR_DIE_ON_ERROR|MKGETDIR_PROTECT_HTACCESS);
2539      }
2540      while ($r = $trash_path . '/' . md5(uniqid(rand(), true)))
2541      {
2542        if (!is_dir($r))
2543        {
2544          @rename($path, $r);
2545          break;
2546        }
2547      }
2548    }
2549    else
2550    {
2551      return false;
2552    }
2553  }
2554}
2555
2556?>
Note: See TracBrowser for help on using the repository browser.