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

Last change on this file since 29393 was 29229, checked in by flop25, 10 years ago

feature:732
unlock sub-albums

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