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

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

Merged revision(s) 26972, 26998 from trunk:
replace more preg_replace callback
........
remove *_version_compare methods in languages & plugins & themes classes, unused and outdated (preg_replace /e modifier)

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