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

Last change on this file since 28532 was 28532, checked in by mistic100, 11 years ago

feature 3077 : improve cache invalidation

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