ADDED .travis.yml
Index: .travis.yml
==================================================================
--- .travis.yml
+++ .travis.yml
@@ -0,0 +1,24 @@
+language: php
+php:
+ - '5.4'
+ - '5.5'
+ - '5.6'
+ - '7.0'
+ - '7.1'
+ - hhvm
+
+install:
+ - make -C src deps
+
+script:
+ - php tests/run.php
+
+notifications:
+ irc:
+ channels:
+ - "chat.freenode.net#garradin"
+ template:
+ - "%{build_number} by %{author} on %{branch}: %{message} "
+ - "Build details: %{build_url}"
+ use_notice: false
+ skip_join: true
Index: src/.htaccess
==================================================================
--- src/.htaccess
+++ src/.htaccess
@@ -1,10 +1,24 @@
+# Désactiver le multiviews (conflit avec /admin/plugin.php) et les index (sécurité)
+Options -MultiViews -Indexes
+
+# Au cas où
- RedirectMatch 403 /include/
- RedirectMatch 403 /cache/
- RedirectMatch 403 /plugins/
- RedirectMatch 403 /templates/
- RedirectMatch 403 /.*\.sqlite
- RedirectMatch 403 /.*\.log
- RedirectMatch 403 /(README|VERSION|COPYING)
+ RedirectMatch 403 /include/
+ RedirectMatch 403 /cache/
+ RedirectMatch 403 /plugins/
+ RedirectMatch 403 /templates/
+ RedirectMatch 403 /.*\.sqlite
+ RedirectMatch 403 /.*\.log
+ RedirectMatch 403 /(README|VERSION|COPYING)
RedirectMatch 403 /config\.(.*)\.php
+
+# Redirection dynamique, pour les installations sans vhost dédié
+# Objectif: supprimer le /www/ de l'URL
+
+ RewriteEngine on
+ RewriteBase /
+ RewriteCond %{REQUEST_URI}::$1 ^(.*?/)(.*)::\2$
+ RewriteRule ^(.*)$ - [E=BASE:%1]
+ RewriteRule (.*) %{ENV:BASE}/www/$1 [QSA]
+
Index: src/Makefile
==================================================================
--- src/Makefile
+++ src/Makefile
@@ -1,11 +1,15 @@
-KD2_BRANCH=trunk
+KD2_BRANCH=dev
deps:
$(eval TMP_KD2=$(shell mktemp -d))
- cd ${TMP_KD2}
+ #cd ${TMP_KD2}
- wget https://fossil.kd2.org/kd2fw/zip/KD2+Framework-${KD2_BRANCH}.zip -O ${TMP_KD2}/kd2.zip
+ wget --no-check-certificate https://fossil.kd2.org/kd2fw/zip/KD2+Framework-${KD2_BRANCH}.zip?uuid=${KD2_BRANCH} -O ${TMP_KD2}/kd2.zip
unzip "${TMP_KD2}/kd2.zip" -d ${TMP_KD2}
+ rm -rf "include/lib/KD2"
mv "${TMP_KD2}/KD2 Framework-${KD2_BRANCH}/src/lib/kd2" "include/lib/KD2"
rm -rf ${TMP_KD2}
+
+dev-server:
+ php -S localhost:8082 -t www www/_route.php
Index: src/VERSION
==================================================================
--- src/VERSION
+++ src/VERSION
@@ -1,1 +1,1 @@
-0.7.6
+0.8.0
Index: src/config.dist.php
==================================================================
--- src/config.dist.php
+++ src/config.dist.php
@@ -5,105 +5,213 @@
* disponibles pour Garradin.
*
* NE PAS MODIFIER CE FICHIER!
*
* Pour configurer Garradin, copiez ce fichier en 'config.local.php'
- * et modifiez ce dont vous avez besoin.
+ * puis décommentez et modifiez ce dont vous avez besoin.
*/
// Nécessaire pour situer les constantes dans le bon namespace
namespace Garradin;
-// Connexion automatique à l'administration avec l'adresse e-mail donnée
-#const LOCAL_LOGIN = 'president@association.net';
-
-// Connexion automatique avec le numéro de membre indiqué
-// Défaut : false (connexion automatique désactivée)
-const LOCAL_LOGIN = 1;
-
-// Répertoire où est le code source de Garradin
-const ROOT = '/usr/share/garradin';
-
-// Répertoire où sont situées les données de Garradin
-// (incluant la base de données SQLite, le cache et les fichiers locaux)
-// Défaut : le même répertoire que le source
-const DATA_ROOT = '/var/www/garradin';
-
-// Emplacement de la base de données
-const DB_FILE = '/var/lib/sqlite/garradin.sqlite';
-
-// Adresse URI de la racine du site Garradin
-// (doit se terminer par un slash)
-// Défaut : découverte automatique à partir de SCRIPT_NAME
-const WWW_URI = '/garradin/';
-
-// Adresse URL HTTP(S) de Garradin
-// Défaut : découverte à partir de HTTP_HOST ou SERVER_NAME + WWW_URI
-define('Garradin\WWW_URL', 'http://garradin.net' . WWW_URI);
-
-// Doit-on suggérer à l'utilisateur d'utiliser la version chiffrée du site ?
-// 1 ou true = affiche un message de suggestion sur l'écran de connexion invitant à utiliser le site chiffré
-// (conseillé si vous avez un certificat auto-signé ou peu connu type CACert)
-// 2 = rediriger automatiquement sur la version chiffrée pour l'administration
-// 3 = rediriger automatiquement sur la version chiffrée pour administration et site public
-// false ou 0 = aucune version chiffrée disponible, donc ne rien proposer ni rediriger
+/**
+ * Clé secrète, doit être unique à chaque instance de Garradin
+ *
+ * Ceci est utilisé afin de sécuriser l'envoi de formulaires
+ * (protection anti-CSRF).
+ *
+ * Si aucune valeur n'est définie, Garradin ajoutera automatiquement
+ * une valeur au hasard dans le fichier config.local.php.
+ */
+const SECRET_KEY = '3xUhIgGwuovRKOjVsVPQ5yUMfXUSIOX2GKzcebsz5OINrYC50r';
+
+/**
+ * Se connecter automatiquement avec l'ID de membre indiqué
+ * Exemple: LOCAL_LOGIN = 42
+ *
+ * Défault : false (connexion automatique désactivée)
+ */
+const LOCAL_LOGIN = false;
+
+/**
+ * Autoriser (ou non) l'import de sauvegarde qui a été modifiée ?
+ *
+ * Si mis à true, un avertissement et une confirmation seront demandés
+ * Si mis à false, tout fichier SQLite importé qui ne comporte pas une signature
+ * valide (hash SHA1) sera refusé.
+ *
+ * Ceci ne s'applique qu'à la page "Sauvegarde et restauration" de l'admin,
+ * il est toujours possible de restaurer une base de données non signée en
+ * la recopiant à la place du fichier association.sqlite
+ *
+ * Défaut : true
+ */
+const ALLOW_MODIFIED_IMPORT = true;
+
+/**
+ * Doit-on suggérer à l'utilisateur d'utiliser la version chiffrée du site ?
+ *
+ * 1 ou true = affiche un message de suggestion sur l'écran de connexion invitant à utiliser le site chiffré
+ * (conseillé si vous avez un certificat auto-signé ou peu connu type CACert)
+ * 2 = rediriger automatiquement sur la version chiffrée pour l'administration (mais pas le site public)
+ * 3 = rediriger automatiquement sur la version chiffrée pour administration et site public
+ * false ou 0 = aucune version chiffrée disponible, donc ne rien proposer ni rediriger
+ *
+ * Défaut : false
+ */
const PREFER_HTTPS = false;
-// Emplacement de stockage des plugins
-define('Garradin\PLUGINS_ROOT', DATA_ROOT . '/plugins');
-
-// Plugins fixes qui ne peuvent être désinstallés (séparés par une virgule)
-const PLUGINS_SYSTEM = 'email,web';
-
-// Affichage des erreurs
-// Si "true" alors un message expliquant l'erreur et comment rapporter le bug s'affiche
-// en cas d'erreur. Sinon rien ne sera affiché.
-// Défaut : true
+/**
+ * Répertoire où se situe le code source de Garradin
+ *
+ * Défaut : répertoire racine de Garradin (__DIR__)
+ */
+const ROOT = __DIR__;
+
+/**
+ * Répertoire où sont situées les données de Garradin
+ * (incluant la base de données SQLite, le cache et les fichiers locaux)
+ *
+ * Défaut : identique à ROOT
+ */
+const DATA_ROOT = ROOT;
+
+/**
+ * Emplacement du fichier de base de données de Garradin
+ *
+ * Défaut : ROOT . '/association.sqlite'
+ */
+const DB_FILE = ROOT . '/association.sqlite';
+
+/**
+ * Emplacement de stockage des plugins
+ *
+ * Défaut : DATA_ROOT . '/plugins'
+ */
+const PLUGINS_ROOT = DATA_ROOT . '/plugins';
+
+/**
+ * Plugins fixes qui ne peuvent être désinstallés par l'utilisateur
+ * (séparés par une virgule)
+ *
+ * Exemple : PLUGINS_SYSTEM = 'gestion_emails,factures'
+ *
+ * Défaut : aucun (chaîne vide)
+ */
+const PLUGINS_SYSTEM = '';
+
+/**
+ * Adresse URI de la racine du site Garradin
+ * (doit se terminer par un slash)
+ *
+ * Défaut : découverte automatique à partir de SCRIPT_NAME
+ */
+
+//const WWW_URI = '/asso/';
+
+/**
+ * Adresse URL HTTP(S) de Garradin
+ *
+ * Défaut : découverte à partir de HTTP_HOST ou SERVER_NAME + WWW_URI
+ */
+
+//const WWW_URL = 'http://garradin.chezmoi.tld' . WWW_URI;
+
+/**
+ * Affichage des erreurs
+ * Si "true" alors un message expliquant l'erreur et comment rapporter le bug s'affiche
+ * en cas d'erreur. Sinon rien ne sera affiché.
+ *
+ * Défaut : true
+ */
const SHOW_ERRORS = true;
-// Envoi des erreurs par e-mail
-// Si rempli, un email sera envoyé à l'adresse indiquée à chaque fois qu'une erreur
-// d'exécution sera rencontrée.
-// Si "false" alors aucun email ne sera envoyé
-// Note : les erreurs sont déjà toutes loguées dans error.log à la racine de DATA_ROOT
+/**
+ * Envoi des erreurs par e-mail
+ *
+ * Si renseigné, un email sera envoyé à l'adresse indiquée à chaque fois qu'une erreur
+ * d'exécution sera rencontrée.
+ * Si "false" alors aucun email ne sera envoyé.
+ * Note : les erreurs sont déjà toutes loguées dans error.log à la racine de DATA_ROOT
+ *
+ * Défaut : false
+ */
const MAIL_ERRORS = false;
-// Utilisation de cron pour les tâches automatiques
-// Si "true" on s'attend à ce qu'une tâche automatisée appelle
-// le script cron.php à la racine toutes les 24 heures. Sinon Garradin
-// effectuera les actions automatiques quand quelqu'un se connecte à
-// l'administration ou visite le site.
-// Défaut : false
+/**
+ * Utilisation de cron pour les tâches automatiques
+ *
+ * Si "true" on s'attend à ce qu'une tâche automatisée appelle
+ * le script cron.php à la racine toutes les 24 heures. Sinon Garradin
+ * effectuera les actions automatiques quand quelqu'un se connecte à
+ * l'administration ou visite le site.
+ *
+ * Défaut : false
+ */
const USE_CRON = false;
-// Activation de l'envoi de fichier directement par le serveur web.
-// Permet d'améliorer la rapidité d'envoi des fichiers.
-// Supporte les serveurs web suivants :
-// - Apache avec mod_xsendfile (paquet libapache2-mod-xsendfile)
-// - Lighttpd
-// N'activer que si vous êtes sûr que le module est installé et activé.
-// Nginx n'est PAS supporté, car X-Accel-Redirect ne peut gérer que des fichiers
-// qui sont *dans* le document root du vhost, ce qui n'est pas le cas ici.
+/**
+ * Activation de l'envoi de fichier directement par le serveur web.
+ * (X-SendFile)
+ *
+ * Permet d'améliorer la rapidité d'envoi des fichiers.
+ * Supporte les serveurs web suivants :
+ * - Apache avec mod_xsendfile (paquet libapache2-mod-xsendfile)
+ * - Lighttpd
+ *
+ * N'activer que si vous êtes sûr que le module est installé et activé (sinon
+ * les fichiers ne pourront être vus ou téléchargés).
+ * Nginx n'est PAS supporté, car X-Accel-Redirect ne peut gérer que des fichiers
+ * qui sont *dans* le document root du vhost, ce qui n'est pas le cas ici.
+ *
+ * Défaut : false
+ */
const ENABLE_XSENDFILE = false;
-// Hôte du serveur SMTP, mettre à false (défaut) pour utiliser la fonction
-// mail() de PHP
+/**
+ * Hôte du serveur SMTP, mettre à false (défaut) pour utiliser la fonction
+ * mail() de PHP
+ *
+ * Défaut : false
+ */
const SMTP_HOST = false;
-// Port du serveur SMTP
-// 25 = port standard pour connexion non chiffrée (465 pour Gmail)
-// 587 = port standard pour connexion SSL
+/**
+ * Port du serveur SMTP
+ *
+ * 25 = port standard pour connexion non chiffrée (465 pour Gmail)
+ * 587 = port standard pour connexion SSL
+ *
+ * Défaut : 587
+ */
const SMTP_PORT = 587;
-// Login utilisateur pour le server SMTP
-// mettre à null pour utiliser un serveur local ou anonyme
-const SMTP_USER = 'garradin@monserveur.com';
-
-// Mot de passe pour le serveur SMTP
-// mettre à null pour utiliser un serveur local ou anonyme
-const SMTP_PASSWORD = 'abcd';
-
-// Sécurité du serveur SMTP
-// NONE = pas de chiffrement
-// SSL = connexion SSL (le plus sécurisé)
-// STARTTLS = utilisation de STARTTLS (moyennement sécurisé)
+/**
+ * Login utilisateur pour le server SMTP
+ *
+ * mettre à null pour utiliser un serveur local ou anonyme
+ *
+ * Défaut : null
+ */
+
+//const SMTP_USER = 'garradin@monserveur.com';
+
+/**
+ * Mot de passe pour le serveur SMTP
+ *
+ * mettre à null pour utiliser un serveur local ou anonyme
+ *
+ * Défaut : null
+ */
+
+//const SMTP_PASSWORD = 'abcd';
+
+/**
+ * Sécurité du serveur SMTP
+ *
+ * NONE = pas de chiffrement
+ * SSL = connexion SSL (le plus sécurisé)
+ * STARTTLS = utilisation de STARTTLS (moyennement sécurisé)
+ *
+ * Défaut : STARTTLS
+ */
const SMTP_SECURITY = 'STARTTLS';
ADDED src/include/data/0.8.0.sql
Index: src/include/data/0.8.0.sql
==================================================================
--- src/include/data/0.8.0.sql
+++ src/include/data/0.8.0.sql
@@ -0,0 +1,56 @@
+-- Ajouter champ pour OTP
+ALTER TABLE membres ADD COLUMN secret_otp TEXT NULL;
+
+-- Ajouter champ clé PGP
+ALTER TABLE membres ADD COLUMN clef_pgp TEXT NULL;
+
+--------------------------------------------------------------------------------
+-- Mise à jour des tables contenant un champ date pour ajouter la contrainte --
+-- Ceci afin de forcer les champs à contenir un format de date correct --
+--------------------------------------------------------------------------------
+
+-- Renommage des tables qu'il faut mettre à jour
+ALTER TABLE cotisations_membres RENAME TO cotisations_membres_old;
+ALTER TABLE rappels_envoyes RENAME TO rappels_envoyes_old;
+ALTER TABLE wiki_pages RENAME TO wiki_pages_old;
+ALTER TABLE wiki_revisions RENAME TO wiki_revisions_old;
+ALTER TABLE compta_exercices RENAME TO compta_exercices_old;
+ALTER TABLE compta_journal RENAME TO compta_journal_old;
+ALTER TABLE compta_rapprochement RENAME TO compta_rapprochement_old;
+ALTER TABLE fichiers RENAME TO fichiers_old;
+
+-- Suppression des index pour que les nouveaux soient liés aux nouvelles tables
+DROP INDEX cm_unique;
+DROP INDEX wiki_uri;
+DROP INDEX wiki_revisions_id_page;
+DROP INDEX wiki_revisions_id_auteur;
+DROP INDEX compta_operations_exercice;
+DROP INDEX compta_operations_date;
+DROP INDEX compta_operations_comptes;
+DROP INDEX compta_operations_auteur;
+DROP INDEX fichiers_date;
+
+CREATE TABLE test (a, b);
+
+-- Création des tables mises à jour (et de leurs index)
+.import schema.sql
+
+-- Copie des données
+INSERT INTO cotisations_membres SELECT * FROM cotisations_membres_old;
+INSERT INTO rappels_envoyes SELECT * FROM rappels_envoyes_old;
+INSERT INTO wiki_pages SELECT * FROM wiki_pages_old;
+INSERT INTO wiki_revisions SELECT * FROM wiki_revisions_old;
+INSERT INTO compta_exercices SELECT * FROM compta_exercices_old;
+INSERT INTO compta_journal SELECT * FROM compta_journal_old;
+INSERT INTO compta_rapprochement SELECT * FROM compta_rapprochement_old;
+INSERT INTO fichiers SELECT * FROM fichiers_old;
+
+-- Suppression des anciennes tables
+DROP TABLE cotisations_membres_old;
+DROP TABLE rappels_envoyes_old;
+DROP TABLE wiki_pages_old;
+DROP TABLE wiki_revisions_old;
+DROP TABLE compta_exercices_old;
+DROP TABLE compta_journal_old;
+DROP TABLE compta_rapprochement_old;
+DROP TABLE fichiers_old;
ADDED src/include/data/dictionary.fr
Index: src/include/data/dictionary.fr
==================================================================
--- src/include/data/dictionary.fr
+++ src/include/data/dictionary.fr
@@ -0,0 +1,3738 @@
+paysage
+lui-même
+erreur
+pénitence
+brûlant
+canon
+prudent
+débarrasser
+séparer
+retomber
+croix
+sauter
+nid
+grand-maman
+satisfait
+défense
+compter
+déboucher
+peintre
+tentation
+devant
+envie
+pleuvoir
+fièvre
+organiser
+persévérer
+ivre
+quart
+pourvu que
+tilleul
+barrière
+police
+pareil
+invention
+fer
+guerre
+cordialement
+campagnard
+infirmier
+crépuscule
+manier
+animal
+transporter
+mobile
+perfection
+marque
+joncher
+nouveau
+désespoir
+joindre
+cordonnier
+ruelle
+risquer
+janvier
+ménage
+couronne
+fusil
+loi
+page
+clou
+armée
+flacon
+blanc
+adresser
+maître
+caverne
+rentrée
+constant
+montre
+surface
+ruban
+cause
+division
+note
+luisant
+poche
+poursuite
+gazon
+ingratitude
+pré
+rose
+activité
+joyeux
+fâcher
+préoccuper
+obligeance
+rivière
+vache
+dessin
+but
+fond
+deux
+ceinture
+lorsque
+cantique
+tâcher
+expirer
+aviser
+gibecière
+lourd
+espiègle
+sommet
+carte
+vendeur
+opérer
+disparaître
+rire
+agréer
+visite
+précédent
+simplement
+dorer
+sagesse
+revivre
+manteau
+croûte
+intelligence
+lion
+chêne
+horreur
+grandeur
+saluer
+mauvais
+sentier
+estomac
+modeste
+nullement
+hâter
+millier
+saigner
+curé
+moineau
+fuite
+flaque
+crêpe
+gant
+s'emparer
+gare
+gentil
+sacrifice
+modérer
+brutal
+feuille
+pouvoir
+dossier
+clef
+terrain
+quarante
+actif
+fou
+réconforter
+gendarme
+ignorant
+grotte
+habiller
+stationner
+implorer
+chapitre
+soupir
+chaise
+rarement
+application
+s'écrier
+ressource
+ministre
+doyen
+jeu
+graisse
+chantre
+écho
+barre
+clouer
+bourgeon
+équipage
+commun
+obliger
+berceau
+outil
+coquille
+procureur
+germer
+séminaire
+partout
+agréablement
+énorme
+radieux
+retrousser
+sorte
+billet
+mademoiselle
+condamner
+aviateur
+tourbillonner
+boucher
+fumer
+décembre
+rang
+étouffer
+sens
+parcours
+tablier
+pleur
+bluet
+cesse
+papier
+bonté
+boucle
+avantageux
+tranche
+coller
+jeune
+drapeau
+gaieté
+chemin
+deviner
+auparavant
+orée
+passage
+souffle
+agir
+s'envoler
+apparition
+hors
+président
+deuil
+berger
+merveilleux
+clin d'oeil
+priver
+oser
+mariage
+forêt
+règne
+commode
+finir
+bosquet
+mouche
+tailleur
+essuyer
+téléphone
+couronner
+château
+tourbillon
+citoyen
+neveu
+paisible
+messager
+ci-joint
+ignorer
+sûrement
+tache
+dompteur
+préférence
+maison
+revêtir
+vicaire
+vendredi
+frapper
+chef
+bâiller
+seigneur
+appuyer
+livre
+complètement
+motif
+adjectif numéral
+train
+ruisselet
+arrêter
+danse
+blottir
+raisin
+raconter
+dahlia
+épauler
+réel
+morceau
+étudier
+amical
+mouchoir
+libre
+printanier
+voiture
+journal
+musée
+village
+charge
+pâquerette
+mari
+ombrage
+attention
+choc
+rapprocher
+prévoir
+bienveillance
+suprême
+sirène
+ceci
+giroflée
+gouverner
+fraîcheur
+circuler
+mélancolie
+exactement
+désireux
+commandement
+annoncer
+cueillette
+duc
+désobéissant
+chauffeur
+dessert
+murmurer
+exclamation
+formidable
+tant
+acclamer
+son
+désaltérer
+soeur
+fourrer
+industrie
+embarquer
+bienfaiteur
+mine
+joyeusement
+fidèle
+grès
+dictionnaire
+nuage
+miroir
+conscience
+équilibre
+tarder
+verdoyant
+parvenir
+boue
+refuser
+manuel
+grand
+réchauffer
+succéder
+bloc
+front
+père
+employé
+moissonneur
+là
+majesté
+réclamer
+auquel
+général
+obscurcir
+commerce
+singulier
+consolation
+passager
+mais
+événement
+administrer
+coucher
+rapidité
+respirer
+quai
+empereur
+farine
+préserver
+glisser
+ver
+sourd
+notaire
+citer
+débris
+habile
+qualité
+inerte
+football
+abri
+miel
+franchement
+essai
+production
+errer
+ah
+idéal
+agile
+tarte
+léger
+raisonnable
+projeter
+poésie
+massif
+pommier
+ensoleillé
+couvent
+sauver
+invitation
+métier
+poussière
+école
+fuir
+pleurer
+imaginer
+sautiller
+édifier
+multitude
+commerçant
+plat
+autel
+toujours
+craindre
+tuer
+cuisinière
+ingrat
+percher
+concert
+doubler
+trésor
+cimetière
+exercice
+savon
+éblouir
+éclat
+parc
+peau
+compléter
+escalier
+sous
+matinée
+recherche
+régner
+épreuve
+autrefois
+fauteuil
+apprendre
+pétale
+sujet
+lequel
+bien-être
+chauffage
+nerveux
+décision
+participer
+gronder
+aussi
+tombe
+éternité
+rejeter
+enveloppe
+habitude
+peut-être
+accabler
+confrère
+prêtre
+parfaitement
+soigneusement
+compliment
+d'après
+chéri
+oiseau
+coureur
+mentir
+faucheur
+héros
+arrêt
+graver
+intrigué
+plaine
+combler
+planche
+recouvrir
+imprudence
+brebis
+poule
+spectacle
+semblable
+anticiper
+mener
+hommage
+déshabiller
+moins
+volume
+poulet
+main
+imposant
+fendre
+paille
+résistance
+écrivain
+franchise
+casquette
+mien
+heure
+an
+oranger
+coucou
+plomb
+télégramme
+amende
+part
+combat
+applaudir
+rédaction
+exécution
+largement
+unique
+jardinier
+belge
+réserver
+colère
+lueur
+moyen
+coiffer
+guérir
+échec
+minute
+conseiller
+fontaine
+scier
+fabrique
+oisillon
+amicalement
+oreille
+laisser
+gosse
+tard
+autoriser
+emmener
+anxiété
+patron
+abbé
+désobéissance
+bazar
+destinée
+plume
+terrasse
+potager
+dessus
+régulièrement
+distinguer
+roseau
+malade
+éteindre
+pendule
+imperméable
+fougère
+pelage
+écriture
+journalier
+comprendre
+manoeuvre
+soudain
+dessous
+parmi
+aire
+bosselé
+goutte
+bateau
+veiller
+économiser
+intelligent
+ferveur
+avertir
+repousser
+ville
+convenable
+farce
+cultiver
+provenir
+acharner
+pic
+avril
+frontière
+patte
+merci
+prince
+précieux
+espoir
+allumette
+poumon
+bleu
+inutile
+proposer
+être
+attester
+couvert
+trou
+démontrer
+effroyable
+localité
+reverdir
+trop
+user
+ours
+locomotive
+ailleurs
+fournir
+piste
+béret
+profondément
+respecter
+lecture
+congrès
+ennuyer
+lendemain
+indigne
+eux
+détail
+compagne
+vérité
+capital
+bavarder
+grue
+sueur
+urgent
+habituel
+fragile
+ôter
+briller
+border
+souper
+marteau
+agent
+moisson
+mou
+repentir
+disperser
+commande
+situation
+atteindre
+musique
+voyage
+remettre
+écharpe
+fruit
+éclaircir
+particulier
+ardent
+foi
+bercer
+papa
+marbre
+guérison
+alcoolique
+apaiser
+marguerite
+hôpital
+séance
+bousculer
+aube
+cordial
+quelque
+chaque
+difficilement
+union
+inquiétude
+filleul
+ami
+explication
+toux
+limpide
+jeter
+réellement
+inquiéter
+activer
+capable
+résister
+reconnaissance
+troupe
+aussitôt
+niveau
+jeunesse
+meunier
+signature
+consulter
+bout
+périr
+amateur
+livrer
+rapide
+engloutir
+usage
+nombreux
+sagement
+tapis
+grossier
+bourgmestre
+demoiselle
+prodiguer
+limite
+clochette
+fortune
+échanger
+inquiet
+parapluie
+quelconque
+propos
+enquête
+réduire
+chance
+énergique
+source
+dent
+voilà
+féroce
+tasse
+envers
+capitaine
+contrée
+obstacle
+île
+plier
+moderne
+allemand
+muet
+introduction
+annuel
+habitation
+jambe
+mouton
+résoudre
+ferrer
+éclater
+sein
+naufrage
+galerie
+foule
+soulier
+voûte
+toutefois
+fourrure
+marquis
+punir
+féliciter
+discussion
+défiler
+dédaigner
+tailler
+tomber
+clé
+côté
+broder
+encore
+jambon
+poulailler
+habiter
+chauffer
+normal
+aventurer
+professeur
+approuver
+peur
+automobile
+conduire
+ardeur
+vôtre
+centime
+emporter
+fois
+moquer
+éducation
+fourmillière
+pieux
+copier
+étinceler
+regagner
+trait
+esclave
+géant
+attrait
+allonger
+rude
+santé
+bébé
+décharger
+obscur
+commercial
+battre
+partir
+parure
+mesure
+encombrer
+reflet
+reproche
+recueillir
+cathédrale
+illustrer
+chiffre
+timbre
+courir
+climat
+genre
+daigner
+tente
+gauche
+imagination
+chérir
+guichet
+bulletin
+classique
+poursuivre
+renseigner
+boisson
+ranimer
+sabot
+chute
+brillant
+volaille
+grandiose
+orgueil
+brun
+plaintif
+moqueur
+bruit
+jadis
+achat
+baigner
+accourir
+inscrire
+plancher
+religieux
+joue
+avec
+accident
+figurer
+surtout
+buis
+courageusement
+moine
+employer
+baguette
+foudre
+signaler
+succès
+parfumer
+découvrir
+bête
+sien
+corniche
+renoncer
+relever
+mérite
+résonner
+type
+tournée
+préparation
+sillon
+étable
+relation
+chasseur
+cas
+description
+porc
+pourrir
+blesser
+favori
+puisque
+laid
+calculer
+veine
+chameau
+indifférent
+dégager
+crucifix
+faine
+renouveler
+principe
+crayon
+revenir
+frémir
+élégant
+secret
+parce que
+opinion
+mélanger
+colonel
+utiliser
+soin
+cinquante
+sonore
+écouler
+tel
+colline
+seulement
+futur
+expédier
+tuile
+chapeau
+mouiller
+fureur
+grâce
+consentement
+fervent
+paletot
+vêtir
+manger
+étude
+ménager
+proie
+héroïque
+pli
+canal
+lin
+effectuer
+condoléances
+savoir
+instructif
+azuré
+cristal
+plus
+chemise
+jouer
+immobile
+achever
+quatrième
+serviteur
+spécial
+science
+rechercher
+alentours
+mont
+soyeux
+cruel
+service
+jugement
+négligence
+oie
+mortel
+voyager
+fourneau
+gâteau
+début
+ciseaux
+carrefour
+mobilier
+gambader
+arroser
+imiter
+redevenir
+rapporter
+gaz
+inviter
+fort
+hardi
+passé
+riche
+oeuvre
+souterrain
+créature
+tendre
+souvent
+ralentir
+agrément
+entasser
+faute
+scintiller
+peiner
+froid
+traiter
+gorge
+remarquable
+veston
+princesse
+peinture
+basse
+provision
+poteau
+guider
+plusieurs
+série
+mûr
+affectueux
+refuge
+salaire
+innocent
+véhicule
+style
+consacrer
+gardien
+retrouver
+coupable
+charmer
+créateur
+renard
+panorama
+couteau
+rêver
+confectionner
+appartement
+traîner
+astre
+appétit
+invisible
+détester
+raide
+réponse
+ceux
+creuser
+carré
+aujourd'hui
+ficelle
+trottoir
+moulin
+glacer
+vêtement
+hâte
+appeler
+mission
+renouvellement
+dernier
+missionnaire
+fille
+gratitude
+juillet
+vieil
+déchaîner
+commettre
+attraper
+parsemer
+quantité
+gazouiller
+blancheur
+foie
+satin
+serein
+salut
+connaître
+novembre
+avenue
+curiosité
+porte-plume
+merveille
+chaland
+article
+geler
+mansarde
+alcool
+monstre
+chanter
+océan
+ressort
+sifflet
+renoncule
+entrée
+balle
+monter
+effort
+introduire
+juin
+vaillant
+valoir
+voisin
+eh
+dresser
+dortoir
+sobre
+médecin
+répéter
+problème
+négliger
+bicyclette
+couche
+facile
+visiteur
+aise
+assiette
+aurore
+sincère
+magnifique
+estimer
+durée
+accorder
+reculer
+café
+fameux
+serviette
+ainsi
+voix
+protéger
+circulation
+agréable
+communier
+chrysanthème
+animation
+végétation
+justement
+cochon
+courant
+légume
+nation
+table
+jardin
+extérieur
+attentif
+important
+noeud
+décrire
+vivant
+merle
+vermeil
+forger
+comparer
+redoubler
+forcer
+incliner
+bien-aimé
+préau
+ériger
+gras
+dentelle
+rayon
+régime
+foncer
+avant
+célébrer
+générosité
+perle
+envoi
+sonnette
+suffire
+crever
+réfectoire
+indication
+paternel
+ronronner
+péril
+second
+grappe
+courageux
+lierre
+cuire
+cousin
+souhaiter
+trembler
+pas
+patronage
+mur
+mâchoire
+paresse
+garde
+ronce
+courber
+furieux
+dedans
+brouillard
+expression
+promener
+intention
+bille
+perte
+déception
+situer
+bonne
+désormais
+azur
+roulotte
+public
+gerbe
+crème
+isoler
+séjour
+repas
+donc
+madame
+vernir
+corridor
+périlleux
+fauvette
+encourir
+position
+fin
+vide
+jusque
+agrémenter
+autorité
+pauvre
+plage
+enfouir
+marché
+vraiment
+signifier
+portière
+griffe
+minuscule
+plonger
+messe
+impossibilité
+ouvrage
+paix
+ambulance
+petit
+vacances
+corriger
+engager
+humble
+longuement
+pratiquer
+jaunir
+salutation
+dérober
+bouche
+nécessaire
+causer
+dessein
+rouler
+huit
+villa
+façonner
+malgré
+sang
+alors
+silencieusement
+nettoyer
+dévouement
+conseil
+tunnel
+parent
+fauve
+cabane
+distraction
+opération
+fruitier
+poupée
+crier
+tapage
+soir
+sot
+sentiment
+prolonger
+grimper
+pur
+produit
+satisfaction
+rustique
+chambre
+excuse
+rouge
+protection
+désolation
+étendre
+crise
+pourtant
+communiant
+enseignement
+ample
+physique
+négligent
+lanterne
+recommencer
+conserver
+chaux
+menteur
+suffisant
+vivre
+abondant
+étagère
+douter
+glace
+liberté
+docile
+pensionnat
+debout
+abattre
+clairon
+noircir
+victoire
+épuiser
+tiroir
+chevalier
+considérer
+cuiller
+réunion
+reste
+depuis
+cortège
+heureusement
+panache
+poignée
+brin
+peser
+lettre
+naissance
+réunir
+lâcher
+venir
+rassurer
+porte
+resplendir
+taper
+associer
+doute
+promettre
+sourire
+surmonter
+bienveillant
+chaleur
+nièce
+flatter
+régaler
+moelleux
+surprise
+écurie
+danger
+distinction
+offrir
+travail
+exposer
+colis
+flot
+quotidien
+permettre
+ennemi
+invoquer
+voler
+artiste
+attrister
+linge
+tombeau
+répondre
+donner
+transmettre
+août
+seul
+planter
+acquitter
+lire
+horizon
+splendeur
+amour
+kilomètre
+jardinage
+ornement
+charme
+vers
+mineur
+tempête
+circonstance
+maint
+caresse
+oeillet
+dimension
+gâter
+immaculé
+veille
+baisser
+peuplier
+rouleau
+lilas
+lumineux
+laborieux
+gland
+écrire
+flocon
+parole
+cuillère
+favoriser
+composition
+gravure
+nature
+tantôt
+dominer
+boire
+fouet
+spécialement
+coquelicot
+fourniture
+lampe
+grange
+pétrir
+port
+retraite
+douloureux
+amusement
+bibelot
+enlever
+clément
+sabre
+lutter
+pluie
+musicien
+pie
+phrase
+aventure
+perspective
+solide
+vertu
+refaire
+apostolique
+orgue
+quinze
+réciter
+bordure
+débarquer
+cycliste
+randonnée
+renvoyer
+épargner
+volonté
+savoureux
+tous
+essayer
+aliment
+haleine
+côte
+humide
+obtenir
+complet
+onduler
+ressentir
+crime
+pigeon
+vapeur
+chaume
+cirque
+chiffon
+ferraille
+ramasser
+s'éloigner
+cent
+manche
+planer
+affreux
+jurer
+trimestre
+triomphe
+pénible
+grelotter
+rigole
+commander
+épine
+suivre
+gourmand
+confier
+hurler
+joujou
+dîner
+dévorer
+plafond
+parler
+enfermer
+avion
+bienheureux
+titre
+lien
+peine
+aucun
+riant
+dont
+admirable
+triompher
+conférence
+tiède
+mourir
+diriger
+terminer
+prononcer
+acier
+splendide
+image
+histoire
+céleste
+brigand
+résultat
+dos
+foin
+rayonner
+venger
+ensuite
+patience
+ébats
+allégresse
+rideau
+malin
+flatteur
+luire
+robuste
+ardoise
+sacoche
+menton
+bassin
+mai
+doux
+date
+bonbon
+dévouer
+enflammer
+goût
+instituteur
+habit
+lier
+sympathie
+naître
+marraine
+animer
+pourquoi
+chausser
+communication
+mouvement
+sève
+aubépine
+vif
+clos
+bal
+hasard
+trouble
+ménagère
+scolaire
+marche
+prouver
+comble
+couver
+théâtre
+affliger
+écouter
+contrarier
+lieu
+étoile
+frotter
+four
+caprice
+fixe
+puissant
+ménagerie
+croître
+culotte
+tort
+pelouse
+maudire
+confus
+occuper
+tournant
+angoisse
+fièrement
+exécuter
+commission
+servir
+poil
+catastrophe
+selon
+bord
+chose
+vaisselle
+rage
+louer
+étirer
+énormément
+entre
+juge
+poutre
+blanchir
+fréquent
+las
+mériter
+inconvénient
+fréquenter
+bourgeonner
+demi
+voie
+centre
+chevelure
+raccourcir
+affiche
+découper
+succulent
+impression
+architecte
+blouse
+procurer
+faner
+utilité
+vigne
+bêche
+vain
+reprise
+chair
+secouer
+anxieux
+recommander
+onze
+velours
+moindre
+sillonner
+cité
+évangile
+duvet
+quatre
+farouche
+noyer
+si
+caillou
+manoeuvrer
+négociant
+chou
+royaume
+attirer
+missel
+rougir
+limiter
+flanc
+dommage
+dehors
+difficile
+litière
+intime
+penser
+huile
+savant
+souhait
+piété
+calmer
+faiblesse
+bénir
+préparatif
+pinson
+goûter
+membre
+écarter
+défendre
+wagon
+gamin
+pain
+insigne
+valeur
+recommandation
+butiner
+hanneton
+redoutable
+consoler
+appartenir
+soupe
+mettre
+hausser
+chocolat
+aligner
+gracieux
+haillon
+niche
+bourdonner
+peindre
+briser
+quoi
+attaquer
+nuit
+paraître
+lisière
+cesser
+recevoir
+sport
+maire
+marin
+violence
+usine
+survenir
+meule
+proprement
+saint
+possession
+cinq
+dessiner
+quant
+falloir
+politesse
+trente
+fier
+épaule
+embaumer
+dès
+abaisser
+pécher
+noir
+accrocher
+tas
+vie
+interdire
+progrès
+sacrement
+rusé
+orgueilleux
+courrier
+déjà
+au-dessus
+guetter
+pâte
+vagabond
+plante
+rôle
+avoine
+voleur
+craquement
+taquiner
+affectueusement
+portée
+grain
+porteur
+buisson
+toile
+tressaillir
+intellectuel
+mordre
+bière
+dette
+apprêter
+camion
+net
+bravo
+groupe
+espace
+aimable
+épée
+dur
+menuisier
+précaution
+volée
+orphelin
+accepter
+estrade
+bambin
+célèbre
+bétail
+déborder
+univers
+tremper
+oeuf
+toit
+bec
+coiffure
+hauteur
+filer
+bouleverser
+réserve
+talus
+expédition
+fatiguer
+annonce
+acte
+vendre
+samedi
+appliquer
+conviction
+ranger
+pan
+camarade
+terrestre
+quelquefois
+poli
+voiler
+dépendre
+directeur
+unir
+suspendre
+paradis
+dormir
+décorer
+cadet
+plan
+saut
+national
+gémir
+gloire
+terme
+indiquer
+mélodie
+exactitude
+tacher
+douzaine
+répartir
+propice
+distance
+région
+mendier
+parterre
+flèche
+idée
+bref
+confondre
+couler
+verre
+oncle
+étalage
+familier
+fumée
+désirer
+boutique
+boîte
+industriel
+corde
+veuf
+refléter
+gaiement
+arrondissement
+refermer
+lèvre
+banque
+tableau
+s'écrouler
+instant
+pardon
+turbulent
+somme
+chrétien
+rompre
+vol
+concours
+enfin
+renaître
+loup
+envelopper
+commune
+bondir
+barbe
+paître
+outre
+corbeille
+exposition
+fleurir
+pension
+pays
+brusquement
+âne
+vue
+soulever
+recourir
+coussin
+avantage
+balancer
+cigarette
+nouvelle
+charité
+pitié
+suffisamment
+secourir
+cela
+long
+longer
+trouver
+doucement
+passant
+demander
+réalité
+demeure
+queue
+procession
+fondre
+aisément
+bonheur
+respect
+changement
+aiguille
+vaste
+centaine
+transformer
+prospérité
+sacrifier
+prochain
+geste
+lointain
+flamand
+tenter
+commencement
+là-bas
+diamant
+prier
+propriété
+hirondelle
+nécéssité
+continuellement
+fatigue
+rive
+travailleur
+kermesse
+quelqu'un
+solitude
+sursauter
+salir
+évidemment
+vieillard
+cadeau
+office
+acquérir
+péniblement
+environner
+grille
+grammaire
+végétal
+pipe
+fête
+semaine
+profondeur
+délicat
+détacher
+retour
+souffrir
+supporter
+gouvernement
+barque
+lambeau
+seuil
+étranger
+froisser
+tourment
+d'abord
+personnel
+prudence
+remède
+intéresser
+étudiant
+manque
+jacinthe
+villageois
+renfermer
+égarer
+herbe
+poire
+armoire
+présent
+prétendre
+joli
+signer
+plaindre
+offre
+sucer
+ressembler
+maladie
+tandis que
+caresser
+couleur
+électricité
+plaisir
+bras
+tonneau
+bruyant
+proclamer
+couture
+bienvenue
+cage
+calvaire
+connaissance
+tenir
+propre
+confesser
+degré
+maintenant
+droit
+lancer
+gelée
+reconnaissant
+ancien
+colonne
+nord
+maussade
+talent
+contempler
+fermer
+vélo
+ni
+garantir
+résigner
+brut
+blond
+reporter
+vite
+aisance
+gêner
+blé
+forge
+nourrir
+barquette
+abord
+teinte
+pardessus
+ravir
+emploi
+étage
+sauf
+frêle
+prêt
+lièvre
+créer
+pâture
+extrême
+victime
+tendresse
+rue
+inconnu
+possible
+croquer
+encre
+anglais
+chasser
+rester
+charbonnage
+sinistre
+carnet
+effrayer
+myosotis
+fouetter
+expliquer
+écorce
+ravage
+sublime
+revue
+entretien
+géographie
+boucler
+gravement
+quel
+or
+lis
+écolier
+dégât
+taire
+insister
+onde
+supplier
+chariot
+mécanique
+baiser
+vouloir
+fossé
+mois
+porter
+exercer
+puis
+poulain
+illusion
+sécurité
+marier
+gîte
+tapisser
+domestique
+amer
+étincelant
+garnir
+providence
+espérer
+cartable
+fonds
+alouette
+ébranler
+estime
+soleil
+valise
+entourer
+insecte
+armer
+sortir
+jouir
+éclore
+mécontent
+loyal
+primaire
+contenu
+généralement
+persuader
+infini
+anniversaire
+fenêtre
+action
+forgeron
+agiter
+fortement
+réveil
+accomplir
+disposition
+ordinairement
+embellir
+mesurer
+arme
+souci
+graine
+soirée
+robe
+proverbe
+manifester
+pantalon
+dictée
+bouleau
+illuminer
+fêter
+relatif
+certes
+élever
+mort
+natal
+drôle
+point
+modèle
+exister
+voeu
+beauté
+admirer
+redresser
+par
+cours
+varier
+envahir
+content
+retenir
+amuser
+s'efforcer
+obéir
+pondre
+logis
+avouer
+museau
+parti
+grandir
+promeneur
+enfance
+autour
+flamme
+durer
+adversaire
+préférer
+retirer
+informer
+mardi
+terreur
+étonner
+aimer
+tricot
+entrer
+consentir
+carrière
+aérer
+réaliser
+régiment
+renverser
+foire
+immédiatement
+chien
+accompagner
+traitement
+inondation
+combien
+épargne
+détruire
+faible
+champ
+aigu
+arranger
+monument
+baptême
+punition
+abandonner
+rez-de-chaussée
+troupeau
+sale
+rien
+afin
+famille
+agitation
+tabac
+coupe
+fillette
+sud
+carton
+file
+habituer
+triste
+catholique
+sévèrement
+permission
+match
+retentir
+fabriquer
+communal
+défunt
+rare
+remporter
+jour
+période
+sec
+labeur
+lenteur
+débattre
+montant
+bouger
+joie
+sac
+demeurer
+muraille
+sage
+facilement
+bas
+abîme
+attentivement
+tuyau
+munir
+tricoter
+raison
+nègre
+morne
+accueil
+exquis
+lisse
+apporter
+bise
+emballer
+examiner
+américain
+réformer
+admettre
+servante
+droite
+occasion
+église
+éléphant
+garniture
+établir
+récolter
+hésiter
+avance
+compassion
+égard
+sensible
+emplacement
+montrer
+docteur
+retourner
+comme
+acheter
+étincelle
+pont
+zèle
+déterminer
+continuer
+vieillesse
+attribuer
+enfoncer
+partager
+course
+rond
+trancher
+tourmenter
+ravissant
+migrateur
+odorant
+s'empresser
+malheureux
+dimanche
+barreau
+cependant
+drap
+haine
+importer
+attachement
+sacré
+croire
+discuter
+plumier
+bouton
+araignée
+romain
+groseillier
+diminuer
+convertir
+saisir
+interroger
+garder
+atelier
+respiration
+chaudement
+distribution
+collège
+société
+tromper
+roue
+réfugier
+patin
+remuer
+mignon
+corbeau
+statue
+perdrix
+croiser
+cygne
+exciter
+peu
+nager
+remords
+découverte
+demande
+paquet
+perche
+meuble
+pis
+ferme
+froment
+symbole
+tellement
+examen
+sable
+art
+rattraper
+charbon
+gonfler
+monde
+correspondance
+soumettre
+entendre
+cerise
+entraîner
+misérable
+admiration
+imprimer
+établissement
+brumeux
+bureau
+crèche
+tirelire
+infirme
+fils
+sinon
+mille
+oui
+charrette
+troisième
+viser
+arrière
+empressement
+péché
+acheminer
+grêle
+coton
+extraire
+maintenir
+chacun
+placer
+avancer
+soutenir
+preuve
+réjouir
+provoquer
+couvercle
+tulipe
+étourdi
+postal
+dépenser
+dater
+produire
+percer
+reprocher
+émouvoir
+cache-cache
+lait
+mémoire
+bonsoir
+étaler
+volontiers
+ouate
+tigre
+naturellement
+davantage
+richesse
+avaler
+brèche
+serrer
+conformément
+paisiblement
+marchandise
+vigueur
+caisse
+darder
+principalement
+racine
+cueillir
+bouquet
+ruine
+baptiser
+épouvantable
+cadran
+arriver
+aboyer
+rôder
+reconnaître
+chaussure
+apôtre
+attendre
+incident
+violent
+fromage
+muscle
+lutte
+pratique
+ange
+propreté
+studieux
+malle
+bossu
+femme
+repos
+reconduire
+spectateur
+accord
+observation
+grouper
+ruisseler
+figure
+charmant
+vitesse
+époux
+familial
+âme
+honorer
+enterrement
+monnaie
+éclabousser
+sapin
+désespérer
+juger
+opposer
+disputer
+menu
+arbuste
+lot
+bourrasque
+supérieur
+puissance
+cuivre
+payer
+papillon
+échantillon
+pièce
+composer
+incendie
+parcourir
+patrie
+calcul
+apprécier
+timide
+orage
+labourer
+appel
+vierge
+chasse
+recours
+embrasser
+salle
+cadre
+voile
+million
+moitié
+feuillage
+haut
+puits
+augmenter
+juste
+derrière
+blessure
+midi
+calendrier
+clair
+hiver
+fleur
+magique
+touffu
+violette
+libérer
+caractère
+choix
+plumage
+remonter
+étonnement
+impatiemment
+fixer
+proposition
+banc
+trois
+irriter
+coffre
+regret
+brise
+divin
+endormir
+précéder
+allée
+noisette
+tourner
+carotte
+mère
+boulevard
+faire
+règle
+témoin
+rame
+affectionner
+sans
+adresse
+chant
+cou
+hermine
+tordre
+chevet
+panier
+fleuve
+argent
+silence
+manquer
+bourgeois
+siège
+replier
+masse
+cime
+dépens
+excuser
+actuel
+mal
+apercevoir
+tribunal
+renouveau
+domicile
+abeille
+cher
+dépêcher
+malheur
+condition
+riz
+passion
+sermon
+pointe
+arrivée
+impatient
+sérieusement
+brave
+soif
+joueur
+muguet
+carrousel
+accueillir
+continuel
+contraire
+dicter
+vanter
+régulier
+sain
+encrier
+vallée
+canne
+tendrement
+imprudent
+décéder
+précipiter
+boule
+portefeuille
+réparer
+universel
+pâtisserie
+démarche
+neiger
+souriant
+branche
+narcisse
+étrange
+eau
+clown
+parrain
+jeudi
+février
+moudre
+fil
+affaire
+salon
+sauvage
+rôti
+ouverture
+poudre
+repasser
+perroquet
+mare
+rameau
+extrémité
+disparition
+réception
+coudre
+pardonner
+central
+observer
+dépouiller
+celle-ci
+absolument
+amitié
+inférieur
+tâche
+profit
+illustre
+honorable
+assembler
+sergent
+faciliter
+pupitre
+politique
+changer
+basse-cour
+spacieux
+officier
+délaisser
+exemple
+trouer
+accuser
+descente
+beau
+chaîne
+saison
+vérifier
+deuxième
+femelle
+abîmer
+tristement
+portrait
+entrouvrir
+défenseur
+redouter
+représenter
+parages
+endosser
+excellent
+bénédiction
+combattre
+récréation
+coup
+horrible
+ramage
+constater
+képi
+paroisse
+tranquille
+décider
+rencontre
+broyer
+maigre
+paire
+ombre
+matière
+obscurité
+scène
+envier
+chanson
+signal
+exprimer
+monotone
+s'absenter
+septembre
+utile
+enrichir
+matin
+catéchisme
+aumône
+marron
+taille
+canot
+projet
+parfum
+congé
+imposer
+départ
+mars
+vitre
+sucre
+semer
+coin
+tartine
+sommeil
+infiniment
+raccommoder
+hypocrite
+intérêt
+tournoyer
+prendre
+adoucir
+averse
+monseigneur
+pompier
+givre
+méchant
+stupéfaction
+aboutir
+genêt
+orange
+malette
+hangar
+ordonner
+crainte
+fiancé
+épi
+clarté
+meilleur
+prêcher
+brochure
+gagner
+calme
+sur
+hier
+dangereux
+pois
+défaire
+convenir
+indispensable
+vainqueur
+fabrication
+prière
+prisonnier
+aîné
+gigantesque
+vente
+s'évanouir
+envoyer
+charger
+pinceau
+pousser
+retard
+autant
+distrait
+arrondir
+simplicité
+pâle
+assez
+céder
+difficulté
+généreux
+perdre
+mousse
+franc
+redescendre
+siècle
+contribuer
+excursion
+marchander
+hygiène
+délivrer
+esprit
+dix
+objet
+néanmoins
+besoin
+diable
+verser
+éprouver
+marcher
+nul
+transport
+vrai
+dame
+exaucer
+présence
+quatorze
+étroit
+épanouir
+commandant
+force
+adroit
+rouiller
+réveiller
+conduite
+mélancolique
+communion
+sursaut
+borne
+conquérir
+clocher
+étoffe
+heurter
+confiture
+épais
+gris
+honteux
+quartier
+dépasser
+ronde
+distribuer
+facteur
+asseoir
+rencontrer
+méthode
+absence
+rappeler
+constituer
+sort
+enfant
+grossir
+mât
+seau
+offenser
+fatal
+honneur
+hérissé
+prêter
+différence
+soulager
+traverser
+pourvoir
+seconde
+adorer
+sévir
+odeur
+colorer
+malheureusement
+charitable
+nu
+mouvoir
+agacer
+loisir
+supposer
+oh
+tricolore
+appui
+approcher
+institut
+bûcheron
+laver
+religion
+respectueux
+avoir
+chercher
+flamber
+savourer
+tortue
+déclarer
+éclair
+honnête
+temps
+danser
+brusque
+même
+particulièrement
+humeur
+durant
+plupart
+réussir
+barrage
+maman
+cacher
+cheval
+absolu
+modestie
+nuisible
+pencher
+tour
+peupler
+plateau
+grippe
+quitter
+lever
+régler
+coffret
+celui-ci
+obéissant
+délice
+sortie
+vivement
+vin
+osier
+enthousiasme
+firmament
+déplacer
+prairie
+enterrer
+costume
+cave
+acide
+trajet
+manière
+discours
+dizaine
+bandit
+empêcher
+poêle
+profession
+mélodieux
+relativement
+cheminée
+soupirer
+convaincre
+faucher
+adopter
+peuple
+été
+méditer
+pénétrer
+passer
+rassembler
+trace
+façade
+façon
+attaque
+apparence
+solitaire
+voici
+échelle
+époque
+immense
+fâcheux
+ici
+principal
+étang
+bonnet
+kilogramme
+français
+mendiant
+aborder
+automne
+lunette
+marchand
+doigt
+air
+conséquence
+acheteur
+haie
+bâton
+probablement
+digne
+bleuet
+sécher
+enseigner
+mêler
+tige
+interruption
+coûter
+noix
+poirier
+os
+redire
+âgé
+vilain
+contact
+pourpre
+douceur
+lac
+ruiner
+aisé
+contenir
+lundi
+langage
+espérance
+direction
+température
+brique
+sûr
+cinéma
+raser
+sel
+primevère
+douze
+charlatan
+remarquer
+entreprendre
+parfait
+mer
+regarder
+bientôt
+piquer
+dérouler
+économie
+balayer
+autrui
+homme
+évêque
+sitôt
+échapper
+minuit
+vingt
+orner
+défaut
+colonial
+couvrir
+cuir
+chaud
+canard
+rafraîchir
+couper
+harmonieux
+poids
+bourse
+purifier
+différent
+amusant
+très
+marronnier
+pensionnaire
+bienfaisant
+médaille
+navire
+comte
+oeil
+interrompre
+miette
+charrue
+mauve
+violet
+guêpe
+endroit
+souverain
+former
+administration
+aide
+bizarre
+désigner
+tonnerre
+hache
+aspirer
+bougie
+rêve
+franchir
+bain
+regard
+journée
+cerisier
+pendre
+touffe
+attente
+ténèbres
+prodigieux
+songer
+forme
+révéler
+verdure
+monsieur
+intérieur
+cri
+plein
+appétissant
+soldat
+sévère
+écluse
+local
+beaucoup
+genou
+coq
+saule
+race
+géranium
+roi
+souiller
+pomme
+privation
+botte
+large
+bâtir
+rejoindre
+thé
+question
+devoir
+terre
+client
+noble
+sombre
+installer
+valet
+habileté
+presser
+risque
+lit
+mirer
+remerciement
+rocher
+élargir
+désastre
+impossible
+misère
+moment
+coutume
+loin
+bon
+frayeur
+cloche
+pâlir
+cadavre
+honte
+carreau
+jaillir
+souvenir
+vulgaire
+hôtel
+dispenser
+inspecteur
+prévenir
+élève
+mieux
+treize
+profond
+suc
+parer
+pâtre
+déployer
+laine
+condisciple
+lys
+influence
+déchirer
+après-midi
+inonder
+occasionner
+surveiller
+déranger
+disposer
+transformation
+verdâtre
+giboulée
+piano
+face
+allure
+écrit
+ton
+matinal
+remise
+vent
+paresseux
+flotter
+grave
+creux
+autrement
+plainte
+ruche
+hêtre
+louange
+alerte
+récompense
+intervenir
+rossignol
+visage
+six
+coude
+visible
+parquet
+mètre
+perpétuel
+pavé
+solliciter
+forestier
+exhaler
+don
+directement
+équipe
+choeur
+témoigner
+faveur
+différer
+friandise
+ennuyeux
+mince
+bois
+gaufre
+tôt
+bond
+tronc
+année
+tenue
+emplir
+rosier
+moustache
+bonjour
+pied
+humanité
+injure
+roux
+gravir
+descendre
+domaine
+pêcher
+souple
+voisinage
+ça
+faim
+reposer
+premier
+élancer
+argenter
+liste
+égal
+propriétaire
+subitement
+salade
+sincèrement
+serviable
+avenir
+concerner
+arbre
+déménager
+correction
+car
+cabine
+griffer
+poing
+aiguiser
+corps
+ligue
+nommer
+sol
+destination
+lutin
+métal
+assidu
+voyageur
+attacher
+successivement
+transparent
+abondamment
+cercle
+bijou
+sifflement
+encourager
+souffler
+machine
+atmosphère
+parfois
+royal
+ramener
+canif
+nez
+détourner
+boeuf
+soigner
+chèvre
+dans
+auteur
+décourager
+aveugle
+fréquemment
+soigneux
+douleur
+langue
+sentir
+assurer
+tracer
+mystérieux
+bâtiment
+lent
+certain
+nombre
+flûte
+taillis
+désagréable
+bourdonnement
+mensonge
+cérémonie
+larme
+constamment
+importance
+allumer
+fée
+bouder
+poste
+chat
+récompenser
+ciel
+ivoire
+amener
+vénérer
+quoique
+autre
+cultivateur
+ventre
+filet
+s'enfuir
+faux
+halte
+embarras
+brume
+vigoureux
+escalader
+dissiper
+miracle
+brouter
+gratter
+exact
+légèrement
+instruction
+photographie
+exprès
+classe
+cour
+compte
+longueur
+laboureur
+protecteur
+fonction
+sou
+s'agenouiller
+favorable
+dire
+loger
+toilette
+balançoire
+résolution
+jonquille
+dénudé
+nom
+satisfaire
+plutôt
+brûler
+court
+pour
+corolle
+hameau
+subir
+majestueux
+chagrin
+également
+habitant
+cirer
+jaune
+merveilleusement
+émerveiller
+campagne
+réflexion
+relire
+affection
+enchanté
+distraire
+boulangerie
+considérable
+mystère
+ordinaire
+soulagement
+coeur
+lumière
+glissant
+interpeller
+neige
+silencieux
+refrain
+ouvrir
+récolte
+lune
+éclatant
+jaloux
+besogne
+tête
+surprendre
+rapidement
+vague
+extraordinaire
+personnage
+foyer
+bouteille
+grive
+tante
+remarque
+tout
+matériel
+espèce
+finalement
+garçon
+betterave
+débiter
+voir
+rhume
+gymnastique
+multicolore
+serrure
+bien
+craquer
+suivant
+démolir
+guère
+entièrement
+poitrine
+conter
+bruyamment
+écraser
+ballon
+effacer
+délicieux
+courage
+retardataire
+contenter
+ennui
+rosée
+gibier
+nappe
+carabine
+déjeuner
+centimètre
+tram
+chef-d'oeuvre
+photographier
+sept
+sembler
+toucher
+exiger
+lasser
+présenter
+surgir
+suave
+grincer
+cuisine
+contre
+fermier
+calice
+verger
+arracher
+quand
+fleurette
+approche
+remplacer
+souris
+pendant
+non
+place
+civil
+hélas
+destiner
+bibliothèque
+éternel
+reprendre
+consister
+élan
+imprévu
+encadrer
+lors
+mot
+liquide
+désert
+gazouillement
+personne
+attarder
+boueux
+fouiller
+entonner
+devenir
+double
+abuser
+simple
+frissonner
+désordre
+voltiger
+communiquer
+instruire
+chanteur
+fourmi
+banquier
+bonhomme
+fait
+rendez-vous
+angle
+écureuil
+remercier
+pin
+renseignement
+pointu
+dernièrement
+cheveu
+rat
+conte
+électrique
+dépôt
+octobre
+promesse
+boulanger
+ensemble
+émotion
+rendre
+ordre
+poète
+couloir
+plaie
+actuellement
+tramway
+fraise
+arbitre
+aller
+conclure
+auprès
+auberge
+abriter
+travailler
+volet
+bataille
+frère
+occupation
+sérieux
+assaut
+rater
+intéressant
+instrument
+intense
+chapelet
+divers
+frais
+couverture
+neuf
+lui
+mélange
+soie
+plaire
+grenier
+désoler
+soutien
+paysan
+vaincre
+tambour
+visiter
+compliquer
+prime
+maîtresse
+sanglot
+ravin
+printemps
+certainement
+éveiller
+remplir
+état
+impatience
+déplaire
+environ
+confiance
+banane
+seize
+moyenne
+vider
+velouté
+pensée
+refroidir
+brindille
+morale
+profiter
+pot
+assister
+téléphoner
+poser
+affairé
+claquer
+entier
+dieu
+suite
+quinzaine
+plaque
+fléau
+solennel
+vitrine
+rigoureux
+leçon
+adieu
+détour
+collection
+gai
+lieue
+construire
+demain
+sincérité
+âge
+horloge
+avis
+apparaître
+trompette
+lugubre
+rapport
+abondance
+anneau
+programme
+viande
+pêcheur
+coquet
+lamentable
+lentement
+inspirer
+ronger
+témoignage
+lapin
+épouser
+poussin
+longtemps
+entretenir
+lécher
+pittoresque
+terrier
+chaussée
+murmure
+comment
+procéder
+répandre
+tissu
+cahier
+houille
+corne
+entrevoir
+jamais
+véritable
+menacer
+éclairer
+laitier
+reparaître
+casser
+pêche
+palais
+marquer
+tinter
+humidité
+compagnon
+tranquillement
+acclamation
+commencer
+représentant
+exemplaire
+étrennes
+bande
+prix
+facilité
+rapiécer
+conversation
+multiple
+embarrasser
+pompe
+prompt
+tirer
+comparaison
+vert
+compagnie
+gros
+prise
+yeux
+vice
+cerf
+ligne
+poisson
+choisir
+excellence
+troubler
+hôte
+absent
+camp
+mode
+reine
+près
+vieux
+trotter
+désobéir
+justice
+proche
+glissoire
+heureux
+route
+étendue
+jouet
+affaiblir
+prison
+poireau
+veau
+aile
+mûrir
+militaire
+moral
+boiteux
+tristesse
+chaumière
+préparer
+partie
+éviter
+rider
+secours
+clairière
+existence
+vêpres
+rétablir
+grenouille
+bagage
+construction
+représentation
+après
+sonner
+balcon
+torrent
+revoir
+montagne
+réfléchir
+aider
+posséder
+bienfait
+expérience
+feu
+ivresse
+naturel
+gentiment
+épouvanter
+développer
+récit
+souffrance
+regretter
+combattant
+grand-père
+siffler
+entrain
+superbe
+station
+moteur
+nourriture
+ajouter
+duquel
+presque
+beurre
+atteler
+guide
+cendre
+rentrer
+signe
+pierre
+numéro
+effet
+maternel
+promenade
+travers
+chapelle
+énergie
+ouvrier
+grand-mère
+humain
+milieu
+gens
+tacheter
+ruisseau
+leur
+vase
+précisément
+désir
+singe
+diviser
+soupçonner
+terrible
+curieux
+déposer
+traîneau
+culture
+mercredi
+goal
+chez
+magasin
+association
+chelou
+teuf
+ordinateur
+tablette
+smartphone
+garradin
+association
+asso
+comptabilité
+compta
+gestion
Index: src/include/data/schema.sql
==================================================================
--- src/include/data/schema.sql
+++ src/include/data/schema.sql
@@ -1,41 +1,53 @@
-CREATE TABLE config (
+CREATE TABLE IF NOT EXISTS config (
-- Configuration de Garradin
- cle TEXT PRIMARY KEY,
+ cle TEXT PRIMARY KEY NOT NULL,
valeur TEXT
);
-- On stocke ici les ID de catégorie de compta correspondant aux types spéciaux
-- compta_categorie_cotisations => id_categorie
-- compta_categorie_dons => id_categorie
-CREATE TABLE membres_categories
+CREATE TABLE IF NOT EXISTS membres_categories
-- Catégories de membres
(
- id INTEGER PRIMARY KEY,
- nom TEXT,
- description TEXT,
-
- droit_wiki INT DEFAULT 1,
- droit_membres INT DEFAULT 1,
- droit_compta INT DEFAULT 1,
- droit_inscription INT DEFAULT 0,
- droit_connexion INT DEFAULT 1,
- droit_config INT DEFAULT 0,
- cacher INT DEFAULT 0,
+ id INTEGER PRIMARY KEY NOT NULL,
+ nom TEXT NOT NULL,
+ description TEXT NULL,
+
+ droit_wiki INTEGER NOT NULL DEFAULT 1,
+ droit_membres INTEGER NOT NULL DEFAULT 1,
+ droit_compta INTEGER NOT NULL DEFAULT 1,
+ droit_inscription INTEGER NOT NULL DEFAULT 0,
+ droit_connexion INTEGER NOT NULL DEFAULT 1,
+ droit_config INTEGER NOT NULL DEFAULT 0,
+ cacher INTEGER NOT NULL DEFAULT 0,
id_cotisation_obligatoire INTEGER NULL REFERENCES cotisations (id)
);
-- Membres de l'asso
-- Table dynamique générée par l'application
--- voir class.champs_membres.php
+-- voir Garradin\Membres\Champs.php
-CREATE TABLE cotisations
+CREATE TABLE membres_sessions
+-- Sessions
+(
+ selecteur TEXT NOT NULL,
+ hash TEXT NOT NULL,
+ id_membre INTEGER NOT NULL,
+ expire TEXT NOT NULL CHECK (datetime(expire) IS NOT NULL AND datetime(expire) = expire),
+
+ FOREIGN KEY (id_membre) REFERENCES membres (id),
+ PRIMARY KEY (selecteur, id_membre)
+);
+
+CREATE TABLE IF NOT EXISTS cotisations
-- Types de cotisations et activités
(
- id INTEGER PRIMARY KEY,
+ id INTEGER PRIMARY KEY NOT NULL,
id_categorie_compta INTEGER NULL, -- NULL si le type n'est pas associé automatiquement à la compta
intitule TEXT NOT NULL,
description TEXT NULL,
montant REAL NOT NULL,
@@ -45,127 +57,127 @@
fin TEXT NULL,
FOREIGN KEY (id_categorie_compta) REFERENCES compta_categories (id)
);
-CREATE TABLE cotisations_membres
+CREATE TABLE IF NOT EXISTS cotisations_membres
-- Enregistrement des cotisations et activités
(
id INTEGER NOT NULL PRIMARY KEY,
id_membre INTEGER NOT NULL REFERENCES membres (id),
id_cotisation INTEGER NOT NULL REFERENCES cotisations (id),
- date TEXT NOT NULL DEFAULT CURRENT_DATE
+ date TEXT NOT NULL DEFAULT CURRENT_DATE CHECK (date(date) IS NOT NULL AND date(date) = date)
);
-CREATE UNIQUE INDEX cm_unique ON cotisations_membres (id_membre, id_cotisation, date);
+CREATE UNIQUE INDEX IF NOT EXISTS cm_unique ON cotisations_membres (id_membre, id_cotisation, date);
-CREATE TABLE membres_operations
+CREATE TABLE IF NOT EXISTS membres_operations
-- Liaision des enregistrement des paiements en compta
(
id_membre INTEGER NOT NULL REFERENCES membres (id),
id_operation INTEGER NOT NULL REFERENCES compta_journal (id),
id_cotisation INTEGER NULL REFERENCES cotisations_membres (id),
PRIMARY KEY (id_membre, id_operation)
);
-CREATE TABLE rappels
+CREATE TABLE IF NOT EXISTS rappels
-- Rappels de devoir renouveller une cotisation
(
- id INTEGER PRIMARY KEY,
+ id INTEGER NOT NULL PRIMARY KEY,
id_cotisation INTEGER NOT NULL REFERENCES cotisations (id),
delai INTEGER NOT NULL, -- Délai en jours pour envoyer le rappel
sujet TEXT NOT NULL,
texte TEXT NOT NULL
);
-CREATE TABLE rappels_envoyes
+CREATE TABLE IF NOT EXISTS rappels_envoyes
-- Enregistrement des rappels envoyés à qui et quand
(
- id INTEGER PRIMARY KEY,
+ id INTEGER NOT NULL PRIMARY KEY,
id_membre INTEGER NOT NULL REFERENCES membres (id),
id_cotisation INTEGER NOT NULL REFERENCES cotisations (id),
id_rappel INTEGER NULL REFERENCES rappels (id),
- date TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ date TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP CHECK (datetime(date) IS NOT NULL AND datetime(date) = date),
media INTEGER NOT NULL -- Média utilisé pour le rappel : 1 = email, 2 = courrier, 3 = autre
);
--
-- WIKI
--
-CREATE TABLE wiki_pages
+CREATE TABLE IF NOT EXISTS wiki_pages
-- Pages du wiki
(
- id INTEGER PRIMARY KEY,
- uri TEXT, -- URI unique (équivalent NomPageWiki)
- titre TEXT,
- date_creation TEXT DEFAULT CURRENT_TIMESTAMP,
- date_modification TEXT DEFAULT CURRENT_TIMESTAMP,
- parent INTEGER DEFAULT 0, -- ID de la page parent
- revision INTEGER DEFAULT 0, -- Numéro de révision (commence à 0 si pas de texte, +1 à chaque changement du texte)
- droit_lecture INTEGER DEFAULT 0, -- Accès en lecture (-1 = public [site web], 0 = tous ceux qui ont accès en lecture au wiki, 1+ = ID de groupe)
- droit_ecriture INTEGER DEFAULT 0 -- Accès en écriture (0 = tous ceux qui ont droit d'écriture sur le wiki, 1+ = ID de groupe)
+ id INTEGER PRIMARY KEY NOT NULL,
+ uri TEXT NOT NULL, -- URI unique (équivalent NomPageWiki)
+ titre TEXT NOT NULL,
+ date_creation TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP CHECK (datetime(date_creation) IS NOT NULL AND datetime(date_creation) = date_creation),
+ date_modification TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP CHECK (datetime(date_modification) IS NOT NULL AND datetime(date_modification) = date_modification),
+ parent INTEGER NOT NULL DEFAULT 0, -- ID de la page parent
+ revision INTEGER NOT NULL DEFAULT 0, -- Numéro de révision (commence à 0 si pas de texte, +1 à chaque changement du texte)
+ droit_lecture INTEGER NOT NULL DEFAULT 0, -- Accès en lecture (-1 = public [site web], 0 = tous ceux qui ont accès en lecture au wiki, 1+ = ID de groupe)
+ droit_ecriture INTEGER NOT NULL DEFAULT 0 -- Accès en écriture (0 = tous ceux qui ont droit d'écriture sur le wiki, 1+ = ID de groupe)
);
-CREATE UNIQUE INDEX wiki_uri ON wiki_pages (uri);
+CREATE UNIQUE INDEX IF NOT EXISTS wiki_uri ON wiki_pages (uri);
-CREATE VIRTUAL TABLE wiki_recherche USING fts4
+CREATE VIRTUAL TABLE IF NOT EXISTS wiki_recherche USING fts4
-- Table dupliquée pour chercher une page
(
id INT PRIMARY KEY NOT NULL, -- Clé externe obligatoire
- titre TEXT,
- contenu TEXT, -- Contenu de la dernière révision
+ titre TEXT NOT NULL,
+ contenu TEXT NULL, -- Contenu de la dernière révision
FOREIGN KEY (id) REFERENCES wiki_pages(id)
);
-CREATE TABLE wiki_revisions
+CREATE TABLE IF NOT EXISTS wiki_revisions
-- Révisions du contenu des pages
(
id_page INTEGER NOT NULL,
revision INTEGER NULL,
id_auteur INTEGER NULL,
- contenu TEXT,
- modification TEXT, -- Description des modifications effectuées
- chiffrement INTEGER DEFAULT 0, -- 1 si le contenu est chiffré, 0 sinon
- date TEXT DEFAULT CURRENT_TIMESTAMP,
+ contenu TEXT NOT NULL,
+ modification TEXT NULL, -- Description des modifications effectuées
+ chiffrement INTEGER NOT NULL DEFAULT 0, -- 1 si le contenu est chiffré, 0 sinon
+ date TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP CHECK (datetime(date) IS NOT NULL AND datetime(date) = date),
PRIMARY KEY(id_page, revision),
FOREIGN KEY (id_page) REFERENCES wiki_pages (id), -- Clé externe obligatoire
FOREIGN KEY (id_auteur) REFERENCES membres (id) -- Clé externe non-obligatoire (peut être supprimée après en cas de suppression de membre)
);
-CREATE INDEX wiki_revisions_id_page ON wiki_revisions (id_page);
-CREATE INDEX wiki_revisions_id_auteur ON wiki_revisions (id_auteur);
+CREATE INDEX IF NOT EXISTS wiki_revisions_id_page ON wiki_revisions (id_page);
+CREATE INDEX IF NOT EXISTS wiki_revisions_id_auteur ON wiki_revisions (id_auteur);
-- Triggers pour synchro avec table wiki_pages
-CREATE TRIGGER wiki_recherche_delete AFTER DELETE ON wiki_pages
+CREATE TRIGGER IF NOT EXISTS wiki_recherche_delete AFTER DELETE ON wiki_pages
BEGIN
DELETE FROM wiki_recherche WHERE id = old.id;
END;
-CREATE TRIGGER wiki_recherche_update AFTER UPDATE OF id, titre ON wiki_pages
+CREATE TRIGGER IF NOT EXISTS wiki_recherche_update AFTER UPDATE OF id, titre ON wiki_pages
BEGIN
UPDATE wiki_recherche SET id = new.id, titre = new.titre WHERE id = old.id;
END;
-- Trigger pour mettre à jour le contenu de la table de recherche lors d'une nouvelle révision
-CREATE TRIGGER wiki_recherche_contenu_insert AFTER INSERT ON wiki_revisions WHEN new.chiffrement != 1
+CREATE TRIGGER IF NOT EXISTS wiki_recherche_contenu_insert AFTER INSERT ON wiki_revisions WHEN new.chiffrement != 1
BEGIN
UPDATE wiki_recherche SET contenu = new.contenu WHERE id = new.id_page;
END;
-- Si le contenu est chiffré, la recherche n'affiche pas de contenu
-CREATE TRIGGER wiki_recherche_contenu_chiffre AFTER INSERT ON wiki_revisions WHEN new.chiffrement = 1
+CREATE TRIGGER IF NOT EXISTS wiki_recherche_contenu_chiffre AFTER INSERT ON wiki_revisions WHEN new.chiffrement = 1
BEGIN
UPDATE wiki_recherche SET contenu = '' WHERE id = new.id_page;
END;
/*
@@ -184,69 +196,69 @@
--
-- COMPTA
--
-CREATE TABLE compta_exercices
+CREATE TABLE IF NOT EXISTS compta_exercices
-- Exercices
(
- id INTEGER PRIMARY KEY,
+ id INTEGER NOT NULL PRIMARY KEY,
libelle TEXT NOT NULL,
- debut TEXT NOT NULL DEFAULT CURRENT_DATE,
- fin TEXT NULL DEFAULT NULL,
+ debut TEXT NOT NULL DEFAULT CURRENT_DATE CHECK (date(debut) IS NOT NULL AND date(debut) = debut),
+ fin TEXT NULL DEFAULT NULL CHECK (fin IS NULL OR (date(fin) IS NOT NULL AND date(fin) = fin)),
cloture INTEGER NOT NULL DEFAULT 0
);
-CREATE TABLE compta_comptes
+CREATE TABLE IF NOT EXISTS compta_comptes
-- Plan comptable
(
- id TEXT PRIMARY KEY, -- peut contenir des lettres, eg. 53A, 53B, etc.
+ id TEXT NOT NULL PRIMARY KEY, -- peut contenir des lettres, eg. 53A, 53B, etc.
parent TEXT NOT NULL DEFAULT 0,
libelle TEXT NOT NULL,
position INTEGER NOT NULL, -- position actif/passif/charge/produit
plan_comptable INTEGER NOT NULL DEFAULT 1, -- 1 = fait partie du plan comptable, 0 = a été ajouté par l'utilisateur
desactive INTEGER NOT NULL DEFAULT 0 -- 1 = compte historique désactivé
);
-CREATE INDEX compta_comptes_parent ON compta_comptes (parent);
+CREATE INDEX IF NOT EXISTS compta_comptes_parent ON compta_comptes (parent);
-CREATE TABLE compta_comptes_bancaires
+CREATE TABLE IF NOT EXISTS compta_comptes_bancaires
-- Comptes bancaires
(
- id TEXT PRIMARY KEY,
+ id TEXT NOT NULL PRIMARY KEY,
banque TEXT NOT NULL,
- iban TEXT,
- bic TEXT,
+ iban TEXT NULL,
+ bic TEXT NULL,
FOREIGN KEY(id) REFERENCES compta_comptes(id)
);
-CREATE TABLE compta_journal
+CREATE TABLE IF NOT EXISTS compta_journal
-- Journal des opérations comptables
(
id INTEGER PRIMARY KEY,
libelle TEXT NOT NULL,
- remarques TEXT,
- numero_piece TEXT, -- N° de pièce comptable
-
- montant REAL,
-
- date TEXT DEFAULT CURRENT_DATE,
- moyen_paiement TEXT DEFAULT NULL,
- numero_cheque TEXT DEFAULT NULL,
-
- compte_debit TEXT, -- N° du compte dans le plan
- compte_credit TEXT, -- N° du compte dans le plan
+ remarques TEXT NULL,
+ numero_piece TEXT NULL, -- N° de pièce comptable
+
+ montant REAL NOT NULL,
+
+ date TEXT NOT NULL DEFAULT CURRENT_DATE CHECK (date(date) IS NOT NULL AND date(date) = date),
+ moyen_paiement TEXT NULL,
+ numero_cheque TEXT NULL,
+
+ compte_debit TEXT NULL, -- N° du compte dans le plan, NULL est utilisé pour une opération qui vient d'un exercice précédent
+ compte_credit TEXT NULL, -- N° du compte dans le plan
id_exercice INTEGER NULL DEFAULT NULL, -- En cas de compta simple, l'exercice est permanent (NULL)
id_auteur INTEGER NULL,
id_categorie INTEGER NULL, -- Numéro de catégorie (en mode simple)
@@ -256,116 +268,116 @@
FOREIGN KEY(id_exercice) REFERENCES compta_exercices(id),
FOREIGN KEY(id_auteur) REFERENCES membres(id),
FOREIGN KEY(id_categorie) REFERENCES compta_categories(id)
);
-CREATE INDEX compta_operations_exercice ON compta_journal (id_exercice);
-CREATE INDEX compta_operations_date ON compta_journal (date);
-CREATE INDEX compta_operations_comptes ON compta_journal (compte_debit, compte_credit);
-CREATE INDEX compta_operations_auteur ON compta_journal (id_auteur);
+CREATE INDEX IF NOT EXISTS compta_operations_exercice ON compta_journal (id_exercice);
+CREATE INDEX IF NOT EXISTS compta_operations_date ON compta_journal (date);
+CREATE INDEX IF NOT EXISTS compta_operations_comptes ON compta_journal (compte_debit, compte_credit);
+CREATE INDEX IF NOT EXISTS compta_operations_auteur ON compta_journal (id_auteur);
-CREATE TABLE compta_moyens_paiement
+CREATE TABLE IF NOT EXISTS compta_moyens_paiement
-- Moyens de paiement
(
- code TEXT PRIMARY KEY,
- nom TEXT
+ code TEXT NOT NULL PRIMARY KEY,
+ nom TEXT NOT NULL
);
--INSERT INTO compta_moyens_paiement (code, nom) VALUES ('AU', 'Autre');
-INSERT INTO compta_moyens_paiement (code, nom) VALUES ('CB', 'Carte bleue');
-INSERT INTO compta_moyens_paiement (code, nom) VALUES ('CH', 'Chèque');
-INSERT INTO compta_moyens_paiement (code, nom) VALUES ('ES', 'Espèces');
-INSERT INTO compta_moyens_paiement (code, nom) VALUES ('PR', 'Prélèvement');
-INSERT INTO compta_moyens_paiement (code, nom) VALUES ('TI', 'TIP');
-INSERT INTO compta_moyens_paiement (code, nom) VALUES ('VI', 'Virement');
-
-CREATE TABLE compta_categories
+INSERT OR IGNORE INTO compta_moyens_paiement (code, nom) VALUES ('CB', 'Carte bleue');
+INSERT OR IGNORE INTO compta_moyens_paiement (code, nom) VALUES ('CH', 'Chèque');
+INSERT OR IGNORE INTO compta_moyens_paiement (code, nom) VALUES ('ES', 'Espèces');
+INSERT OR IGNORE INTO compta_moyens_paiement (code, nom) VALUES ('PR', 'Prélèvement');
+INSERT OR IGNORE INTO compta_moyens_paiement (code, nom) VALUES ('TI', 'TIP');
+INSERT OR IGNORE INTO compta_moyens_paiement (code, nom) VALUES ('VI', 'Virement');
+
+CREATE TABLE IF NOT EXISTS compta_categories
-- Catégories pour simplifier le plan comptable
(
- id INTEGER PRIMARY KEY,
- type INTEGER DEFAULT 1, -- 1 = recette, -1 = dépense, 0 = autre (utilisé uniquement pour l'interface)
+ id INTEGER NOT NULL PRIMARY KEY,
+ type INTEGER NOT NULL DEFAULT 1, -- 1 = recette, -1 = dépense, 0 = autre (utilisé uniquement pour l'interface)
intitule TEXT NOT NULL,
- description TEXT,
+ description TEXT NULL,
compte TEXT NOT NULL, -- Compte affecté par cette catégorie
FOREIGN KEY(compte) REFERENCES compta_comptes(id)
);
-CREATE TABLE plugins
+CREATE TABLE IF NOT EXISTS plugins
(
- id TEXT PRIMARY KEY,
+ id TEXT NOT NULL PRIMARY KEY,
officiel INTEGER NOT NULL DEFAULT 0,
nom TEXT NOT NULL,
- description TEXT,
- auteur TEXT,
- url TEXT,
+ description TEXT NULL,
+ auteur TEXT NULL,
+ url TEXT NULL,
version TEXT NOT NULL,
menu INTEGER NOT NULL DEFAULT 0,
- config TEXT
+ config TEXT NULL
);
-CREATE TABLE plugins_signaux
+CREATE TABLE IF NOT EXISTS plugins_signaux
-- Association entre plugins et signaux (hooks)
(
signal TEXT NOT NULL,
plugin TEXT NOT NULL REFERENCES plugins (id),
callback TEXT NOT NULL,
PRIMARY KEY (signal, plugin)
);
-CREATE TABLE compta_rapprochement
+CREATE TABLE IF NOT EXISTS compta_rapprochement
-- Rapprochement entre compta et relevés de comptes
(
id_operation INTEGER NOT NULL PRIMARY KEY REFERENCES compta_journal (id),
- date TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ date TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP CHECK (datetime(date) IS NOT NULL AND datetime(date) = date),
id_auteur INTEGER NULL REFERENCES membres (id)
);
-CREATE TABLE fichiers
+CREATE TABLE IF NOT EXISTS fichiers
-- Données sur les fichiers
(
id INTEGER NOT NULL PRIMARY KEY,
nom TEXT NOT NULL, -- nom de fichier (par exemple image1234.jpeg)
type TEXT NULL, -- Type MIME
image INTEGER NOT NULL DEFAULT 0, -- 1 = image reconnue
- datetime TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, -- Date d'ajout ou mise à jour du fichier
+ datetime TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP CHECK (datetime(datetime) IS NOT NULL AND datetime(datetime) = datetime), -- Date d'ajout ou mise à jour du fichier
id_contenu INTEGER NOT NULL REFERENCES fichiers_contenu (id)
);
-CREATE INDEX fichiers_date ON fichiers (datetime);
+CREATE INDEX IF NOT EXISTS fichiers_date ON fichiers (datetime);
-CREATE TABLE fichiers_contenu
+CREATE TABLE IF NOT EXISTS fichiers_contenu
-- Contenu des fichiers
(
id INTEGER NOT NULL PRIMARY KEY,
hash TEXT NOT NULL, -- Hash SHA1 du contenu du fichier
taille INTEGER NOT NULL, -- Taille en octets
contenu BLOB NULL
);
-CREATE UNIQUE INDEX fichiers_hash ON fichiers_contenu (hash);
+CREATE UNIQUE INDEX IF NOT EXISTS fichiers_hash ON fichiers_contenu (hash);
-CREATE TABLE fichiers_membres
+CREATE TABLE IF NOT EXISTS fichiers_membres
-- Associations entre fichiers et membres (photo de profil par exemple)
(
fichier INTEGER NOT NULL REFERENCES fichiers (id),
id INTEGER NOT NULL REFERENCES membres (id),
PRIMARY KEY(fichier, id)
);
-CREATE TABLE fichiers_wiki_pages
+CREATE TABLE IF NOT EXISTS fichiers_wiki_pages
-- Associations entre fichiers et pages du wiki
(
fichier INTEGER NOT NULL REFERENCES fichiers (id),
id INTEGER NOT NULL REFERENCES wiki_pages (id),
PRIMARY KEY(fichier, id)
);
-CREATE TABLE fichiers_compta_journal
+CREATE TABLE IF NOT EXISTS fichiers_compta_journal
-- Associations entre fichiers et journal de compta (pièce comptable par exemple)
(
fichier INTEGER NOT NULL REFERENCES fichiers (id),
id INTEGER NOT NULL REFERENCES compta_journal (id),
PRIMARY KEY(fichier, id)
);
Index: src/include/init.php
==================================================================
--- src/include/init.php
+++ src/include/init.php
@@ -1,8 +1,12 @@
DATA_ROOT . '/cache',
- 'DB_FILE' => DATA_ROOT . '/association.sqlite',
- 'DB_SCHEMA' => ROOT . '/include/data/schema.sql',
- 'PLUGINS_ROOT' => DATA_ROOT . '/plugins',
- 'PREFER_HTTPS' => false,
- 'PLUGINS_SYSTEM' => '',
- 'SHOW_ERRORS' => true,
- 'MAIL_ERRORS' => false,
- 'USE_CRON' => false,
- 'ENABLE_XSENDFILE' => false,
- 'SMTP_HOST' => false,
- 'SMTP_USER' => null,
- 'SMTP_PASSWORD' => null,
- 'SMTP_PORT' => 587,
- 'SMTP_SECURITY' => 'STARTTLS',
+ 'CACHE_ROOT' => DATA_ROOT . '/cache',
+ 'DB_FILE' => DATA_ROOT . '/association.sqlite',
+ 'DB_SCHEMA' => ROOT . '/include/data/schema.sql',
+ 'PLUGINS_ROOT' => DATA_ROOT . '/plugins',
+ 'PREFER_HTTPS' => false,
+ 'ALLOW_MODIFIED_IMPORT' => true,
+ 'PLUGINS_SYSTEM' => '',
+ 'SHOW_ERRORS' => false,
+ 'MAIL_ERRORS' => false,
+ 'USE_CRON' => false,
+ 'ENABLE_XSENDFILE' => false,
+ 'SMTP_HOST' => false,
+ 'SMTP_USER' => null,
+ 'SMTP_PASSWORD' => null,
+ 'SMTP_PORT' => 587,
+ 'SMTP_SECURITY' => 'STARTTLS',
];
foreach ($default_config as $const => $value)
{
$const = sprintf('Garradin\\%s', $const);
@@ -108,12 +117,14 @@
{
define($const, $value);
}
}
-define('Garradin\WEBSITE', 'http://garradin.eu/');
-define('Garradin\PLUGINS_URL', 'https://garradin.eu/plugins/list.json');
+const WEBSITE = 'http://garradin.eu/';
+const PLUGINS_URL = 'https://garradin.eu/plugins/list.json';
+
+const NTP_SERVER = 'fr.pool.ntp.org';
// PHP devrait être assez intelligent pour chopper la TZ système mais nan
// il sait pas faire (sauf sur Debian qui a le bon patch pour ça), donc pour
// éviter le message d'erreur à la con on définit une timezone par défaut
// Pour utiliser une autre timezone, il suffit de définir date.timezone dans
@@ -128,156 +139,36 @@
{
ini_set('date.timezone', 'Europe/Paris');
}
}
-ini_set('error_log', DATA_ROOT . '/error.log');
-ini_set('log_errors', true);
-
-if (SHOW_ERRORS)
-{
- // Gestion par défaut des erreurs
- ini_set('display_errors', true);
- ini_set('html_errors', false);
-
- if (PHP_SAPI != 'cli')
- {
- ini_set('error_prepend_string', '
-
\__/
(xx)
//||\\\\
- Erreur fatale
- Une erreur fatale s\'est produite à l\'exécution de Garradin. Pour rapporter ce bug
- merci d\'inclure le message ci-dessous :
- ');
- ini_set('error_append_string', '
- Comment rapporter un bug
');
- }
-}
-
-/*
- * Gestion des erreurs et exceptions
- */
-
-class UserException extends \LogicException
-{
-}
-
-function exception_error_handler($errno, $errstr, $errfile, $errline )
-{
- // For @ ignored errors
- if (error_reporting() === 0) return;
- throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
-}
-
-function exception_handler($e)
-{
- if ($e instanceOf UserException || $e instanceOf \KD2\MiniSkelMarkupException)
- {
- try {
- if (PHP_SAPI == 'cli')
- {
- echo $e->getMessage();
- }
- else
- {
- $tpl = Template::getInstance();
-
- $tpl->assign('error', $e->getMessage());
- $tpl->display('error.tpl');
- }
-
- exit;
- }
- catch (Exception $e)
- {
- }
- }
-
- $file = str_replace(ROOT, '', $e->getFile());
-
- $error = "Exception of type ".get_class($e)." happened !\n\n".
- $e->getCode()." - ".$e->getMessage()."\n\nIn: ".
- $file . ":" . $e->getLine()."\n\n";
-
- if (!empty($_SERVER['HTTP_HOST']) && !empty($_SERVER['REQUEST_URI']))
- $error .= 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']."\n\n";
-
- $error .= $e->getTraceAsString();
- $error .= "\n-------------\n";
- $error .= 'Garradin version: ' . garradin_version() . "\n";
- $error .= 'Garradin manifest: ' . garradin_manifest() . "\n";
- $error .= 'PHP version: ' . phpversion() . "\n";
- $error .= 'Garradin data root: ' . \Garradin\DATA_ROOT . "\n";
-
- foreach ($_SERVER as $key=>$value)
- {
- if (is_array($value))
- $value = json_encode($value);
-
- $error .= $key . ': ' . $value . "\n";
- }
-
- $error = str_replace("\r", '', $error);
- error_log($error);
-
- if (MAIL_ERRORS)
- {
- mail(MAIL_ERRORS, '[Garradin] Erreur d\'exécution', $error, 'From: "' . WWW_URL . '" ');
- }
-
- if (PHP_SAPI == 'cli')
- {
- echo $error;
- }
- else
- {
- echo '
- \__/
(xx)
//||\\\\
- Erreur d\'exécution
';
-
- if (SHOW_ERRORS)
- {
- echo 'Une erreur s\'est produite à l\'exécution de Garradin. Pour rapporter ce bug
- merci d\'inclure le message suivant :
-
-
- Comment rapporter un bug
';
- }
- else
- {
- echo 'Une erreur s\'est produite à l\'exécution de Garradin.
- Le webmaster a été prévenu.
';
- }
- }
-
- exit;
-}
-
-set_error_handler('Garradin\exception_error_handler');
-set_exception_handler('Garradin\exception_handler');
-
/**
- * Auto-load classes and libs
+ * Auto-chargement des dépendances
*/
class Loader
{
/**
- * Already loaded filenames
+ * Liste des classes déjà chargées
* @var array
*/
static protected $loaded = [];
/**
- * Loads a class from the $name
- * @param stringg $classname
- * @return bool true
+ * Inclure un fichier de classe depuis le nom de la classe
+ * @param string $classname
+ * @return void
*/
static public function load($classname)
{
$classname = ltrim($classname, '\\');
+
+ if (array_key_exists($classname, self::$loaded))
+ {
+ return true;
+ }
+ // Plugins
if (substr($classname, 0, 16) == 'Garradin\\Plugin\\')
{
$classname = substr($classname, 16);
$plugin_name = substr($classname, 0, strpos($classname, '\\'));
$filename = str_replace('\\', '/', substr($classname, strpos($classname, '\\')+1));
@@ -284,35 +175,114 @@
$path = 'phar://' . PLUGINS_ROOT . '/' . strtolower($plugin_name) . '.tar.gz/lib/' . $filename . '.php';
}
else
{
+ // PSR-0 autoload
$filename = str_replace('\\', '/', $classname);
$path = ROOT . '/include/lib/' . $filename . '.php';
}
-
- if (array_key_exists($path, self::$loaded))
+
+ if (!file_exists($path))
{
- return true;
- }
-
- if (!file_exists($path)) {
throw new \Exception('File '.$path.' doesn\'t exists');
}
- self::$loaded[$path] = true;
+ self::$loaded[$classname] = true;
require $path;
}
}
\spl_autoload_register(['Garradin\Loader', 'load'], true);
-$n = new Membres;
+/*
+ * Gestion des erreurs et exceptions
+ */
+
+class UserException extends \LogicException
+{
+}
+
+// activer le gestionnaire d'erreurs/exceptions
+ErrorManager::enable(SHOW_ERRORS ? ErrorManager::DEVELOPMENT : ErrorManager::PRODUCTION);
+ErrorManager::setLogFile(DATA_ROOT . '/error.log');
+
+// activer l'envoi de mails si besoin est
+if (MAIL_ERRORS)
+{
+ ErrorManager::setEmail(MAIL_ERRORS);
+}
+
+ErrorManager::setExtraDebugEnv([
+ 'Garradin version' => garradin_version(),
+ 'Garradin data root' => DATA_ROOT,
+ ]);
+
+ErrorManager::setProductionErrorTemplate('Erreur interne
+ Erreur interne
Désolé mais le serveur a rencontré une erreur interne
+ et ne peut répondre à votre requête. Merci de ré-essayer plus tard.
+ Si vous suspectez un bug dans Garradin, vous pouvez suivre
+ ces instructions
+ pour le rapporter.
+ Un-e responsable a été notifié-e et cette erreur sera corrigée dès que possible.
+ Référence : {$ref}
+ ← Retour à la page d\'accueil
+ ');
+
+ErrorManager::setHtmlFooter('
Cette erreur est peut-être un bug dans Garradin ? En ce cas vous pouvez le rapporter en suivant ces instructions.');
+
+function user_error($e)
+{
+ if (PHP_SAPI == 'cli')
+ {
+ echo $e->getMessage();
+ }
+ else
+ {
+ $tpl = Template::getInstance();
+
+ $tpl->assign('error', $e->getMessage());
+ $tpl->display('error.tpl');
+ }
+
+ exit;
+}
+
+// Message d'erreur simple pour les erreurs de l'utilisateur
+ErrorManager::setCustomExceptionHandler('\Garradin\UserException', '\Garradin\user_error');
+ErrorManager::setCustomExceptionHandler('\KD2\MiniSkelMarkupException', '\Garradin\user_error');
+
+// Clé secrète utilisée pour chiffrer les tokens CSRF etc.
+if (!defined('Garradin\SECRET_KEY'))
+{
+ $key = base64_encode(Security::random_bytes(64));
+ Install::setLocalConfig('SECRET_KEY', $key);
+ define('Garradin\SECRET_KEY', $key);
+}
+
+// Intégration du secret pour les tokens
+Form::tokenSetSecret(SECRET_KEY);
+
+// Fonctions utilitaires bien utiles d'avoir dans le namespace global de Garradin
+function obj_has($obj, $pattern)
+{
+ return \KD2\Helpers::obj_has($obj, $pattern);
+}
+
+function obj_get($src, $pattern, $default = null)
+{
+ return \KD2\Helpers::obj_get($src, $pattern, $default);
+}
/*
- * Inclusion des fichiers de base
+ * Vérifications pour enclencher le processus d'installation ou de mise à jour
*/
if (!defined('Garradin\INSTALL_PROCESS') && !defined('Garradin\UPGRADE_PROCESS'))
{
if (!file_exists(DB_FILE))
Index: src/include/lib/Garradin/Compta/Categories.php
==================================================================
--- src/include/lib/Garradin/Compta/Categories.php
+++ src/include/lib/Garradin/Compta/Categories.php
@@ -1,13 +1,16 @@
simpleQuerySingle('SELECT 1 FROM compta_comptes WHERE id = ?;', false, $data['compte']))
+ if (!$db->firstColumn('SELECT 1 FROM compta_comptes WHERE id = ?;', $data['compte']))
{
throw new UserException('Le compte associé n\'existe pas.');
}
if (!isset($data['type']) ||
@@ -41,11 +44,11 @@
{
// Catégories "autres" pas possibles pour le moment
throw new UserException('Type de catégorie inconnu.');
}
- $db->simpleInsert('compta_categories', [
+ $db->insert('compta_categories', [
'intitule' => $data['intitule'],
'description'=> $data['description'],
'compte' => $data['compte'],
'type' => (int)$data['type'],
]);
@@ -57,62 +60,68 @@
{
$this->_checkFields($data);
$db = DB::getInstance();
- $db->simpleUpdate('compta_categories',
+ $db->update('compta_categories',
[
'intitule' => $data['intitule'],
'description'=> $data['description'],
],
- 'id = \''.$db->escapeString(trim($id)).'\'');
+ 'id = :id_select',
+ ['id_select' => (int) $id]
+ );
return true;
}
public function delete($id)
{
$db = DB::getInstance();
+
+ $id = (int) $id;
// Ne pas supprimer une catégorie qui est utilisée !
- if ($db->simpleQuerySingle('SELECT 1 FROM compta_journal WHERE id_categorie = ? LIMIT 1;', false, $id))
+ if ($db->firstColumn('SELECT 1 FROM compta_journal WHERE id_categorie = ? LIMIT 1;', $id))
{
throw new UserException('Cette catégorie ne peut être supprimée car des opérations comptables y sont liées.');
}
- $db->simpleExec('DELETE FROM compta_categories WHERE id = ?;', $id);
+ $db->delete('compta_categories', 'id = ?', $id);
return true;
}
public function get($id)
{
$db = DB::getInstance();
- return $db->simpleQuerySingle('SELECT * FROM compta_categories WHERE id = ?;', true, (int)$id);
+ return $db->first('SELECT * FROM compta_categories WHERE id = ?;', (int)$id);
}
public function getList($type = null)
{
$db = DB::getInstance();
- $type = is_null($type) ? '' : 'cat.type = '.(int)$type;
- return $db->simpleStatementFetchAssocKey('
- SELECT cat.id, cat.*, cc.libelle AS compte_libelle
+ $where = is_null($type) ? '1' : 'cat.type = '.(int)$type;
+
+ $query = sprintf('SELECT cat.id, cat.*, cc.libelle AS compte_libelle
FROM compta_categories AS cat INNER JOIN compta_comptes AS cc
ON cc.id = cat.compte
- WHERE '.$type.' ORDER BY cat.intitule;', SQLITE3_ASSOC);
+ WHERE %s ORDER BY cat.intitule;', $where);
+
+ return $db->getAssocKey($query);
}
public function listMoyensPaiement()
{
$db = DB::getInstance();
- return $db->simpleStatementFetchAssocKey('SELECT code, nom FROM compta_moyens_paiement ORDER BY nom COLLATE NOCASE;');
+ return $db->getAssocKey('SELECT code, nom FROM compta_moyens_paiement ORDER BY nom COLLATE NOCASE;');
}
public function getMoyenPaiement($code)
{
$db = DB::getInstance();
- return $db->simpleQuerySingle('SELECT nom FROM compta_moyens_paiement WHERE code = ?;', false, $code);
+ return $db->firstColumn('SELECT nom FROM compta_moyens_paiement WHERE code = ?;', $code);
}
protected function _checkFields(&$data)
{
if (empty($data['intitule']) || !trim($data['intitule']))
Index: src/include/lib/Garradin/Compta/Comptes.php
==================================================================
--- src/include/lib/Garradin/Compta/Comptes.php
+++ src/include/lib/Garradin/Compta/Comptes.php
@@ -18,11 +18,11 @@
public function importPlan()
{
$plan = json_decode(file_get_contents(\Garradin\ROOT . '/include/data/plan_comptable.json'), true);
$db = DB::getInstance();
- $db->exec('BEGIN;');
+ $db->begin();
$ids = [];
foreach ($plan as $id=>$compte)
{
$ids[] = $id;
@@ -48,11 +48,11 @@
}
}
$db->exec('DELETE FROM compta_comptes WHERE id NOT IN(\''.implode('\', \'', $ids).'\') AND plan_comptable = 1;');
- $db->exec('END;');
+ $db->commit();
return true;
}
public function add($data)
Index: src/include/lib/Garradin/Compta/Exercices.php
==================================================================
--- src/include/lib/Garradin/Compta/Exercices.php
+++ src/include/lib/Garradin/Compta/Exercices.php
@@ -85,11 +85,11 @@
if (!Utils::checkDate($end))
{
throw new UserException('Date de fin vide ou invalide.');
}
- $db->exec('BEGIN;');
+ $db->begin();
// Clôture de l'exercice
$db->simpleUpdate('compta_exercices', [
'cloture' => 1,
'fin' => $end,
@@ -117,11 +117,11 @@
// Ré-attribution des opérations de l'exercice à clôturer qui ne sont pas dans son
// intervale au nouvel exercice
$db->simpleExec('UPDATE compta_journal SET id_exercice = ? WHERE id_exercice = ? AND date >= ?;',
$new_id, $id, $new_begin);
- $db->exec('END;');
+ $db->commit();
return $new_id;
}
/**
@@ -133,11 +133,11 @@
*/
public function doReports($old_id, $date)
{
$db = DB::getInstance();
- $db->exec('BEGIN;');
+ $db->begin();
$report_crediteur = 110;
$report_debiteur = 119;
$comptes = new Comptes;
@@ -195,11 +195,11 @@
]);
}
// FIXME utiliser $diff pour équilibrer
- $db->exec('END;');
+ $db->commit();
return true;
}
/**
Index: src/include/lib/Garradin/Compta/Import.php
==================================================================
--- src/include/lib/Garradin/Compta/Import.php
+++ src/include/lib/Garradin/Compta/Import.php
@@ -80,11 +80,11 @@
{
return false;
}
$db = DB::getInstance();
- $db->exec('BEGIN;');
+ $db->begin();
$comptes = new Comptes;
$banques = new Comptes_Bancaires;
$cats = new Categories;
$journal = new Journal;
@@ -127,26 +127,26 @@
continue;
}
if (count($row) != count($columns))
{
- $db->exec('ROLLBACK;');
+ $db->rollback();
throw new UserException('Erreur sur la ligne ' . $line . ' : le nombre de colonnes est incorrect.');
}
if (trim($row[0]) !== '' && !is_numeric($row[0]))
{
- $db->exec('ROLLBACK;');
+ $db->rollback();
throw new UserException('Erreur sur la ligne ' . $line . ' : la première colonne doit être vide ou contenir le numéro unique d\'opération.');
}
$id = $col('Numéro mouvement');
$date = $col('Date');
if (!preg_match('!^\d{2}/\d{2}/\d{4}$!', $date))
{
- $db->exec('ROLLBACK;');
+ $db->rollback();
throw new UserException('Erreur sur la ligne ' . $line . ' : la date n\'est pas au format jj/mm/aaaa.');
}
$date = explode('/', $date);
$date = $date[2] . '-' . $date[1] . '-' . $date[0];
@@ -209,11 +209,11 @@
{
$journal->edit($id, $data);
}
}
- $db->exec('END;');
+ $db->commit();
fclose($fp);
return true;
}
@@ -230,11 +230,11 @@
{
return false;
}
$db = DB::getInstance();
- $db->exec('BEGIN;');
+ $db->begin();
$comptes = new Comptes;
$banques = new Comptes_Bancaires;
$cats = new Categories;
$journal = new Journal;
@@ -306,11 +306,11 @@
$date = $col('Date');
if (!preg_match('!^\d{2}/\d{2}/\d{4}$!', $date))
{
- $db->exec('ROLLBACK;');
+ $db->rollback();
throw new UserException('Erreur sur la ligne ' . $line . ' : la date n\'est pas au format jj/mm/aaaa.');
}
$date = explode('/', $date);
$date = $date[2] . '-' . $date[1] . '-' . $date[0];
@@ -379,11 +379,11 @@
}
$journal->add($data);
}
- $db->exec('END;');
+ $db->commit();
fclose($fp);
return true;
}
}
Index: src/include/lib/Garradin/Compta/Journal.php
==================================================================
--- src/include/lib/Garradin/Compta/Journal.php
+++ src/include/lib/Garradin/Compta/Journal.php
@@ -149,15 +149,15 @@
if (!$this->_checkOpenExercice($db->simpleQuerySingle('SELECT id_exercice FROM compta_journal WHERE id = ?;', false, $id)))
{
throw new UserException('Cette opération fait partie d\'un exercice qui a été clôturé.');
}
- $db->exec('BEGIN;');
+ $db->begin();
$db->simpleExec('DELETE FROM membres_operations WHERE id_operation = ?;', (int)$id);
$db->simpleExec('DELETE FROM compta_rapprochement WHERE id_operation = ?;', (int)$id);
$db->simpleExec('DELETE FROM compta_journal WHERE id = ?;', (int)$id);
- $db->exec('END;');
+ $db->commit();
return true;
}
public function get($id)
Index: src/include/lib/Garradin/Compta/Rapprochement.php
==================================================================
--- src/include/lib/Garradin/Compta/Rapprochement.php
+++ src/include/lib/Garradin/Compta/Rapprochement.php
@@ -65,11 +65,11 @@
{
$cases = [];
}
$db = DB::getInstance();
- $db->exec('BEGIN;');
+ $db->begin();
// Synchro des trucs cochés
$st = $db->prepare('INSERT OR REPLACE INTO compta_rapprochement (id_operation, id_auteur)
VALUES (:operation, :auteur);');
$st->bindValue(':auteur', (int)$auteur, \SQLITE3_INTEGER);
@@ -93,9 +93,9 @@
$st->bindValue(':id', (int)$row['id'], \SQLITE3_INTEGER);
$st->execute();
}
- $db->exec('END;');
+ $db->commit();
return true;
}
}
Index: src/include/lib/Garradin/Config.php
==================================================================
--- src/include/lib/Garradin/Config.php
+++ src/include/lib/Garradin/Config.php
@@ -67,11 +67,11 @@
'version' => $string,
];
$db = DB::getInstance();
- $this->config = $db->simpleStatementFetchAssoc('SELECT cle, valeur FROM config ORDER BY cle;');
+ $this->config = $db->getAssoc('SELECT cle, valeur FROM config ORDER BY cle;');
foreach ($this->config as $key=>&$value)
{
if (!array_key_exists($key, $this->fields_types))
{
@@ -109,11 +109,11 @@
return true;
$values = [];
$db = DB::getInstance();
- $db->exec('BEGIN;');
+ $db->begin();
foreach ($this->modified as $key=>$modified)
{
$value = $this->config[$key];
@@ -139,11 +139,11 @@
// Création de l'index unique
$db->exec('DROP INDEX IF EXISTS membres_identifiant;');
$db->exec('CREATE UNIQUE INDEX membres_identifiant ON membres ('.$this->get('champ_identifiant').');');
}
- $db->exec('END;');
+ $db->commit();
$this->modified = [];
return true;
}
@@ -273,20 +273,20 @@
case 'categorie_cotisations':
case 'categorie_dons':
{
return false;
$db = DB::getInstance();
- if (!$db->simpleQuerySingle('SELECT 1 FROM compta_categories WHERE id = ?;', false, $value))
+ if (!$db->firstColumn('SELECT 1 FROM compta_categories WHERE id = ?;', $value))
{
throw new UserException('Champ '.$key.' : La catégorie comptable numéro \''.$value.'\' ne semble pas exister.');
}
break;
}
case 'categorie_membres':
{
$db = DB::getInstance();
- if (!$db->simpleQuerySingle('SELECT 1 FROM membres_categories WHERE id = ?;', false, $value))
+ if (!$db->firstColumn('SELECT 1 FROM membres_categories WHERE id = ?;', $value))
{
throw new UserException('La catégorie de membres par défaut numéro \''.$value.'\' ne semble pas exister.');
}
break;
}
Index: src/include/lib/Garradin/Cotisations.php
==================================================================
--- src/include/lib/Garradin/Cotisations.php
+++ src/include/lib/Garradin/Cotisations.php
@@ -85,14 +85,12 @@
{
$db = DB::getInstance();
$this->_checkFields($data);
- $db->simpleInsert('cotisations', $data);
- $id = $db->lastInsertRowId();
-
- return $id;
+ $db->insert('cotisations', $data);
+ return $db->lastInsertRowId();
}
/**
* Modifier une cotisation
* @param integer $id ID de la cotisation à modifier
@@ -103,11 +101,11 @@
{
$db = DB::getInstance();
$this->_checkFields($data);
- return $db->simpleUpdate('cotisations', $data, 'id = \''.(int) $id.'\'');
+ return $db->update('cotisations', $data, 'id = :id', ['id' => (int) $id]);
}
/**
* Supprimer une cotisation
* @param integer $id ID de la cotisation à supprimer
@@ -115,22 +113,24 @@
*/
public function delete($id)
{
$db = DB::getInstance();
- $db->exec('BEGIN;');
+ $db->begin();
// Inscrire à NULL les opérations liées à cette cotisation, ainsi on conserve le lien avec les membres
- $db->simpleExec('UPDATE membres_operations SET id_cotisation = NULL
- WHERE id_cotisation IN (SELECT id FROM cotisations_membres WHERE id_cotisation = ?);', (int) $id);
-
- $db->simpleExec('DELETE FROM rappels WHERE id_cotisation = ?;', (int) $id);
- $db->simpleExec('DELETE FROM rappels_envoyes WHERE id_cotisation = ?;', (int) $id);
-
- $db->simpleExec('DELETE FROM cotisations_membres WHERE id_cotisation = ?;', (int) $id);
- $db->simpleExec('DELETE FROM cotisations WHERE id = ?;', (int) $id);
- $db->exec('END;');
+ $db->update('membres_operations', ['id_cotisation' => null],
+ 'id_cotisation IN (SELECT id FROM cotisations_membres WHERE id_cotisation = :id_select)',
+ ['id_select' => (int) $id]);
+
+ $db->delete('rappels', 'id_cotisation = ?', (int) $id);
+ $db->delete('rappels_envoyes', 'id_cotisation = ?', (int) $id);
+
+ $db->delete('cotisations_membres', 'id_cotisation = ?', (int) $id);
+ $db->delete('cotisations', 'id = ?', (int) $id);
+
+ $db->commit();
return true;
}
/**
@@ -139,38 +139,38 @@
* @return array Infos de la cotisation
*/
public function get($id)
{
$db = DB::getInstance();
- return $db->simpleQuerySingle('SELECT co.*,
+ return $db->first('SELECT co.*,
(SELECT COUNT(DISTINCT id_membre) FROM cotisations_membres WHERE id_cotisation = co.id) AS nb_membres,
(SELECT COUNT(DISTINCT id_membre) FROM cotisations_membres AS cm WHERE id_cotisation = co.id
AND ((co.duree IS NOT NULL AND date(cm.date, \'+\'||co.duree||\' days\') >= date())
OR (co.fin IS NOT NULL AND co.debut <= cm.date AND co.fin >= cm.date))) AS nb_a_jour
- FROM cotisations AS co WHERE id = :id;', true, ['id' => (int) $id]);
+ FROM cotisations AS co WHERE id = :id;', ['id' => (int) $id]);
}
public function listByName()
{
$db = DB::getInstance();
- return $db->simpleStatementFetch('SELECT * FROM cotisations ORDER BY intitule;');
+ return $db->get('SELECT * FROM cotisations ORDER BY intitule;');
}
public function listCurrent()
{
$db = DB::getInstance();
- return $db->simpleStatementFetch('SELECT * FROM cotisations WHERE fin >= date(\'now\') OR fin IS NULL
+ return $db->get('SELECT * FROM cotisations WHERE fin >= date(\'now\') OR fin IS NULL
ORDER BY transliterate_to_ascii(intitule) COLLATE NOCASE;');
}
public function listWithStats()
{
$db = DB::getInstance();
- return $db->simpleStatementFetch('SELECT co.*,
+ return $db->get('SELECT co.*,
(SELECT COUNT(DISTINCT id_membre) FROM cotisations_membres WHERE id_cotisation = co.id) AS nb_membres,
(SELECT COUNT(DISTINCT id_membre) FROM cotisations_membres AS cm WHERE id_cotisation = co.id
AND ((co.duree IS NOT NULL AND date(cm.date, \'+\'||co.duree||\' days\') >= date())
OR (co.fin IS NOT NULL AND co.debut <= cm.date AND co.fin >= cm.date))) AS nb_a_jour
FROM cotisations AS co
ORDER BY transliterate_to_ascii(intitule) COLLATE NOCASE;');
}
}
Index: src/include/lib/Garradin/DB.php
==================================================================
--- src/include/lib/Garradin/DB.php
+++ src/include/lib/Garradin/DB.php
@@ -1,158 +1,149 @@
flags = \SQLITE3_OPEN_READWRITE;
if ($create)
{
- $flags |= SQLITE3_OPEN_CREATE;
+ $this->flags |= \SQLITE3_OPEN_CREATE;
+ }
+
+ // Ne pas se connecter ici, on ne se connectera que quand une requête sera faite
+ }
+
+ public function close()
+ {
+ $this->db->close();
+ $this->db = null;
+ }
+
+ public function connect()
+ {
+ if ($this->db)
+ {
+ return true;
}
- parent::__construct(DB_FILE, $flags);
+ $this->db = new \SQLite3(DB_FILE, $this->flags);
- $this->enableExceptions(true);
+ $this->db->enableExceptions(true);
// Le timeout par défaut est 0, on le met à 1 seconde, si ça ne suffit pas on augmentera plus tard
- $this->busyTimeout(1000);
+ $this->db->busyTimeout(1000);
// Activer les contraintes des foreign keys
- $this->exec('PRAGMA foreign_keys = ON;');
-
- $this->createFunction('transliterate_to_ascii', ['Garradin\Utils', 'transliterateToAscii']);
- $this->createFunction('base64', 'base64_encode');
- $this->createFunction('rank', [$this, 'sql_rank']);
- }
-
- public function sql_rank($aMatchInfo)
- {
- $iSize = 4; // byte size
- $iPhrase = (int) 0; // Current phrase //
- $score = (double)0.0; // Value to return //
-
- /* Check that the number of arguments passed to this function is correct.
- ** If not, jump to wrong_number_args. Set aMatchinfo to point to the array
- ** of unsigned integer values returned by FTS function matchinfo. Set
- ** nPhrase to contain the number of reportable phrases in the users full-text
- ** query, and nCol to the number of columns in the table.
- */
- $aMatchInfo = (string) func_get_arg(0);
- $nPhrase = ord(substr($aMatchInfo, 0, $iSize));
- $nCol = ord(substr($aMatchInfo, $iSize, $iSize));
-
- if (func_num_args() > (1 + $nCol))
- {
- throw new \Exception("Invalid number of arguments : ".$nCol);
- }
-
- // Iterate through each phrase in the users query. //
- for ($iPhrase = 0; $iPhrase < $nPhrase; $iPhrase++)
- {
- $iCol = (int) 0; // Current column //
-
- /* Now iterate through each column in the users query. For each column,
- ** increment the relevancy score by:
- **
- ** ( / ) *
- **
- ** aPhraseinfo[] points to the start of the data for phrase iPhrase. So
- ** the hit count and global hit counts for each column are found in
- ** aPhraseinfo[iCol*3] and aPhraseinfo[iCol*3+1], respectively.
- */
- $aPhraseinfo = substr($aMatchInfo, (2 + $iPhrase * $nCol * 3) * $iSize);
-
- for ($iCol = 0; $iCol < $nCol; $iCol++)
- {
- $nHitCount = ord(substr($aPhraseinfo, 3 * $iCol * $iSize, $iSize));
- $nGlobalHitCount = ord(substr($aPhraseinfo, (3 * $iCol + 1) * $iSize, $iSize));
- $weight = ($iCol < func_num_args() - 1) ? (double) func_get_arg($iCol + 1) : 0;
-
- if ($nHitCount > 0)
- {
- $score += ((double)$nHitCount / (double)$nGlobalHitCount) * $weight;
- }
- }
- }
-
- return $score;
+ $this->db->exec('PRAGMA foreign_keys = ON;');
+
+ $this->db->createFunction('transliterate_to_ascii', ['Garradin\Utils', 'transliterateToAscii']);
+ $this->db->createFunction('base64', 'base64_encode');
+ $this->db->createFunction('rank', ['\KD2\DB', 'sqlite_rank']);
+ $this->db->createFunction('haversine_distance', ['\KD2\DB', 'sqlite_haversine']);
}
public function escape($str)
{
- return $this->escapeString($str);
+ // escapeString n'est pas binary safe: https://bugs.php.net/bug.php?id=62361
+ $str = str_replace("\0", "\\0", $str);
+
+ $this->connect();
+ return $this->db->escapeString($str);
}
- public function e($str)
+ public function quote($str)
{
- return $this->escapeString($str);
+ return '\'' . $this->escape($str) . '\'';
}
public function begin()
{
- if (!$this->_transaction)
+ if (!$this->transaction)
{
- $this->exec('BEGIN;');
+ $this->connect();
+ $this->db->exec('BEGIN;');
}
- $this->_transaction++;
+ $this->transaction++;
- return $this->_transaction == 1 ? true : false;
+ return $this->transaction == 1 ? true : false;
}
public function commit()
{
- if ($this->_transaction == 1)
+ if ($this->transaction == 1)
{
- $this->exec('END;');
+ $this->connect();
+ $this->db->exec('END;');
}
- if ($this->_transaction > 0)
+ if ($this->transaction > 0)
{
- $this->_transaction--;
+ $this->transaction--;
}
- return $this->_transaction ? false : true;
+ return $this->transaction ? false : true;
}
public function rollback()
{
- $this->exec('ROLLBACK;');
- $this->_transaction = 0;
+ $this->connect();
+ $this->db->exec('ROLLBACK;');
+ $this->transaction = 0;
return true;
}
public function getArgType(&$arg, $name = '')
{
@@ -174,18 +165,45 @@
$type = $arg[0];
$arg = $arg[1];
return $type;
}
+ case 'object':
+ if ($arg instanceof \DateTime)
+ {
+ $arg = clone $arg;
+ $arg->setTimezone(new \DateTimezone('UTC'));
+ $arg = $arg->format(self::DATE_FORMAT);
+ return \SQLITE3_TEXT;
+ }
default:
throw new \InvalidArgumentException('Argument '.$name.' is of invalid type '.gettype($arg));
}
}
- public function simpleStatement($query, $args = [])
+ /**
+ * Performe une requête en utilisant les arguments contenus dans le tableau $args
+ * @param string $query Requête SQL
+ * @param array|object $args Arguments à utiliser comme bindings pour la requête
+ * @return \SQLite3Statement|boolean Retourne un booléen si c'est une requête
+ * qui exécute une opération d'écriture, ou un statement si c'est une requête de lecture.
+ *
+ * Note: le fait que cette fonction retourne un booléen est un comportement
+ * volontaire pour éviter un bug dans le module SQLite3 de PHP, qui provoque
+ * un risque de faire des opérations en double en cas d'exécution de
+ * ->fetchResult() sur un statement d'écriture.
+ */
+ public function query($query, Array $args = [])
{
- $statement = $this->prepare($query);
+ assert(is_string($query));
+ assert(is_array($args) || is_object($args));
+
+ // Forcer en tableau
+ $args = (array) $args;
+
+ $this->connect();
+ $statement = $this->db->prepare($query);
$nb = $statement->paramCount();
if (!empty($args))
{
if (is_array($args) && count($args) == 1 && is_array(current($args)))
@@ -202,10 +220,15 @@
if (is_int(key($args)))
{
foreach ($args as $i=>$arg)
{
+ if (is_string($i))
+ {
+ throw new \InvalidArgumentException(sprintf('%s requires argument to be a keyed array, but key %s is a string.', __FUNCTION__, $i));
+ }
+
$type = $this->getArgType($arg, $i+1);
$statement->bindValue((int)$i+1, $arg, $type);
}
}
else
@@ -212,194 +235,480 @@
{
foreach ($args as $key=>$value)
{
if (is_int($key))
{
- throw new \InvalidArgumentException(__FUNCTION__ . ' requires argument to be a named-associative array, but key '.$key.' is an integer.');
+ throw new \InvalidArgumentException(sprintf('%s requires argument to be a named-associative array, but key %s is an integer.', __FUNCTION__, $key));
}
$type = $this->getArgType($value, $key);
$statement->bindValue(':'.$key, $value, $type);
}
}
}
try {
- return $statement->execute();
+ // Return a boolean for write queries to avoid accidental duplicate execution
+ // see https://bugs.php.net/bug.php?id=64531
+
+ $result = $statement->execute();
+ return $statement->readOnly() ? $result : (bool) $result;
}
catch (\Exception $e)
{
- throw new \Exception($e->getMessage() . "\n" . $query . "\n" . json_encode($args, true));
- }
- }
-
- public function simpleStatementFetch($query, $mode = SQLITE3_BOTH)
- {
- if ($mode != SQLITE3_BOTH && $mode != SQLITE3_ASSOC && $mode != SQLITE3_NUM)
- {
- throw new \InvalidArgumentException('Mode argument should be either SQLITE3_BOTH, SQLITE3_ASSOC or SQLITE3_NUM.');
- }
-
- $args = array_slice(func_get_args(), 2);
- return $this->fetchResult($this->simpleStatement($query, $args), $mode);
- }
-
- public function simpleStatementFetchAssoc($query)
+ throw new \RuntimeException($e->getMessage() . "\n" . $query . "\n" . json_encode($args, true));
+ }
+ }
+
+ /**
+ * Exécute une requête et retourne le résultat sous forme de tableau
+ * @param string $query Requête SQL
+ * @return array Tableau contenant des objets
+ *
+ * Accepte un ou plusieurs arguments supplémentaires utilisés comme bindings
+ * pour la clause WHERE.
+ */
+ public function get($query)
+ {
+ $args = array_slice(func_get_args(), 1);
+
+ $out = [];
+
+ foreach ($this->fetch($this->query($query, $args), self::OBJ) as $key=>$row)
+ {
+ $out[$key] = $row;
+ }
+
+ return $out;
+ }
+
+ /**
+ * Exécute une requête et retourne le résultat sous forme de tableau associatif
+ * en utilisant les deux premières colonnes retournées,
+ * de la forme [colonne1 => colonne2, colonne1 => colonne2, ...]
+ * @param string $query Requête SQL
+ * @return array Tableau associatif
+ *
+ * Accepte un ou plusieurs arguments supplémentaires utilisés comme bindings
+ * pour la clause WHERE.
+ */
+ public function getAssoc($query)
+ {
+ $args = array_slice(func_get_args(), 1);
+
+ $out = [];
+
+ foreach ($this->fetchAssoc($this->query($query, $args)) as $key=>$row)
+ {
+ $out[$key] = $row;
+ }
+
+ return $out;
+ }
+
+ /**
+ * Exécute une requête et retourne le résultat sous forme de tableau associatif
+ * en utilisant la première colonne comme clé:
+ * [colonne1 => (object) [colonne1 => valeur1, colonne2 => valeur2, ...], ...]
+ * @param string $query Requête SQL
+ * @return array Tableau associatif contenant des objets
+ *
+ * Accepte un ou plusieurs arguments supplémentaires utilisés comme bindings
+ * pour la clause WHERE.
+ */
+ public function getAssocKey($query)
{
$args = array_slice(func_get_args(), 1);
- return $this->fetchResultAssoc($this->simpleStatement($query, $args));
- }
-
- public function simpleStatementFetchAssocKey($query, $mode = SQLITE3_BOTH)
- {
- if ($mode != SQLITE3_BOTH && $mode != SQLITE3_ASSOC && $mode != SQLITE3_NUM)
- {
- throw new \InvalidArgumentException('Mode argument should be either SQLITE3_BOTH, SQLITE3_ASSOC or SQLITE3_NUM.');
- }
-
- $args = array_slice(func_get_args(), 2);
- return $this->fetchResultAssocKey($this->simpleStatement($query, $args), $mode);
- }
-
- public function escapeAuto($value, $name = '')
- {
- $type = $this->getArgType($value, $name);
-
- switch ($type)
- {
- case \SQLITE3_FLOAT:
- return floatval($value);
- case \SQLITE3_INTEGER:
- return intval($value);
- case \SQLITE3_NULL:
- return 'NULL';
- case \SQLITE3_TEXT:
- return '\'' . $this->escapeString($value) . '\'';
- }
- }
-
- /**
- * Simple INSERT query
- */
- public function simpleInsert($table, $fields)
- {
+
+ $out = [];
+
+ foreach ($this->fetchAssocKey($this->query($query, $args), self::OBJ) as $key=>$row)
+ {
+ $out[$key] = $row;
+ }
+
+ return $out;
+ }
+
+ /**
+ * Insère une ligne dans la table $table, en remplissant avec les champs donnés
+ * dans $fields (tableau associatif ou objet)
+ * @param string $table Table où insérer
+ * @param string $fields Champs à remplir
+ * @return boolean
+ */
+ public function insert($table, $fields)
+ {
+ assert(is_array($fields) || is_object($fields));
+
+ $fields = (array) $fields;
+
$fields_names = array_keys($fields);
- return $this->simpleStatement('INSERT INTO '.$table.' ('.implode(', ', $fields_names).')
- VALUES (:'.implode(', :', $fields_names).');', $fields);
+ $query = sprintf('INSERT INTO %s (%s) VALUES (:%s);', $table,
+ implode(', ', $fields_names), implode(', :', $fields_names));
+
+ return $this->query($query, $fields);
}
- public function simpleUpdate($table, $fields, $where)
+ /**
+ * Met à jour une ou plusieurs lignes de la table
+ * @param string $table Nom de la table
+ * @param array|object $fields Liste des champs à mettre à jour
+ * @param string $where Clause WHERE
+ * @param array|object $args Arguments pour la clause WHERE
+ * @return boolean
+ */
+ public function update($table, $fields, $where = null, $args = [])
{
+ assert(is_string($table));
+ assert((is_string($where) && strlen($where)) || is_null($where));
+ assert(is_array($fields) || is_object($fields));
+ assert(is_array($args) || is_object($args));
+
+ // Forcer en tableau
+ $fields = (array) $fields;
+ $args = (array) $args;
+
+ // No fields to update? no need to do a query
if (empty($fields))
+ {
return false;
-
- $query = 'UPDATE '.$table.' SET ';
+ }
+ $column_updates = [];
+
foreach ($fields as $key=>$value)
{
- $query .= $key . ' = :'.$key.', ';
+ // Append to arguments
+ $args['field_' . $key] = $value;
+
+ $column_updates[] = sprintf('%s = :field_%s', $key, $key);
+ }
+
+ if (is_null($where))
+ {
+ $where = '1';
+ }
+
+ // Assemblage de la requête
+ $column_updates = implode(', ', $column_updates);
+ $query = sprintf('UPDATE %s SET %s WHERE %s;', $table, $column_updates, $where);
+
+ return $this->query($query, $args);
+ }
+
+ /**
+ * Supprime une ou plusieurs lignes d'une table
+ * @param string $table Nom de la table
+ * @param string $where Clause WHERE
+ * @return boolean
+ *
+ * Accepte un ou plusieurs arguments supplémentaires utilisés comme bindings
+ * pour la clause WHERE.
+ */
+ public function delete($table, $where)
+ {
+ $query = sprintf('DELETE FROM %s WHERE %s;', $table, $where);
+ return $this->query($query, array_slice(func_get_args(), 2));
+ }
+
+ /**
+ * Exécute une requête SQL (alias pour query)
+ * @param string $query Requête SQL
+ * @return boolean
+ *
+ * Accepte un ou plusieurs arguments supplémentaires utilisés comme bindings.
+ */
+ public function exec($query)
+ {
+ return $this->db->exec($query);
+ }
+
+ /**
+ * Import a file containing SQL commands
+ * Allows to use the statement ".import other_file.sql" to load other files
+ * @param string $file Path to file containing SQL commands
+ * @return boolean
+ */
+ public function import($file)
+ {
+ $sql = file_get_contents($file);
+
+ $dir = dirname($file);
+
+ $sql = preg_replace_callback('/^\.import (.+\.sql)$/m', function ($match) use ($dir) {
+ return file_get_contents($dir . DIRECTORY_SEPARATOR . $match[1]) . "\n";
+ }, $sql);
+
+ return $this->db->exec($sql);
+ }
+
+ /**
+ * Exécute une requête et retourne la première ligne
+ * @param string $query Requête SQL
+ * @return object
+ *
+ * Accepte un ou plusieurs arguments supplémentaires utilisés comme bindings.
+ */
+ public function first($query)
+ {
+ $res = $this->query($query, array_slice(func_get_args(), 1));
+
+ $row = $res->fetchArray(SQLITE3_ASSOC);
+ $res->finalize();
+
+ return is_array($row) ? (object) $row : false;
+ }
+
+ /**
+ * Exécute une requête et retourne la première colonne de la première ligne
+ * @param string $query Requête SQL
+ * @return object
+ *
+ * Accepte un ou plusieurs arguments supplémentaires utilisés comme bindings.
+ */
+ public function firstColumn($query)
+ {
+ $res = $this->query($query, array_slice(func_get_args(), 1));
+
+ $row = $res->fetchArray(\SQLITE3_NUM);
+
+ return count($row) > 0 ? $row[0] : false;
+ }
+
+ /**
+ * Récupère le résultat d'un statement
+ * @param \SQLite3Result $result Résultat de statement
+ * @param integer $mode Mode de récupération (BOTH, OBJ, NUM ou ASSOC)
+ * @return array
+ */
+ public function fetch(\SQLite3Result $result, $mode = null)
+ {
+ $as_obj = false;
+
+ if ($mode === self::OBJ)
+ {
+ $as_obj = true;
+ $mode = self::ASSOC;
+ }
+
+ while ($row = $result->fetchArray($mode))
+ {
+ yield ($as_obj ? (object) $row : $row);
+ }
+
+ $result->finalize();
+ unset($result, $row);
+
+ return;
+ }
+
+ /**
+ * Récupère le résultat d'un statement sous forme de tableau associatif
+ * avec colonne1 comme clé et colonne2 comme valeur.
+ *
+ * @param \SQLite3Result $result Résultat de statement
+ * @param integer $mode Mode de récupération (BOTH, OBJ, NUM ou ASSOC)
+ * @return array
+ */
+ protected function fetchAssoc(\SQLite3Result $result)
+ {
+ while ($row = $result->fetchArray(\SQLITE3_NUM))
+ {
+ yield $row[0] => $row[1];
+ }
+
+ $result->finalize();
+ unset($result, $row);
+
+ return;
+ }
+
+ /**
+ * Récupère le résultat d'un statement sous forme de tableau associatif
+ * avec colonne1 comme clé et la ligne comme valeur.
+ * @param \SQLite3Result $result Résultat de statement
+ * @param integer $mode Mode de récupération (BOTH, OBJ, NUM ou ASSOC)
+ * @return array
+ */
+ protected function fetchAssocKey(\SQLite3Result $result, $mode = null)
+ {
+ $as_obj = false;
+
+ if ($mode === self::OBJ)
+ {
+ $as_obj = true;
+ $mode = self::ASSOC;
+ }
+
+ while ($row = $result->fetchArray($mode))
+ {
+ $key = current($row);
+ yield $key => ($as_obj ? (object) $row : $row);
+ }
+
+ $result->finalize();
+ unset($result, $row, $key);
+
+ return;
+ }
+
+ /**
+ * Compte le nombre de lignes dans un résultat
+ * @param \SQLite3Result $result Résultat SQLite3
+ * @return integer
+ */
+ public function countRows(\SQLite3Result $result)
+ {
+ $i = 0;
+
+ while ($result->fetchArray(\SQLITE3_NUM))
+ {
+ $i++;
}
- $query = substr($query, 0, -2);
- $query .= ' WHERE '.$where.';';
- return $this->simpleStatement($query, $fields);
+ $result->reset();
+
+ return $i;
+ }
+
+ public function lastInsertRowId()
+ {
+ return $this->db->lastInsertRowId();
+ }
+
+ /**
+ * Préparer un statement SQLite3
+ * @param string $query Requête SQL
+ * @return \SQLite3Statement
+ */
+ public function prepare($query)
+ {
+ return $this->db->prepare($query);
+ }
+
+ /**
+ * @deprecated
+ */
+ public function simpleInsert($table, Array $fields)
+ {
+ return $this->insert($table, $fields);
+ }
+
+ /**
+ * @deprecated
+ */
+ public function simpleUpdate($table, Array $fields, $where)
+ {
+ return $this->update($table, $fields, $where);
}
/**
- * Formats and escapes a statement and then returns the result of exec()
+ * @deprecated
*/
public function simpleExec($query)
{
return $this->simpleStatement($query, array_slice(func_get_args(), 1));
}
- public function simpleQuerySingle($query, $all_columns = false)
- {
- $res = $this->simpleStatement($query, array_slice(func_get_args(), 2));
-
- $row = $res->fetchArray($all_columns ? SQLITE3_ASSOC : SQLITE3_NUM);
-
- if (!$all_columns)
- {
- if (isset($row[0]))
- return $row[0];
- return false;
- }
- else
- {
- return $row;
- }
- }
-
- public function queryFetch($query, $mode = SQLITE3_BOTH)
- {
- return $this->fetchResult($this->query($query), $mode);
- }
-
- public function queryFetchAssoc($query)
- {
- return $this->fetchResultAssoc($this->query($query));
- }
-
- public function queryFetchAssocKey($query, $mode = SQLITE3_BOTH)
- {
- return $this->fetchResultAssocKey($this->query($query), $mode);
- }
-
- public function fetchResult($result, $mode = \SQLITE3_BOTH)
- {
- $out = [];
-
- while ($row = $result->fetchArray($mode))
- {
- $out[] = $row;
- }
-
- $result->finalize();
- unset($result, $row);
-
- return $out;
- }
-
- protected function fetchResultAssoc($result)
- {
- $out = [];
-
- while ($row = $result->fetchArray(SQLITE3_NUM))
- {
- $out[$row[0]] = $row[1];
- }
-
- $result->finalize();
- unset($result, $row);
-
- return $out;
- }
-
- protected function fetchResultAssocKey($result, $mode = \SQLITE3_BOTH)
- {
+ /**
+ * @deprecated
+ */
+ public function escapeString($str)
+ {
+ return $this->escape($str);
+ }
+
+ /**
+ * @deprecated
+ */
+ public function simpleStatement($query, Array $args = [])
+ {
+ return $this->query($query, $args);
+ }
+
+ /**
+ * @deprecated
+ */
+ public function simpleStatementFetch($query, $mode = null)
+ {
+ $args = array_slice(func_get_args(), 2);
+ $result = $this->query($query, $args);
+ return $this->fetch($result, $mode);
+ }
+
+ /**
+ * @deprecated
+ */
+ public function simpleStatementFetchAssoc($query)
+ {
+ $args = array_slice(func_get_args(), 1);
+ return $this->getAssoc($query, $args);
+ }
+
+ /**
+ * @deprecated
+ */
+ public function simpleStatementFetchAssocKey($query, $mode = self::ASSOC)
+ {
+ $args = array_slice(func_get_args(), 2);
+ $result = $this->query($query, $args);
$out = [];
while ($row = $result->fetchArray($mode))
{
$key = current($row);
$out[$key] = $row;
}
- $result->finalize();
- unset($result, $row);
-
- return $out;
- }
-
- public function countRows($result)
- {
- $i = 0;
-
- while ($result->fetchArray(SQLITE3_NUM))
- $i++;
-
- return $i;
- }
-}
-
-?>
+ return $out;
+ }
+
+ /**
+ * @deprecated
+ */
+ public function queryFetch($query, $mode = null)
+ {
+ return $this->fetch($this->query($query), $mode);
+ }
+
+ /**
+ * @deprecated
+ */
+ public function queryFetchAssoc($query)
+ {
+ return $this->fetchAssoc($this->query($query));
+ }
+
+ /**
+ * @deprecated
+ */
+ public function queryFetchAssocKey($query, $mode = null)
+ {
+ return $this->fetchAssocKey($this->query($query), $mode);
+ }
+
+ /**
+ * @deprecated
+ */
+ public function simpleQuerySingle($query, $all_columns = false)
+ {
+ $res = $this->query($query, array_slice(func_get_args(), 2));
+
+ $row = $res->fetchArray($all_columns ? SQLITE3_ASSOC : SQLITE3_NUM);
+ $res->finalize();
+
+ if (!$all_columns)
+ {
+ if (isset($row[0]))
+ {
+ return $row[0];
+ }
+
+ return false;
+ }
+ else
+ {
+ return $row;
+ }
+ }
+}
Index: src/include/lib/Garradin/Fichiers.php
==================================================================
--- src/include/lib/Garradin/Fichiers.php
+++ src/include/lib/Garradin/Fichiers.php
@@ -205,11 +205,11 @@
* @return boolean TRUE en cas de succès
*/
public function remove()
{
$db = DB::getInstance();
- $db->exec('BEGIN;');
+ $db->begin();
$db->simpleExec('DELETE FROM fichiers_compta_journal WHERE fichier = ?;', (int)$this->id);
$db->simpleExec('DELETE FROM fichiers_wiki_pages WHERE fichier = ?;', (int)$this->id);
$db->simpleExec('DELETE FROM fichiers_membres WHERE fichier = ?;', (int)$this->id);
$db->simpleExec('DELETE FROM fichiers WHERE id = ?;', (int)$this->id);
@@ -228,11 +228,11 @@
foreach (self::$allowed_thumb_sizes as $size)
{
Static_Cache::remove($cache_id . '.thumb.' . (int)$size);
}
- return $db->exec('END;');
+ return $db->commit();
}
/**
* Renvoie le chemin vers le fichier local en cache, et le crée s'il n'existe pas
* @return string Chemin local
@@ -452,11 +452,11 @@
$hash = sha1_file($file['tmp_name']);
$size = filesize($file['tmp_name']);
$db = DB::getInstance();
- $db->exec('BEGIN;');
+ $db->begin();
// Il peut arriver que l'on renvoie ici un fichier déjà stocké, auquel cas, ne pas le re-stocker
if (!($id_contenu = $db->simpleQuerySingle('SELECT id FROM fichiers_contenu WHERE hash = ?;', false, $hash)))
{
$db->simpleInsert('fichiers_contenu', [
@@ -473,11 +473,11 @@
'nom' => $name,
'type' => $type,
'image' => (int)$is_image,
]);
- $db->exec('END;');
+ $db->commit();
return new Fichiers($db->lastInsertRowID());
}
/**
@@ -579,36 +579,45 @@
* @param string $content Contenu éventuel (en mode bloc)
* @param object $skriv Objet SkrivLite
*/
static public function SkrivFichier($args, $content, $skriv)
{
- $_args = [];
+ $id = $caption = null;
foreach ($args as $value)
{
- if (preg_match('/^\d+$/', $value))
+ if (preg_match('/^\d+$/', $value) && !$id)
{
- $_args['id'] = (int)$value;
+ $id = (int)$value;
break;
}
+ else
+ {
+ $caption = trim($value);
+ }
}
- if (empty($_args['id']))
+ if (empty($id))
{
return $skriv->parseError('/!\ Tag fichier : aucun numéro de fichier indiqué.');
}
try {
- $file = new Fichiers($_args['id']);
+ $file = new Fichiers($id);
}
catch (\InvalidArgumentException $e)
{
return $skriv->parseError('/!\ Tag fichier : ' . $e->getMessage());
}
+
+ if (empty($caption))
+ {
+ $caption = $file->nom;
+ }
$out = '';
return $out;
}
@@ -618,36 +627,38 @@
* @param string $content Contenu éventuel (en mode bloc)
* @param object $skriv Objet SkrivLite
*/
static public function SkrivImage($args, $content, $skriv)
{
- $_args = ['align' => 'centre'];
- $_align_values = ['droite', 'gauche', 'centre'];
+ static $align_values = ['droite', 'gauche', 'centre'];
+
+ $align = '';
+ $id = $caption = null;
foreach ($args as $value)
{
- if (preg_match('/^\d+$/', $value) && !array_key_exists('id', $_args))
+ if (preg_match('/^\d+$/', $value) && !$id)
{
- $_args['id'] = (int)$value;
+ $id = (int)$value;
}
- else if (in_array($value, $_align_values) && !array_key_exists('align', $_args))
+ else if (in_array($value, $_align_values) && !$align)
{
- $_args['align'] = $value;
+ $align = $value;
}
else
{
- $_args['caption'] = $value;
+ $caption = $value;
}
}
- if (empty($_args['id']))
+ if (!$id)
{
return $skriv->parseError('/!\ Tag image : aucun numéro de fichier indiqué.');
}
try {
- $file = new Fichiers($_args['id']);
+ $file = new Fichiers($id);
}
catch (\InvalidArgumentException $e)
{
return $skriv->parseError('/!\ Tag image : ' . $e->getMessage());
}
@@ -655,35 +666,30 @@
if (!$file->image)
{
return $skriv->parseError('/!\ Tag image : ce fichier n\'est pas une image.');
}
- if (empty($_args['caption']))
- {
- $_args['caption'] = false;
- }
-
- $out = '';
- $out .= 'getURL().'" class="internal-image">';
+ $out .= '';
- if (!empty($_args['align']))
+ if (!empty($align))
{
- $out = '