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

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

feature 3010 : replace trigger_action/event by trigger_notify/change

  • 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_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 = array_from_query($query, '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 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_change('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_notify('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_change('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  // TODO : remove all these @
2064  if (function_exists('curl_init') && function_exists('curl_exec'))
2065  {
2066    $ch = @curl_init();
2067    @curl_setopt($ch, CURLOPT_URL, $src);
2068    @curl_setopt($ch, CURLOPT_HEADER, 1);
2069    @curl_setopt($ch, CURLOPT_USERAGENT, $user_agent);
2070    @curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
2071    if ($method == 'POST')
2072    {
2073      @curl_setopt($ch, CURLOPT_POST, 1);
2074      @curl_setopt($ch, CURLOPT_POSTFIELDS, $request);
2075    }
2076    $content = @curl_exec($ch);
2077    $header_length = @curl_getinfo($ch, CURLINFO_HEADER_SIZE);
2078    $status = @curl_getinfo($ch, CURLINFO_HTTP_CODE);
2079    @curl_close($ch);
2080    if ($content !== false and $status >= 200 and $status < 400)
2081    {
2082      if (preg_match('/Location:\s+?(.+)/', substr($content, 0, $header_length), $m))
2083      {
2084        return fetchRemote($m[1], $dest, array(), array(), $user_agent, $step+1);
2085      }
2086      $content = substr($content, $header_length);
2087      is_resource($dest) ? @fwrite($dest, $content) : $dest = $content;
2088      return true;
2089    }
2090  }
2091
2092  // Try file_get_contents to read remote file
2093  if (ini_get('allow_url_fopen'))
2094  {
2095    $opts = array(
2096      'http' => array(
2097        'method' => $method,
2098        'user_agent' => $user_agent,
2099      )
2100    );
2101    if ($method == 'POST')
2102    {
2103      $opts['http']['content'] = $request;
2104    }
2105    $context = @stream_context_create($opts);
2106    $content = @file_get_contents($src, false, $context);
2107    if ($content !== false)
2108    {
2109      is_resource($dest) ? @fwrite($dest, $content) : $dest = $content;
2110      return true;
2111    }
2112  }
2113
2114  // Try fsockopen to read remote file
2115  $src = parse_url($src);
2116  $host = $src['host'];
2117  $path = isset($src['path']) ? $src['path'] : '/';
2118  $path .= isset($src['query']) ? '?'.$src['query'] : '';
2119
2120  if (($s = @fsockopen($host,80,$errno,$errstr,5)) === false)
2121  {
2122    return false;
2123  }
2124
2125  $http_request  = $method." ".$path." HTTP/1.0\r\n";
2126  $http_request .= "Host: ".$host."\r\n";
2127  if ($method == 'POST')
2128  {
2129    $http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n";
2130    $http_request .= "Content-Length: ".strlen($request)."\r\n";
2131  }
2132  $http_request .= "User-Agent: ".$user_agent."\r\n";
2133  $http_request .= "Accept: */*\r\n";
2134  $http_request .= "\r\n";
2135  $http_request .= $request;
2136
2137  fwrite($s, $http_request);
2138
2139  $i = 0;
2140  $in_content = false;
2141  while (!feof($s))
2142  {
2143    $line = fgets($s);
2144
2145    if (rtrim($line,"\r\n") == '' && !$in_content)
2146    {
2147      $in_content = true;
2148      $i++;
2149      continue;
2150    }
2151    if ($i == 0)
2152    {
2153      if (!preg_match('/HTTP\/(\\d\\.\\d)\\s*(\\d+)\\s*(.*)/',rtrim($line,"\r\n"), $m))
2154      {
2155        fclose($s);
2156        return false;
2157      }
2158      $status = (integer) $m[2];
2159      if ($status < 200 || $status >= 400)
2160      {
2161        fclose($s);
2162        return false;
2163      }
2164    }
2165    if (!$in_content)
2166    {
2167      if (preg_match('/Location:\s+?(.+)$/',rtrim($line,"\r\n"),$m))
2168      {
2169        fclose($s);
2170        return fetchRemote(trim($m[1]),$dest,array(),array(),$user_agent,$step+1);
2171      }
2172      $i++;
2173      continue;
2174    }
2175    is_resource($dest) ? @fwrite($dest, $line) : $dest .= $line;
2176    $i++;
2177  }
2178  fclose($s);
2179  return true;
2180}
2181
2182/**
2183 * Returns the groupname corresponding to the given group identifier if exists.
2184 *
2185 * @param int $group_id
2186 * @return string|false
2187 */
2188function get_groupname($group_id)
2189{
2190  $query = '
2191SELECT name
2192  FROM '.GROUPS_TABLE.'
2193  WHERE id = '.intval($group_id).'
2194;';
2195  $result = pwg_query($query);
2196  if (pwg_db_num_rows($result) > 0)
2197  {
2198    list($groupname) = pwg_db_fetch_row($result);
2199  }
2200  else
2201  {
2202    return false;
2203  }
2204
2205  return $groupname;
2206}
2207
2208/**
2209 * Returns the username corresponding to the given user identifier if exists.
2210 *
2211 * @param int $user_id
2212 * @return string|false
2213 */
2214function get_username($user_id)
2215{
2216  global $conf;
2217
2218  $query = '
2219SELECT '.$conf['user_fields']['username'].'
2220  FROM '.USERS_TABLE.'
2221  WHERE '.$conf['user_fields']['id'].' = '.intval($user_id).'
2222;';
2223  $result = pwg_query($query);
2224  if (pwg_db_num_rows($result) > 0)
2225  {
2226    list($username) = pwg_db_fetch_row($result);
2227  }
2228  else
2229  {
2230    return false;
2231  }
2232
2233  return stripslashes($username);
2234}
2235
2236/**
2237 * Get url on piwigo.org for newsletter subscription
2238 *
2239 * @param string $language (unused)
2240 * @return string
2241 */
2242function get_newsletter_subscribe_base_url($language='en_UK')
2243{
2244  return PHPWG_URL.'/announcement/subscribe/';
2245}
2246
2247/**
2248 * Return admin menu id for accordion.
2249 *
2250 * @param string $menu_page
2251 * @return int
2252 */
2253function get_active_menu($menu_page)
2254{
2255  global $page;
2256
2257  if (isset($page['active_menu']))
2258  {
2259    return $page['active_menu'];
2260  }
2261
2262  switch ($menu_page)
2263  {
2264    case 'photo':
2265    case 'photos_add':
2266    case 'rating':
2267    case 'tags':
2268    case 'batch_manager':
2269      return 0;
2270
2271    case 'album':
2272    case 'cat_list':
2273    case 'cat_move':
2274    case 'cat_options':
2275    case 'permalinks':
2276      return 1;
2277
2278    case 'user_list':
2279    case 'user_perm':
2280    case 'group_list':
2281    case 'group_perm':
2282    case 'notification_by_mail':
2283      return 2;
2284
2285    case 'plugins':
2286    case 'plugin':
2287      return 3;
2288
2289    case 'site_manager':
2290    case 'site_update':
2291    case 'stats':
2292    case 'history':
2293    case 'maintenance':
2294    case 'comments':
2295    case 'updates':
2296      return 4;
2297
2298    case 'configuration':
2299    case 'derivatives':
2300    case 'extend_for_templates':
2301    case 'menubar':
2302    case 'themes':
2303    case 'theme':
2304    case 'languages':
2305      return 5;
2306
2307    default:
2308      return 0;
2309  }
2310}
2311
2312/**
2313 * Get tags list from SQL query (ids are surrounded by ~~, for get_tag_ids()).
2314 *
2315 * @param string $query
2316 * @param boolean $only_user_language - if true, only local name is returned for
2317 *    multilingual tags (if ExtendedDescription plugin is active)
2318 * @return array[] ('id', 'name')
2319 */
2320function get_taglist($query, $only_user_language=true)
2321{
2322  $result = pwg_query($query);
2323
2324  $taglist = array();
2325  $altlist = array();
2326  while ($row = pwg_db_fetch_assoc($result))
2327  {
2328    $raw_name = $row['name'];
2329    $name = trigger_change('render_tag_name', $raw_name, $row);
2330
2331    $taglist[] =  array(
2332        'name' => $name,
2333        'id' => '~~'.$row['id'].'~~',
2334      );
2335
2336    if (!$only_user_language)
2337    {
2338      $alt_names = trigger_change('get_tag_alt_names', array(), $raw_name);
2339
2340      foreach( array_diff( array_unique($alt_names), array($name) ) as $alt)
2341      {
2342        $altlist[] =  array(
2343            'name' => $alt,
2344            'id' => '~~'.$row['id'].'~~',
2345          );
2346      }
2347    }
2348  }
2349
2350  usort($taglist, 'tag_alpha_compare');
2351  if (count($altlist))
2352  {
2353    usort($altlist, 'tag_alpha_compare');
2354    $taglist = array_merge($taglist, $altlist);
2355  }
2356
2357  return $taglist;
2358}
2359
2360/**
2361 * Get tags ids from a list of raw tags (existing tags or new tags).
2362 *
2363 * In $raw_tags we receive something like array('~~6~~', '~~59~~', 'New
2364 * tag', 'Another new tag') The ~~34~~ means that it is an existing
2365 * tag. We added the surrounding ~~ to permit creation of tags like "10"
2366 * or "1234" (numeric characters only)
2367 *
2368 * @param string|string[] $raw_tags - array or comma separated string
2369 * @param boolean $allow_create
2370 * @return int[]
2371 */
2372function get_tag_ids($raw_tags, $allow_create=true)
2373{
2374  $tag_ids = array();
2375  if (!is_array($raw_tags))
2376  {
2377    $raw_tags = explode(',',$raw_tags);
2378  }
2379
2380  foreach ($raw_tags as $raw_tag)
2381  {
2382    if (preg_match('/^~~(\d+)~~$/', $raw_tag, $matches))
2383    {
2384      $tag_ids[] = $matches[1];
2385    }
2386    elseif ($allow_create)
2387    {
2388      // we have to create a new tag
2389      $tag_ids[] = tag_id_from_tag_name($raw_tag);
2390    }
2391  }
2392
2393  return $tag_ids;
2394}
2395
2396/**
2397 * Returns the argument_ids array with new sequenced keys based on related
2398 * names. Sequence is not case sensitive.
2399 * Warning: By definition, this function breaks original keys.
2400 *
2401 * @param int[] $elements_ids
2402 * @param string[] $name - names of elements, indexed by ids
2403 * @return int[]
2404 */
2405function order_by_name($element_ids, $name)
2406{
2407  $ordered_element_ids = array();
2408  foreach ($element_ids as $k_id => $element_id)
2409  {
2410    $key = strtolower($name[$element_id]) .'-'. $name[$element_id] .'-'. $k_id;
2411    $ordered_element_ids[$key] = $element_id;
2412  }
2413  ksort($ordered_element_ids);
2414  return $ordered_element_ids;
2415}
2416
2417/**
2418 * Grant access to a list of categories for a list of users.
2419 *
2420 * @param int[] $category_ids
2421 * @param int[] $user_ids
2422 */
2423function add_permission_on_category($category_ids, $user_ids)
2424{
2425  if (!is_array($category_ids))
2426  {
2427    $category_ids = array($category_ids);
2428  }
2429  if (!is_array($user_ids))
2430  {
2431    $user_ids = array($user_ids);
2432  }
2433
2434  // check for emptiness
2435  if (count($category_ids) == 0 or count($user_ids) == 0)
2436  {
2437    return;
2438  }
2439
2440  // make sure categories are private and select uppercats or subcats
2441  $cat_ids = get_uppercat_ids($category_ids);
2442  if (isset($_POST['apply_on_sub']))
2443  {
2444    $cat_ids = array_merge($cat_ids, get_subcat_ids($category_ids));
2445  }
2446
2447  $query = '
2448SELECT id
2449  FROM '.CATEGORIES_TABLE.'
2450  WHERE id IN ('.implode(',', $cat_ids).')
2451    AND status = \'private\'
2452;';
2453  $private_cats = array_from_query($query, 'id');
2454
2455  if (count($private_cats) == 0)
2456  {
2457    return;
2458  }
2459 
2460  $inserts = array();
2461  foreach ($private_cats as $cat_id)
2462  {
2463    foreach ($user_ids as $user_id)
2464    {
2465      $inserts[] = array(
2466        'user_id' => $user_id,
2467        'cat_id' => $cat_id
2468        );
2469    }
2470  }
2471 
2472  mass_inserts(
2473    USER_ACCESS_TABLE,
2474    array('user_id','cat_id'),
2475    $inserts,
2476    array('ignore'=>true)
2477    );
2478}
2479
2480/**
2481 * Returns the list of admin users.
2482 *
2483 * @param boolean $include_webmaster
2484 * @return int[]
2485 */
2486function get_admins($include_webmaster=true)
2487{
2488  $status_list = array('admin');
2489
2490  if ($include_webmaster)
2491  {
2492    $status_list[] = 'webmaster';
2493  }
2494
2495  $query = '
2496SELECT
2497    user_id
2498  FROM '.USER_INFOS_TABLE.'
2499  WHERE status in (\''.implode("','", $status_list).'\')
2500;';
2501
2502  return array_from_query($query, 'user_id');
2503}
2504
2505/**
2506 * Delete all derivative files for one or several types
2507 *
2508 * @param 'all'|int[] $types
2509 */
2510function clear_derivative_cache($types='all')
2511{
2512  if ($types === 'all')
2513  {
2514    $types = ImageStdParams::get_all_types();
2515    $types[] = IMG_CUSTOM;
2516  }
2517  elseif (!is_array($types))
2518  {
2519    $types = array($types);
2520  }
2521
2522  for ($i=0; $i<count($types); $i++)
2523  {
2524    $type = $types[$i];
2525    if ($type == IMG_CUSTOM)
2526    {
2527      $type = derivative_to_url($type).'[a-zA-Z0-9]+';
2528    }
2529    elseif (in_array($type, ImageStdParams::get_all_types()))
2530    {
2531      $type = derivative_to_url($type);
2532    }
2533    else
2534    {//assume a custom type
2535      $type = derivative_to_url(IMG_CUSTOM).'_'.$type;
2536    }
2537    $types[$i] = $type;
2538  }
2539
2540  $pattern='#.*-';
2541  if (count($types)>1)
2542  {
2543    $pattern .= '(' . implode('|',$types) . ')';
2544  }
2545  else
2546  {
2547    $pattern .= $types[0];
2548  }
2549  $pattern.='\.[a-zA-Z0-9]{3,4}$#';
2550
2551  if ($contents = @opendir(PHPWG_ROOT_PATH.PWG_DERIVATIVE_DIR))
2552  {
2553    while (($node = readdir($contents)) !== false)
2554    {
2555      if ($node != '.'
2556          and $node != '..'
2557          and is_dir(PHPWG_ROOT_PATH.PWG_DERIVATIVE_DIR.$node))
2558      {
2559        clear_derivative_cache_rec(PHPWG_ROOT_PATH.PWG_DERIVATIVE_DIR.$node, $pattern);
2560      }
2561    }
2562    closedir($contents);
2563  }
2564}
2565
2566/**
2567 * Used by clear_derivative_cache()
2568 * @ignore
2569 */
2570function clear_derivative_cache_rec($path, $pattern)
2571{
2572  $rmdir = true;
2573  $rm_index = false;
2574
2575  if ($contents = opendir($path))
2576  {
2577    while (($node = readdir($contents)) !== false)
2578    {
2579      if ($node == '.' or $node == '..')
2580        continue;
2581      if (is_dir($path.'/'.$node))
2582      {
2583        $rmdir &= clear_derivative_cache_rec($path.'/'.$node, $pattern);
2584      }
2585      else
2586      {
2587        if (preg_match($pattern, $node))
2588        {
2589          unlink($path.'/'.$node);
2590        }
2591        elseif ($node=='index.htm')
2592        {
2593          $rm_index = true;
2594        }
2595        else
2596        {
2597          $rmdir = false;
2598        }
2599      }
2600    }
2601    closedir($contents);
2602
2603    if ($rmdir)
2604    {
2605      if ($rm_index)
2606      {
2607        unlink($path.'/index.htm');
2608      }
2609      clearstatcache();
2610      @rmdir($path);
2611    }
2612    return $rmdir;
2613  }
2614}
2615
2616/**
2617 * Deletes derivatives of a particular element
2618 *
2619 * @param array $infos ('path'[, 'representative_ext'])
2620 * @param 'all'|int $type
2621 */
2622function delete_element_derivatives($infos, $type='all')
2623{
2624  $path = $infos['path'];
2625  if (!empty($infos['representative_ext']))
2626  {
2627    $path = original_to_representative( $path, $infos['representative_ext']);
2628  }
2629  if (substr_compare($path, '../', 0, 3)==0)
2630  {
2631    $path = substr($path, 3);
2632  }
2633  $dot = strrpos($path, '.');
2634  if ($type=='all')
2635  {
2636    $pattern = '-*';
2637  }
2638  else
2639  {
2640    $pattern = '-'.derivative_to_url($type).'*';
2641  }
2642  $path = substr_replace($path, $pattern, $dot, 0);
2643  if ( ($glob=glob(PHPWG_ROOT_PATH.PWG_DERIVATIVE_DIR.$path)) !== false)
2644  {
2645    foreach( $glob as $file)
2646    {
2647      @unlink($file);
2648    }
2649  }
2650}
2651
2652/**
2653 * Returns an array containing sub-directories, excluding ".svn"
2654 *
2655 * @param string $directory
2656 * @return string[]
2657 */
2658function get_dirs($directory)
2659{
2660  $sub_dirs = array();
2661  if ($opendir = opendir($directory))
2662  {
2663    while ($file = readdir($opendir))
2664    {
2665      if ($file != '.'
2666          and $file != '..'
2667          and is_dir($directory.'/'.$file)
2668          and $file != '.svn')
2669      {
2670        $sub_dirs[] = $file;
2671      }
2672    }
2673    closedir($opendir);
2674  }
2675  return $sub_dirs;
2676}
2677
2678/**
2679 * Recursively delete a directory.
2680 *
2681 * @param string $path
2682 * @param string $trash_path, try to move the directory to this path if it cannot be delete
2683 */
2684function deltree($path, $trash_path=null)
2685{
2686  if (is_dir($path))
2687  {
2688    $fh = opendir($path);
2689    while ($file = readdir($fh))
2690    {
2691      if ($file != '.' and $file != '..')
2692      {
2693        $pathfile = $path . '/' . $file;
2694        if (is_dir($pathfile))
2695        {
2696          deltree($pathfile, $trash_path);
2697        }
2698        else
2699        {
2700          @unlink($pathfile);
2701        }
2702      }
2703    }
2704    closedir($fh);
2705   
2706    if (@rmdir($path))
2707    {
2708      return true;
2709    }
2710    elseif (!empty($trash_path))
2711    {
2712      if (!is_dir($trash_path))
2713      {
2714        @mkgetdir($trash_path, MKGETDIR_RECURSIVE|MKGETDIR_DIE_ON_ERROR|MKGETDIR_PROTECT_HTACCESS);
2715      }
2716      while ($r = $trash_path . '/' . md5(uniqid(rand(), true)))
2717      {
2718        if (!is_dir($r))
2719        {
2720          @rename($path, $r);
2721          break;
2722        }
2723      }
2724    }
2725    else
2726    {
2727      return false;
2728    }
2729  }
2730}
2731
2732/**
2733 * Returns keys to identify the state of main tables. A key consists of the
2734 * last modification timestamp and the total of items (separated by a _).
2735 * Additionally returns the hash of root path.
2736 * Used to invalidate LocalStorage cache on admin pages.
2737 *
2738 * @param string|string[] list of keys to retrieve (categories,groups,images,tags,users)
2739 * @return string[]
2740 */
2741function get_admin_client_cache_keys($requested=array())
2742{
2743  $tables = array(
2744    'categories' => CATEGORIES_TABLE,
2745    'groups' => GROUPS_TABLE,
2746    'images' => IMAGES_TABLE,
2747    'tags' => TAGS_TABLE,
2748    'users' => USER_INFOS_TABLE
2749    );
2750   
2751  if (!is_array($requested))
2752  {
2753    $requested = array($requested);
2754  }
2755  if (empty($requested))
2756  {
2757    $requested = array_keys($tables);
2758  }
2759  else
2760  {
2761    $requested = array_intersect($requested, array_keys($tables));
2762  }
2763 
2764  $keys = array(
2765    '_hash' => md5(get_absolute_root_url()),
2766    );
2767 
2768  foreach ($requested as $item)
2769  {
2770    $query = '
2771SELECT CONCAT(
2772    UNIX_TIMESTAMP(MAX(lastmodified)),
2773    "_",
2774    COUNT(*)
2775  )
2776  FROM '. $tables[$item] .'
2777;';
2778    list($keys[$item]) = pwg_db_fetch_row(pwg_query($query));
2779  }
2780 
2781  return $keys;
2782}
Note: See TracBrowser for help on using the repository browser.