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

Last change on this file since 27926 was 27926, checked in by plg, 10 years ago

merge r27925 from branch 2.6 to trunk

bug 3057: avoid warnings and SQL crash when encountering inconsistent
permissions + rewrite permissions consistancy check when setting albums
to private status.

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