source: trunk/include/functions_search.inc.php @ 1677

Last change on this file since 1677 was 1677, checked in by rub, 17 years ago

Feature Issue ID 0000601: Filter all public pages with only recent elements

It's a finalized version.
Obsolete code of draft are removed.

You can filter categories and images with recent date period on your screen selection.
In the future, filter could be easy done on other type data (plugin?)

You can flat categories and sub-categories with a recent date period of your choice.

Next, perhaps, a panel to choice recent date for the 2 features.

On draft, there have problem with MySql 5, be careful!

Css problem not resolved:

  • Menu "Categories" is bad centered
  • Icon on dark too on the top
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 12.7 KB
Line 
1<?php
2// +-----------------------------------------------------------------------+
3// | PhpWebGallery - a PHP based picture gallery                           |
4// | Copyright (C) 2002-2003 Pierrick LE GALL - pierrick@phpwebgallery.net |
5// | Copyright (C) 2003-2006 PhpWebGallery Team - http://phpwebgallery.net |
6// +-----------------------------------------------------------------------+
7// | branch        : BSF (Best So Far)
8// | file          : $Id: functions_search.inc.php 1677 2006-12-21 21:38:20Z rub $
9// | last update   : $Date: 2006-12-21 21:38:20 +0000 (Thu, 21 Dec 2006) $
10// | last modifier : $Author: rub $
11// | revision      : $Revision: 1677 $
12// +-----------------------------------------------------------------------+
13// | This program is free software; you can redistribute it and/or modify  |
14// | it under the terms of the GNU General Public License as published by  |
15// | the Free Software Foundation                                          |
16// |                                                                       |
17// | This program is distributed in the hope that it will be useful, but   |
18// | WITHOUT ANY WARRANTY; without even the implied warranty of            |
19// | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU      |
20// | General Public License for more details.                              |
21// |                                                                       |
22// | You should have received a copy of the GNU General Public License     |
23// | along with this program; if not, write to the Free Software           |
24// | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, |
25// | USA.                                                                  |
26// +-----------------------------------------------------------------------+
27
28
29/**
30 * Prepends and appends a string at each value of the given array.
31 *
32 * @param array
33 * @param string prefix to each array values
34 * @param string suffix to each array values
35 */
36function prepend_append_array_items($array, $prepend_str, $append_str)
37{
38  array_walk(
39    $array,
40    create_function('&$s', '$s = "'.$prepend_str.'".$s."'.$append_str.'";')
41    );
42
43  return $array;
44}
45
46/**
47 * returns search rules stored into a serialized array in "search"
48 * table. Each search rules set is numericaly identified.
49 *
50 * @param int search_id
51 * @return array
52 */
53function get_search_array($search_id)
54{
55  if (!is_numeric($search_id))
56  {
57    die('Search id must be an integer');
58  }
59
60  $query = '
61SELECT rules
62  FROM '.SEARCH_TABLE.'
63  WHERE id = '.$search_id.'
64;';
65  list($serialized_rules) = mysql_fetch_row(pwg_query($query));
66
67  return unserialize($serialized_rules);
68}
69
70/**
71 * returns the SQL clause from a search identifier
72 *
73 * Search rules are stored in search table as a serialized array. This array
74 * need to be transformed into an SQL clause to be used in queries.
75 *
76 * @param array search
77 * @return string
78 */
79function get_sql_search_clause($search)
80{
81  // SQL where clauses are stored in $clauses array during query
82  // construction
83  $clauses = array();
84
85  foreach (array('file','name','comment','author') as $textfield)
86  {
87    if (isset($search['fields'][$textfield]))
88    {
89      $local_clauses = array();
90      foreach ($search['fields'][$textfield]['words'] as $word)
91      {
92        array_push($local_clauses, $textfield." LIKE '%".$word."%'");
93      }
94
95      // adds brackets around where clauses
96      $local_clauses = prepend_append_array_items($local_clauses, '(', ')');
97
98      array_push(
99        $clauses,
100        implode(
101          ' '.$search['fields'][$textfield]['mode'].' ',
102          $local_clauses
103          )
104        );
105    }
106  }
107
108  if (isset($search['fields']['allwords']))
109  {
110    $fields = array('file', 'name', 'comment', 'author');
111    // in the OR mode, request bust be :
112    // ((field1 LIKE '%word1%' OR field2 LIKE '%word1%')
113    // OR (field1 LIKE '%word2%' OR field2 LIKE '%word2%'))
114    //
115    // in the AND mode :
116    // ((field1 LIKE '%word1%' OR field2 LIKE '%word1%')
117    // AND (field1 LIKE '%word2%' OR field2 LIKE '%word2%'))
118    $word_clauses = array();
119    foreach ($search['fields']['allwords']['words'] as $word)
120    {
121      $field_clauses = array();
122      foreach ($fields as $field)
123      {
124        array_push($field_clauses, $field." LIKE '%".$word."%'");
125      }
126      // adds brackets around where clauses
127      array_push(
128        $word_clauses,
129        implode(
130          "\n          OR ",
131          $field_clauses
132          )
133        );
134    }
135
136    array_walk(
137      $word_clauses,
138      create_function('&$s','$s="(".$s.")";')
139      );
140
141    array_push(
142      $clauses,
143      "\n         ".
144      implode(
145        "\n         ".
146              $search['fields']['allwords']['mode'].
147        "\n         ",
148        $word_clauses
149        )
150      );
151  }
152
153  foreach (array('date_available', 'date_creation') as $datefield)
154  {
155    if (isset($search['fields'][$datefield]))
156    {
157      array_push(
158        $clauses,
159        $datefield." = '".$search['fields'][$datefield]['date']."'"
160        );
161    }
162
163    foreach (array('after','before') as $suffix)
164    {
165      $key = $datefield.'-'.$suffix;
166
167      if (isset($search['fields'][$key]))
168      {
169        array_push(
170          $clauses,
171
172          $datefield.
173          ($suffix == 'after'             ? ' >' : ' <').
174          ($search['fields'][$key]['inc'] ? '='  : '').
175          " '".$search['fields'][$key]['date']."'"
176
177          );
178      }
179    }
180  }
181
182  if (isset($search['fields']['cat']))
183  {
184    if ($search['fields']['cat']['sub_inc'])
185    {
186      // searching all the categories id of sub-categories
187      $cat_ids = get_subcat_ids($search['fields']['cat']['words']);
188    }
189    else
190    {
191      $cat_ids = $search['fields']['cat']['words'];
192    }
193
194    $local_clause = 'category_id IN ('.implode(',', $cat_ids).')';
195    array_push($clauses, $local_clause);
196  }
197
198  // adds brackets around where clauses
199  $clauses = prepend_append_array_items($clauses, '(', ')');
200
201  $where_separator =
202    implode(
203      "\n    ".$search['mode'].' ',
204      $clauses
205      );
206
207  $search_clause = $where_separator;
208
209  return $search_clause;
210}
211
212/**
213 * returns the list of items corresponding to the advanced search array
214 *
215 * @param array search
216 * @return array
217 */
218function get_regular_search_results($search)
219{
220  $items = array();
221
222  $search_clause = get_sql_search_clause($search);
223
224  if (!empty($search_clause))
225  {
226    $query = '
227SELECT DISTINCT(id)
228  FROM '.IMAGES_TABLE.'
229    INNER JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON id = ic.image_id
230  WHERE '.$search_clause.'
231;';
232    $items = array_from_query($query, 'id');
233  }
234
235  if (isset($search['fields']['tags']))
236  {
237    $tag_items = get_image_ids_for_tags(
238      $search['fields']['tags']['words'],
239      $search['fields']['tags']['mode']
240      );
241
242    switch ($search['mode'])
243    {
244      case 'AND':
245      {
246        if (empty($search_clause))
247        {
248          $items = $tag_items;
249        }
250        else
251        {
252          $items = array_intersect($items, $tag_items);
253        }
254        break;
255      }
256      case 'OR':
257      {
258        $items = array_unique(
259          array_merge(
260            $items,
261            $tag_items
262            )
263          );
264        break;
265      }
266    }
267  }
268
269  return $items;
270}
271
272
273if (!function_exists('array_intersect_key')) {
274   function array_intersect_key()
275   {
276       $arrs = func_get_args();
277       $result = array_shift($arrs);
278       foreach ($arrs as $array) {
279           foreach ($result as $key => $v) {
280               if (!array_key_exists($key, $array)) {
281                   unset($result[$key]);
282               }
283           }
284       }
285       return $result;
286   }
287}
288
289/**
290 * returns the LIKE sql clause corresponding to the quick search query $q
291 * and the field $field. example q="john bill", field="file" will return
292 * file LIKE "%john%" OR file LIKE "%bill%". Special characters for MySql
293 * full text search (+,<,>) are omitted.
294 * @param string q
295 * @param string field
296 * @return string
297 */
298function get_qsearch_like_clause($q, $field)
299{
300  $tokens = preg_split('/[\s,.;!\?]+/', $q);
301  for ($i=0; $i<count($tokens); $i++)
302  {
303    $tokens[$i]=str_replace('*','%', $tokens[$i]);
304    if (preg_match('/^[+<>]/',$tokens[$i]) )
305      $tokens[$i]=substr($tokens[$i], 1);
306    else if (substr($tokens[$i], 0, 1)=='-')
307    {
308      unset($tokens[$i]);
309      $i--;
310    }
311  }
312
313  if (!empty($tokens))
314  {
315    $query = '(';
316    for ($i=0; $i<count($tokens); $i++)
317    {
318      if ($i>0) $query .= 'OR ';
319      $query .= ' '.$field.' LIKE "%'.$tokens[$i].'%" ';
320    }
321    $query .= ')';
322    return $query;
323  }
324  return null;
325}
326
327
328/**
329 * returns the search results (array of image ids) corresponding to a
330 * quick/query search. A quick/query search returns many items (search is
331 * not strict), but results are sorted by relevance.
332 *
333 * @param string q
334 * @return array
335 */
336function get_quick_search_results($q)
337{
338  global $user, $page, $filter;
339  $search_results = array();
340
341  // first search tag names corresponding to the query $q. we could also search
342  // tags later during the big join, but for the sake of the performance and
343  // because tags have only a simple name we do it separately
344  $q_like_clause = get_qsearch_like_clause($q, 'CONVERT(name, CHAR)' );
345  $by_tag_weights=array();
346  if (!empty($q_like_clause))
347  {
348    $query = '
349SELECT id
350  FROM '.TAGS_TABLE.'
351  WHERE '.$q_like_clause;
352    $tag_ids = array_from_query( $query, 'id');
353    if (!empty($tag_ids))
354    { // we got some tags
355      $query = '
356SELECT image_id, COUNT(tag_id) AS q
357  FROM '.IMAGE_TAG_TABLE.'
358  WHERE tag_id IN ('.implode(',',$tag_ids).')
359  GROUP BY image_id';
360      $result = pwg_query($query);
361      while ($row = mysql_fetch_assoc($result))
362      { // weight is important when sorting images by relevance
363        $by_tag_weights[(int)$row['image_id']] = $row['q'];
364      }
365    }
366  }
367
368  // prepare the big join on images, comments and categories
369  $query = '
370SELECT
371  i.id, i.file, CAST( CONCAT_WS(" ",
372    IFNULL(i.name,""),
373    IFNULL(i.comment,""),
374    IFNULL(GROUP_CONCAT(DISTINCT co.content),""),
375    IFNULL(GROUP_CONCAT(DISTINCT c.dir),""),
376    IFNULL(GROUP_CONCAT(DISTINCT c.name),""),
377    IFNULL(GROUP_CONCAT(DISTINCT c.comment),"") ) AS CHAR) AS ft
378FROM (
379  (
380    '.IMAGES_TABLE.' i LEFT JOIN '.COMMENTS_TABLE.' co on i.id=co.image_id
381  )
382    INNER JOIN
383  '.IMAGE_CATEGORY_TABLE.' ic on ic.image_id=i.id
384  )
385    INNER JOIN
386  '.CATEGORIES_TABLE.' c on c.id=ic.category_id
387'.get_sql_condition_FandF
388  (
389    array
390      (
391        'forbidden_categories' => 'category_id',
392        'visible_categories' => 'category_id',
393        'visible_images' => 'ic.image_id'
394      ),
395    'WHERE'
396  ).'
397GROUP BY i.id';
398
399  $query = 'SELECT id, MATCH(ft) AGAINST( "'.$q.'" IN BOOLEAN MODE) AS q FROM ('.$query.') AS Y
400WHERE MATCH(ft) AGAINST( "'.$q.'" IN BOOLEAN MODE)';
401
402  //also inlcude the file name (but avoid full text which is slower because
403  //the filename in pwg doesn't have spaces so full text is meaningless anyway)
404  $q_like_clause = get_qsearch_like_clause($q, 'file' );
405  if (! empty($q_like_clause) )
406  {
407    $query .= ' OR '.$q_like_clause;
408  }
409
410  $by_weights=array();
411  $result = pwg_query($query);
412  while ($row = mysql_fetch_array($result))
413  {
414    $by_weights[(int)$row['id']] = $row['q'] ? $row['q'] : 0;
415  }
416
417  // finally merge the results (tags and big join) sorted by "relevance"
418  foreach ( $by_weights as $image=>$w )
419  {
420    $by_tag_weights[$image] = 2*$w+ (isset($by_tag_weights[$image])?$by_tag_weights[$image]:0);
421  }
422
423  //at this point, found images might contain images not allowed for the user
424  if ( empty($by_tag_weights) or isset($page['super_order_by']) )
425  {
426    // no aditionnal query here for permissions (will be done by section_init
427    // while sorting items as the user requested it)
428    $search_results['items'] = array_keys($by_tag_weights);
429  }
430  else
431  {
432    // before returning the result "as is", make sure the user has the
433    // permissions for every item
434    $query = '
435SELECT DISTINCT(id)
436  FROM '.IMAGES_TABLE.'
437    INNER JOIN '.IMAGE_CATEGORY_TABLE.' AS ic ON id = ic.image_id
438  WHERE id IN ('.implode(',', array_keys($by_tag_weights) ).')
439'.get_sql_condition_FandF
440  (
441    array
442      (
443        'forbidden_categories' => 'category_id',
444        'visible_categories' => 'category_id',
445        'visible_images' => 'ic.image_id'
446      ),
447    'AND'
448  );
449    $allowed_image_ids = array_from_query( $query, 'id');
450    $by_tag_weights = array_intersect_key($by_tag_weights, array_flip($allowed_image_ids));
451    arsort($by_tag_weights, SORT_NUMERIC);
452    $search_results = array(
453          'items'=>array_keys($by_tag_weights),
454          'as_is'=>1
455        );
456  }
457  return $search_results;
458}
459
460/**
461 * returns an array of 'items' corresponding to the search id
462 *
463 * @param int search id
464 * @return array
465 */
466function get_search_results($search_id)
467{
468  $search = get_search_array($search_id);
469  if ( !isset($search['q']) )
470  {
471    $result['items'] = get_regular_search_results($search);
472    return $result;
473  }
474  else
475  {
476    return get_quick_search_results($search['q']);
477  }
478}
479?>
Note: See TracBrowser for help on using the repository browser.