');
foreach ($tokens as $t) {
$i = -1;
while ( ($i = strpos($text,$t,$i+1))!==false) { $n++; }
}
return $n;
}
// }}}
// {{{ class ThrowError
/** handle errors for end-users queries
* assign the error message and runs the templates
*
* @author Jean-Sebastien Bedo
*/
class ThrowError
{
/** constuctor
* @param $explain string the error (in natural language)
*/
function ThrowError($explain)
{
global $page;
$page->trig('Erreur : '.$explain);
$page->run();
}
}
// }}}
// {{{ class SField [Base class]
/** classe de base représentant un champ de recherche
* (correspond à un champ du formulaire mais peut être à plusieurs champs de la bdd)
* interface étendue pour chaque type de champ particulier
*/
class SField
{
// {{{ properties
/** le nom du champ dans le formulaire HTML */
var $fieldFormName;
/** champs de la bdd correspondant à ce champ sous forme d'un tableau */
var $fieldDbName;
/** champ résultat dans la requête MySQL correspondant à ce champ
* (alias utilisé pour la clause ORDER BY) */
var $fieldResultName;
/** valeur du champ instanciée par l'utilisateur */
var $value;
// }}}
// {{{ constructor
/** constructeur
* (récupère la requête de l'utilisateur pour ce champ) */
function SField($_fieldFormName, $_fieldDbName='', $_fieldResultName='')
{
$this->fieldFormName = $_fieldFormName;
$this->fieldDbName = $_fieldDbName;
$this->fieldResultName = $_fieldResultName;
$this->get_request();
}
// }}}
// {{{ function get_request()
/** récupérer la requête de l'utilisateur
* on met une chaîne vide si le champ n'a pas été complété */
function get_request()
{
$this->value = trim(Env::v($this->fieldFormName));
}
// }}}
// {{{ function get_where_statement()
/** récupérer la clause correspondant au champ dans la clause WHERE de la requête
* on parcourt l'ensemble des champs de la bdd de $fieldDbName et on associe
* à chacun d'entre eux une clause spécifique
* la clause totale et la disjonction de ces clauses spécifiques */
function get_where_statement()
{
if ($this->value=='') {
return false;
}
$res = implode(' OR ', array_filter(array_map(array($this, 'get_single_where_statement'), $this->fieldDbName)));
return empty($res) ? '' : "($res)";
}
// }}}
// {{{ function get_order_statement()
/** récupérer la clause correspondant au champ dans la clause ORDER BY de la requête
* utilisé par exemple pour placer d'abord le nom égal à la requête avant les approximations */
function get_order_statement()
{
return false;
}
// }}}
// {{{ function get_select_statement()
function get_select_statement()
{
return false;
}
// }}}
// {{{ function get_url()
/** récupérer le bout d'URL correspondant aux paramètres permettant d'imiter une requête d'un
* utilisateur assignant la valeur $this->value à ce champ */
function get_url()
{
if (empty($this->value)) {
return false;
} else {
return $this->fieldFormName.'='.urlencode($this->value);
}
}
// }}}
}
// }}}
// {{{ class QuickSearch [Google Like]
class QuickSearch extends SField
{
// {{{ properties
/** stores tokens */
var $strings;
/** stores numerical ranges */
var $ranges;
// }}}
// {{{ constructor
function QuickSearch($_fieldFormName)
{
$this->fieldFormName = $_fieldFormName;
$this->get_request();
if (preg_match(":[\]\[{}~/§_`|%$^=+]|\*\*:", $this->value)) {
new ThrowError('Un champ contient un caractère interdit rendant la recherche impossible.');
}
}
// }}}
// {{{ function isempty()
function isempty()
{
return empty($this->strings) && empty($this->ranges);
}
// }}}
// {{{ function get_request()
function get_request()
{
parent::get_request();
$s = replace_accent(trim($this->value));
$s = preg_replace('!\d+!', ' ', $s);
$s = str_replace('*','%',$s);
$this->strings = preg_split("![^a-zA-Z%]+!",$s, -1, PREG_SPLIT_NO_EMPTY);
$s = trim($this->value);
$s = preg_replace('! *- *!', '-', $s);
$s = preg_replace('!([<>]) *!', ' \1', $s);
$s = preg_replace('![^0-9\-><]!', ' ', $s);
$s = preg_replace('![<>\-] !', '', $s);
$ranges = preg_split('! +!', $s, -1, PREG_SPLIT_NO_EMPTY);
$this->ranges=Array();
foreach ($ranges as $r) {
if (preg_match('!^([<>]\d{4}|\d{4}(-\d{4})?)$!', $r)) $this->ranges[] = $r;
}
}
// }}}
// {{{ function get_where_statement()
function get_where_statement()
{
$where = Array();
foreach ($this->strings as $i => $s) {
$t = str_replace('*', '%', $s).'%';
$t = str_replace('%%', '%', $t);
$where[] = "sn$i.token LIKE '$t'";
}
$wherep = Array();
foreach ($this->ranges as $r) {
if (preg_match('!^\d{4}$!', $r)) {
$wherep[] = "u.promo=$r";
} elseif (preg_match('!^(\d{4})-(\d{4})$!', $r, $matches)) {
$p1=min(intval($matches[1]), intval($matches[2]));
$p2=max(intval($matches[1]), intval($matches[2]));
$wherep[] = "(u.promo>=$p1 AND u.promo<=$p2)";
} elseif (preg_match('!^<(\d{4})!', $r, $matches)) {
$wherep[] = "u.promo<={$matches[1]}";
} elseif (preg_match('!^>(\d{4})!', $r, $matches)) {
$wherep[] = "u.promo>={$matches[1]}";
}
}
if (!empty($wherep)) {
$where[] = '('.join(' OR ',$wherep).')';
}
return join(" AND ", $where);
}
// }}}
// {{{ get_select_statement
function get_select_statement()
{
$join = "";
foreach ($this->strings as $i => $s) {
$join .= "INNER JOIN search_name AS sn$i ON (u.user_id = sn$i.uid)\n";
}
return $join;
}
// }}}
// {{{ function get_order_statement()
function get_order_statement()
{
return false;
}
// }}}
// {{{ function get_score_statement
function get_score_statement()
{
$sum = array('0');
foreach ($this->strings as $i => $s) {
$sum[] .= "SUM(sn$i.score + IF('$s'=sn$i.token,5,0))";
}
return join('+', $sum).' AS score';
}
// }}}
}
// }}}
// {{{ class NumericSField [Integer fields]
/** classe de champ numérique entier (offset par exemple)
*/
class NumericSField extends SField
{
// {{{ constructor
/** constructeur
* (récupère la requête de l'utilisateur pour ce champ) */
function NumericSField($_fieldFormName)
{
$this->fieldFormName = $_fieldFormName;
$this->get_request();
}
// }}}
// {{{ function get_request()
/** récupère la requête de l'utilisateur et échoue s'il ne s'agit pas d'un entier */
function get_request()
{
parent::get_request();
if (empty($this->value)) {
$this->value = 0;
}
if (!preg_match("/^[0-9]+$/", $this->value)) {
new ThrowError('Un champ numérique contient des caractères alphanumériques.');
}
}
// }}}
}
// }}}
// {{{ class RefSField [ ??? ]
class RefSField extends SField
{
// {{{ properties
var $refTable;
var $refAlias;
var $refCondition;
var $exact = true;
// }}}
// {{{ constructor
function RefSField($_fieldFormName, $_fieldDbName='', $_refTable, $_refAlias, $_refCondition, $_exact=true)
{
$this->fieldFormName = $_fieldFormName;
$this->fieldDbName = $_fieldDbName;
$this->refTable = $_refTable;
$this->refAlias = $_refAlias;
$this->refCondition = $_refCondition;
$this->exact = $_exact;
$this->get_request();
}
// }}}
// {{{ function get_request()
function get_request() {
parent::get_request();
if ($this->value=='00' || $this->value=='0') {
$this->value='';
}
}
// }}}
// {{{ function too_large()
function too_large()
{
return ($this->value=='');
}
// }}}
// {{{ function compare()
function compare()
{
$val = addslashes($this->value);
return $this->exact ? "='$val'" : " LIKE '%$val%'";
}
// }}}
// {{{ function get_single_match_statement()
function get_single_match_statement($field)
{
return $field.$this->compare();
}
// }}}
// {{{ function get_single_where_statement()
function get_single_where_statement($field)
{
return $this->refTable=='' ? $this->get_single_match_statement($field) : false;
}
// }}}
// {{{ function get_select_statement()
function get_select_statement()
{
if ($this->value=='' || $this->refTable=='') {
return false;
}
$res = implode(' OR ', array_filter(array_map(array($this, 'get_single_match_statement'), $this->fieldDbName)));
return "INNER JOIN {$this->refTable} AS {$this->refAlias} ON ({$this->refCondition} AND ($res) )";
}
// }}}
}
// }}}
// {{{ class RefSFieldMultipleTable
class MapSField extends RefSField
{
var $mapId;
function MapSField($_fieldFormName, $_fieldDbName='', $_refTable, $_refAlias, $_refCondition, $_mapId=false)
{
if ($_mapId === false)
$this->mapId = Env::v($_fieldFormName, '');
else
$this->mapId = $_mapId;
$this->RefSField($_fieldFormName, $_fieldDbName, $_refTable, $_refAlias, $_refCondition, true, false);
}
function get_select_statement()
{
if ($this->mapId === '') return false;
$res = implode(' OR ', array_filter(array_map(array($this, 'get_single_match_statement'), $this->fieldDbName)));
foreach ($this->refTable as $i => $refT)
$last = $i;
$inner = "";
foreach ($this->refTable as $i => $refT)
$inner .= " INNER JOIN {$refT} AS {$this->refAlias[$i]} ON ({$this->refCondition[$i]} ".(($i == $last)?"AND ($res) ":"").")";
return $inner;
}
function get_request()
{
$this->value = $this->mapId;
}
}
// {{{ class RefWithSoundexSField [ ??? ]
class RefWithSoundexSField extends RefSField
{
// {{{ function compare()
function compare()
{
return "='".soundex_fr($this->value)."'";
}
// }}}
}
// }}}
// {{{ class StringSField [String fields]
/** classe de champ texte (nom par exemple)
*/
class StringSField extends SField
{
// {{{ function get_request()
/** récupère la requête de l'utilisateur et échoue si la chaîne contient des caractères
* interdits */
function get_request()
{
parent::get_request();
if (preg_match(":[\]\[<>{}~/§_`|%$^=+]|\*\*:", $this->value)) {
new ThrowError('Un champ contient un caractère interdit rendant la recherche impossible.');
}
}
// }}}
// {{{ function length()
/** donne la longueur de la requête de l'utilisateur
* (au sens strict i.e. pas d'* ni d'espace ou de trait d'union -> les contraintes réellement
* imposées par l'utilisateur) */
function length()
{
global $lc_accent,$uc_accent;
return strlen($this->value) - strlen(ereg_replace('[a-z'.$lc_accent.$uc_accent.']', '', strtolower($this->value)));
}
// }}}
// {{{ function too_large()
function too_large()
{
return ($this->length()<2);
}
// }}}
// {{{ function get_single_where_statement()
/** clause WHERE correspondant à un champ de la bdd et à ce champ de formulaire
* @param field nom de champ de la bdd concerné par la clause */
function get_single_where_statement($field)
{
$regexp = strtr(addslashes($this->value), '-*', '_%');
return "$field LIKE '$regexp%'";
}
// }}}
// {{{ function get_order_statement()
/** clause ORDER BY correspondant à ce champ de formulaire */
function get_order_statement()
{
if ($this->value!='' && $this->fieldResultName!='') {
return "{$this->fieldResultName}!='".addslashes($this->value)."'";
} else {
return false;
}
}
// }}}
}
// }}}
// {{{ class NameSField [Names : serach 'n%' + '% b']
/** classe pour les noms : on cherche en plus du like 'foo%' le like '% foo' (particules)
+*/
class NameSField extends StringSField
{
// {{{ function get_single_where_statement()
function get_single_where_statement($field)
{
$regexp = strtr(addslashes($this->value), '-*', '_%');
return "$field LIKE '$regexp%' OR $field LIKE '% $regexp%' OR $field LIKE '%-$regexp%'";
}
// }}}
// {{{ function get_order_statement()
function get_order_statement()
{
if ($this->value!='' && $this->fieldResultName!='') {
return "{$this->fieldResultName} NOT LIKE '".addslashes($this->value)."'";
} else {
return false;
}
}
// }}}
}
// }}}
// {{{ class StringWithSoundexSField [Strings + soundex]
/** classe de champ texte avec soundex (nom par exemple)
*/
class StringWithSoundexSField extends StringSField
{
// {{{ function get_single_where_statement()
/** clause WHERE correspondant à un champ de la bdd et à ce champ de formulaire
* @param field nom de champ de la bdd concerné par la clause */
function get_single_where_statement($field) {
return $field.'="'.soundex_fr($this->value).'"';
}
// }}}
}
// }}}
// {{{ class PromoSField [Prom field]
/** classe de champ de promotion */
class PromoSField extends SField
{
// {{{ properties
/** opérateur de comparaison (<,>,=) de la promo utilisé pour ce champ de formulaire */
var $compareField;
// }}}
// {{{ constructor
/** constructeur
* compareField est un champ de formulaire très simple qui ne sert qu'à la construction de la
* clause WHERE de la promo */
function PromoSField($_fieldFormName, $_compareFieldFormName, $_fieldDbName, $_fieldResultName)
{
parent::SField($_fieldFormName, $_fieldDbName, $_fieldResultName);
$this->compareField = new SField($_compareFieldFormName);
}
// }}}
// {{{ function get_request()
/** récupère la requête utilisateur et échoue si le champ du formulaire ne représente pas une
* promotion (nombre à 4 chiffres) */
function get_request()
{
parent::get_request();
if (preg_match('/^[0-9]{2}$/', $this->value)){
$this->value = intval($this->value) + 1900;
}
if (!(empty($this->value) or preg_match('/^[0-9]{4}$/', $this->value))) {
new ThrowError('La promotion est une année à quatre chiffres.');
}
}
// }}}
// {{{ function is_a_single_promo()
/** teste si la requête est de la forme =promotion -> contrainte forte imposée -> elle suffit
* pour autoriser un affichage des résultats alors que
compareField->value=='=' && $this->value!='');
}
// }}}
// {{{ function too_large()
function too_large()
{
return !$this->is_a_single_promo();
}
// }}}
// {{{ function get_single_where_statement()
/** clause WHERE correspondant à ce champ */
function get_single_where_statement($field)
{
return $field.$this->compareField->value.$this->value;
}
// }}}
// {{{ function get_url()
/** récupérer le bout d'URL correspondant aux paramètres permettant d'imiter une requête
* d'un utilisateur assignant la valeur $this->value à ce champ et assignant l'opérateur de
* comparaison adéquat */
function get_url()
{
if (!($u=parent::get_url())) {
return false;
}
return $u.'&'.$this->compareField->get_url();
}
// }}}
}
// }}}
// {{{ class SFieldGroup [Group fields]
/** classe groupant des champs de formulaire de recherche */
class SFieldGroup
{
// {{{ properties
/** tableau des classes correspondant aux champs groupés */
var $fields;
/** type de groupe : ET ou OU */
var $and;
// }}}
// {{{ constuctor
/** constructeur */
function SFieldGroup($_and, $_fields)
{
$this->fields = $_fields;
$this->and = $_and;
}
// }}}
// {{{ function too_large()
function too_large()
{
$b = true;
for ($i=0; $b && $ifields); $i++) {
$b &= $this->fields[$i]->too_large();
}
return $b;
}
// }}}
// {{{ function field_get_select()
function field_get_select($f)
{
return $f->get_select_statement();
}
// }}}
// {{{ function field_get_where()
/** récupérer la clause WHERE d'un objet champ de recherche */
function field_get_where($f)
{
return $f->get_where_statement();
}
// }}}
// {{{ function field_get_order()
/** récupérer la clause ORDER BY d'un objet champ de recherche */
function field_get_order($f)
{
return $f->get_order_statement();
}
// }}}
// {{{ function field_get_url()
/** récupérer le bout d'URL correspondant à un objet champ de recherche */
function field_get_url($f)
{
return $f->get_url();
}
// }}}
// {{{ function get_select_statement()
function get_select_statement()
{
return implode(' ', array_filter(array_map(array($this, 'field_get_select'), $this->fields)));
}
// }}}
// {{{ function get_where_statement()
/** récupérer la clause WHERE du groupe de champs = conjonction (ET) ou disjonction (OU) de
* clauses des champs élémentaires */
function get_where_statement()
{
$joinText = $this->and ? ' AND ' : ' OR ';
$res = implode($joinText, array_filter(array_map(array($this, 'field_get_where'), $this->fields)));
return $res == '' ? '' : "($res)";
}
// }}}
// {{{ function get_order_statement()
/** récupérer la clause ORDER BY du groupe de champs = conjonction (ET) ou disjonction (OU) de
* clauses des champs élémentaires */
function get_order_statement()
{
$order = array_filter(array_map(array($this, 'field_get_order'), $this->fields));
return count($order)>0 ? implode(',', $order) : false;
}
// }}}
// {{{ function get_url()
/** récupérer le bout d'URL correspondant à ce groupe de champs = concaténation des bouts d'URL
* des champs élémentaires */
function get_url($others=Array())
{
$url = array_filter(array_map(array($this, 'field_get_url'), $this->fields));
foreach ($url as $key=>$val) {
if (empty($val)) {
unset($url[$key]);
}
}
foreach ($others as $key=>$val) {
if (!empty($val)) {
$url[] = "$key=$val";
}
}
return count($url)>0 ? implode('&', $url) : false;
}
// }}}
}
// }}}
// vim:set et sw=4 sts=4 sws=4 foldmethod=marker:
?>