source: trunk/include/ws_functions/pwg.categories.php @ 26953

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

merge r26952 from branch 2.6 to trunk

bug 3035 fixed: the output of pwg.categories.getList with option
tree_output=true was modified in r22729 to fix a bug. But fixing this bug has
broken iOS/Android applications which uses this parameter. The immediate
solution is to reintroduce the bug for now and fix it cleanly later (requires
a new version of iOS/Android apps)

File size: 21.8 KB
RevLine 
[25281]1<?php
2// +-----------------------------------------------------------------------+
3// | Piwigo - a PHP based photo gallery                                    |
4// +-----------------------------------------------------------------------+
[26461]5// | Copyright(C) 2008-2014 Piwigo Team                  http://piwigo.org |
[25281]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 * API method
26 * Returns images per category
27 * @param mixed[] $params
28 *    @option int[] cat_id (optional)
29 *    @option bool recursive
30 *    @option int per_page
31 *    @option int page
32 *    @option string order (optional)
33 */
34function ws_categories_getImages($params, &$service)
35{
36  global $user, $conf;
37
38  $images = array();
39
40  //------------------------------------------------- get the related categories
41  $where_clauses = array();
42  foreach ($params['cat_id'] as $cat_id)
43  {
44    if ($params['recursive'])
45    {
46      $where_clauses[] = 'uppercats '.DB_REGEX_OPERATOR.' \'(^|,)'.$cat_id.'(,|$)\'';
47    }
48    else
49    {
50      $where_clauses[] = 'id='.$cat_id;
51    }
52  }
53  if (!empty($where_clauses))
54  {
55    $where_clauses = array('('. implode("\n    OR ", $where_clauses) . ')');
56  }
57  $where_clauses[] = get_sql_condition_FandF(
58    array('forbidden_categories' => 'id'),
59    null, true
60    );
61
62  $query = '
63SELECT id, name, permalink, image_order
64  FROM '. CATEGORIES_TABLE .'
65  WHERE '. implode("\n    AND ", $where_clauses) .'
66;';
67  $result = pwg_query($query);
68
69  $cats = array();
70  while ($row = pwg_db_fetch_assoc($result))
71  {
72    $row['id'] = (int)$row['id'];
73    $cats[ $row['id'] ] = $row;
74  }
75
76  //-------------------------------------------------------- get the images
77  if (!empty($cats))
78  {
79    $where_clauses = ws_std_image_sql_filter($params, 'i.');
80    $where_clauses[] = 'category_id IN ('. implode(',', array_keys($cats)) .')';
81    $where_clauses[] = get_sql_condition_FandF(
82      array('visible_images' => 'i.id'),
83      null, true
84      );
85
86    $order_by = ws_std_image_sql_order($params, 'i.');
87    if ( empty($order_by)
88          and count($params['cat_id'])==1
89          and isset($cats[ $params['cat_id'][0] ]['image_order'])
90        )
91    {
92      $order_by = $cats[ $params['cat_id'][0] ]['image_order'];
93    }
94    $order_by = empty($order_by) ? $conf['order_by'] : 'ORDER BY '.$order_by;
95
96    $query = '
97SELECT i.*, GROUP_CONCAT(category_id) AS cat_ids
98  FROM '. IMAGES_TABLE .' i
99    INNER JOIN '. IMAGE_CATEGORY_TABLE .' ON i.id=image_id
100  WHERE '. implode("\n    AND ", $where_clauses) .'
101  GROUP BY i.id
102  '. $order_by .'
103  LIMIT '. $params['per_page'] .'
104  OFFSET '. ($params['per_page']*$params['page']) .'
105;';
106    $result = pwg_query($query);
107
108    while ($row = pwg_db_fetch_assoc($result))
109    {
110      $image = array();
111      foreach (array('id', 'width', 'height', 'hit') as $k)
112      {
113        if (isset($row[$k]))
114        {
115          $image[$k] = (int)$row[$k];
116        }
117      }
118      foreach (array('file', 'name', 'comment', 'date_creation', 'date_available') as $k)
119      {
120        $image[$k] = $row[$k];
121      }
122      $image = array_merge($image, ws_std_get_urls($row));
123
124      $image_cats = array();
125      foreach (explode(',', $row['cat_ids']) as $cat_id)
126      {
127        $url = make_index_url(
128          array(
129            'category' => $cats[$cat_id],
130            )
131          );
132        $page_url = make_picture_url(
133          array(
134            'category' => $cats[$cat_id],
135            'image_id' => $row['id'],
136            'image_file' => $row['file'],
137            )
138          );
139        $image_cats[] = array(
140          'id' => (int)$cat_id,
141          'url' => $url,
142          'page_url' => $page_url,
143          );
144      }
145
146      $image['categories'] = new PwgNamedArray(
147        $image_cats,
148        'category',
149        array('id', 'url', 'page_url')
150        );
151      $images[] = $image;
152    }
153  }
154
155  return array(
156    'paging' => new PwgNamedStruct(
157      array(
158        'page' => $params['page'],
159        'per_page' => $params['per_page'],
160        'count' => count($images)
161        )
162      ),
163    'images' => new PwgNamedArray(
164      $images, 'image',
165      ws_std_get_image_xml_attributes()
166      )
167    );
168}
169
170/**
171 * API method
172 * Returns a list of categories
173 * @param mixed[] $params
174 *    @option int cat_id (optional)
175 *    @option bool recursive
176 *    @option bool public
177 *    @option bool tree_output
178 *    @option bool fullname
179 */
180function ws_categories_getList($params, &$service)
181{
182  global $user, $conf;
183
184  $where = array('1=1');
185  $join_type = 'INNER';
186  $join_user = $user['id'];
187
188  if (!$params['recursive'])
189  {
190    if ($params['cat_id']>0)
191    {
192      $where[] = '(
193        id_uppercat = '. (int)($params['cat_id']) .'
194        OR id='.(int)($params['cat_id']).'
195      )';
196    }
197    else
198    {
199      $where[] = 'id_uppercat IS NULL';
200    }
201  }
202  else if ($params['cat_id']>0)
203  {
204    $where[] = 'uppercats '. DB_REGEX_OPERATOR .' \'(^|,)'.
205      (int)($params['cat_id']) .'(,|$)\'';
206  }
207
208  if ($params['public'])
209  {
210    $where[] = 'status = "public"';
211    $where[] = 'visible = "true"';
212
213    $join_user = $conf['guest_id'];
214  }
215  else if (is_admin())
216  {
217    // in this very specific case, we don't want to hide empty
218    // categories. Function calculate_permissions will only return
219    // categories that are either locked or private and not permitted
220    //
221    // calculate_permissions does not consider empty categories as forbidden
222    $forbidden_categories = calculate_permissions($user['id'], $user['status']);
223    $where[]= 'id NOT IN ('.$forbidden_categories.')';
224    $join_type = 'LEFT';
225  }
226
227  $query = '
228SELECT
229    id, name, comment, permalink,
230    uppercats, global_rank, id_uppercat,
231    nb_images, count_images AS total_nb_images,
232    representative_picture_id, user_representative_picture_id, count_images, count_categories,
233    date_last, max_date_last, count_categories AS nb_categories
234  FROM '. CATEGORIES_TABLE .'
235    '.$join_type.' JOIN '. USER_CACHE_CATEGORIES_TABLE .'
236    ON id=cat_id AND user_id='.$join_user.'
237  WHERE '. implode("\n    AND ", $where) .'
238;';
239  $result = pwg_query($query);
240
241  // management of the album thumbnail -- starts here
242  $image_ids = array();
243  $categories = array();
244  $user_representative_updates_for = array();
245  // management of the album thumbnail -- stops here
246
247  $cats = array();
248  while ($row = pwg_db_fetch_assoc($result))
249  {
250    $row['url'] = make_index_url(
251      array(
252        'category' => $row
253        )
254      );
255    foreach (array('id','nb_images','total_nb_images','nb_categories') as $key)
256    {
257      $row[$key] = (int)$row[$key];
258    }
259
260    if ($params['fullname'])
261    {
[25425]262      $row['name'] = strip_tags(get_cat_display_name_cache($row['uppercats'], null));
[25281]263    }
264    else
265    {
266      $row['name'] = strip_tags(
267        trigger_event(
268          'render_category_name',
269          $row['name'],
270          'ws_categories_getList'
271          )
272        );
273    }
274
275    $row['comment'] = strip_tags(
276      trigger_event(
277        'render_category_description',
278        $row['comment'],
279        'ws_categories_getList'
280        )
281      );
282
283    // management of the album thumbnail -- starts here
284    //
285    // on branch 2.3, the algorithm is duplicated from
286    // include/category_cats, but we should use a common code for Piwigo 2.4
287    //
288    // warning : if the API method is called with $params['public'], the
289    // album thumbnail may be not accurate. The thumbnail can be viewed by
290    // the connected user, but maybe not by the guest. Changing the
291    // filtering method would be too complicated for now. We will simply
292    // avoid to persist the user_representative_picture_id in the database
293    // if $params['public']
294    if (!empty($row['user_representative_picture_id']))
295    {
296      $image_id = $row['user_representative_picture_id'];
297    }
298    else if (!empty($row['representative_picture_id']))
299    { // if a representative picture is set, it has priority
300      $image_id = $row['representative_picture_id'];
301    }
302    else if ($conf['allow_random_representative'])
303    {
304      // searching a random representant among elements in sub-categories
305      $image_id = get_random_image_in_category($row);
306    }
307    else
308    { // searching a random representant among representant of sub-categories
309      if ($row['count_categories']>0 and $row['count_images']>0)
310      {
311        $query = '
312SELECT representative_picture_id
313  FROM '. CATEGORIES_TABLE .'
314    INNER JOIN '. USER_CACHE_CATEGORIES_TABLE .'
315    ON id=cat_id AND user_id='.$user['id'].'
316  WHERE uppercats LIKE \''.$row['uppercats'].',%\'
317    AND representative_picture_id IS NOT NULL
318        '.get_sql_condition_FandF(
319          array('visible_categories' => 'id'),
320          "\n  AND"
321          ).'
322  ORDER BY '. DB_RANDOM_FUNCTION .'()
323  LIMIT 1
324;';
325        $subresult = pwg_query($query);
326
327        if (pwg_db_num_rows($subresult) > 0)
328        {
329          list($image_id) = pwg_db_fetch_row($subresult);
330        }
331      }
332    }
333
334    if (isset($image_id))
335    {
336      if ($conf['representative_cache_on_subcats'] and $row['user_representative_picture_id'] != $image_id)
337      {
338        $user_representative_updates_for[ $row['id'] ] = $image_id;
339      }
340
341      $row['representative_picture_id'] = $image_id;
342      $image_ids[] = $image_id;
343      $categories[] = $row;
344    }
345    unset($image_id);
346    // management of the album thumbnail -- stops here
347
348    $cats[] = $row;
349  }
350  usort($cats, 'global_rank_compare');
351
352  // management of the album thumbnail -- starts here
353  if (count($categories) > 0)
354  {
355    $thumbnail_src_of = array();
356    $new_image_ids = array();
357
358    $query = '
359SELECT id, path, representative_ext, level
360  FROM '. IMAGES_TABLE .'
361  WHERE id IN ('. implode(',', $image_ids) .')
362;';
363    $result = pwg_query($query);
364
365    while ($row = pwg_db_fetch_assoc($result))
366    {
367      if ($row['level'] <= $user['level'])
368      {
369        $thumbnail_src_of[$row['id']] = DerivativeImage::thumb_url($row);
370      }
371      else
372      {
373        // problem: we must not display the thumbnail of a photo which has a
374        // higher privacy level than user privacy level
375        //
376        // * what is the represented category?
377        // * find a random photo matching user permissions
378        // * register it at user_representative_picture_id
379        // * set it as the representative_picture_id for the category
380        foreach ($categories as &$category)
381        {
382          if ($row['id'] == $category['representative_picture_id'])
383          {
384            // searching a random representant among elements in sub-categories
385            $image_id = get_random_image_in_category($category);
386
387            if (isset($image_id) and !in_array($image_id, $image_ids))
388            {
389              $new_image_ids[] = $image_id;
390            }
391            if ($conf['representative_cache_on_level'])
392            {
393              $user_representative_updates_for[ $category['id'] ] = $image_id;
394            }
395
396            $category['representative_picture_id'] = $image_id;
397          }
398        }
399        unset($category);
400      }
401    }
402
403    if (count($new_image_ids) > 0)
404    {
405      $query = '
406SELECT id, path, representative_ext
407  FROM '. IMAGES_TABLE .'
408  WHERE id IN ('. implode(',', $new_image_ids) .')
409;';
410      $result = pwg_query($query);
411
412      while ($row = pwg_db_fetch_assoc($result))
413      {
414        $thumbnail_src_of[ $row['id'] ] = DerivativeImage::thumb_url($row);
415      }
416    }
417  }
418
419  // compared to code in include/category_cats, we only persist the new
420  // user_representative if we have used $user['id'] and not the guest id,
421  // or else the real guest may see thumbnail that he should not
422  if (!$params['public'] and count($user_representative_updates_for))
423  {
424    $updates = array();
425
426    foreach ($user_representative_updates_for as $cat_id => $image_id)
427    {
428      $updates[] = array(
429        'user_id' => $user['id'],
430        'cat_id' => $cat_id,
431        'user_representative_picture_id' => $image_id,
432        );
433    }
434
435    mass_updates(
436      USER_CACHE_CATEGORIES_TABLE,
437      array(
438        'primary' => array('user_id', 'cat_id'),
439        'update'  => array('user_representative_picture_id')
440        ),
441      $updates
442      );
443  }
444
445  foreach ($cats as &$cat)
446  {
447    foreach ($categories as $category)
448    {
449      if ($category['id'] == $cat['id'] and isset($category['representative_picture_id']))
450      {
451        $cat['tn_url'] = $thumbnail_src_of[$category['representative_picture_id']];
452      }
453    }
454    // we don't want them in the output
455    unset($cat['user_representative_picture_id'], $cat['count_images'], $cat['count_categories']);
456  }
457  unset($cat);
458  // management of the album thumbnail -- stops here
459
460  if ($params['tree_output'])
461  {
[26953]462    return categories_flatlist_to_tree($cats);
[25281]463  }
464
465  return array(
466    'categories' => new PwgNamedArray(
467      $cats,
468      'category',
469      ws_std_get_category_xml_attributes()
470      )
471    );
472}
473
474/**
475 * API method
476 * Returns the list of categories as you can see them in administration
477 * @param mixed[] $params
478 *
479 * Only admin can run this method and permissions are not taken into
480 * account.
481 */
482function ws_categories_getAdminList($params, &$service)
483{
484  $query = '
485SELECT category_id, COUNT(*) AS counter
486  FROM '. IMAGE_CATEGORY_TABLE .'
487  GROUP BY category_id
488;';
489  $nb_images_of = simple_hash_from_query($query, 'category_id', 'counter');
490
491  $query = '
492SELECT id, name, comment, uppercats, global_rank
493  FROM '. CATEGORIES_TABLE .'
494;';
495  $result = pwg_query($query);
496
497  $cats = array();
498  while ($row = pwg_db_fetch_assoc($result))
499  {
500    $id = $row['id'];
501    $row['nb_images'] = isset($nb_images_of[$id]) ? $nb_images_of[$id] : 0;
502
503    $row['name'] = strip_tags(
504      trigger_event(
505        'render_category_name',
506        $row['name'],
507        'ws_categories_getAdminList'
508        )
509      );
510    $row['comment'] = strip_tags(
511      trigger_event(
512        'render_category_description',
513        $row['comment'],
514        'ws_categories_getAdminList'
515        )
516      );
517
518    $cats[] = $row;
519  }
520
521  usort($cats, 'global_rank_compare');
522  return array(
523    'categories' => new PwgNamedArray(
524      $cats,
525      'category',
526      array('id', 'nb_images', 'name', 'uppercats', 'global_rank')
527      )
528    );
529}
530
531/**
532 * API method
533 * Adds a category
534 * @param mixed[] $params
535 *    @option string name
536 *    @option int parent (optional)
537 *    @option string comment (optional)
538 *    @option bool visible
539 *    @option string status (optional)
540 *    @option bool commentable
541 */
542function ws_categories_add($params, &$service)
543{
544  include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
545
546  $options = array();
547  if (!empty($params['status']) and in_array($params['status'], array('private','public')))
548  {
549    $options['status'] = $params['status'];
550  }
551
552  if (!empty($params['comment']))
553  {
554    $options['comment'] = $params['comment'];
555  }
556
557  $creation_output = create_virtual_category(
558    $params['name'],
559    $params['parent'],
560    $options
561    );
562
563  if (isset($creation_output['error']))
564  {
565    return new PwgError(500, $creation_output['error']);
566  }
567
568  invalidate_user_cache();
569
570  return $creation_output;
571}
572
573/**
574 * API method
575 * Sets details of a category
576 * @param mixed[] $params
577 *    @option int cat_id
578 *    @option string name (optional)
579 *    @option string comment (optional)
580 */
581function ws_categories_setInfo($params, &$service)
582{
583  $update = array(
584    'id' => $params['category_id'],
585    );
586
587  $info_columns = array('name', 'comment',);
588
589  $perform_update = false;
590  foreach ($info_columns as $key)
591  {
592    if (isset($params[$key]))
593    {
594      $perform_update = true;
595      $update[$key] = $params[$key];
596    }
597  }
598
599  if ($perform_update)
600  {
601    single_update(
602      CATEGORIES_TABLE,
603      $update,
604      array('id' => $update['id'])
605      );
606  }
607}
608
609/**
610 * API method
611 * Sets representative image of a category
612 * @param mixed[] $params
613 *    @option int category_id
614 *    @option int image_id
615 */
616function ws_categories_setRepresentative($params, &$service)
617{
618  // does the category really exist?
619  $query = '
620SELECT COUNT(*)
621  FROM '. CATEGORIES_TABLE .'
622  WHERE id = '. $params['category_id'] .'
623;';
624  list($count) = pwg_db_fetch_row(pwg_query($query));
625  if ($count == 0)
626  {
627    return new PwgError(404, 'category_id not found');
628  }
629
630  // does the image really exist?
631  $query = '
632SELECT COUNT(*)
633  FROM '. IMAGES_TABLE .'
634  WHERE id = '. $params['image_id'] .'
635;';
636  list($count) = pwg_db_fetch_row(pwg_query($query));
637  if ($count == 0)
638  {
639    return new PwgError(404, 'image_id not found');
640  }
641
642  // apply change
643  $query = '
644UPDATE '. CATEGORIES_TABLE .'
645  SET representative_picture_id = '. $params['image_id'] .'
646  WHERE id = '. $params['category_id'] .'
647;';
648  pwg_query($query);
649
650  $query = '
651UPDATE '. USER_CACHE_CATEGORIES_TABLE .'
652  SET user_representative_picture_id = NULL
653  WHERE cat_id = '. $params['category_id'] .'
654;';
655  pwg_query($query);
656}
657
658/**
659 * API method
660 * Deletes a category
661 * @param mixed[] $params
662 *    @option string|int[] category_id
663 *    @option string photo_deletion_mode
664 *    @option string pwg_token
665 */
666function ws_categories_delete($params, &$service)
667{
668  if (get_pwg_token() != $params['pwg_token'])
669  {
670    return new PwgError(403, 'Invalid security token');
671  }
672
673  $modes = array('no_delete', 'delete_orphans', 'force_delete');
674  if (!in_array($params['photo_deletion_mode'], $modes))
675  {
676    return new PwgError(500,
677      '[ws_categories_delete]'
678      .' invalid parameter photo_deletion_mode "'.$params['photo_deletion_mode'].'"'
679      .', possible values are {'.implode(', ', $modes).'}.'
680      );
681  }
682
683  if (!is_array($params['category_id']))
684  {
685    $params['category_id'] = preg_split(
686      '/[\s,;\|]/',
687      $params['category_id'],
688      -1,
689      PREG_SPLIT_NO_EMPTY
690      );
691  }
692  $params['category_id'] = array_map('intval', $params['category_id']);
693
694  $category_ids = array();
695  foreach ($params['category_id'] as $category_id)
696  {
697    if ($category_id > 0)
698    {
699      $category_ids[] = $category_id;
700    }
701  }
702
703  if (count($category_ids) == 0)
704  {
705    return;
706  }
707
708  $query = '
709SELECT id
710  FROM '. CATEGORIES_TABLE .'
711  WHERE id IN ('. implode(',', $category_ids) .')
712;';
713  $category_ids = array_from_query($query, 'id');
714
715  if (count($category_ids) == 0)
716  {
717    return;
718  }
719
720  include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
721  delete_categories($category_ids, $params['photo_deletion_mode']);
722  update_global_rank();
723}
724
725/**
726 * API method
727 * Moves a category
728 * @param mixed[] $params
729 *    @option string|int[] category_id
730 *    @option int parent
731 *    @option string pwg_token
732 */
733function ws_categories_move($params, &$service)
734{
735  global $page;
736
737  if (get_pwg_token() != $params['pwg_token'])
738  {
739    return new PwgError(403, 'Invalid security token');
740  }
741
742  if (!is_array($params['category_id']))
743  {
744    $params['category_id'] = preg_split(
745      '/[\s,;\|]/',
746      $params['category_id'],
747      -1,
748      PREG_SPLIT_NO_EMPTY
749      );
750  }
751  $params['category_id'] = array_map('intval', $params['category_id']);
752
753  $category_ids = array();
754  foreach ($params['category_id'] as $category_id)
755  {
756    if ($category_id > 0)
757    {
758      $category_ids[] = $category_id;
759    }
760  }
761
762  if (count($category_ids) == 0)
763  {
764    return new PwgError(403, 'Invalid category_id input parameter, no category to move');
765  }
766
767  // we can't move physical categories
768  $categories_in_db = array();
769
770  $query = '
771SELECT id, name, dir
772  FROM '. CATEGORIES_TABLE .'
773  WHERE id IN ('. implode(',', $category_ids) .')
774;';
775  $result = pwg_query($query);
776  while ($row = pwg_db_fetch_assoc($result))
777  {
778    $categories_in_db[ $row['id'] ] = $row;
779
780    // we break on error at first physical category detected
781    if (!empty($row['dir']))
782    {
783      $row['name'] = strip_tags(
784        trigger_event(
785          'render_category_name',
786          $row['name'],
787          'ws_categories_move'
788          )
789        );
790
791      return new PwgError(403,
792        sprintf(
793          'Category %s (%u) is not a virtual category, you cannot move it',
794          $row['name'],
795          $row['id']
796          )
797        );
798    }
799  }
800
801  if (count($categories_in_db) != count($category_ids))
802  {
803    $unknown_category_ids = array_diff($category_ids, array_keys($categories_in_db));
804
805    return new PwgError(403,
806      sprintf(
807        'Category %u does not exist',
808        $unknown_category_ids[0]
809        )
810      );
811  }
812
813  // does this parent exists? This check should be made in the
814  // move_categories function, not here
815  // 0 as parent means "move categories at gallery root"
816  if (0 != $params['parent'])
817  {
818    $subcat_ids = get_subcat_ids(array($params['parent']));
819    if (count($subcat_ids) == 0)
820    {
821      return new PwgError(403, 'Unknown parent category id');
822    }
823  }
824
825  $page['infos'] = array();
826  $page['errors'] = array();
827
828  include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
829  move_categories($category_ids, $params['parent']);
830  invalidate_user_cache();
831
832  if (count($page['errors']) != 0)
833  {
834    return new PwgError(403, implode('; ', $page['errors']));
835  }
836}
837
838?>
Note: See TracBrowser for help on using the repository browser.