Subversion Repositories eFlore/Applications.del

Rev

Rev 1422 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 1422 Rev 1430
Line 63... Line 63...
63
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&protocole=3
63
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&protocole=3
64
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&protocole=3&masque=plop
64
// del/services/0.1/images?navigation.depart=0&navigation.limite=12&tri=votes&ordre=desc&protocole=3&masque=plop
Line 65... Line 65...
65
 
65
 
Line -... Line 66...
-
 
66
class ListeImages2 {
66
class ListeImages2 {
67
 
Line 67... Line 68...
67
 
68
	// TODO: PHP-x.y, ces variables devrait être des "const"
Line 68... Line 69...
68
	static $format_image_possible = array('O','CRX2S','CRS','CXS','CS','XS','S','M','L','XL','X2L','X3L');
69
	static $format_image_possible = array('O','CRX2S','CRS','CXS','CS','XS','S','M','L','XL','X2L','X3L');
69
 
70
 
Line 70... Line 71...
70
	static $tri_possible = array('date_observation', 'votes', 'tags');
71
	static $tri_possible = array('date_transmission', 'date_observation', 'votes', 'tags');
Line 140... Line 141...
140
		$params_ip = ListeObservations2::requestFilterParams($parametres,
141
		$params_ip = ListeObservations2::requestFilterParams($parametres,
141
														  array_diff(ListeObservations2::$parametres_autorises,
142
														  array_diff(ListeObservations2::$parametres_autorises,
142
																	 array('masque.type')),
143
																	 array('masque.type')),
143
														  $this->conteneur);
144
														  $this->conteneur);
Line 144... Line -...
144
 
-
 
145
		// Cette variable est utile au filtrage des tags
-
 
146
		// Elle peut-être redéfinie en interne par sqlAddMasqueConstraint() ci-dessous
-
 
147
		// lorsque celui-ci transforme le masque général en masque par champ
-
 
148
		// et ré-appelle self::requestFilterParams()
-
 
149
		$parametres['tag_explode_char'] = ','; // " " (whitespace) ou "," ou NULL
-
 
150
		// tag_explode_semantic est lui utilisé lors de l'ajout des contraintes sur tags self::sqlAddConstraint()
-
 
151
		$parametres['tag_explode_semantic'] = 'OR'; // "AND" ou "OR" (si 'tag_explode_char' NOT NULL)
145
 
152
		// notre propre filtrage sur l'INPUT
146
		// notre propre filtrage sur l'INPUT
153
		$params_pf = self::requestFilterParams($parametres,
147
		$params_pf = self::requestFilterParams($parametres,
154
											   array_merge(ListeObservations2::$parametres_autorises,
148
											   array_merge(ListeObservations2::$parametres_autorises,
Line -... Line 149...
-
 
149
														   self::$parametres_autorises));
-
 
150
 
-
 
151
		/* filtrage des tags + sémantique des valeurs multiples:
-
 
152
		   Lorsqu'on utilise masque.tag* pour chercher des tags, ils sont
-
 
153
		   postulés comme séparés par des virgule, et l'un au moins des tags doit matcher. */
Line 155... Line 154...
155
														   self::$parametres_autorises));
154
		$params_pf['masque.tag_cel'] = self::buildTagsAST(@$parametres['masque.tag_cel'], 'OR', ',');
156
 
155
		$params_pf['masque.tag_pictoflora'] = self::buildTagsAST(@$parametres['masque.tag_pictoflora'], 'OR', ',');
157
 
156
 
158
		$params = array_merge(ListeObservations2::$default_params, // paramètre par défaut Identiplante
157
		$params = array_merge(ListeObservations2::$default_params, // paramètre par défaut Identiplante
Line 230... Line 229...
230
	 * les "mieux notées", ou bien les images ayant le "plus de tags" (COUNT())
229
	 * les "mieux notées", ou bien les images ayant le "plus de tags" (COUNT())
231
	 */
230
	 */
232
	static function sqlOrderBy($p, $db, &$req) {
231
	static function sqlOrderBy($p, $db, &$req) {
233
		// parmi self::$tri_possible
232
		// parmi self::$tri_possible
234
		if($p['tri'] == 'votes') { // LEFT JOIN sur "dis" ci-dessous
233
		if($p['tri'] == 'votes') { // LEFT JOIN sur "dis" ci-dessous
235
			$req['orderby'] = 'dis.moyenne ' . $p['ordre'];
234
			$req['orderby'] = 'dis.moyenne ' . $p['ordre'] . ', dis.nb_votes ' . $p['ordre'];
236
			return;
235
			return;
237
		}
236
		}
Line 238... Line 237...
238
		
237
		
239
		if($p['tri'] == 'tags') { // LEFT JOIN sur "dis" ci-dessous
238
		if($p['tri'] == 'tags') { // LEFT JOIN sur "dis" ci-dessous
240
			$req['orderby'] = 'dis.nb_tags ' . $p['ordre'];
239
			$req['orderby'] = 'dis.nb_tags ' . $p['ordre'];
241
			return;
240
			return;
Line -... Line 241...
-
 
241
		}
-
 
242
 
-
 
243
		if($p['tri'] == 'date_observation') {
-
 
244
			$req['orderby'] = 'date_observation ' . $p['ordre'] . ', id_observation ' . $p['ordre'];
-
 
245
			return;
242
		}
246
		}
243
 
-
 
244
		// tri == 'date_transmission'
247
 
245
		$req['orderby'] = 'date_transmission ' . $p['ordre'];
248
		// tri == 'date_transmission'
246
		// avant cel:r1860, date_transmission pouvait être NULL
249
		// avant cel:r1860, date_transmission pouvait être NULL
247
		// or nous voulons de la consistence (notamment pour phpunit)
250
		// or nous voulons de la consistence (notamment pour phpunit)
Line 248... Line 251...
248
		$req['orderby'] .= ', id_observation ' . $p['ordre'];
251
		$req['orderby'] = 'date_transmission ' . $p['ordre'] . ', id_observation ' . $p['ordre'];
249
	}
252
	}
250
 
253
 
Line 313... Line 316...
313
									 ($p['ordre'] == 'desc') ? 'INNER' : 'LEFT');
316
									 ($p['ordre'] == 'desc') ? 'INNER' : 'LEFT');
314
			// nécessaire (dup ce_image dans del_image_stat)
317
			// nécessaire (dup ce_image dans del_image_stat)
315
			$req['groupby'][] = 'vdi.id_observation';
318
			$req['groupby'][] = 'vdi.id_observation';
316
		}
319
		}
Line 317... Line -...
317
 
-
 
318
 
-
 
319
		// TODO: support du "ET", "OU", "(", ")"
320
 
320
		// http://codehackit.blogspot.fr/2011/08/expression-parser-in-php.html
321
		// car il ne sont pas traités par la générique requestFilterParams() les clefs "masque.tag_*"
321
		// http://blog.angeloff.name/post/2012/08/05/php-recursive-patterns/
322
		// sont toujours présentes; bien que parfois NULL.
322
		if(!empty($p['masque.tag_cel'])) {
323
		if($p['masque.tag_cel']) {
323
			if($p['tag_explode_semantic'] == 'AND') {
324
			if(isset($p['masque.tag_cel']['AND'])) {
324
				// TODO: utiliser les tables de mots clefs normaliées dans tb_cel ?
325
				// TODO: utiliser les tables de mots clefs normaliées dans tb_cel ?
325
				// et auquel cas laisser au client le choix du couteux "%" ?
326
				// et auquel cas laisser au client le choix du couteux "%" ?
326
				$tags = $p['masque.tag_cel'];
327
				$tags = $p['masque.tag_cel']['AND'];
327
				array_walk($tags, create_function('&$val, $k, $db',
328
				array_walk($tags, create_function('&$val, $k, $db',
328
												  '$val = sprintf("CONCAT(vdi.mots_cles_texte,vdi.i_mots_cles_texte) LIKE %s",
329
												  '$val = sprintf("CONCAT(vdi.mots_cles_texte,vdi.i_mots_cles_texte) LIKE %s",
329
 																  $db->proteger("%".$val."%"));'),
330
 																  $db->proteger("%".$val."%"));'),
330
						   $db);
331
						   $db);
331
				$req['where'][] = implode(' AND ', $tags);
332
				$req['where'][] = '(' . implode(' AND ', $tags) . ')';
332
			}
333
			}
333
			else { // OR assumed
334
			else {
334
				$req['where'][] = sprintf("CONCAT(vdi.mots_cles_texte,vdi.mots_cles_texte) REGEXP %s",
335
				$req['where'][] = sprintf("CONCAT(vdi.mots_cles_texte,vdi.i_mots_cles_texte) REGEXP %s",
335
										  $db->proteger(implode('|', $p['masque.tag_cel'])));
336
										  $db->proteger(implode('|', $p['masque.tag_cel']['OR'])));
336
			}
337
			}
Line 337... Line 338...
337
		}
338
		}
338
 
339
 
339
 
340
 
340
		// XXX: utiliser tag plutôt que tag_normalise ?
341
		// XXX: utiliser tag plutôt que tag_normalise ?
341
		if(!empty($p['masque.tag_pictoflora'])) {
342
		if($p['masque.tag_pictoflora']) {
Line 342... Line 343...
342
			// pas de LEFT JOIN ? ou bien peut-être en cas de tri, mais nous parlons bien ici d'un masque
343
			// pas de LEFT JOIN ? ou bien peut-être en cas de tri, mais nous parlons bien ici d'un masque
343
			/* $req['join'][] = 'LEFT JOIN del_image_tag dit ON dit.ce_image = vdi.id_image';
344
			/* $req['join'][] = 'LEFT JOIN del_image_tag dit ON dit.ce_image = vdi.id_image';
344
			   $req['where'][] = 'dit.actif = 1'; */
345
			   $req['where'][] = 'dit.actif = 1'; */
345
 
346
 
346
			if($p['tag_explode_semantic'] == 'AND') {
347
			if(isset($p['masque.tag_pictoflora']['AND'])) {
347
				// optimsation: en cas de "AND" on sort() l'input et le GROUP_CONCAT()
348
				// optimsation: en cas de "AND" on sort() l'input et le GROUP_CONCAT()
348
				// donc nous utilisons des ".*" plutôt que de multiples conditions et "|"
349
				// donc nous utilisons des ".*" plutôt que de multiples conditions et "|"
349
				sort($p['masque.tag_pictoflora']);
350
				sort($p['masque.tag_pictoflora']['AND']);
350
				$req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
351
				$req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
351
										  " GROUP BY ce_image".
352
										  " GROUP BY ce_image".
352
										  " HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s)",
353
										  " HAVING GROUP_CONCAT(tag_normalise ORDER BY tag_normalise) REGEXP %s)",
353
										  $db->proteger(implode('.*', $p['masque.tag_pictoflora'])));
354
										  $db->proteger(implode('.*', $p['masque.tag_pictoflora']['AND'])));
354
			}
355
			}
355
			else { // OR assumed
356
			else {
356
				$req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
357
				$req['where'][] = sprintf("vdi.id_image IN (SELECT ce_image FROM del_image_tag WHERE actif = 1".
357
										  " GROUP BY ce_image".
358
										  " GROUP BY ce_image".
358
										  " HAVING GROUP_CONCAT(tag_normalise) REGEXP %s)",
-
 
359
										  $db->proteger(implode('|', $p['masque.tag_pictoflora'])));
359
										  " HAVING GROUP_CONCAT(tag_normalise) REGEXP %s)",
Line -... Line 360...
-
 
360
										  $db->proteger(implode('|', $p['masque.tag_pictoflora']['OR'])));
360
			}
361
			}
361
		}
362
		}
362
 
363
	}
363
	}
364
 
364
 
365
 
Line 410... Line 411...
410
			$or_params = array('masque.auteur' => $p['masque'],
411
			$or_params = array('masque.auteur' => $p['masque'],
411
							   'masque.departement' => $p['masque'],
412
							   'masque.departement' => $p['masque'],
412
							   'masque.commune' => $p['masque'], // TODO/XXX ?
413
							   'masque.commune' => $p['masque'], // TODO/XXX ?
413
							   'masque.id_zone_geo' => $p['masque'],
414
							   'masque.id_zone_geo' => $p['masque'],
Line 414... Line 415...
414
 
415
 
-
 
416
							   /* tous-deux remplacent masque.tag
415
							   // tous-deux remplacent masque.tag
417
								  mais sont traité séparément des requestFilterParams() */
416
							   'masque.tag_cel' => $p['masque'],
418
							   // 'masque.tag_cel' => $p['masque'],
Line 417... Line 419...
417
							   'masque.tag_pictoflora' => $p['masque'],
419
							   // 'masque.tag_pictoflora' => $p['masque'],
418
 
420
 
419
							   'masque.ns' => $p['masque'],
421
							   'masque.ns' => $p['masque'],
420
							   'masque.famille' => $p['masque'],
422
							   'masque.famille' => $p['masque'],
Line 426... Line 428...
426
							   'tri' => $p['tri'],
428
							   'tri' => $p['tri'],
427
							   'ordre' => $p['ordre']);
429
							   'ordre' => $p['ordre']);
Line 428... Line 430...
428
 
430
 
429
			$or_masque = array_merge(
431
			$or_masque = array_merge(
430
				ListeObservations2::requestFilterParams($or_params, NULL, $c /* pour masque.departement */),
432
				ListeObservations2::requestFilterParams($or_params, NULL, $c /* pour masque.departement */),
-
 
433
				self::requestFilterParams($or_params));
-
 
434
 
-
 
435
			/* Lorsqu'on utilise le masque général pour chercher des tags, ils sont
431
				self::requestFilterParams($or_params),
436
			   postulés comme séparés par des espaces, et doivent être tous matchés. */
-
 
437
			$or_masque['masque.tag_cel'] = self::buildTagsAST($p['masque'], 'AND', ' ');
Line -... Line 438...
-
 
438
			$or_masque['masque.tag_pictoflora'] = self::buildTagsAST($p['masque'], 'AND', ' ');
432
				array('tag_explode_char' => ' ', 'tag_explode_semantic' => 'AND')); // spéciaux
439
 
433
 
440
 
434
			// pas de select, groupby & co ici
441
			// pas de select, groupby & co ici: uniquement 'join' et 'where'
435
			$or_req = array('join' => array(), 'where' => array());
442
			$or_req = array('join' => array(), 'where' => array());
Line 436... Line 443...
436
			ListeObservations2::sqlAddConstraint($or_masque, $db, $or_req);
443
			ListeObservations2::sqlAddConstraint($or_masque, $db, $or_req);
Line 442... Line 449...
442
				$req['join'] = array_unique(array_merge($req['join'], $or_req['join']));
449
				$req['join'] = array_unique(array_merge($req['join'], $or_req['join']));
443
			}
450
			}
444
		}
451
		}
445
	}
452
	}
Line 446... Line 453...
446
 
453
 
447
	// complete & override ListeObservations2::requestFilterParams()
454
	// complete & override ListeObservations2::requestFilterParams() (même usage)
448
	static function requestFilterParams(Array $params, $parametres_autorises = NULL) {
455
	static function requestFilterParams(Array $params, $parametres_autorises = NULL) {
449
		if($parametres_autorises) { // filtrage de toute clef inconnue
456
		if($parametres_autorises) { // filtrage de toute clef inconnue
450
			$params = array_intersect_key($params, array_flip($parametres_autorises));
457
			$params = array_intersect_key($params, array_flip($parametres_autorises));
Line 451... Line 458...
451
		}
458
		}
452
 
459
 
453
		$p = array();
460
		$p = array();
Line 454... Line 461...
454
		$p['tri'] = ListeObservations2::unsetIfInvalid($params, 'tri', self::$tri_possible);
461
		$p['tri'] = ListeObservations2::unsetIfInvalid($params, 'tri', self::$tri_possible);
455
		$p['format'] = ListeObservations2::unsetIfInvalid($params, 'format', self::$format_image_possible);
462
		$p['format'] = ListeObservations2::unsetIfInvalid($params, 'format', self::$format_image_possible);
Line 456... Line 463...
456
 
463
 
457
		// et pour identiplantes ?
464
		// "milieu" inutile pour IdentiPlantes ?
458
		if(isset($params['masque.milieu'])) $p['masque.milieu'] = trim($params['masque.milieu']);
465
		if(isset($params['masque.milieu'])) $p['masque.milieu'] = trim($params['masque.milieu']);
459
 
466
 
Line 460... Line -...
460
		// compatibilité
-
 
461
		if(isset($params['masque.tag'])) {
-
 
462
			$params['masque.tag_cel'] = $params['masque.tag_pictoflora'] = $params['masque.tag'];
-
 
463
		}
-
 
464
 
-
 
465
		/* Nous n'implod()ons pas ici, car la sémantique des éléments multiples est
-
 
466
		   variable (tag_explode_semantic) et considérée plus tard, notamment lors de la
-
 
467
		   construction du SQL (sqlAddConstraint()).
-
 
468
		   Cette sémantique peut varier selon que ces tags proviennent du formulaire de
-
 
469
		   recherche avancée ou via le masque général (et l'appel récursif à requestFilterParams()
-
 
470
		   qu'il implique) */
-
 
471
		if(isset($params['masque.tag_cel'])) {
-
 
472
			if(isset($params['tag_explode_char'])) {
-
 
473
				$x = explode($params['tag_explode_char'], $params['masque.tag_cel']);
-
 
474
				$x = array_map('trim', $x);
-
 
475
				$p['masque.tag_cel'] = array_filter($x);
-
 
476
			} else {
-
 
477
				// toujours un tableau
-
 
478
				$p['masque.tag_cel'] = array(trim($params['masque.tag_cel']));
-
 
479
			}
-
 
480
 
-
 
481
		}
-
 
482
 
-
 
483
		if(isset($params['masque.tag_pictoflora'])) {
-
 
484
			if(isset($params['tag_explode_char'])) {
-
 
485
				$x = explode($params['tag_explode_char'], $params['masque.tag_pictoflora']);
-
 
486
				$x = array_map('trim', $x);
-
 
487
				$p['masque.tag_pictoflora'] = array_filter($x);
-
 
488
			} else {
-
 
489
				// toujours un tableau
467
		// compatibilité
490
				$p['masque.tag_pictoflora'] = array(trim($params['masque.tag_pictoflora']));
468
		if(isset($params['masque.tag'])) {
491
			}
469
			$params['masque.tag_cel'] = $params['masque.tag_pictoflora'] = $params['masque.tag'];
492
		}
470
		}
493
 
471
 
Line 501... Line 479...
501
 
479
 
502
		return array_filter($p, create_function('$a','return !in_array($a, array("",false,null),true);'));
480
		return array_filter($p, create_function('$a','return !in_array($a, array("",false,null),true);'));
Line -... Line 481...
-
 
481
	}
-
 
482
 
-
 
483
 
-
 
484
	/* Construit un (vulgaire) abstract syntax tree:
-
 
485
	   "AND" => [ "tag1", "tag2" ]
-
 
486
	   Idéalement (avec un parser simple comme proposé par http://hoa-project.net/Literature/Hack/Compiler.html#Langage_PP)
-
 
487
	   nous aurions:
-
 
488
	   "AND" => [ "tag1", "tag2", "OR" => [ "tag3", "tag4" ] ]
-
 
489
 
-
 
490
	   Ici nous devons traiter les cas suivants:
-
 
491
	   tags séparés par des "ET/AND OU/OR", séparés par des espaces ou des virgules.
-
 
492
	   Mais la chaîne peut aussi avoir été issue du "masque général" (la barre de recherche générique).
-
 
493
	   ce qui implique des comportement par défaut différents afin de préserver la compatibilité.
-
 
494
 
-
 
495
	   Théorie:
-
 
496
	   1) tags passés par "champ tag":
-
 
497
	   - support du ET/OU, et explode par virgule.
-
 
498
	   - si pas d'opérande détectée: "OU"
-
 
499
 
-
 
500
	   2) tags passés par "recherche générale":
-
 
501
	   - support du ET/OU, et explode par whitespace.
-
 
502
	   - si pas d'opérande détectée: "ET"
-
 
503
 
-
 
504
	   La présence de $additional_sep s'explique car ET/OU sous-entendent une séparation par des espaces.
-
 
505
	   Mais ce n'est pas toujours pertinent car: 1) la compatibilité suggère de considérer parfois
-
 
506
	   la virgule comme séparateur et 2) les tags *peuvent* contenir des espaces. Par conséquent:
-
 
507
	   * a,b,c => "a" $default_op "b" $default_op "c"
-
 
508
	   * a,b AND c => "a" AND "b" AND "c"
-
 
509
	   * a OR b AND c,d => "a" AND "b" AND "c" AND "d"
-
 
510
	   C'est à dire par ordre décroissant de priorité:
-
 
511
	   1) opérande contenu dans la chaîne
-
 
512
	   2) opérande par défaut
-
 
513
	   3) les séparateurs présents sont substitués par l'opérande déterminée par 1) ou 2)
-
 
514
 
-
 
515
	   // TODO: support des parenthèses, imbrications & co: "(", ")"
-
 
516
	   // http://codehackit.blogspot.fr/2011/08/expression-parser-in-php.html
-
 
517
	   // http://blog.angeloff.name/post/2012/08/05/php-recursive-patterns/
-
 
518
 
-
 
519
	   @param $str: la chaîne à "parser"
-
 
520
	   @param $default_op: "AND" ou "OR"
-
 
521
	   @param $additional_sep: séparateur de mots:
-
 
522
	*/
-
 
523
	static function buildTagsAST($str = NULL, $default_op, $additional_sep = ',') {
-
 
524
		if(!$str) return;
-
 
525
		$words = preg_split('/ (OR|AND|ET|OU) /', $str, -1, PREG_SPLIT_NO_EMPTY);
-
 
526
 
-
 
527
		if(preg_match('/\b(ET|AND)\b/', $str)) $op = 'AND';
-
 
528
		elseif(preg_match('/\b(OU|OR)\b/', $str)) $op = 'OR';
-
 
529
		else $op = $default_op;
-
 
530
 
-
 
531
		if($additional_sep) {
-
 
532
			array_walk($words,
-
 
533
					   create_function('&$v, $k, $sep', '$v = preg_split("/".$sep."/", $v, -1, PREG_SPLIT_NO_EMPTY);'),
-
 
534
					   $additional_sep);
-
 
535
		}
-
 
536
		$words = self::array_flatten($words);
-
 
537
		$words = array_map('trim', $words);
Line 503... Line 538...
503
	}
538
		return array($op => array_filter($words));
504
 
539
	}
505
 
540
 
506
 
541
 
507
	// met à jour *toutes* les stats de nombre de tags et de moyenne des votes
542
	// met à jour *toutes* les stats de nombre de tags et de moyenne des votes
508
	static function _update_statistics($db) {
543
	static function _update_statistics($db) {
509
		$db->requeter("TRUNCATE TABLE del_image_stat");
544
		$db->requeter("TRUNCATE TABLE del_image_stat");
510
		$db->requeter(<<<EOF
545
		$db->requeter(<<<EOF
511
INSERT INTO `del_image_stat` (
546
INSERT INTO `del_image_stat` (
512
       SELECT id_image, divo.ce_protocole, divo.moyenne, dit.ctags 
547
	SELECT id_image, divo.ce_protocole, divo.moyenne, divo.nb_votes, dit.ctags 
513
       FROM `BASECEL`.`cel_images` ci 
548
	FROM `tb_cel`.`cel_images` ci 
514
       LEFT JOIN 
549
	LEFT JOIN 
515
            ( SELECT ce_image, ce_protocole, AVG(valeur) AS moyenne FROM del_image_vote 
550
	( SELECT ce_image, ce_protocole, AVG(valeur) AS moyenne, COUNT(valeur) AS nb_votes FROM del_image_vote 
516
              GROUP BY ce_image, ce_protocole ) AS divo
551
	  GROUP BY ce_image, ce_protocole ) AS divo
517
       ON ci.id_image = divo.ce_image 
552
	ON ci.id_image = divo.ce_image 
518
       LEFT JOIN 
553
	LEFT JOIN 
519
            ( SELECT ce_image, COUNT(id_tag) as ctags FROM del_image_tag 
554
	( SELECT ce_image, COUNT(id_tag) as ctags FROM del_image_tag 
520
              GROUP BY ce_image ) AS dit 
555
	  GROUP BY ce_image ) AS dit 
-
 
556
	ON ci.id_image = dit.ce_image )
-
 
557
EOF
-
 
558
		);
-
 
559
	}
-
 
560
 
-
 
561
	static function revOrderBy($orderby) {
-
 
562
		return $orderby == 'asc' ? 'desc' : 'asc';
-
 
563
	}
-
 
564
 
-
 
565
	static function array_flatten($arr) {
-
 
566
		$arr = array_values($arr);
-
 
567
		while (list($k,$v)=each($arr)) {
-
 
568
			if (is_array($v)) {
-
 
569
				array_splice($arr,$k,1,$v);
-
 
570
				next($arr);
521
       ON ci.id_image = dit.ce_image )
571
			}
522
EOF
572
		}