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

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

rbuilder is now accessible from public pages

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