source: extensions/GrumPluginClasses/classes/GPCRequestBuilder.class.inc.php @ 6894

Last change on this file since 6894 was 6894, checked in by grum, 14 years ago

Version 3.2.0
Enhance the request builder

File size: 39.1 KB
Line 
1<?php
2/* -----------------------------------------------------------------------------
3  class name: GCPRequestBuilder
4  class version  : 1.1.0
5  plugin version : 3.1.0
6  date           : 2010-09-08
7
8  ------------------------------------------------------------------------------
9  Author     : Grum
10    email    : grum@piwigo.org
11    website  : http://photos.grum.com
12    PWG user : http://forum.phpwebgallery.net/profile.php?id=3706
13
14    << May the Little SpaceFrog be with you ! >>
15  ------------------------------------------------------------------------------
16  *
17  * theses classes provides base functions to manage search pictures in the
18  * database
19  *
20  *
21  * HOW TO USE IT ?
22  * ===============
23  *
24  * when installing the plugin, you have to register the usage of the request
25  * builder
26  *
27  * 1/ Create a RBCallback class
28  *  - extends the GPCSearchCallback class ; the name of the extended class must
29  *    be "RBCallBack%" => replace the "%" by the plugin name
30  *     for example : 'ThePlugin' => 'RBCallBackThePlugin'
31  *
32  * 2/ In the plugin 'maintain.inc.php' file :
33  *  - function plugin_install, add :
34  *       GPCRequestBuilder::register('plugin name', 'path to the RBCallback classe');
35  *          for example : GPCRequestBuilder::register('ThePlugin', $piwigo_path.'plugins/ThePlugin/rbcallback_file_name.php');
36  *
37  *
38  *  - function plugin_uninstall, add :
39  *       GPCRequestBuilder::unregister('plugin name');
40  *          for example : GPCRequestBuilder::unregister('ThePlugin');
41  *
42  * 3/ In the plugin code, put somewhere
43  *     GPCRequestBuilder::loadJSandCSS();
44  *     => this will load specific JS and CSS in the page, by adding url in the
45  *        the header, so try to put this where you're used to prepare the header
46  *
47  * 4/ to display the request builder, just add the returned string in the html
48  *    page
49  *       $stringForTheTemplate=GPCRequestBuilder::displaySearchPage();
50  *
51  *
52  *
53  * HOW DOES THE REQUEST BUILDER WORKS ?
54  * ====================================
55  *
56  * the request builder works in 2 steps :
57  *  - first step  : build a cache, to associate all image id corresponding to
58  *                  the search criterion
59  *                  the cache is an association of request ID/image id
60  *  - second step : use the cache to retrieve images informations
61  *
62  ------------------------------------------------------------------------------
63  :: HISTORY
64
65| release | date       |
66| 1.0.0   | 2010/04/30 | * start coding
67|         |            |
68| 1.1.0   | 2010/09/08 | * add functionnalities to manage complex requests
69|         |            |
70|         |            |
71|         |            |
72|         |            |
73|         |            |
74|         |            |
75|         |            |
76|         |            |
77|         |            |
78
79  --------------------------------------------------------------------------- */
80
81include_once('GPCAjax.class.inc.php');
82include_once('GPCTables.class.inc.php');
83
84/**
85 *
86 * Preparing the cache
87 * -------------------
88 * To prepare the cache, the request builder use the following functions :
89 *  - getFrom
90 *  - getWhere
91 *  - getJoin
92 *  - getImageId (used for multirecord requests only)
93 *
94 * Retrieving the results
95 * ----------------------
96 * To retrieve the image informations, the request builder uses the following
97 * functions :
98 *  - getSelect
99 *  - getFrom
100 *  - getJoin
101 *  - getFilter (in fact, the result of this function is stored while the cache
102 *               is builded, but it is used only when retrieving the results for
103 *               multirecord tables)
104 *
105 *
106 * Example 1 : single record request
107 * ---------------------------------
108 * Consider the table "tableA" like this
109 *
110 *  - (*) imageId
111 *  -     att1
112 *  -     att2
113 *  The primary key is the 'imageId' attribute
114 *    => for one imageId, you can only have ZERO or ONE record
115 *
116 *  getSelect returns : "tableA.att1, tableA.att2"
117 *  getFrom returns   : "tableA"
118 *  getWhere returns  : "tableA.att1 = zzzz"
119 *  getJoin returns   : "tableA.imageId = pit.id"
120 *  getFilter returns : ""
121 *
122 *
123 * Example 2 : multi records request
124 * ---------------------------------
125 * Consider the table "tableA" like this
126 *
127 *  - (*) imageId
128 *  - (*) localId
129 *  -     att1
130 *  -     att2
131 *  The primary key is the 'imageId'+'localId' attributes
132 *    => for one imageId, you can have ZERO or more than ONE record
133 *       when you register the class, you have to set the $multiRecord parameter
134 *       to 'y'
135 *
136 *  gatImageId returns : "tableA.imageId"
137 *  getSelect returns  : "tableA.att1, tableA.att2"
138 *  getFrom returns    : "tableA"
139 *  getWhere returns   : "tableA.att1 = zzzz AND tableA.att1 = xxxx"
140 *  getJoin returns    : "tableA.id = pit.id"
141 *  getFilter returns  : ""
142 *
143 */
144class GPCSearchCallback {
145
146  /**
147   * the getImageId returns the name of the image id attribute
148   * return String
149   */
150  static public function getImageId()
151  {
152    return("");
153  }
154
155  /**
156   * the getSelect function must return an attribute list separated with a comma
157   *
158   * "att1, att2, att3, att4"
159   *
160   * you can specifie tables names and aliases
161   *
162   * "table1.att1 AS alias1, table1.att2 AS alias2, table2.att3 AS alias3"
163   */
164  static public function getSelect($param="")
165  {
166    return("");
167  }
168
169  /**
170   * the getFrom function must return a tables list separated with a comma
171   *
172   * "table1, (table2 left join table3 on table2.key = table3.key), table4"
173   */
174  static public function getFrom($param="")
175  {
176    return("");
177  }
178
179  /**
180   * the getWhere function must return a ready to use where clause
181   *
182   * "(att1 = value0 OR att2 = value1) AND att4 LIKE value2 "
183   */
184  static public function getWhere($param="")
185  {
186    return("");
187  }
188
189  /**
190   * the getJoin function must return a ready to use sql statement allowing to
191   * join the IMAGES table (key : pit.id) with given conditions
192   *
193   * "att3 = pit.id "
194   */
195  static public function getJoin($param="")
196  {
197    return("");
198  }
199
200
201  /**
202   * the getFilter function must return a ready to use where clause
203   * this where clause is used to filter the cache when the used tables can
204   * return more than one result
205   *
206   * the filter can be empty, can be equal to the where clause, or can be equal
207   * to a sub part of the where clause
208   *
209   */
210  static public function getFilter($param="")
211  {
212    return(self::getWhere($param));
213  }
214
215  /**
216   * this function is called by the request builder, allowing to display plugin
217   * data with a specific format
218   *
219   * @param Array $attributes : array of ('attribute_name' => 'attribute_value')
220   * @return String : HTML formatted value
221   */
222  static public function formatData($attributes)
223  {
224    return(print_r($attributes, true));
225  }
226
227
228  /**
229   * this function is called by the request builder to make the search page, and
230   * must return the HTML & JS code of the dialogbox used to select criterion
231   *
232   * Notes :
233   *  - the dialogbox is a JS object with a public method 'show'
234   *  - when the method show is called, one parameter is given by the request
235   *    builder ; the parameter is an object defined as this :
236   *      {
237   *        cBuilder: an instance of the criteriaBuilder object used in the page,
238   *      }
239   *
240   *
241   *
242   *
243   * @param String $mode : can take 'admin' or 'public' values, allowing to
244   *                       return different interface if needed
245   * @return String : HTML formatted value
246   */
247  static public function getInterfaceContent($mode='admin')
248  {
249    return("");
250  }
251
252  /**
253   * this function returns the label displayed in the criterion menu
254   *
255   * @return String : label displayed in the criterions menu
256   */
257  static public function getInterfaceLabel()
258  {
259    return(l10n('gpc_rb_unknown_interface'));
260  }
261
262  /**
263   * this function returns the name of the dialog box class
264   *
265   * @return String : name of the dialogbox class
266   */
267  static public function getInterfaceDBClass()
268  {
269    return('');
270  }
271
272
273}
274
275
276
277class GPCRequestBuilder {
278
279  static public $pluginName = 'GPCRequestBuilder';
280  static public $version = '1.1.0';
281
282  static private $tables = Array();
283  static protected $tGlobalId=0;
284
285  /**
286   * register a plugin using GPCRequestBuilder
287   *
288   * @param String $pluginName : the plugin name
289   * @param String $fileName : the php filename where the callback function can
290   *                           be found
291   * @return Boolean : true if registering is Ok, otherwise false
292   */
293  static public function register($pluginName, $fileName)
294  {
295    $config=Array();
296    if(GPCCore::loadConfig(self::$pluginName, $config))
297    {
298      $config['registered'][$pluginName]=Array(
299        'name' => $pluginName,
300        'fileName' => $fileName,
301        'date' => date("Y-m-d H:i:s"),
302        'version' => self::$version
303      );
304      return(GPCCore::saveConfig(self::$pluginName, $config));
305    }
306    return(false);
307  }
308
309  /**
310   * unregister a plugin using GPCRequestBuilder
311   *
312   * assume that if the plugin was not registerd before, unregistering returns
313   * a true value
314   *
315   * @param String $pluginName : the plugin name
316   * @return Boolean : true if registering is Ok, otherwise false
317   */
318  static public function unregister($plugin)
319  {
320    $config=Array();
321    if(GPCCore::loadConfig(self::$pluginName, $config))
322    {
323      if(array_key_exists('registered', $config))
324      {
325        if(array_key_exists($plugin, $config['registered']))
326        {
327          unset($config['registered'][$plugin]);
328          return(GPCCore::saveConfig(self::$pluginName, $config));
329        }
330      }
331    }
332    // assume if the plugin was not registered before, unregistering it is OK
333    return(true);
334  }
335
336  /**
337   * @return Array : list of registered plugins
338   */
339  static public function getRegistered()
340  {
341    $config=Array();
342    if(GPCCore::loadConfig(self::$pluginName, $config))
343    {
344      if(array_key_exists('registered', $config))
345      {
346        return($config['registered']);
347      }
348    }
349    return(Array());
350  }
351
352
353  /**
354   * initialise the class
355   *
356   * @param String $prefixeTable : the piwigo prefixe used on tables name
357   * @param String $pluginNameFile : the plugin name used for tables name
358   */
359  static public function init($prefixeTable, $pluginNameFile)
360  {
361    $list=Array('request', 'result_cache', 'temp');
362
363    for($i=0;$i<count($list);$i++)
364    {
365      self::$tables[$list[$i]]=$prefixeTable.$pluginNameFile.'_'.$list[$i];
366    }
367  }
368
369  /**
370   * create the tables needed by RequestBuilder (used during the gpc process install)
371   */
372  static public function createTables()
373  {
374    $tablesDef=array(
375"CREATE TABLE `".self::$tables['request']."` (
376  `id` int(10) unsigned NOT NULL auto_increment,
377  `user_id` int(10) unsigned NOT NULL,
378  `date` datetime NOT NULL,
379  `num_items` int(10) unsigned NOT NULL default '0',
380  `execution_time` float unsigned NOT NULL default '0',
381  `connected_plugin` char(255) NOT NULL,
382  `filter` text NOT NULL,
383  PRIMARY KEY  (`id`)
384)
385CHARACTER SET utf8 COLLATE utf8_general_ci",
386
387"CREATE TABLE `".self::$tables['result_cache']."` (
388  `id` int(10) unsigned NOT NULL,
389  `image_id` int(10) unsigned NOT NULL,
390  PRIMARY KEY  (`id`,`image_id`)
391)
392CHARACTER SET utf8 COLLATE utf8_general_ci",
393
394"CREATE TABLE `".self::$tables['temp']."` (
395  `requestId` char(30) NOT NULL,
396  `imageId` mediumint(8) unsigned NOT NULL,
397  PRIMARY KEY  (`request`,`id`)
398)
399CHARACTER SET utf8 COLLATE utf8_general_ci",
400  );
401
402    $tablef= new GPCTables(self::$tables);
403    $tablef->create($tablesDef);
404
405    return(true);
406  }
407
408  /**
409   * delete the tables needed by RequestBuilder
410   */
411  static public function deleteTables()
412  {
413    $tablef= new GPCTables(self::$tables);
414    $tablef->drop();
415    return(true);
416  }
417
418
419  /**
420   * this function add and handler on the 'loc_end_page_header' to add request
421   * builder JS script & specific CSS on the page
422   *
423   * use it when the displayed page need an access to the criteriaBuilder GUI
424   *
425   */
426  static public function loadJSandCSS()
427  {
428    add_event_handler('loc_end_page_header', array('GPCRequestBuilder', 'insertJSandCSSFiles'));
429  }
430
431
432  /**
433   * insert JS a CSS file in header
434   *
435   * the function is declared public because it used by the 'loc_end_page_header'
436   * event callback
437   *
438   * DO NOT USE IT DIRECTLY
439   *
440   */
441  static public function insertJSandCSSFiles()
442  {
443    global $template;
444
445    $baseName=basename(dirname(dirname(__FILE__))).'/css/';
446    $template->append('head_elements', '<link href="plugins/'.$baseName.'rbuilder.css" type="text/css" rel="stylesheet"/>');
447
448    $baseName=basename(dirname(dirname(__FILE__))).'/js/';
449    $template->append('head_elements', '<script type="text/javascript" src="plugins/'.$baseName.'external/interface/interface.js"></script>');
450    $template->append('head_elements', '<script type="text/javascript" src="plugins/'.$baseName.'external/inestedsortable.pack.js"></script>');
451    $template->append('head_elements', '<script type="text/javascript" src="plugins/'.$baseName.'criteriaBuilder.js"></script>');
452    $template->append('head_elements',
453"<script type=\"text/javascript\">
454  requestBuilderOptions = {
455      textAND:'".l10n('gpc_rb_textAND')."',
456      textOR:'".l10n('gpc_rb_textOR')."',
457      imgEditUrl:'',
458      imgDeleteUrl:'',
459      ajaxUrl:'admin.php?page=plugin&section=".basename(GPC_DIR)."/admin/plugin_admin.php&searchRequest=',
460  }
461</script>");
462  }
463
464
465  /**
466   * execute request from the ajax call
467   *
468   * @return String : a ready to use HTML code
469   */
470  static public function executeRequest()
471  {
472    if(self::checkAjaxRequest())
473    {
474      switch($_REQUEST['searchRequest'])
475      {
476        case 'execute':
477          $result=self::doCache();
478          break;
479        case 'getPage':
480          $result=self::getPage($_REQUEST['requestNumber'], $_REQUEST['page'], $_REQUEST['numPerPage']);
481          break;
482      }
483      GPCAjax::returnResult($result);
484    }
485    else
486    {
487      GPCAjax::returnResult(l10n('gpc_rb_invalid_request'));
488    }
489  }
490
491
492  /**
493   * clear the cache table
494   *
495   * @param Boolean $clearAll : if set to true, clear all records without
496   *                            checking timestamp
497   */
498  static public function clearCache($clearAll=false)
499  {
500    if($clearAll)
501    {
502      $sql="DELETE FROM ".self::$tables['result_cache'];
503    }
504    else
505    {
506      $sql="DELETE pgrc FROM ".self::$tables['result_cache']." pgrc
507              LEFT JOIN ".self::$tables['request']." pgr
508                ON pgrc.id = pgr.id
509              WHERE pgr.date < '".date('Y-m-d H:i:s', strtotime("-2 hour"))."'";
510    }
511    pwg_query($sql);
512  }
513
514  /**
515   * prepare the temporary table used for multirecord requests
516   *
517   * @param Integer $requestNumber : id of request
518   * @return String : name of the request key temporary table
519   */
520  static private function prepareTempTable($requestNumber)
521  {
522    //$tableName=call_user_func(Array('RBCallBack'.$plugin, 'getFrom'));
523    //$imageIdName=call_user_func(Array('RBCallBack'.$plugin, 'getImageId'));
524
525    $tempWHERE=array();
526    foreach($_REQUEST['extraData'] as $key => $extraData)
527    {
528      $tmpWHERE[$key]=array(
529        'plugin' => $extraData['owner'],
530        'where' => call_user_func(Array('RBCallBack'.$extraData['owner'], 'getWhere'), $extraData['param'])
531      );
532    }
533
534    $sql="INSERT INTO ".self::$tables['temp']." ".self::buildGroupRequest($_REQUEST[$_REQUEST['requestName']], $tmpWHERE, $_REQUEST['operator'], ' AND ', $requestNumber);
535//echo $sql;
536    $result=pwg_query($sql);
537
538    return($requestNumber);
539  }
540
541  /**
542   * clear the temporary table used for multirecord requests
543   *
544   * @param Array $requestNumber : the requestNumber to delete
545   */
546  static private function clearTempTable($requestNumber)
547  {
548    $sql="DELETE FROM ".self::$tables['temp']." WHERE requestId = '$requestNumber';";
549    pwg_query($sql);
550  }
551
552
553  /**
554   * execute a query, and place result in cache
555   *
556   *
557   * @return String : queryNumber;numberOfItems
558   */
559  static private function doCache()
560  {
561    global $user;
562
563    self::clearCache();
564
565    $registeredPlugin=self::getRegistered();
566    $requestNumber=self::getNewRequest($user['id']);
567
568    $build=Array(
569      'SELECT' => 'pit.id',
570      'FROM' => '',
571      'WHERE' => '',
572      'GROUPBY' => '',
573      'FILTER' => '',
574    );
575    $tmpBuild=Array(
576      'FROM' => Array(
577        '('.IMAGES_TABLE.' pit LEFT JOIN '.IMAGE_CATEGORY_TABLE.' pic ON pit.id = pic.image_id)' /*JOIN IMAGES & IMAGE_CATEGORY tables*/
578       .'   JOIN '.USER_CACHE_CATEGORIES_TABLE.' pucc ON pucc.cat_id=pic.category_id',  /* IMAGE_CATEGORY & USER_CACHE_CATEGORIES_TABLE tables*/
579
580      ),
581      'WHERE' => Array(),
582      'JOIN' => Array(999=>'pucc.user_id='.$user['id']),
583      'GROUPBY' => Array(
584        'pit.id'
585      ),
586      'FILTER' => Array(),
587    );
588
589    /* build data request for plugins
590     *
591     * Array('Plugin1' =>
592     *          Array(
593     *            criteriaNumber1 => pluginParam1,
594     *            criteriaNumber2 => pluginParam2,
595     *            criteriaNumberN => pluginParamN
596     *          ),
597     *       'Plugin2' =>
598     *          Array(
599     *            criteriaNumber1 => pluginParam1,
600     *            criteriaNumber2 => pluginParam2,
601     *            criteriaNumberN => pluginParamN
602     *          )
603     * )
604     *
605     */
606    $pluginNeeded=Array();
607    $pluginList=Array();
608    $tempName=Array();
609    foreach($_REQUEST['extraData'] as $key => $val)
610    {
611      $pluginNeeded[$val['owner']][$key]=$_REQUEST['extraData'][$key]['param'];
612      $pluginList[$val['owner']]=$val['owner'];
613    }
614
615    /* for each plugin, include the rb callback class file */
616    foreach($pluginList as $val)
617    {
618      if(file_exists($registeredPlugin[$val]['fileName']))
619      {
620        include_once($registeredPlugin[$val]['fileName']);
621      }
622    }
623
624    /* prepare the temp table for the request */
625    self::prepareTempTable($requestNumber);
626    $tmpBuild['FROM'][]=self::$tables['temp'];
627    $tmpBuild['JOIN'][]=self::$tables['temp'].".requestId = '".$requestNumber."'
628                        AND ".self::$tables['temp'].".imageId = pit.id";
629
630    /* for each needed plugin, prepare the filter */
631    foreach($pluginNeeded as $key => $val)
632    {
633      foreach($val as $itemNumber => $param)
634      {
635        $tmpBuild['FILTER'][$key][]='('.call_user_func(Array('RBCallBack'.$key, 'getFilter'), $param).')';
636      }
637    }
638
639
640    /* build FROM
641     *
642     */
643    $build['FROM']=implode(',', $tmpBuild['FROM']);
644    unset($tmpBuild['FROM']);
645
646    /* build WHERE
647     */
648    self::cleanArray($tmpBuild['WHERE']);
649    if(count($tmpBuild['WHERE'])>0)
650    {
651      $build['WHERE']=' ('.self::buildGroup($_REQUEST[$_REQUEST['requestName']], $tmpBuild['WHERE'], $_REQUEST['operator'], ' AND ').') ';
652    }
653    unset($tmpBuild['WHERE']);
654
655
656    /* build FILTER
657     */
658    self::cleanArray($tmpBuild['FILTER']);
659    if(count($tmpBuild['FILTER'])>0)
660    {
661      $tmp=array();
662      foreach($tmpBuild['FILTER'] as $key=>$val)
663      {
664        $tmp[$key]='('.implode(' OR ', $val).')';
665      }
666      $build['FILTER']=' ('.implode(' AND ', $tmp).') ';
667      // array_flip twice => simply remove identical values...
668      //$build['FILTER']=' ('.implode(' AND ', array_flip(array_flip($tmpBuild['FILTER']))).') ';
669    }
670    unset($tmpBuild['FILTER']);
671
672
673    /* for each plugin, adds jointure with the IMAGE table
674     */
675    self::cleanArray($tmpBuild['JOIN']);
676    if(count($tmpBuild['JOIN'])>0)
677    {
678      if($build['WHERE']!='') $build['WHERE'].=' AND ';
679      $build['WHERE'].=' ('.implode(' AND ', $tmpBuild['JOIN']).') ';
680    }
681    unset($tmpBuild['JOIN']);
682
683    self::cleanArray($tmpBuild['GROUPBY']);
684    if(count($tmpBuild['GROUPBY'])>0)
685    {
686      $build['GROUPBY'].=' '.implode(', ', $tmpBuild['GROUPBY']).' ';
687    }
688    unset($tmpBuild['GROUPBY']);
689
690
691
692    $sql=' FROM '.$build['FROM'];
693    if($build['WHERE']!='')
694    {
695      $sql.=' WHERE '.$build['WHERE'];
696    }
697    if($build['GROUPBY']!='')
698    {
699      $sql.=' GROUP BY '.$build['GROUPBY'];
700    }
701
702    $sql.=" ORDER BY pit.id ";
703
704    $sql="INSERT INTO ".self::$tables['result_cache']." (SELECT DISTINCT $requestNumber, ".$build['SELECT']." $sql)";
705
706//echo $sql;
707
708    $returned="0;0";
709
710    $result=pwg_query($sql);
711    if($result)
712    {
713      $numberItems=pwg_db_changes($result);
714      self::updateRequest($requestNumber, $numberItems, 0, implode(',', $pluginList), $build['FILTER']);
715
716      $returned="$requestNumber;".$numberItems;
717    }
718
719    self::clearTempTable($requestNumber);
720
721    return($returned);
722  }
723
724  /**
725   * return a page content. use the cache table to find request result
726   *
727   * @param Integer $requestNumber : the request number (from cache table)
728   * @param Integer $pageNumber : the page to be returned
729   * @param Integer $numPerPage : the number of items returned on a page
730   * @return String : formatted HTML code
731   */
732  static private function getPage($requestNumber, $pageNumber, $numPerPage)
733  {
734    global $conf, $user;
735    $request=self::getRequest($requestNumber);
736
737    if($request===false)
738    {
739      return("KO");
740    }
741
742    $limitFrom=$numPerPage*($pageNumber-1);
743
744    $pluginNeeded=explode(',', $request['connected_plugin']);
745    $registeredPlugin=self::getRegistered();
746
747    $build=Array(
748      'SELECT' => '',
749      'FROM' => '',
750      'WHERE' => '',
751      'GROUPBY' => '',
752    );
753    $tmpBuild=Array(
754      'SELECT' => Array(
755        'RB_PIT' => "pit.id AS imageId, pit.name AS imageName, pit.path AS imagePath", // from the piwigo's image table
756        'RB_PIC' => "GROUP_CONCAT(DISTINCT pic.category_id SEPARATOR ',') AS imageCategoriesId",     // from the piwigo's image_category table
757        'RB_PCT' => "GROUP_CONCAT(DISTINCT CASE WHEN pct.name IS NULL THEN '' ELSE pct.name END SEPARATOR '#sep#') AS imageCategoriesNames,
758                     GROUP_CONCAT(DISTINCT CASE WHEN pct.permalink IS NULL THEN '' ELSE pct.permalink END SEPARATOR '#sep#') AS imageCategoriesPLink,
759                     GROUP_CONCAT(DISTINCT CASE WHEN pct.dir IS NULL THEN 'V' ELSE 'P' END) AS imageCategoriesDir",   //from the piwigo's categories table
760      ),
761      'FROM' => Array(
762        // join rb result_cache table with piwigo's images table, joined with the piwigo's image_category table, joined with the categories table
763        'RB' => "(((".self::$tables['result_cache']." pgrc
764                  RIGHT JOIN ".IMAGES_TABLE." pit
765                  ON pgrc.image_id = pit.id)
766                    RIGHT JOIN ".IMAGE_CATEGORY_TABLE." pic
767                    ON pit.id = pic.image_id)
768                       RIGHT JOIN ".CATEGORIES_TABLE." pct
769                       ON pct.id = pic.category_id)
770                          RIGHT JOIN ".USER_CACHE_CATEGORIES_TABLE." pucc
771                          ON pucc.cat_id = pic.category_id",
772      ),
773      'WHERE' => Array(
774        'RB' => "pgrc.id=".$requestNumber." AND pucc.user_id=".$user['id'],
775        ),
776      'JOIN' => Array(),
777      'GROUPBY' => Array(
778        'RB' => "pit.id"
779      )
780    );
781
782    /* for each needed plugin :
783     *  - include the file
784     *  - call the static public function getFrom, getJoin, getSelect
785     */
786    foreach($pluginNeeded as $key => $val)
787    {
788      if(array_key_exists($val, $registeredPlugin))
789      {
790        if(file_exists($registeredPlugin[$val]['fileName']))
791        {
792          include_once($registeredPlugin[$val]['fileName']);
793
794          $tmp=explode(',', call_user_func(Array('RBCallBack'.$val, 'getSelect')));
795          foreach($tmp as $key2=>$val2)
796          {
797            $tmp[$key2]=self::groupConcatAlias($val2, '#sep#');
798          }
799          $tmpBuild['SELECT'][$val]=implode(',', $tmp);
800          $tmpBuild['FROM'][$val]=call_user_func(Array('RBCallBack'.$val, 'getFrom'));
801          $tmpBuild['JOIN'][$val]=call_user_func(Array('RBCallBack'.$val, 'getJoin'));
802        }
803      }
804    }
805
806    /* build SELECT
807     *
808     */
809    $build['SELECT']=implode(',', $tmpBuild['SELECT']);
810
811    /* build FROM
812     *
813     */
814    $build['FROM']=implode(',', $tmpBuild['FROM']);
815    unset($tmpBuild['FROM']);
816
817
818    /* build WHERE
819     */
820    if($request['filter']!='') $tmpBuild['WHERE'][]=$request['filter'];
821    $build['WHERE']=implode(' AND ', $tmpBuild['WHERE']);
822    unset($tmpBuild['WHERE']);
823
824
825    /* for each plugin, adds jointure with the IMAGE table
826     */
827    self::cleanArray($tmpBuild['JOIN']);
828    if(count($tmpBuild['JOIN'])>0)
829    {
830      $build['WHERE'].=' AND ('.implode(' AND ', $tmpBuild['JOIN']).') ';
831    }
832    unset($tmpBuild['JOIN']);
833
834    self::cleanArray($tmpBuild['GROUPBY']);
835    if(count($tmpBuild['GROUPBY'])>0)
836    {
837      $build['GROUPBY'].=' '.implode(', ', $tmpBuild['GROUPBY']).' ';
838    }
839    unset($tmpBuild['GROUPBY']);
840
841
842    $imagesList=Array();
843
844    $sql='SELECT '.$build['SELECT']
845        .' FROM '.$build['FROM']
846        .' WHERE '.$build['WHERE']
847        .' GROUP BY '.$build['GROUPBY']
848        .' ORDER BY pit.id '
849        .' LIMIT '.$limitFrom.', '.$numPerPage;
850//echo $sql;
851    $result=pwg_query($sql);
852    if($result)
853    {
854      while($row=pwg_db_fetch_assoc($result))
855      {
856        // affect standard datas
857        $datas['imageThumbnail']=dirname($row['imagePath'])."/".$conf['dir_thumbnail']."/".$conf['prefix_thumbnail'].basename($row['imagePath']);
858        $datas['imageId']=$row['imageId'];
859        $datas['imagePath']=$row['imagePath'];
860        $datas['imageName']=$row['imageName'];
861
862        $datas['imageCategoriesId']=explode(',', $row['imageCategoriesId']);
863        $datas['imageCategoriesNames']=explode('#sep#', $row['imageCategoriesNames']);
864        $datas['imageCategoriesPLink']=explode('#sep#', $row['imageCategoriesPLink']);
865        $datas['imageCategoriesDir']=explode(',', $row['imageCategoriesDir']);
866
867
868        $datas['imageCategories']=Array();
869        for($i=0;$i<count($datas['imageCategoriesId']);$i++)
870        {
871          $datas['imageCategories'][]=array(
872            'id' => $datas['imageCategoriesId'][$i],
873            'name' => $datas['imageCategoriesNames'][$i],
874            'dirType' => $datas['imageCategoriesDir'][$i],
875            'pLinks' => $datas['imageCategoriesPLink'][$i],
876            'link'=> make_index_url(
877                              array(
878                                'category' => array(
879                                  'id' => $datas['imageCategoriesId'][$i],
880                                  'name' => $datas['imageCategoriesNames'][$i],
881                                  'permalink' => $datas['imageCategoriesPLink'][$i])
882                              )
883                            )
884          );
885        }
886
887        /* affect datas for each plugin
888         *
889         * each plugin have to format the data in an HTML code
890         *
891         * so, for each plugin :
892         *  - look the attributes given in the SELECT clause
893         *  - for each attributes, associate the returned value of the record
894         *  - affect in datas an index equals to the plugin pluginName, with returned HTML code ; HTML code is get from a formatData function
895         *
896         * Example :
897         *  plugin ColorStart provide 2 attributes 'csColors' and 'csColorsPct'
898         *
899         *  we affect to the $attributes var :
900         *  $attributes['csColors'] = $row['csColors'];
901         *  $attributes['csColorsPct'] = $row['csColorsPct'];
902         *
903         *  call the ColorStat RB callback formatData with the $attributes => the function return a HTML code ready to use in the template
904         *
905         *  affect $datas['ColorStat'] = $the_returned_html_code;
906         *
907         *
908         */
909        foreach($tmpBuild['SELECT'] as $key => $val)
910        {
911          if($key!='RB_PIT' && $key!='RB_PIC' && $key!='RB_PCT')
912          {
913            $tmp=explode(',', $val);
914
915            $attributes=Array();
916
917            foreach($tmp as $key2 => $val2)
918            {
919              $name=self::getAttribute($val2);
920              $attributes[$name]=$row[$name];
921            }
922
923            $datas['plugin'][$key]=call_user_func(Array('RBCallBack'.$key, 'formatData'), $attributes);
924
925            unset($tmp);
926            unset($attributes);
927          }
928        }
929        $imagesList[]=$datas;
930        unset($datas);
931      }
932    }
933
934    return(self::toHtml($imagesList));
935    //return("get page : $requestNumber, $pageNumber, $numPerPage<br>$debug<br>$sql");
936  }
937
938  /**
939   * remove all empty value from an array
940   * @param Array a$array : the array to clean
941   */
942  static private function cleanArray(&$array)
943  {
944    foreach($array as $key => $val)
945    {
946      if(is_array($val))
947      {
948        self::cleanArray($val);
949        if(count($val)==0) unset($array[$key]);
950      }
951      elseif(trim($val)=='') unset($array[$key]);
952    }
953  }
954
955  /**
956   * returns the alias for an attribute
957   *
958   *  item1                          => returns item1
959   *  table1.item1                   => returns item1
960   *  table1.item1 AS alias1         => returns alias1
961   *  item1 AS alias1                => returns alias1
962   *  GROUP_CONCAT( .... ) AS alias1 => returns alias1
963   *
964   * @param String $var : value to examine
965   * @return String : the attribute name
966   */
967  static private function getAttribute($val)
968  {
969    preg_match('/(?:GROUP_CONCAT\(.*\)|(?:[A-Z0-9_]*)\.)?([A-Z0-9_]*)(?:\s+AS\s+([A-Z0-9_]*))?/i', trim($val), $result);
970    if(array_key_exists(2, $result))
971    {
972      return($result[2]);
973    }
974    elseif(array_key_exists(1, $result))
975    {
976      return($result[1]);
977    }
978    else
979    {
980      return($val);
981    }
982  }
983
984
985  /**
986   * returns a a sql statement GROUP_CONCAT for an alias
987   *
988   *  item1                  => returns GROUP_CONCAT(item1 SEPARATOR $sep) AS item1
989   *  table1.item1           => returns GROUP_CONCAT(table1.item1 SEPARATOR $sep) AS item1
990   *  table1.item1 AS alias1 => returns GROUP_CONCAT(table1.item1 SEPARATOR $sep) AS alias1
991   *  item1 AS alias1        => returns GROUP_CONCAT(item1 SEPARATOR $sep) AS alias1
992   *
993   * @param String $val : value to examine
994   * @param String $sep : the separator
995   * @return String : the attribute name
996   */
997  static private function groupConcatAlias($val, $sep=',')
998  {
999    /*
1000     * table1.item1 AS alias1
1001     *
1002     * $result[3] = alias1
1003     * $result[2] = item1
1004     * $result[1] = table1.item1
1005     */
1006    preg_match('/((?:(?:[A-Z0-9_]*)\.)?([A-Z0-9_]*))(?:\s+AS\s+([A-Z0-9_]*))?/i', trim($val), $result);
1007    if(array_key_exists(3, $result))
1008    {
1009      return("GROUP_CONCAT(".$result[1]." SEPARATOR '$sep') AS ".$result[3]);
1010    }
1011    elseif(array_key_exists(2, $result))
1012    {
1013      return("GROUP_CONCAT(".$result[1]." SEPARATOR '$sep') AS ".$result[2]);
1014    }
1015    else
1016    {
1017      return("GROUP_CONCAT($val SEPARATOR '$sep') AS ".$val);
1018    }
1019  }
1020
1021
1022  /**
1023   * get a new request number and create it in the request table
1024   *
1025   * @param Integer $userId : id of the user
1026   * @return Integer : the new request number, -1 if something wrong appened
1027   */
1028  static private function getNewRequest($userId)
1029  {
1030    $sql="INSERT INTO ".self::$tables['request']." VALUES('', '$userId', '".date('Y-m-d H:i:s')."', 0, 0, '', '')";
1031    $result=pwg_query($sql);
1032    if($result)
1033    {
1034      return(pwg_db_insert_id());
1035    }
1036    return(-1);
1037  }
1038
1039  /**
1040   * update request properties
1041   *
1042   * @param Integer $request_id : the id of request to update
1043   * @param Integer $numItems : number of items found in the request
1044   * @param Float $executionTime : time in second to execute the request
1045   * @param String $pluginList : list of used plugins
1046   * @return Boolean : true if request was updated, otherwise false
1047   */
1048  static private function updateRequest($requestId, $numItems, $executionTime, $pluginList, $additionalFilter)
1049  {
1050    $sql="UPDATE ".self::$tables['request']."
1051            SET num_items = $numItems,
1052                execution_time = $executionTime,
1053                connected_plugin = '$pluginList',
1054                filter = '".mysql_escape_string($additionalFilter)."'
1055            WHERE id = $requestId";
1056    $result=pwg_query($sql);
1057    if($result)
1058    {
1059      return(true);
1060    }
1061    return(false);
1062  }
1063
1064  /**
1065   * returns request properties
1066   *
1067   * @param Integer $request_id : the id of request to update
1068   * @return Array : properties for request, false if request doesn't exist
1069   */
1070  static private function getRequest($requestId)
1071  {
1072    $returned=false;
1073    $sql="SELECT user_id, date, num_items, execution_time, connected_plugin, filter
1074          FROM ".self::$tables['request']."
1075          WHERE id = $requestId";
1076    $result=pwg_query($sql);
1077    if($result)
1078    {
1079      while($row=pwg_db_fetch_assoc($result))
1080      {
1081        $returned=$row;
1082      }
1083    }
1084    return($returned);
1085  }
1086
1087
1088  /**
1089   * internal function used by the executeRequest function for single record
1090   * requests
1091   *
1092   * this function is called recursively
1093   *
1094   * @param Array $groupContent :
1095   * @param Array $items :
1096   * @return String : a where clause
1097   */
1098  static private function buildGroup($groupContent, $items, $groups, $operator)
1099  {
1100    $returned=Array();
1101    foreach($groupContent as $key => $val)
1102    {
1103      if(strpos($val['id'], 'iCbGroup')!==false)
1104      {
1105        preg_match('/[0-9]*$/i', $val['id'], $groupNumber);
1106        $returned[]=self::buildGroup($val['children'], $items, $groups, $groups[$groupNumber[0]]);
1107      }
1108      else
1109      {
1110        preg_match('/[0-9]*$/i', $val['id'], $itemNumber);
1111        $returned[]=" (".$items[$itemNumber[0]].") ";
1112      }
1113    }
1114    return('('.implode($operator, $returned).')');
1115  }
1116
1117
1118  /**
1119   * internal function used by the executeRequest function for multi records
1120   * requests
1121   *
1122   * this function is called recursively
1123   *
1124   * @param Array $groupContent :
1125   * @param Array $items :
1126   * @return String : a SQL request
1127   */
1128  static private function buildGroupRequest($groupContent, $whereItems, $groups, $operator, $requestNumber)
1129  {
1130    $returnedS='';
1131    $returned=Array();
1132    foreach($groupContent as $key => $val)
1133    {
1134      if(strpos($val['id'], 'iCbGroup')!==false)
1135      {
1136        preg_match('/[0-9]*$/i', $val['id'], $groupNumber);
1137
1138        $groupValue=self::buildGroupRequest($val['children'], $whereItems, $groups, $groups[$groupNumber[0]], $requestNumber);
1139
1140        if($groupValue!='')
1141          $returned[]=array(
1142            'mode'  => 'group',
1143            'value' => $groupValue
1144          );
1145      }
1146      else
1147      {
1148        preg_match('/[0-9]*$/i', $val['id'], $itemNumber);
1149
1150        $returned[]=array(
1151          'mode'  => 'item',
1152          'plugin' => $whereItems[$itemNumber[0]]['plugin'],
1153          'value' => " (".$whereItems[$itemNumber[0]]['where'].") "
1154        );
1155      }
1156    }
1157
1158    if(count($returned)>0)
1159    {
1160      if(strtolower(trim($operator))=='and')
1161      {
1162        $tId=0;
1163        foreach($returned as $key=>$val)
1164        {
1165          if($tId>0) $returnedS.=" JOIN ";
1166
1167          if($val['mode']=='item')
1168          {
1169            $returnedS.="(SELECT ".call_user_func(Array('RBCallBack'.$val['plugin'], 'getImageId'))." AS imageId
1170                          FROM ".call_user_func(Array('RBCallBack'.$val['plugin'], 'getFrom'))."
1171                          WHERE ".$val['value'].") t".self::$tGlobalId." ";
1172          }
1173          else
1174          {
1175            $returnedS.="(".$val['value'].") t".self::$tGlobalId." ";
1176          }
1177
1178          if($tId>0) $returnedS.=" ON t".(self::$tGlobalId-1).".imageId = t".self::$tGlobalId.".imageId ";
1179          $tId++;
1180          self::$tGlobalId++;
1181        }
1182        $returnedS="SELECT '$requestNumber', t".(self::$tGlobalId-$tId).".imageId FROM ".$returnedS;
1183      }
1184      else
1185      {
1186        foreach($returned as $key=>$val)
1187        {
1188          if($returnedS!='') $returnedS.=" UNION DISTINCT ";
1189
1190          if($val['mode']=='item')
1191          {
1192            $returnedS.="SELECT '$requestNumber', t".self::$tGlobalId.".imageId
1193                          FROM (SELECT ".call_user_func(Array('RBCallBack'.$val['plugin'], 'getImageId'))." AS imageId
1194                                FROM ".call_user_func(Array('RBCallBack'.$val['plugin'], 'getFrom'))."
1195                                WHERE ".$val['value'].") t".self::$tGlobalId." ";
1196          }
1197          else
1198          {
1199            $returnedS.="SELECT '$requestNumber', t".self::$tGlobalId.".imageId FROM (".$val['value'].") t".self::$tGlobalId;
1200          }
1201
1202          self::$tGlobalId++;
1203        }
1204      }
1205    }
1206
1207    return($returnedS);
1208  }
1209
1210
1211  /**
1212   * convert a list of images to HTML
1213   *
1214   * @param Array $imagesList : list of images id & associated datas
1215   * @return String : list formatted into HTML code
1216   */
1217  static protected function toHtml($imagesList)
1218  {
1219    global $template;
1220
1221    $template->set_filename('result_items',
1222                dirname(dirname(__FILE__)).'/templates/GPCRequestBuilder_result.tpl');
1223
1224
1225
1226    $template->assign('datas', $imagesList);
1227
1228    return($template->parse('result_items', true));
1229  }
1230
1231
1232  /**
1233   * returns allowed (or not allowed) categories for a user
1234   *
1235   * used the USER_CACHE_TABLE if possible
1236   *
1237   * @param Integer $userId : a valid user Id
1238   * @return String : IN(...), NOT IN(...) or nothing if there is no restriction
1239   *                  for the user
1240   */
1241  public function getUserCategories($userId)
1242  {
1243/*
1244    $returned='';
1245    if($user['forbidden_categories']!='')
1246    {
1247      $returned=Array(
1248        'JOIN' => 'AND ('.IMAGE_CATEGORY.'.category_id NOT IN ('.$user['forbidden_categories'].') ) ',
1249        'FROM' => IMAGE_CATEGORY
1250      );
1251
1252
1253    }
1254    *
1255    *
1256    */
1257  }
1258
1259
1260  /**
1261   * check if this is a valid ajax request
1262   *
1263   * @return Boolean : true if this is a valide ajax request
1264   */
1265  static protected function checkAjaxRequest()
1266  {
1267    if(isset($_REQUEST['searchRequest']))
1268    {
1269      if($_REQUEST['searchRequest']=='execute')
1270      {
1271        if(!isset($_REQUEST['requestName'])) return(false);
1272
1273        return(true);
1274      }
1275
1276      if($_REQUEST['searchRequest']=='getPage')
1277      {
1278        if(!isset($_REQUEST['requestNumber'])) return(false);
1279
1280        if(!isset($_REQUEST['page']))
1281        {
1282          $_REQUEST['page']=0;
1283        }
1284        if($_REQUEST['page']<0) $_REQUEST['page']=0;
1285
1286        if(!isset($_REQUEST['numPerPage']))
1287        {
1288          $_REQUEST['numPerPage']=25;
1289        }
1290
1291        if($_REQUEST['numPerPage']>100) $_REQUEST['numPerPage']=100;
1292
1293        return(true);
1294      }
1295
1296    }
1297    return(false);
1298  }
1299
1300
1301
1302  /**
1303   * display search page
1304   *
1305   * @param Array $filter : an array of string ; each item is the name of a
1306   *                        registered plugin
1307   *                        if no parameters are given, no filter is applied
1308   *                        otherwise only plugin wich name is given are
1309   *                        accessible
1310   */
1311  static public function displaySearchPage($filter=array())
1312  {
1313    global $template, $lang;
1314
1315    load_language('rbuilder.lang', GPC_PATH);
1316
1317    if(is_string($filter)) $filter=array($filter);
1318    $filter=array_flip($filter);
1319
1320    $template->set_filename('gpc_search_page',
1321                dirname(dirname(__FILE__)).'/templates/GPCRequestBuilder_search.tpl');
1322
1323    $registeredPlugin=self::getRegistered();
1324    $dialogBox=Array();
1325    foreach($registeredPlugin as $key=>$val)
1326    {
1327      if(array_key_exists($key, $registeredPlugin) and
1328         (count($filter)==0 or array_key_exists($key, $filter)))
1329      {
1330        if(file_exists($registeredPlugin[$key]['fileName']))
1331        {
1332          include_once($registeredPlugin[$key]['fileName']);
1333
1334          $dialogBox[]=Array(
1335            'handle' => $val['name'].'DB',
1336            'dialogBoxClass' => call_user_func(Array('RBCallBack'.$key, 'getInterfaceDBClass')),
1337            'label' => call_user_func(Array('RBCallBack'.$key, 'getInterfaceLabel')),
1338            'content' => call_user_func(Array('RBCallBack'.$key, 'getInterfaceContent')),
1339          );
1340        }
1341      }
1342    }
1343
1344    $datas=Array(
1345      'dialogBox' => $dialogBox,
1346      'themeName' => $template->get_themeconf('name'),
1347    );
1348
1349    $template->assign('datas', $datas);
1350
1351    return($template->parse('gpc_search_page', true));
1352  } //displaySearchPage
1353
1354}
1355
1356
1357?>
Note: See TracBrowser for help on using the repository browser.