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

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

feature 3077 : factorize code for categories cache (TODO for other collections) + fix incorrect categories list for dissociation

File size: 21.9 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 * 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    {
262      $row['name'] = strip_tags(get_cat_display_name_cache($row['uppercats'], null));
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  {
462    return categories_flatlist_to_tree($cats);
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 = query2array($query, 'category_id', 'counter');
490
491  $query = '
492SELECT id, name, comment, uppercats, global_rank, dir
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['fullname'] = strip_tags(
511      get_cat_display_name_cache(
512        $row['uppercats'],
513        null
514        )
515      );
516    $row['comment'] = strip_tags(
517      trigger_event(
518        'render_category_description',
519        $row['comment'],
520        'ws_categories_getAdminList'
521        )
522      );
523
524    $cats[] = $row;
525  }
526
527  usort($cats, 'global_rank_compare');
528  return array(
529    'categories' => new PwgNamedArray(
530      $cats,
531      'category',
532      array('id', 'nb_images', 'name', 'uppercats', 'global_rank')
533      )
534    );
535}
536
537/**
538 * API method
539 * Adds a category
540 * @param mixed[] $params
541 *    @option string name
542 *    @option int parent (optional)
543 *    @option string comment (optional)
544 *    @option bool visible
545 *    @option string status (optional)
546 *    @option bool commentable
547 */
548function ws_categories_add($params, &$service)
549{
550  include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
551
552  $options = array();
553  if (!empty($params['status']) and in_array($params['status'], array('private','public')))
554  {
555    $options['status'] = $params['status'];
556  }
557
558  if (!empty($params['comment']))
559  {
560    $options['comment'] = $params['comment'];
561  }
562
563  $creation_output = create_virtual_category(
564    $params['name'],
565    $params['parent'],
566    $options
567    );
568
569  if (isset($creation_output['error']))
570  {
571    return new PwgError(500, $creation_output['error']);
572  }
573
574  invalidate_user_cache();
575
576  return $creation_output;
577}
578
579/**
580 * API method
581 * Sets details of a category
582 * @param mixed[] $params
583 *    @option int cat_id
584 *    @option string name (optional)
585 *    @option string comment (optional)
586 */
587function ws_categories_setInfo($params, &$service)
588{
589  $update = array(
590    'id' => $params['category_id'],
591    );
592
593  $info_columns = array('name', 'comment',);
594
595  $perform_update = false;
596  foreach ($info_columns as $key)
597  {
598    if (isset($params[$key]))
599    {
600      $perform_update = true;
601      $update[$key] = $params[$key];
602    }
603  }
604
605  if ($perform_update)
606  {
607    single_update(
608      CATEGORIES_TABLE,
609      $update,
610      array('id' => $update['id'])
611      );
612  }
613}
614
615/**
616 * API method
617 * Sets representative image of a category
618 * @param mixed[] $params
619 *    @option int category_id
620 *    @option int image_id
621 */
622function ws_categories_setRepresentative($params, &$service)
623{
624  // does the category really exist?
625  $query = '
626SELECT COUNT(*)
627  FROM '. CATEGORIES_TABLE .'
628  WHERE id = '. $params['category_id'] .'
629;';
630  list($count) = pwg_db_fetch_row(pwg_query($query));
631  if ($count == 0)
632  {
633    return new PwgError(404, 'category_id not found');
634  }
635
636  // does the image really exist?
637  $query = '
638SELECT COUNT(*)
639  FROM '. IMAGES_TABLE .'
640  WHERE id = '. $params['image_id'] .'
641;';
642  list($count) = pwg_db_fetch_row(pwg_query($query));
643  if ($count == 0)
644  {
645    return new PwgError(404, 'image_id not found');
646  }
647
648  // apply change
649  $query = '
650UPDATE '. CATEGORIES_TABLE .'
651  SET representative_picture_id = '. $params['image_id'] .'
652  WHERE id = '. $params['category_id'] .'
653;';
654  pwg_query($query);
655
656  $query = '
657UPDATE '. USER_CACHE_CATEGORIES_TABLE .'
658  SET user_representative_picture_id = NULL
659  WHERE cat_id = '. $params['category_id'] .'
660;';
661  pwg_query($query);
662}
663
664/**
665 * API method
666 * Deletes a category
667 * @param mixed[] $params
668 *    @option string|int[] category_id
669 *    @option string photo_deletion_mode
670 *    @option string pwg_token
671 */
672function ws_categories_delete($params, &$service)
673{
674  if (get_pwg_token() != $params['pwg_token'])
675  {
676    return new PwgError(403, 'Invalid security token');
677  }
678
679  $modes = array('no_delete', 'delete_orphans', 'force_delete');
680  if (!in_array($params['photo_deletion_mode'], $modes))
681  {
682    return new PwgError(500,
683      '[ws_categories_delete]'
684      .' invalid parameter photo_deletion_mode "'.$params['photo_deletion_mode'].'"'
685      .', possible values are {'.implode(', ', $modes).'}.'
686      );
687  }
688
689  if (!is_array($params['category_id']))
690  {
691    $params['category_id'] = preg_split(
692      '/[\s,;\|]/',
693      $params['category_id'],
694      -1,
695      PREG_SPLIT_NO_EMPTY
696      );
697  }
698  $params['category_id'] = array_map('intval', $params['category_id']);
699
700  $category_ids = array();
701  foreach ($params['category_id'] as $category_id)
702  {
703    if ($category_id > 0)
704    {
705      $category_ids[] = $category_id;
706    }
707  }
708
709  if (count($category_ids) == 0)
710  {
711    return;
712  }
713
714  $query = '
715SELECT id
716  FROM '. CATEGORIES_TABLE .'
717  WHERE id IN ('. implode(',', $category_ids) .')
718;';
719  $category_ids = array_from_query($query, 'id');
720
721  if (count($category_ids) == 0)
722  {
723    return;
724  }
725
726  include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
727  delete_categories($category_ids, $params['photo_deletion_mode']);
728  update_global_rank();
729}
730
731/**
732 * API method
733 * Moves a category
734 * @param mixed[] $params
735 *    @option string|int[] category_id
736 *    @option int parent
737 *    @option string pwg_token
738 */
739function ws_categories_move($params, &$service)
740{
741  global $page;
742
743  if (get_pwg_token() != $params['pwg_token'])
744  {
745    return new PwgError(403, 'Invalid security token');
746  }
747
748  if (!is_array($params['category_id']))
749  {
750    $params['category_id'] = preg_split(
751      '/[\s,;\|]/',
752      $params['category_id'],
753      -1,
754      PREG_SPLIT_NO_EMPTY
755      );
756  }
757  $params['category_id'] = array_map('intval', $params['category_id']);
758
759  $category_ids = array();
760  foreach ($params['category_id'] as $category_id)
761  {
762    if ($category_id > 0)
763    {
764      $category_ids[] = $category_id;
765    }
766  }
767
768  if (count($category_ids) == 0)
769  {
770    return new PwgError(403, 'Invalid category_id input parameter, no category to move');
771  }
772
773  // we can't move physical categories
774  $categories_in_db = array();
775
776  $query = '
777SELECT id, name, dir
778  FROM '. CATEGORIES_TABLE .'
779  WHERE id IN ('. implode(',', $category_ids) .')
780;';
781  $result = pwg_query($query);
782  while ($row = pwg_db_fetch_assoc($result))
783  {
784    $categories_in_db[ $row['id'] ] = $row;
785
786    // we break on error at first physical category detected
787    if (!empty($row['dir']))
788    {
789      $row['name'] = strip_tags(
790        trigger_event(
791          'render_category_name',
792          $row['name'],
793          'ws_categories_move'
794          )
795        );
796
797      return new PwgError(403,
798        sprintf(
799          'Category %s (%u) is not a virtual category, you cannot move it',
800          $row['name'],
801          $row['id']
802          )
803        );
804    }
805  }
806
807  if (count($categories_in_db) != count($category_ids))
808  {
809    $unknown_category_ids = array_diff($category_ids, array_keys($categories_in_db));
810
811    return new PwgError(403,
812      sprintf(
813        'Category %u does not exist',
814        $unknown_category_ids[0]
815        )
816      );
817  }
818
819  // does this parent exists? This check should be made in the
820  // move_categories function, not here
821  // 0 as parent means "move categories at gallery root"
822  if (0 != $params['parent'])
823  {
824    $subcat_ids = get_subcat_ids(array($params['parent']));
825    if (count($subcat_ids) == 0)
826    {
827      return new PwgError(403, 'Unknown parent category id');
828    }
829  }
830
831  $page['infos'] = array();
832  $page['errors'] = array();
833
834  include_once(PHPWG_ROOT_PATH.'admin/include/functions.php');
835  move_categories($category_ids, $params['parent']);
836  invalidate_user_cache();
837
838  if (count($page['errors']) != 0)
839  {
840    return new PwgError(403, implode('; ', $page['errors']));
841  }
842}
843
844?>
Note: See TracBrowser for help on using the repository browser.