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

Last change on this file since 26972 was 26972, checked in by mistic100, 10 years ago

replace more preg_replace callback

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