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

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

bug if (584 == )

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