source: branches/2.6/admin/include/functions.php @ 26921

Revision 26921, 58.9 KB checked in by plg, 6 years ago (diff)

bug 3032 fixed: it seems that the SQL update in invalidate_user_cache_nb_tags corrupts the pwg_db_insert_id (very unexpected)

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