Compare commits

..
49 Commits
Author SHA1 Message Date
pvincent 88b2549243 tagged as 1.10.0 2021-08-27 22:04:15 +04:00
pvincent a1ece5258a sendemail is working 2021-08-27 22:03:50 +04:00
pvincent 6bcec1c572 tagged as 1.9.0 2021-08-27 17:54:30 +04:00
pvincent 445448f43b sugoi internally added 2021-08-27 17:54:17 +04:00
pvincent ef31c26525 tagged as 1.8.2 2021-08-27 17:41:56 +04:00
pvincent ed931b7b2c App.sendMail 2021-08-27 17:41:44 +04:00
pvincent 089f137636 tagged as 1.8.1 2021-08-26 17:11:58 +04:00
pvincent 0426a175aa App.log 2021-08-26 17:11:16 +04:00
pvincent 59b7c48a89 tagged as 1.8.0 2021-08-25 22:23:45 +04:00
pvincent d13c85be61 force semver 2021-08-25 22:23:28 +04:00
pvincent 2714f44b44 README 2021-08-25 22:21:59 +04:00
pvincent 04cb4343ad semver 2021-08-25 22:20:29 +04:00
pvincent e94c4eae67 App.log for DEBUG 2021-08-25 22:17:03 +04:00
pvincent 47abd95962 tagged as 1.7.3 2021-08-25 19:23:35 +04:00
pvincent c346584d46 change readme 2021-08-25 19:23:20 +04:00
cagettepei.lxd bbeea934e4 tagged as 1.7.2 2021-08-25 19:10:48 +04:00
cagettepei.lxd 3c8a2507d9 no package-lock.json 2021-08-25 19:10:35 +04:00
cagettepei.lxd 6c0e632ef2 tagged as 1.7.1 2021-08-25 19:06:37 +04:00
cagettepei.lxd f94e005a91 fix Makefile 2021-08-25 19:06:24 +04:00
cagettepei.lxd 15f89039b3 tagged as 1.7.0 2021-08-25 19:01:52 +04:00
cagettepei.lxd 0d3a32a018 no more cagette-pro and tests 2021-08-25 19:01:17 +04:00
pvincent 2641e70eaf update README 2021-08-12 21:59:43 +04:00
pvincent 0ebb698c54 tagged as 1.6.0 2021-08-12 21:55:17 +04:00
pvincent 3621289fee install from scratch 2021-08-12 21:55:06 +04:00
pvincent 544db390ef install from scratch 2021-08-12 21:54:14 +04:00
pvincent f357d716b7 tagged as 1.5.4 2021-08-12 21:34:28 +04:00
pvincent a49bee1a52 fix: gitignore 2021-08-12 21:34:20 +04:00
pvincent 286bfd14ea tagged as 1.5.3 2021-08-12 21:31:45 +04:00
pvincent 068288ddd1 fix: cagetteJs.hxml 2021-08-12 21:31:37 +04:00
pvincent 30d4c85250 tagged as 1.5.2 2021-08-12 21:29:21 +04:00
pvincent 6170e5f499 change: config.xml.dist 2021-08-12 21:29:14 +04:00
pvincent b1d56d85c0 fix: git missing 2021-08-12 21:25:59 +04:00
pvincent 3c76533541 git via https 2021-08-12 15:40:56 +04:00
pvincent 076c03d6c6 git via https 2021-08-12 15:40:09 +04:00
beta3:ct7 4f1b4d90a6 tagged as 1.5.1 2021-03-10 21:54:53 +04:00
beta3:ct7 10f916f262 export commande moins de colonnes (special Maddie) 2021-03-10 21:54:21 +04:00
artnuc.ct9 744581bad9 tagged as 1.5.0 2021-03-10 21:40:13 +04:00
artnuc.ct9 b80fe490dc export commandes à la Maddie, orienté numéro commande 2021-03-10 21:39:49 +04:00
artnuc.ct9 179e35a88b tagged as 1.4.11 2021-03-05 11:37:03 +04:00
artnuc.ct9 28b825db07 zouritpad link 2021-03-05 11:36:49 +04:00
artnuc.ct9 d8c72cf249 tagged as 1.4.10 2021-03-05 10:45:11 +04:00
artnuc.ct9 c4250cc201 package.json 2021-03-05 10:44:58 +04:00
artnuc.ct9 f54f78b03e tagged as 1.4.9 2021-03-05 10:43:09 +04:00
artnuc.ct9 97a9c1c4e3 newlogo 2021-03-05 10:33:07 +04:00
artnuc.ct9 6ca6e89cd5 gt symbol 2021-03-04 20:47:54 +04:00
artnuc.ct9 f5d09537b9 no more XXX 2021-03-04 20:30:31 +04:00
artnuc.ct9 a5c34cc6a0 XXX 2021-03-04 20:27:52 +04:00
artnuc.ct9 5c9fe38c4b Makefile 2021-03-04 20:20:33 +04:00
artnuc.ct9 feff65ab36 email cagettePéi 2021-03-04 19:57:04 +04:00
134 changed files with 10129 additions and 10134 deletions
+3 -2
View File
@@ -1,6 +1,7 @@
*.n *.n
*.php *.php
config.xml config.xml
package-lock.json
www/plugin/* www/plugin/*
tmp/* tmp/*
*node_modules* *node_modules*
@@ -29,9 +30,9 @@ www/file/*
www/js/app.js www/js/app.js
www/js/libs.js www/js/libs.js
www/css/.sass-cache www/css/.sass-cache
www/css/*.css www/css/*.css
!www/css/bootstrap.min.css !www/css/bootstrap.min.css
config-dev.xml
config-prod.xml
+1
View File
@@ -0,0 +1 @@
1.10.0
+2 -22
View File
@@ -26,12 +26,6 @@ install_dev:
#template tools #template tools
haxelib run templo haxelib run templo
sudo mv temploc2 /usr/bin sudo mv temploc2 /usr/bin
#extra plugins
@if [ $(PLUGINS) = 1 ]; then \
haxelib git cagette-pro git@bitbucket.org:bubar/cagette-pro.git; \
haxelib git cagette-hosted git@bitbucket.org:bubar/cagette-hosted.git; \
haxelib git cagette-wholesale-order git@github.com:bablukid/cagette-wholesale-order.git; \
fi
#install npm dependencies #install npm dependencies
npm install npm install
npm run libs:dev npm run libs:dev
@@ -50,7 +44,7 @@ compile:
echo "compile Cagette.net with plugins"; \ echo "compile Cagette.net with plugins"; \
haxe cagetteAllPlugins.hxml; \ haxe cagetteAllPlugins.hxml; \
else \ else \
echo "compile Cagette.net core"; \ echo "compile CagettePéi core"; \
haxe cagette.hxml; \ haxe cagette.hxml; \
fi fi
@@ -80,22 +74,8 @@ templates:
@make LANG=fr ctemplates @make LANG=fr ctemplates
msgfmt www/lang/texts_fr.po -o www/lang/texts_fr.mo msgfmt www/lang/texts_fr.po -o www/lang/texts_fr.mo
chown www-data:www-data -R www chown www-data:www-data -R www
chown www-data:www-data -R lang
ctemplates: ctemplates:
# (cd lang/$(LANG)/tpl; temploc2 -macros macros.mtt -output ../tmp/ *.mtt */*.mtt */*/*.mtt */*/*/*.mtt */*/*/*/*.mtt)
(cd lang/$(LANG)/tpl; temploc2 -macros macros.mtt -output ../tmp/ *.mtt */*.mtt */*/*.mtt) (cd lang/$(LANG)/tpl; temploc2 -macros macros.mtt -output ../tmp/ *.mtt */*.mtt */*/*.mtt)
#Compile and run unit tests
tests:
@if [ $(PLUGINS) = 1 ]; then \
haxe testsPlugins.hxml; \
else \
haxe tests.hxml; \
fi
@docker-compose run --workdir="/var/www/www" neko neko tests.n mysql://root:root@mysql/tests
cp_plugin:
cp -R .haxelib/cagette-hosted/git/tpl/* lang/master/tpl/plugin/hosted/
cp -R .haxelib/cagette-pro/git/tpl/* lang/master/tpl/plugin/pro/
cp -R .haxelib/cagette-wholesale-order/git/tpl/* lang/master/tpl/plugin/who/
+167 -1
View File
@@ -1,8 +1,174 @@
# Cagette péi CAGETTE PEI
===========
Fourche du logiciel [Cagette.net](https://github.com/CagetteNet) à partir de la version 0.9.2 Novembre 2018 Fourche du logiciel [Cagette.net](https://github.com/CagetteNet) à partir de la version 0.9.2 Novembre 2018
Certaines portions du code source de Cagette.net n'étant plus disponibles en téléchargement, Certaines portions du code source de Cagette.net n'étant plus disponibles en téléchargement,
j'ai repris sur la base d'un existant pour adapter au marché local réunionnais et leurs AMAPéi. j'ai repris sur la base d'un existant pour adapter au marché local réunionnais et leurs AMAPéi.
## INSTALL FROM SCRATCH (debian-buster)
* apt install -y curl
* curl -L https://deb.nodesource.com/setup_8.x | bash -
* apt install -y nodejs=8.17.0-1nodesource1
* apt install -y sudo haxe apache2 make git imagemagick gettext libapache2-mod-neko mariadb-server
* mysql -u root <<EOF
CREATE DATABASE cagettepei;
GRANT ALL ON cagettepei.* TO 'cagettepei'@'%' IDENTIFIED BY 'cagettepei';
EOF
* cd /var/www
* git clone https://git.artcode.re/cagetters/cagettepei.git
* cd cagettepei
* make install ENV=dev # first time
* make compile # classes
* make templates # templates + i18n
* vim /etc/apache2/sites-available/cagettepei.conf
```
<VirtualHost *:80>
ServerName cagettepei
DirectoryIndex index.n
DocumentRoot /var/www/cagettepei/www/
</VirtualHost>
```
* sed -i '/<Directory \/var\/www\/>/,/<\/Directory>/ s/AllowOverride None/AllowOverride All/' /etc/apache2/apache2.conf
* a2dissite 000-default.conf
* a2ensite cagettepei.conf
* a2enmod neko rewrite
* systemctl restart apache2
## CONFIGURATION
* cd /var/www/cagettepei
* vim config.xml
```
<config
host="<DOMAIN.TLD>"
key="<SECRET_PHRASE>"
name = "CagettePéi"
default_email = "<EMAIL>"
webmaster_email = "<EMAIL>"
database="mysql://cagettepei:cagettepei@localhost/cagettepei"
smtp_host="localhost"
smtp_port="25"
smtp_user=""
smtp_pass=""
lang="fr"
langs="fr"
langnames="Français"
maintain="0"
debug="0"
sqllog="0"
cache="1"
cachetpl="0"
/>
```
ADMIN RESET PASSWORD
--------------------
go to http://localhost for ERROR + table regeneration, then
* cd /var/www/cagettepei
* SECRET=$(grep key= config.xml | cut -d= -f2 | cut -d'"' -f2)
* echo "insert into User values (1,'fr',MD5('${SECRET}cagettepei'),1,'Administrateur','Cagette Péi', 'admin', null, null, null, null, null, null, null, null, null, now(), now(),6,null, null)" | mysql -h localhost -u cagettepei -pcagettepei cagettepei
then, connect with admin / cagettepei
### change admin password
* echo "update User set pass=MD5('${SECRET}cagettepei') where id=1" | mysql -h localhost -u cagettepei -pcagettepei cagettepei
SYSTEMD-TIMERS
--------------
* vim /var/www/cagettepei-batch
```
#!/bin/bash
case $1 in
minute);;
daily);;
*) echo "expected [minute|daily]" && exit 1;;
esac
SELECTOR=$1
for i in /var/www/cagettepei-* ; do
if [[ -d $i ]]; then
cd $i/www
echo "cron-$SELECTOR in: $i"
neko index.n cron/$SELECTOR
echo
fi
done
```
* chmod +x /var/www/cagettepei-batch
* vim /etc/systemd/system/cagettepei-batch-minute.service
```
[Unit]
Description=Run batch cagettepei every minute
[Service]
User=www-data
SyslogIdentifier=cagettepei
ExecStart=/var/www/cagettepei-batch minute
[Install]
WantedBy=multi-user.target
```
* vim /etc/systemd/system/cagettepei-batch-day.service
```
[Unit]
Description=Run batch cagettepei every day
[Service]
User=www-data
SyslogIdentifier=cagettepei
ExecStart=/var/www/cagettepei-batch daily
[Install]
WantedBy=multi-user.target
```
* vim /etc/systemd/system/cagettepei-batch-minute.timer
```
[Unit]
Description=Timer for batch cagettepei every minute
Requires=apache2.service
[Timer]
OnCalendar=minutely
Unit=cagettepei-batch-minute.service
[Install]
WantedBy=timers.target
```
* vim /etc/systemd/system/cagettepei-batch-day.timer
```
[Unit]
Description=Timer for batch cagettepei every day
Requires=apache2.service
[Timer]
OnCalendar=daily
Unit=cagettepei-batch-day.service
[Install]
WantedBy=timers.target
```
* systemctl daemon-reload
* systemctl edit apache2 # bind the 2 timers to apache2 cagette
```
[Unit]
BindsTo = cagettepei-batch-minute.timer cagettepei-batch-day.timer
```
* systemctl enable cagettepei-batch-minute.timer cagettepei-batch-day.timer
* systemctl start cagettepei-batch-minute.timer cagettepei-batch-day.timer
-22
View File
@@ -1,22 +0,0 @@
-lib dbadmin
-lib templo
-lib weblog
-lib sugoi
-lib hxevents
-lib cagette-hosted
-lib hscript
-lib ufront-mail
-cp common
-cp src
-php www/
-D spod_macro
-main App
-D debugip=localhost:18080
-D weblogid=cagette
--next
-dce no
-cp common
-cp js
-lib sugoi
-main App
-js www/js/app.js
+3 -3
View File
@@ -23,9 +23,9 @@
-lib tink_typecrawler:0.7.0 -lib tink_typecrawler:0.7.0
## GIT ## GIT
-lib sugoi:git:git@artcode.re:cagetters/sugoi.git # -lib sugoi:git:https://git.artcode.re/cagetters/sugoi.git
-lib record-macros:git:git@artcode.re:cagetters/record-macros.git -lib record-macros:git:https://git.artcode.re/cagetters/record-macros.git
-lib dbadmin:git:git@artcode.re:cagetters/dbadmin.git -lib dbadmin:git:https://git.artcode.re/cagetters/dbadmin.git
## SCRIPT ## SCRIPT
-cp common -cp common
-26
View File
@@ -1,26 +0,0 @@
-lib templo
-lib hxevents
-lib thx.semver
-lib thx.csv
-lib datetime
-lib hscript
-lib smtpmailer
-lib tink_json
-lib sugoi:git:https://github.com/bablukid/sugoi#master
-lib record-macros:git:https://github.com/HaxeFoundation/record-macros#master
-lib dbadmin:git:https://github.com/ncannasse/dbadmin.git#master
-lib cagette-pro:git:https://bubar@bitbucket.org/bubar/cagette-pro.git#master
-lib cagette-hosted:git:https://bubar@bitbucket.org/bubar/cagette-hosted.git#master
-lib cagette-wholesale-order:git:https://github.com/bablukid/cagette-wholesale-order.git#master
-cp common
-cp src
-cp lang/master/tpl
-D plugins
-neko www/index.n
-main App
-76
View File
@@ -1,76 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<project version="2">
<!-- Output SWF options -->
<output>
<movie outputType="Application" />
<movie input="" />
<movie path="www\index.n" />
<movie fps="30" />
<movie width="800" />
<movie height="600" />
<movie version="0" />
<movie minorVersion="0" />
<movie platform="Neko" />
<movie background="#FFFFFF" />
</output>
<!-- Other classes to be compiled into your SWF -->
<classpaths>
<class path="common" />
<class path="src" />
<class path="lang\master\tpl" />
</classpaths>
<!-- Build options -->
<build>
<option directives="plugins" />
<option flashStrict="False" />
<option noInlineOnDebug="False" />
<option mainClass="App" />
<option enabledebug="False" />
<option additional="" />
</build>
<!-- haxelib libraries -->
<haxelib>
<library name="templo" />
<library name="thx.semver" />
<library name="thx.csv" />
<library name="hxevents" />
<library name="hscript" />
<library name="record-macros" />
<library name="smtpmailer" />
<library name="datetime" />
<library name="sugoi:git:https://github.com/bablukid/sugoi#i18n" />
<library name="record-macros:git:https://github.com/HaxeFoundation/record-macros#maste" />
<library name="dbadmin:git:https://github.com/ncannasse/dbadmin.git#master" />
<library name="cagette-pro:git:https://bubar@bitbucket.org/bubar/cagette-pro.git#master" />
<library name="cagette-connector:git:https://bubar@bitbucket.org/bubar/cagette-connector.git#master" />
<library name="cagette-wholesale-order:git:https://bubar@bitbucket.org/bubar/cagette-wholesale-order.git#master" />
<library name="cagette-hosted:git:https://bubar@bitbucket.org/bubar/cagette-hosted.git#master" />
</haxelib>
<!-- Class files to compile (other referenced classes will automatically be included) -->
<compileTargets>
<!-- example: <compile path="..." /> -->
</compileTargets>
<!-- Paths to exclude from the Project Explorer tree -->
<hiddenPaths>
<hidden path="project.hxml" />
<hidden path="lang\fr\tmp" />
<hidden path="preLaunch" />
<hidden path="art" />
<hidden path="www\file" />
<hidden path="config.tpl.xml" />
<hidden path="clearFileCache.bat" />
<hidden path="obj" />
</hiddenPaths>
<!-- Executed before build -->
<preBuildCommand />
<!-- Executed after build -->
<postBuildCommand alwaysRun="False" />
<!-- Other project options -->
<options>
<option showHiddenPaths="True" />
<option testMovie="Custom" />
<option testMovieCommand="" />
</options>
<!-- Plugin storage -->
<storage />
</project>
-17
View File
@@ -1,17 +0,0 @@
-dce no
-cp common
-cp js
-lib sugoi:git:https://github.com/bablukid/sugoi#i18n
-lib hxleaflet:git:https://github.com/bablukid/hxleaflet
-lib cagette-hosted
-lib react
-lib react-router-4
-lib modular
-lib tink_json
-D plugins
-D no-deprecation-warnings
-main App
-js www/js/app.js
+2 -2
View File
@@ -1,8 +1,8 @@
-dce no -dce no
-cp common -cp common
-cp js -cp js
-lib sugoi:git:git@artcode.re:cagetters/sugoi.git -lib sugoi:git:https://git.artcode.re/cagetters/sugoi.git
-lib hxleaflet:git:git@artcode.re:cagetters/hxleaflet.git -lib hxleaflet:git:https://git.artcode.re/cagetters/hxleaflet.git
-lib react:1.4.0 -lib react:1.4.0
-lib react-router-4:0.2.3 -lib react-router-4:0.2.3
-lib modular:0.10.15 -lib modular:0.10.15
+3 -3
View File
@@ -3,9 +3,9 @@
database = "mysql://<USER>:<PASSWORD>@<HOST>/<DBNAME>" database = "mysql://<USER>:<PASSWORD>@<HOST>/<DBNAME>"
host = "<URL without http>" host = "<URL without http>"
name = "<HTML head title>" name = "<HTML head title>"
default_email = "<default email>" default_email = "<EMAIL>"
webmaster_email = "<EMAIL>"
key="carotteMagique" key = "<SECRET_PHRASE>"
maintain="0" maintain="0"
cache="1" cache="1"
+4 -2
View File
@@ -142,9 +142,11 @@
<a href="http://www.cagette.net/wiki" target="_blank">::_("Documentation")::</a> <a href="http://www.cagette.net/wiki" target="_blank">::_("Documentation")::</a>
</li> </li>
<li> <li>
::if (user!=null && user.isAmapManager()):: <a href="https://cagette.uservoice.com" target="_blank">::_("Propose features")::</a> ::end:: <a href="https://pad1.zourit.net/p/4a3b09afd25b4fdcb3f0a9598db21e61" target="_blank">Proposer une amélioration</a>
</li>
<li>
<a href="https://pad1.zourit.net/p/4a3b09afd25b4fdcb3f0a9598db21e61" target="_blank">Soumettre une anomalie</a>
</li> </li>
</ul> </ul>
<!--<p style="position: absolute;display: block;top: 96px;width: 700px;"> <!--<p style="position: absolute;display: block;top: 96px;width: 700px;">
+1 -1
View File
@@ -17,7 +17,7 @@ table.table td{
<hr/> <hr/>
<div style="color:#666;font-size:12px;"> <div style="color:#666;font-size:12px;">
::_("This e-mail was sent from"):: <a href="http://::HOST::">::_("Cagette.net")::</a>. &gt; ::_("This e-mail was sent from"):: <a href="http://::raw HOST::">::_("Cagette.net")::</a>.
::if group!=null:: ::if group!=null::
<br/> <br/>
+1 -1
View File
@@ -2,7 +2,7 @@
<div style="max-width:400px;margin:auto;border:solid 1px #AAA;border-radius:8px;padding:12px;text-align:center;font-family:Arial;"> <div style="max-width:400px;margin:auto;border:solid 1px #AAA;border-radius:8px;padding:12px;text-align:center;font-family:Arial;">
<img src="http://::HOST::/img/logo2.png" title="Cagette.net" /> <img src="http://::HOST::/img/logo2.png" title="CagettePéi" />
<p> <p>
::_("Hello"):: $$nullSafe(::name::), ::_("Hello"):: $$nullSafe(::name::),
-6187
View File
File diff suppressed because it is too large Load Diff
+4 -5
View File
@@ -1,9 +1,7 @@
{ {
"name": "Cagette.net", "name": "CagettePei",
"description": "le logiciel libre du circuit court", "description": "le logiciel libre du circuit court",
"author": "François Barbut <francois@alilo.fr>", "license": "AGPL-3.0",
"license": "GPL-2.0",
"homepage": "http://www.cagette.net",
"dependencies": { "dependencies": {
"bootstrap": "^3.3.4", "bootstrap": "^3.3.4",
"eonasdan-bootstrap-datetimepicker": "^4.7.14", "eonasdan-bootstrap-datetimepicker": "^4.7.14",
@@ -48,5 +46,6 @@
}, },
"bugs": { "bugs": {
"url": "https://github.com/bablukid/cagette/issues" "url": "https://github.com/bablukid/cagette/issues"
} },
"version": "1.10.0"
} }
+41 -32
View File
@@ -3,7 +3,6 @@ import thx.semver.Version;
import Common; import Common;
class App extends sugoi.BaseApp { class App extends sugoi.BaseApp {
public static var current:App = null; public static var current:App = null;
public static var t:sugoi.i18n.translator.ITranslator; public static var t:sugoi.i18n.translator.ITranslator;
public static var config = sugoi.BaseApp.config; public static var config = sugoi.BaseApp.config;
@@ -24,7 +23,6 @@ class App extends sugoi.BaseApp {
} }
public static function main() { public static function main() {
App.t = sugoi.form.Form.translator = new sugoi.i18n.translator.TMap(getTranslationArray(), "fr"); App.t = sugoi.form.Form.translator = new sugoi.i18n.translator.TMap(getTranslationArray(), "fr");
sugoi.BaseApp.main(); sugoi.BaseApp.main();
} }
@@ -51,8 +49,10 @@ class App extends sugoi.BaseApp {
} }
public function getCurrentGroup() { public function getCurrentGroup() {
if (session == null) return null; if (session == null)
if (session.data == null ) return null; return null;
if (session.data == null)
return null;
var a = session.data.amapId; var a = session.data.amapId;
if (a == null) { if (a == null) {
return null; return null;
@@ -62,7 +62,6 @@ class App extends sugoi.BaseApp {
} }
override function beforeDispatch() { override function beforeDispatch() {
// send "current page" event // send "current page" event
event(Page(this.uri)); event(Page(this.uri));
@@ -71,22 +70,26 @@ class App extends sugoi.BaseApp {
public function getPlugin(name:String):sugoi.plugin.IPlugIn { public function getPlugin(name:String):sugoi.plugin.IPlugIn {
for (p in plugins) { for (p in plugins) {
if (p.getName() == name) return p; if (p.getName() == name)
return p;
} }
return null; return null;
} }
public static function log(t:Dynamic) { public static function log(t:Dynamic, ?infos:haxe.PosInfos) {
if(App.config.DEBUG) { var message:String = '[${infos.fileName}:${infos.lineNumber} ${infos.className}#${infos.methodName}] ';
neko.Web.logMessage(Std.string(t)); //write in Apache error log message += Std.string(t);
#if weblog
Weblog.log(t); //write en Weblog console (https://lib.haxe.org/p/weblog/) // trace(message);
#end if (neko.Web.isModNeko)
} neko.Web.logMessage(message);
else
Sys.println(message);
} }
public function event(e:Event) { public function event(e:Event) {
if(e==null) return null; if (e == null)
return null;
this.eventDispatcher.dispatch(e); this.eventDispatcher.dispatch(e);
return e; return e;
} }
@@ -231,20 +234,28 @@ class App extends sugoi.BaseApp {
} }
public static function getMailer():sugoi.mail.IMailer { public static function getMailer():sugoi.mail.IMailer {
var typeOfMailer = sugoi.db.Variable.get("mailer");
App.log('type of mailer is <$typeOfMailer>');
var mailer : sugoi.mail.IMailer = new sugoi.mail.BufferedMailer(); if (typeOfMailer == null) {
if(App.config.DEBUG){
//Dev env : emails are written to tmp folder
mailer = new sugoi.mail.DebugMailer();
}else{
if (sugoi.db.Variable.get("mailer") == null){
var msg = sugoi.i18n.Locale.texts._("Please configure the email settings in a <href='/admin/emails'>this section</a>"); var msg = sugoi.i18n.Locale.texts._("Please configure the email settings in a <href='/admin/emails'>this section</a>");
throw sugoi.ControllerAction.ErrorAction("/", msg); throw sugoi.ControllerAction.ErrorAction("/", msg);
} }
if (App.config.DEBUG) {
App.log('mailer in DEBUG mode, no deferred mail from CRON');
var conf = {
smtp_host: sugoi.db.Variable.get("smtp_host"),
smtp_port: sugoi.db.Variable.getInt("smtp_port"),
smtp_user: sugoi.db.Variable.get("smtp_user"),
smtp_pass: sugoi.db.Variable.get("smtp_pass")
};
return new sugoi.mail.SendEmailMailer().init(conf);
}
var mailer:sugoi.mail.IMailer = new sugoi.mail.BufferedMailer();
if (sugoi.db.Variable.get("mailer") == "mandrill") { if (sugoi.db.Variable.get("mailer") == "mandrill") {
// Buffered emails with Mandrill // Buffered emails with Mandrill
untyped mailer.defineFinalMailer("mandrill"); untyped mailer.defineFinalMailer("mandrill");
@@ -252,7 +263,6 @@ class App extends sugoi.BaseApp {
// Buffered emails with SMTP // Buffered emails with SMTP
untyped mailer.defineFinalMailer("smtp"); untyped mailer.defineFinalMailer("smtp");
} }
}
return mailer; return mailer;
} }
@@ -260,18 +270,20 @@ class App extends sugoi.BaseApp {
* Send an email * Send an email
*/ */
public static function sendMail(m:sugoi.mail.Mail, ?group:db.Amap, ?listId:String, ?sender:db.User) { public static function sendMail(m:sugoi.mail.Mail, ?group:db.Amap, ?listId:String, ?sender:db.User) {
if (group == null)
if (group == null) group = App.current.user == null ? null:App.current.user.getAmap(); group = App.current.user == null ? null : App.current.user.getAmap();
current.event(SendEmail(m)); current.event(SendEmail(m));
var params = group == null ? null : {remoteId: group.id}; var params = group == null ? null : {remoteId: group.id};
App.log('about to send email with subject=<${m.getSubject()}> ...');
getMailer().send(m, params, function(o) {}); getMailer().send(m, params, function(o) {});
} }
public static function quickMail(to:String, subject:String, html:String, ?group:db.Amap) { public static function quickMail(to:String, subject:String, html:String, ?group:db.Amap) {
App.log('quickMail subject=$subject');
var e = new sugoi.mail.Mail(); var e = new sugoi.mail.Mail();
e.setSubject(subject); e.setSubject(subject);
e.setRecipient(to); e.setRecipient(to);
@@ -287,7 +299,6 @@ class App extends sugoi.BaseApp {
* @param ctx * @param ctx
*/ */
public function processTemplate(tpl:String, ctx:Dynamic):String { public function processTemplate(tpl:String, ctx:Dynamic):String {
Reflect.setField(ctx, 'HOST', App.config.HOST); Reflect.setField(ctx, 'HOST', App.config.HOST);
Reflect.setField(ctx, 'hDate', App.current.view.hDate); Reflect.setField(ctx, 'hDate', App.current.view.hDate);
// i18n functions // i18n functions
@@ -297,11 +308,9 @@ class App extends sugoi.BaseApp {
var tpl = loadTemplate(tpl); var tpl = loadTemplate(tpl);
var html = tpl.execute(ctx); var html = tpl.execute(ctx);
#if php #if php
if ( html.substr(0, 4) == "null") html = html.substr(4); if (html.substr(0, 4) == "null")
html = html.substr(4);
#end #end
return html; return html;
} }
} }
+133 -127
View File
@@ -1,4 +1,5 @@
package controller; package controller;
import db.UserContract; import db.UserContract;
import sugoi.form.elements.Checkbox; import sugoi.form.elements.Checkbox;
import sugoi.form.elements.Selectbox; import sugoi.form.elements.Selectbox;
@@ -6,19 +7,20 @@ import sugoi.form.Form;
import sugoi.form.elements.StringInput; import sugoi.form.elements.StringInput;
import Common; import Common;
import datetime.DateTime; import datetime.DateTime;
using tools.ObjectListTool; using tools.ObjectListTool;
using tools.DateTool; using tools.DateTool;
import service.OrderService; import service.OrderService;
class ContractAdmin extends Controller class ContractAdmin extends Controller {
{ public function new() {
public function new()
{
super(); super();
if (!app.user.isContractManager()) throw Error("/", t._("You don't have the authorization to manage contracts")); if (!app.user.isContractManager())
throw Error("/", t._("You don't have the authorization to manage contracts"));
view.nav = ["contractadmin"]; view.nav = ["contractadmin"];
App.log("first message");
} }
public function sendNav(c) { public function sendNav(c) {
@@ -33,7 +35,6 @@ class ContractAdmin extends Controller
*/ */
@tpl("contractadmin/default.mtt") @tpl("contractadmin/default.mtt")
function doDefault(?args:{old:Bool}) { function doDefault(?args:{old:Bool}) {
view.nav.push("default"); view.nav.push("default");
var now = Date.now(); var now = Date.now();
@@ -48,7 +49,8 @@ class ContractAdmin extends Controller
// filter if current user is not manager // filter if current user is not manager
if (!app.user.isAmapManager()) { if (!app.user.isAmapManager()) {
for (c in Lambda.array(contracts).copy()) { for (c in Lambda.array(contracts).copy()) {
if(!app.user.canManageContract(c)) contracts.remove(c); if (!app.user.canManageContract(c))
contracts.remove(c);
} }
} }
@@ -65,7 +67,6 @@ class ContractAdmin extends Controller
if(md.hasOnlyConstantOrders()) multidistribs.remove(md); if(md.hasOnlyConstantOrders()) multidistribs.remove(md);
}*/ }*/
view.multidistribs = multidistribs; view.multidistribs = multidistribs;
} else { } else {
view.multidistribs = []; view.multidistribs = [];
} }
@@ -79,14 +80,18 @@ class ContractAdmin extends Controller
view.nav.push("products"); view.nav.push("products");
sendNav(contract); sendNav(contract);
if (!app.user.canManageContract(contract)) throw Error("/", t._("Access forbidden") ); if (!app.user.canManageContract(contract))
throw Error("/", t._("Access forbidden"));
view.c = contract; view.c = contract;
// checks // checks
if (app.user.amap.hasShopMode() && !app.user.amap.hasTaxonomy()) { if (app.user.amap.hasShopMode() && !app.user.amap.hasTaxonomy()) {
for (p in contract.getProducts(false)) { for (p in contract.getProducts(false)) {
if (p.getCategories().length == 0) { if (p.getCategories().length == 0) {
app.session.addMessage(t._("Warning, at least one product does not have any category. <a href='/product/categorize/::contractid::'>Click here to add categories</a>", {contractid:contract.id}), true); app.session.addMessage(t._("Warning, at least one product does not have any category. <a href='/product/categorize/::contractid::'>Click here to add categories</a>",
{
contractid: contract.id
}), true);
break; break;
} }
} }
@@ -94,7 +99,6 @@ class ContractAdmin extends Controller
// batch enable / disable products // batch enable / disable products
if (args != null) { if (args != null) {
if (args.disable != null) { if (args.disable != null) {
var pids = Lambda.array(Lambda.map(args.disable.split("|"), function(x) return Std.parseInt(x))); var pids = Lambda.array(Lambda.map(args.disable.split("|"), function(x) return Std.parseInt(x)));
service.ProductService.batchDisableProducts(pids); service.ProductService.batchDisableProducts(pids);
@@ -104,14 +108,12 @@ class ContractAdmin extends Controller
var pids = Lambda.array(Lambda.map(args.enable.split("|"), function(x) return Std.parseInt(x))); var pids = Lambda.array(Lambda.map(args.enable.split("|"), function(x) return Std.parseInt(x)));
service.ProductService.batchEnableProducts(pids); service.ProductService.batchEnableProducts(pids);
} }
} }
// generate a token // generate a token
checkToken(); checkToken();
} }
/** /**
* - hidden page - * - hidden page -
* copy products from a contract to an other * copy products from a contract to an other
@@ -125,7 +127,6 @@ class ContractAdmin extends Controller
form.addElement(new sugoi.form.elements.Selectbox("source", t._("Copy products from: "), Lambda.array(contracts))); form.addElement(new sugoi.form.elements.Selectbox("source", t._("Copy products from: "), Lambda.array(contracts)));
form.addElement(new sugoi.form.elements.Checkbox("delete", t._("Delete existing products (all orders will be deleted!)"))); form.addElement(new sugoi.form.elements.Checkbox("delete", t._("Delete existing products (all orders will be deleted!)")));
if (form.checkToken()) { if (form.checkToken()) {
if (form.getValueOf("delete") == "1") { if (form.getValueOf("delete") == "1") {
for (p in contract.getProducts()) { for (p in contract.getProducts()) {
p.lock(); p.lock();
@@ -145,11 +146,8 @@ class ContractAdmin extends Controller
} }
throw Ok("/contractAdmin/products/" + contract.id, t._("Products copied from ") + source.name); throw Ok("/contractAdmin/products/" + contract.id, t._("Products copied from ") + source.name);
} }
view.form = form; view.form = form;
} }
@@ -159,7 +157,6 @@ class ContractAdmin extends Controller
*/ */
@tpl('contractadmin/calendar.mtt') @tpl('contractadmin/calendar.mtt')
public function doCalendar() { public function doCalendar() {
var contracts = db.Contract.getActiveContracts(app.user.amap, true, false); var contracts = db.Contract.getActiveContracts(app.user.amap, true, false);
// Events of the month in a calendar // Events of the month in a calendar
@@ -203,18 +200,13 @@ class ContractAdmin extends Controller
v.push({name: t._("End of orders") + d.contract.name, color: Calendar.COLOR_ORDER}); v.push({name: t._("End of orders") + d.contract.name, color: Calendar.COLOR_ORDER});
cal.set(k, v); cal.set(k, v);
} }
} }
} }
} }
var n = Date.now(); var n = Date.now();
view.now = new Date(n.getFullYear(), n.getMonth(), n.getDate(), 0, 0, 0).getTime(); view.now = new Date(n.getFullYear(), n.getMonth(), n.getDate(), 0, 0, 0).getTime();
view.calendar = Calendar.mapToArray(cal); view.calendar = Calendar.mapToArray(cal);
} }
/** /**
@@ -222,16 +214,13 @@ class ContractAdmin extends Controller
*/ */
@tpl('contractadmin/ordersByTimeFrame.mtt') @tpl('contractadmin/ordersByTimeFrame.mtt')
function doOrdersByTimeFrame(?from:Date, ?to:Date /*, ?place:db.Place*/) { function doOrdersByTimeFrame(?from:Date, ?to:Date /*, ?place:db.Place*/) {
if (from == null) { if (from == null) {
var f = new sugoi.form.Form("listBydate", null, sugoi.form.Form.FormMethod.GET); var f = new sugoi.form.Form("listBydate", null, sugoi.form.Form.FormMethod.GET);
var now = DateTime.now(); var now = DateTime.now();
var from = now.snap(Month(Down)).getDate(); var from = now.snap(Month(Down)).getDate();
var to = now.snap(Month(Up)).add(Day(-1)).getDate(); var to = now.snap(Month(Up)).add(Day(-1)).getDate();
var el = new sugoi.form.elements.DatePicker("from", t._("Start date"), from, true); var el = new sugoi.form.elements.DatePicker("from", t._("Start date"), from, true);
el.format = 'LL'; el.format = 'LL';
f.addElement(el); f.addElement(el);
@@ -248,32 +237,33 @@ class ContractAdmin extends Controller
app.setTemplate("form.mtt"); app.setTemplate("form.mtt");
if (f.checkToken()) { if (f.checkToken()) {
var url = '/contractAdmin/ordersByTimeFrame/' + f.getValueOf("from").toString().substr(0, 10) + "/"
var url = '/contractAdmin/ordersByTimeFrame/' + f.getValueOf("from").toString().substr(0, 10) +"/"+f.getValueOf("to").toString().substr(0, 10); + f.getValueOf("to").toString().substr(0, 10);
// var p = f.getValueOf("placeId"); // var p = f.getValueOf("placeId");
// if (p != null) url += "/"+p; // if (p != null) url += "/"+p;
throw Redirect(url); throw Redirect(url);
} }
return; return;
} else { } else {
var d1 = tools.DateTool.setHourMinute(from, 0, 0); var d1 = tools.DateTool.setHourMinute(from, 0, 0);
var d2 = tools.DateTool.setHourMinute(to, 23, 59); var d2 = tools.DateTool.setHourMinute(to, 23, 59);
var contracts = app.user.amap.getActiveContracts(true); var contracts = app.user.amap.getActiveContracts(true);
var cconst = []; var cconst = [];
var cvar = []; var cvar = [];
for (c in contracts) { for (c in contracts) {
if (c.type == db.Contract.TYPE_CONSTORDERS) cconst.push(c.id); if (c.type == db.Contract.TYPE_CONSTORDERS)
if (c.type == db.Contract.TYPE_VARORDER) cvar.push(c.id); cconst.push(c.id);
if (c.type == db.Contract.TYPE_VARORDER)
cvar.push(c.id);
} }
// distribs // distribs
var vdistribs = db.Distribution.manager.search(($contractId in cvar) && $date >= d1 && $date <= d2 /*&& place.id==$placeId*/, false); var vdistribs = db.Distribution.manager.search(($contractId in cvar) && $date >= d1 && $date <= d2 /*&& place.id==$placeId*/, false);
var cdistribs = db.Distribution.manager.search(($contractId in cconst) && $date >= d1 && $date <= d2 /*&& place.id==$placeId*/, false); var cdistribs = db.Distribution.manager.search(($contractId in cconst) && $date >= d1 && $date <= d2 /*&& place.id==$placeId*/, false);
if (vdistribs.length == 0 && cdistribs.length == 0) throw Error("/contractAdmin/ordersByDate", t._("There is no delivery at this date")); if (vdistribs.length == 0 && cdistribs.length == 0)
throw Error("/contractAdmin/ordersByDate", t._("There is no delivery at this date"));
// varying orders // varying orders
var varorders = db.UserContract.manager.search($distributionId in vdistribs.getIds(), {orderBy: userId}); var varorders = db.UserContract.manager.search($distributionId in vdistribs.getIds(), {orderBy: userId});
@@ -293,11 +283,7 @@ class ContractAdmin extends Controller
view.from = from; view.from = from;
view.to = to; view.to = to;
view.ctotal = app.params.exists("ctotal"); view.ctotal = app.params.exists("ctotal");
} }
} }
/** /**
@@ -308,7 +294,6 @@ class ContractAdmin extends Controller
@tpl('contractadmin/ordersByDate.mtt') @tpl('contractadmin/ordersByDate.mtt')
function doOrdersByDate(?date:Date, ?place:db.Place) { function doOrdersByDate(?date:Date, ?place:db.Place) {
if (date == null) { if (date == null) {
var f = new sugoi.form.Form("listBydate", null, sugoi.form.Form.FormMethod.GET); var f = new sugoi.form.Form("listBydate", null, sugoi.form.Form.FormMethod.GET);
var el = new sugoi.form.elements.DatePicker("date", t._("Delivery date"), true); var el = new sugoi.form.elements.DatePicker("date", t._("Delivery date"), true);
el.format = 'LL'; el.format = 'LL';
@@ -324,32 +309,33 @@ class ContractAdmin extends Controller
app.setTemplate("form.mtt"); app.setTemplate("form.mtt");
if (f.checkToken()) { if (f.checkToken()) {
var url = '/contractAdmin/ordersByDate/' + f.getValueOf("date").toString().substr(0, 10); var url = '/contractAdmin/ordersByDate/' + f.getValueOf("date").toString().substr(0, 10);
var p = f.getValueOf("placeId"); var p = f.getValueOf("placeId");
if (p != null) url += "/"+p; if (p != null)
url += "/" + p;
throw Redirect(url); throw Redirect(url);
} }
return; return;
} else { } else {
var d1 = date.setHourMinute(0, 0); var d1 = date.setHourMinute(0, 0);
var d2 = date.setHourMinute(23, 59); var d2 = date.setHourMinute(23, 59);
var contracts = app.user.amap.getActiveContracts(true); var contracts = app.user.amap.getActiveContracts(true);
var cconst = []; var cconst = [];
var cvar = []; var cvar = [];
for (c in contracts) { for (c in contracts) {
if (c.type == db.Contract.TYPE_CONSTORDERS) cconst.push(c.id); if (c.type == db.Contract.TYPE_CONSTORDERS)
if (c.type == db.Contract.TYPE_VARORDER) cvar.push(c.id); cconst.push(c.id);
if (c.type == db.Contract.TYPE_VARORDER)
cvar.push(c.id);
} }
// distribs // distribs
var vdistribs = db.Distribution.manager.search(($contractId in cvar) && $date >= d1 && $date <= d2 && place.id == $placeId, false); var vdistribs = db.Distribution.manager.search(($contractId in cvar) && $date >= d1 && $date <= d2 && place.id == $placeId, false);
var cdistribs = db.Distribution.manager.search(($contractId in cconst) && $date >= d1 && $date <= d2 && place.id == $placeId, false); var cdistribs = db.Distribution.manager.search(($contractId in cconst) && $date >= d1 && $date <= d2 && place.id == $placeId, false);
if (vdistribs.length == 0 && cdistribs.length == 0) throw Error("/contractAdmin/ordersByDate", t._("There is no delivery at this date")); if (vdistribs.length == 0 && cdistribs.length == 0)
throw Error("/contractAdmin/ordersByDate", t._("There is no delivery at this date"));
// varying orders // varying orders
var varorders = db.UserContract.manager.search($distributionId in vdistribs.getIds(), {orderBy: userId}); var varorders = db.UserContract.manager.search($distributionId in vdistribs.getIds(), {orderBy: userId});
@@ -372,13 +358,11 @@ class ContractAdmin extends Controller
} }
} }
/** /**
* Global view on orders, producer view * Global view on orders, producer view
*/ */
@tpl('contractadmin/vendorsByDate.mtt') @tpl('contractadmin/vendorsByDate.mtt')
function doVendorsByDate(date:Date, place:db.Place) { function doVendorsByDate(date:Date, place:db.Place) {
var d1 = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0); var d1 = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
var d2 = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59); var d2 = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59);
var contracts = app.user.amap.getActiveContracts(true); var contracts = app.user.amap.getActiveContracts(true);
@@ -387,8 +371,8 @@ class ContractAdmin extends Controller
// distribs for both types in active contracts // distribs for both types in active contracts
var distribs = db.Distribution.manager.search(($contractId in cids) && $date >= d1 && $date <= d2 && $place == place, false); var distribs = db.Distribution.manager.search(($contractId in cids) && $date >= d1 && $date <= d2 && $place == place, false);
if (distribs.length == 0)
if ( distribs.length == 0 ) throw Error("/contractAdmin/ordersByDate", t._("There is no delivery at this date")); throw Error("/contractAdmin/ordersByDate", t._("There is no delivery at this date"));
var out = new Map<Int, Dynamic>(); // key : vendor id var out = new Map<Int, Dynamic>(); // key : vendor id
@@ -399,10 +383,8 @@ class ContractAdmin extends Controller
if (o == null) { if (o == null) {
out.set(vid, {contract: d.contract, distrib: d, orders: service.ReportService.getOrdersByProduct(d)}); out.set(vid, {contract: d.contract, distrib: d, orders: service.ReportService.getOrdersByProduct(d)});
} else { } else {
// add orders with existing ones // add orders with existing ones
for (x in service.ReportService.getOrdersByProduct(d)) { for (x in service.ReportService.getOrdersByProduct(d)) {
// find record in existing orders // find record in existing orders
var f:Dynamic = Lambda.find(o.orders, function(a) return a.pid == x.pid); var f:Dynamic = Lambda.find(o.orders, function(a) return a.pid == x.pid);
if (f == null) { if (f == null) {
@@ -422,13 +404,11 @@ class ContractAdmin extends Controller
view.date = date; view.date = date;
} }
/** /**
* Global view on orders, producer view * Global view on orders, producer view
*/ */
@tpl('contractadmin/vendorsByTimeFrame.mtt') @tpl('contractadmin/vendorsByTimeFrame.mtt')
function doVendorsByTimeFrame(from:Date, to:Date) { function doVendorsByTimeFrame(from:Date, to:Date) {
var d1 = tools.DateTool.setHourMinute(from, 0, 0); var d1 = tools.DateTool.setHourMinute(from, 0, 0);
var d2 = tools.DateTool.setHourMinute(to, 23, 59); var d2 = tools.DateTool.setHourMinute(to, 23, 59);
var contracts = app.user.amap.getActiveContracts(true); var contracts = app.user.amap.getActiveContracts(true);
@@ -436,7 +416,8 @@ class ContractAdmin extends Controller
// distribs for both types in active contracts // distribs for both types in active contracts
var distribs = db.Distribution.manager.search(($contractId in cids) && $date >= d1 && $date <= d2 /*&& $place==place*/, false); var distribs = db.Distribution.manager.search(($contractId in cids) && $date >= d1 && $date <= d2 /*&& $place==place*/, false);
if ( distribs.length == 0 ) throw Error("/contractAdmin/", t._("There is no delivery during this period")); if (distribs.length == 0)
throw Error("/contractAdmin/", t._("There is no delivery during this period"));
var out = new Map<Int, {contract:db.Contract, distrib:db.Distribution, orders:Array<OrderByProduct>}>(); // key : vendor id var out = new Map<Int, {contract:db.Contract, distrib:db.Distribution, orders:Array<OrderByProduct>}>(); // key : vendor id
@@ -447,10 +428,8 @@ class ContractAdmin extends Controller
if (o == null) { if (o == null) {
out.set(vid, {contract: d.contract, distrib: d, orders: service.ReportService.getOrdersByProduct(d)}); out.set(vid, {contract: d.contract, distrib: d, orders: service.ReportService.getOrdersByProduct(d)});
} else { } else {
// add orders with existing ones // add orders with existing ones
for (x in service.ReportService.getOrdersByProduct(d)) { for (x in service.ReportService.getOrdersByProduct(d)) {
// find record in existing orders // find record in existing orders
var f:OrderByProduct = Lambda.find(o.orders, function(a:OrderByProduct) return a.pid == x.pid); var f:OrderByProduct = Lambda.find(o.orders, function(a:OrderByProduct) return a.pid == x.pid);
if (f == null) { if (f == null) {
@@ -476,11 +455,28 @@ class ContractAdmin extends Controller
var orders = []; var orders = [];
for (x in out) { for (x in out) {
// empty line // empty line
orders.push({"quantity":null, "pname":null, "ref":null, "priceHT":null, "priceTTC":null, "totalHT":null, "totalTTC":null}); orders.push({
orders.push({"quantity":null, "pname":x.contract.vendor.name, "ref":null, "priceHT":null, "priceTTC":null, "totalHT":null, "totalTTC":null}); "quantity": null,
"pname": null,
"ref": null,
"priceHT": null,
"priceTTC": null,
"totalHT": null,
"totalTTC": null
});
orders.push({
"quantity": null,
"pname": x.contract.vendor.name,
"ref": null,
"priceHT": null,
"priceTTC": null,
"totalHT": null,
"totalTTC": null
});
for (o in x.orders) { for (o in x.orders) {
if(o.vat==null) o.vat = 0; if (o.vat == null)
o.vat = 0;
orders.push({ orders.push({
"quantity": view.formatNum(o.quantity), "quantity": view.formatNum(o.quantity),
"pname": o.pname, "pname": o.pname,
@@ -506,9 +502,9 @@ class ContractAdmin extends Controller
}); });
totalTTC = 0; totalTTC = 0;
totalHT = 0; totalHT = 0;
} }
var fileName = t._("Orders from the ::fromDate:: to the ::toDate:: per supplier.csv", {fromDate:from.toString().substr(0, 10), toDate:to.toString().substr(0, 10)}); var fileName = t._("Orders from the ::fromDate:: to the ::toDate:: per supplier.csv",
{fromDate: from.toString().substr(0, 10), toDate: to.toString().substr(0, 10)});
sugoi.tools.Csv.printCsvDataFromObjects(orders, ["quantity", "pname", "ref", "priceHT", "priceTTC", "totalHT", "totalTTC"], fileName); sugoi.tools.Csv.printCsvDataFromObjects(orders, ["quantity", "pname", "ref", "priceHT", "priceTTC", "totalHT", "totalTTC"], fileName);
return; return;
} }
@@ -517,7 +513,6 @@ class ContractAdmin extends Controller
view.to = to; view.to = to;
} }
/** /**
* Overview of orders for this contract in backoffice * Overview of orders for this contract in backoffice
*/ */
@@ -527,7 +522,8 @@ class ContractAdmin extends Controller
sendNav(contract); sendNav(contract);
// Checking permissions // Checking permissions
if (!app.user.canManageContract(contract)) throw Error("/", t._("You do not have the authorization to manage this contract")); if (!app.user.canManageContract(contract))
throw Error("/", t._("You do not have the authorization to manage this contract"));
if (contract.type == db.Contract.TYPE_VARORDER && args != null && args.d == null) { if (contract.type == db.Contract.TYPE_VARORDER && args != null && args.d == null) {
throw Redirect("/contractAdmin/selectDistrib/" + contract.id); throw Redirect("/contractAdmin/selectDistrib/" + contract.id);
} }
@@ -544,7 +540,6 @@ class ContractAdmin extends Controller
} else { } else {
throw Ok("/contractAdmin/orders/" + contract.id, t._("The order has been deleted.")); throw Ok("/contractAdmin/orders/" + contract.id, t._("The order has been deleted."));
} }
} }
var d = null; var d = null;
@@ -556,7 +551,6 @@ class ContractAdmin extends Controller
var orders = db.UserContract.getOrders(contract, d, app.params.exists("csv")); var orders = db.UserContract.getOrders(contract, d, app.params.exists("csv"));
if (!app.params.exists("csv")) { if (!app.params.exists("csv")) {
// show orders on disabled products // show orders on disabled products
var disabledProducts = 0; var disabledProducts = 0;
for (o in orders) { for (o in orders) {
@@ -569,18 +563,16 @@ class ContractAdmin extends Controller
view.disabledProducts = disabledProducts; view.disabledProducts = disabledProducts;
view.orders = orders; view.orders = orders;
} }
} }
/** /**
* hidden feature : updates orders by setting current product price. * hidden feature : updates orders by setting current product price.
*/ */
function doUpdatePrices(contract:db.Contract, args:{?d:db.Distribution}) { function doUpdatePrices(contract:db.Contract, args:{?d:db.Distribution}) {
sendNav(contract); sendNav(contract);
if (!app.user.canManageContract(contract)) throw Error("/", t._("You do not have the authorization to manage this contract")); if (!app.user.canManageContract(contract))
throw Error("/", t._("You do not have the authorization to manage this contract"));
if (contract.type == db.Contract.TYPE_VARORDER && args.d == null) { if (contract.type == db.Contract.TYPE_VARORDER && args.d == null) {
throw Redirect("/contractAdmin/selectDistrib/" + contract.id); throw Redirect("/contractAdmin/selectDistrib/" + contract.id);
} }
@@ -597,7 +589,6 @@ class ContractAdmin extends Controller
o.feesRate = contract.percentageValue; o.feesRate = contract.percentageValue;
} }
o.update(); o.update();
} }
throw Ok("/contractAdmin/orders/" + contract.id + "?d=" + args.d.id, t._("Prices are now up to date.")); throw Ok("/contractAdmin/orders/" + contract.id + "?d=" + args.d.id, t._("Prices are now up to date."));
} }
@@ -607,9 +598,9 @@ class ContractAdmin extends Controller
*/ */
@tpl("form.mtt") @tpl("form.mtt")
function doDuplicate(contract:db.Contract) { function doDuplicate(contract:db.Contract) {
sendNav(contract); sendNav(contract);
if (!app.user.isAmapManager()) throw Error("/", t._("You do not have the authorization to manage this contract")); if (!app.user.isAmapManager())
throw Error("/", t._("You do not have the authorization to manage this contract"));
view.title = "Dupliquer le contrat '" + contract.name + "'"; view.title = "Dupliquer le contrat '" + contract.name + "'";
var form = new Form("duplicate"); var form = new Form("duplicate");
@@ -619,7 +610,6 @@ class ContractAdmin extends Controller
form.addElement(new Checkbox("copyDeliveries", t._("Copy deliveries"), true)); form.addElement(new Checkbox("copyDeliveries", t._("Copy deliveries"), true));
if (form.checkToken()) { if (form.checkToken()) {
var nc = new db.Contract(); var nc = new db.Contract();
nc.name = form.getValueOf("name"); nc.name = form.getValueOf("name");
nc.startDate = contract.startDate; nc.startDate = contract.startDate;
@@ -641,7 +631,6 @@ class ContractAdmin extends Controller
ua.giveRight(ContractAdmin(nc.id)); ua.giveRight(ContractAdmin(nc.id));
} }
if (form.getValueOf("copyProducts") == true) { if (form.getValueOf("copyProducts") == true) {
var prods = contract.getProducts(); var prods = contract.getProducts();
for (source_p in prods) { for (source_p in prods) {
@@ -695,27 +684,27 @@ class ContractAdmin extends Controller
view.form = form; view.form = form;
} }
/** /**
* Orders grouped by product * Orders grouped by product
*/ */
@tpl("contractadmin/ordersByProduct.mtt") @tpl("contractadmin/ordersByProduct.mtt")
function doOrdersByProduct(contract:db.Contract, args:{?d:db.Distribution}) { function doOrdersByProduct(contract:db.Contract, args:{?d:db.Distribution}) {
sendNav(contract); sendNav(contract);
if (!app.user.canManageContract(contract)) throw Error("/", t._("You do not have the authorization to manage this contract")); if (!app.user.canManageContract(contract))
if (contract.type == db.Contract.TYPE_VARORDER && args.d == null ) throw Redirect("/contractAdmin/selectDistrib/" + contract.id); throw Error("/", t._("You do not have the authorization to manage this contract"));
if (contract.type == db.Contract.TYPE_VARORDER && args.d == null)
throw Redirect("/contractAdmin/selectDistrib/" + contract.id);
var d = args != null ? args.d : null; var d = args != null ? args.d : null;
if (d == null) d = contract.getDistribs(false).first(); if (d == null)
if (d == null) throw Error("/contractAdmin/orders/"+contract.id,t._("There is no delivery in this contract, please create at least one distribution.")); d = contract.getDistribs(false).first();
if (d == null)
throw Error("/contractAdmin/orders/" + contract.id, t._("There is no delivery in this contract, please create at least one distribution."));
var orders = service.ReportService.getOrdersByProduct(d, app.params.exists("csv")); var orders = service.ReportService.getOrdersByProduct(d, app.params.exists("csv"));
view.orders = orders; view.orders = orders;
view.distribution = d; view.distribution = d;
view.c = contract; view.c = contract;
} }
/** /**
@@ -723,17 +712,21 @@ class ContractAdmin extends Controller
*/ */
@tpl("contractadmin/ordersByProductList.mtt") @tpl("contractadmin/ordersByProductList.mtt")
function doOrdersByProductList(contract:db.Contract, args:{?d:db.Distribution}) { function doOrdersByProductList(contract:db.Contract, args:{?d:db.Distribution}) {
sendNav(contract); sendNav(contract);
if (!app.user.canManageContract(contract)) throw Error("/", t._("You do not have the authorization to manage this contract")); if (!app.user.canManageContract(contract))
if (contract.type == db.Contract.TYPE_VARORDER && args.d == null ) throw Redirect("/contractAdmin/selectDistrib/" + contract.id); throw Error("/", t._("You do not have the authorization to manage this contract"));
if (contract.type == db.Contract.TYPE_VARORDER && args.d == null)
throw Redirect("/contractAdmin/selectDistrib/" + contract.id);
if (contract.type == db.Contract.TYPE_VARORDER ) view.distribution = args.d; if (contract.type == db.Contract.TYPE_VARORDER)
view.distribution = args.d;
view.c = contract; view.c = contract;
view.group = contract.amap; view.group = contract.amap;
var d = args != null ? args.d : null; var d = args != null ? args.d : null;
if (d == null) d = contract.getDistribs(false).first(); if (d == null)
if (d == null) throw t._("No delivery in this contract"); d = contract.getDistribs(false).first();
if (d == null)
throw t._("No delivery in this contract");
var orders = service.ReportService.getOrdersByProduct(d, false); var orders = service.ReportService.getOrdersByProduct(d, false);
view.orders = orders; view.orders = orders;
@@ -747,18 +740,20 @@ class ContractAdmin extends Controller
view.nav.push("distributions"); view.nav.push("distributions");
sendNav(contract); sendNav(contract);
if (!app.user.canManageContract(contract)) throw Error("/", t._("You do not have the authorization to manage this contract")); if (!app.user.canManageContract(contract))
throw Error("/", t._("You do not have the authorization to manage this contract"));
view.c = contract; view.c = contract;
if (args != null && args.old) { if (args != null && args.old) {
// display also old deliveries // display also old deliveries
view.deliveries = contract.getDistribs(false); view.deliveries = contract.getDistribs(false);
} else { } else {
view.deliveries = db.Distribution.manager.search($end > DateTools.delta(Date.now(), -1000.0 * 60 * 60 * 24 * 30) && $contract == contract, { orderBy:date} ); view.deliveries = db.Distribution.manager.search($end > DateTools.delta(Date.now(), -1000.0 * 60 * 60 * 24 * 30)
&& $contract == contract,
{orderBy: date});
} }
view.cycles = db.DistributionCycle.manager.search($contract == contract && $endDate > Date.now(), false); view.cycles = db.DistributionCycle.manager.search($contract == contract && $endDate > Date.now(), false);
} }
/** /**
@@ -769,16 +764,19 @@ class ContractAdmin extends Controller
view.nav.push("distributions"); view.nav.push("distributions");
sendNav(contract); sendNav(contract);
if (!app.user.canManageContract(contract)) throw Error("/", t._("You do not have the authorization to manage this contract")); if (!app.user.canManageContract(contract))
throw Error("/", t._("You do not have the authorization to manage this contract"));
var distribs = contract.getDistribs(false); var distribs = contract.getDistribs(false);
var userCount = new Map<Int, Int>(); var userCount = new Map<Int, Int>();
var suscribers = contract.getUsers(); var suscribers = contract.getUsers();
for( u in suscribers) userCount[u.id] = 0; for (u in suscribers)
userCount[u.id] = 0;
// populate // populate
var increment = function(user:db.User) { var increment = function(user:db.User) {
if(user==null) return; if (user == null)
return;
var x = userCount[user.id]; var x = userCount[user.id];
if (x == null) { if (x == null) {
userCount[user.id] = 1; userCount[user.id] = 1;
@@ -802,7 +800,6 @@ class ContractAdmin extends Controller
} else { } else {
out2.push({user: db.User.manager.get(k), count: userCount[k]}); out2.push({user: db.User.manager.get(k), count: userCount[k]});
} }
} }
var num = (distribs.length * contract.distributorNum) / suscribers.length; var num = (distribs.length * contract.distributorNum) / suscribers.length;
@@ -821,18 +818,20 @@ class ContractAdmin extends Controller
view.nav.push("view"); view.nav.push("view");
sendNav(contract); sendNav(contract);
if (!app.user.canManageContract(contract)) throw Error("/", t._("You do not have the authorization to manage this contract")); if (!app.user.canManageContract(contract))
throw Error("/", t._("You do not have the authorization to manage this contract"));
view.c = view.contract = contract; view.c = view.contract = contract;
} }
@tpl("contractadmin/stats.mtt") @tpl("contractadmin/stats.mtt")
function doStats(contract:db.Contract, ?args:{stat:Int}) { function doStats(contract:db.Contract, ?args:{stat:Int}) {
sendNav(contract); sendNav(contract);
if (!app.user.canManageContract(contract)) throw Error("/", t._("You do not have the authorization to manage this contract")); if (!app.user.canManageContract(contract))
throw Error("/", t._("You do not have the authorization to manage this contract"));
view.c = contract; view.c = contract;
if (args == null) args = { stat:0 }; if (args == null)
args = {stat: 0};
view.stat = args.stat; view.stat = args.stat;
var pids = contract.getProducts().map(function(x) return x.id); var pids = contract.getProducts().map(function(x) return x.id);
switch (args.stat) { switch (args.stat) {
@@ -842,7 +841,10 @@ class ContractAdmin extends Controller
if (pids.length == 0) { if (pids.length == 0) {
view.anciennete = new List(); view.anciennete = new List();
} else { } else {
view.anciennete = sys.db.Manager.cnx.request("select YEAR(u.cdate) as uyear ,count(DISTINCT u.id) as cnt from User u, UserContract up where up.userId=u.id and up.productId IN (" + pids.join(",") + ") group by uyear;").results(); view.anciennete = sys.db.Manager.cnx.request("select YEAR(u.cdate) as uyear ,count(DISTINCT u.id) as cnt from User u, UserContract up where up.userId=u.id and up.productId IN ("
+ pids.join(",")
+ ") group by uyear;")
.results();
} }
case 1: case 1:
@@ -851,7 +853,10 @@ class ContractAdmin extends Controller
var total = 0; var total = 0;
var totalPrice = 0; var totalPrice = 0;
if (pids.length != 0) { if (pids.length != 0) {
var repartition = sys.db.Manager.cnx.request("select sum(quantity) as quantity,productId,p.name,p.price from UserContract up, Product p where up.productId IN (" + contract.getProducts().map(function(x) return x.id).join(",") + ") and up.productId=p.id group by productId").results(); var repartition = sys.db.Manager.cnx.request("select sum(quantity) as quantity,productId,p.name,p.price from UserContract up, Product p where up.productId IN ("
+ contract.getProducts().map(function(x) return x.id).join(",")
+ ") and up.productId=p.id group by productId")
.results();
for (r in repartition) { for (r in repartition) {
total += r.quantity; total += r.quantity;
totalPrice += r.price * r.quantity; totalPrice += r.price * r.quantity;
@@ -861,32 +866,29 @@ class ContractAdmin extends Controller
} }
if (app.params.exists("csv")) { if (app.params.exists("csv")) {
sugoi.tools.Csv.printCsvDataFromObjects(Lambda.array(repartition), ["quantity","productId","name","price","percent"], "stats-" + contract.name+".csv"); sugoi.tools.Csv.printCsvDataFromObjects(Lambda.array(repartition), ["quantity", "productId", "name", "price", "percent"],
"stats-" + contract.name + ".csv");
} }
} }
view.repartition = repartition; view.repartition = repartition;
view.totalQuantity = total; view.totalQuantity = total;
view.totalPrice = totalPrice; view.totalPrice = totalPrice;
} }
} }
/** /**
* Efface une commande * Efface une commande
* @param uc * @param uc
*/ */
function doDelete(uc:UserContract) { function doDelete(uc:UserContract) {
if (!app.user.canManageContract(uc.product.contract)) throw Error("/", t._("You do not have the authorization to manage this contract")); if (!app.user.canManageContract(uc.product.contract))
throw Error("/", t._("You do not have the authorization to manage this contract"));
uc.lock(); uc.lock();
uc.delete(); uc.delete();
throw Ok('/contractAdmin/orders/' + uc.product.contract.id, t._("The contract has been canceled")); throw Ok('/contractAdmin/orders/' + uc.product.contract.id, t._("The contract has been canceled"));
} }
@tpl("contractadmin/selectDistrib.mtt") @tpl("contractadmin/selectDistrib.mtt")
function doSelectDistrib(c:db.Contract, ?args:{old:Bool}) { function doSelectDistrib(c:db.Contract, ?args:{old:Bool}) {
view.nav.push("orders"); view.nav.push("orders");
@@ -898,7 +900,6 @@ class ContractAdmin extends Controller
} else { } else {
view.distributions = c.getDistribs(true); view.distributions = c.getDistribs(true);
} }
} }
/** /**
@@ -909,8 +910,10 @@ class ContractAdmin extends Controller
view.nav.push("orders"); view.nav.push("orders");
sendNav(c); sendNav(c);
if (!app.user.canManageContract(c)) throw Error("/", t._("You do not have the authorization to manage this contract")); if (!app.user.canManageContract(c))
if (args.d != null && args.d.validated) throw Error("/contractAdmin/orders/" + c.id + "?d=" + args.d.id, t._("This delivery has been already validated")); throw Error("/", t._("You do not have the authorization to manage this contract"));
if (args.d != null && args.d.validated)
throw Error("/contractAdmin/orders/" + c.id + "?d=" + args.d.id, t._("This delivery has been already validated"));
view.c = view.contract = c; view.c = view.contract = c;
view.u = user; view.u = user;
@@ -918,11 +921,8 @@ class ContractAdmin extends Controller
// need to select a distribution for varying orders contracts // need to select a distribution for varying orders contracts
if (c.type == db.Contract.TYPE_VARORDER && args.d == null) { if (c.type == db.Contract.TYPE_VARORDER && args.d == null) {
throw Redirect("/contractAdmin/orders/" + c.id); throw Redirect("/contractAdmin/orders/" + c.id);
} else { } else {
// members of the group // members of the group
view.users = app.user.amap.getMembersFormElementData(); view.users = app.user.amap.getMembersFormElementData();
@@ -939,13 +939,13 @@ class ContractAdmin extends Controller
order = db.UserContract.manager.select($user == user && $productId == p.id, true); order = db.UserContract.manager.select($user == user && $productId == p.id, true);
} }
if (order != null) ua.order = order; if (order != null)
ua.order = order;
userOrders.push(ua); userOrders.push(ua);
} }
// form check // form check
if (checkToken()) { if (checkToken()) {
// it's a new order, the user has been defined in the form. // it's a new order, the user has been defined in the form.
if (user == null) { if (user == null) {
user = db.User.manager.get(Std.parseInt(app.params.get("user"))); user = db.User.manager.get(Std.parseInt(app.params.get("user")));
@@ -953,7 +953,8 @@ class ContractAdmin extends Controller
var user = app.params.get("user"); var user = app.params.get("user");
throw t._("Unable to find user #::num::", {num: user}); throw t._("Unable to find user #::num::", {num: user});
} }
if (!user.isMemberOf(app.user.amap)) throw user + " is not member of this group"; if (!user.isMemberOf(app.user.amap))
throw user + " is not member of this group";
} }
// get distrib if needed // get distrib if needed
@@ -967,11 +968,11 @@ class ContractAdmin extends Controller
for (k in app.params.keys()) { for (k in app.params.keys()) {
var param = app.params.get(k); var param = app.params.get(k);
if (k.substr(0, "product".length) == "product") { if (k.substr(0, "product".length) == "product") {
// trouve le produit dans userOrders // trouve le produit dans userOrders
var pid = Std.parseInt(k.substr("product".length)); var pid = Std.parseInt(k.substr("product".length));
var uo = Lambda.find(userOrders, function(uo) return uo.product.id == pid); var uo = Lambda.find(userOrders, function(uo) return uo.product.id == pid);
if (uo == null) throw t._("Unable to find product ::pid::", {pid:pid}); if (uo == null)
throw t._("Unable to find product ::pid::", {pid: pid});
// user2 ? // user2 ?
var user2:db.User = null; var user2:db.User = null;
@@ -982,8 +983,10 @@ class ContractAdmin extends Controller
var user = app.params.get("user2"); var user = app.params.get("user2");
throw t._("Unable to find user #::num::", {num: user}); throw t._("Unable to find user #::num::", {num: user});
} }
if (!user2.isMemberOf(app.user.amap)) throw t._("::user:: is not part of this group",{user:user2}); if (!user2.isMemberOf(app.user.amap))
if (user.id == user2.id) throw t._("Both selected accounts must be different ones"); throw t._("::user:: is not part of this group", {user: user2});
if (user.id == user2.id)
throw t._("Both selected accounts must be different ones");
invert = app.params.get("invert" + pid) == "1"; invert = app.params.get("invert" + pid) == "1";
} }
@@ -996,17 +999,21 @@ class ContractAdmin extends Controller
} else { } else {
q = Std.parseInt(param); q = Std.parseInt(param);
} }
if(q==null) continue; //ignore bad qt input if (q == null)
continue; // ignore bad qt input
// record order // record order
if (uo.order != null) { if (uo.order != null) {
// existing record // existing record
var o = OrderService.edit(uo.order, q, (app.params.get("paid" + pid) == "1"), user2, invert); var o = OrderService.edit(uo.order, q, (app.params.get("paid" + pid) == "1"), user2, invert);
if (o != null) orders.push(o); if (o != null)
orders.push(o);
} else { } else {
// new record // new record
var o = OrderService.make(user, q, uo.product, distrib == null ? null : distrib.id, (app.params.get("paid" + pid) == "1"), user2, invert); var o = OrderService.make(user, q, uo.product, distrib == null ? null : distrib.id, (app.params.get("paid" + pid) == "1"), user2,
if (o != null) orders.push(o); invert);
if (o != null)
orders.push(o);
} }
} }
} }
@@ -1023,5 +1030,4 @@ class ContractAdmin extends Controller
view.userOrders = userOrders; view.userOrders = userOrders;
} }
} }
} }
+71 -59
View File
@@ -1,18 +1,15 @@
package controller; package controller;
import sugoi.db.Cache; import sugoi.db.Cache;
import sugoi.Web; import sugoi.Web;
import sugoi.mail.Mail; import sugoi.mail.Mail;
import Common; import Common;
using Lambda; using Lambda;
using tools.DateTool; using tools.DateTool;
class Cron extends Controller class Cron extends Controller {
{ public function doDefault() {}
public function doDefault()
{
}
/** /**
* CLI only en prod * CLI only en prod
@@ -23,7 +20,6 @@ class Cron extends Controller
} else if (App.config.DEBUG) { } else if (App.config.DEBUG) {
return true; return true;
} else { } else {
if (Web.isModNeko) { if (Web.isModNeko) {
Sys.print("only CLI."); Sys.print("only CLI.");
return false; return false;
@@ -34,15 +30,14 @@ class Cron extends Controller
} }
public function doMinute() { public function doMinute() {
print("Cron.doMinute is called"); print("Cron.doMinute is called");
if (!canRun()) return; if (!canRun())
return;
app.event(MinutelyCron); app.event(MinutelyCron);
sendEmailsfromBuffer(); sendEmailsfromBuffer();
} }
/** /**
@@ -51,7 +46,6 @@ class Cron extends Controller
* this function can be locally tested with `neko index.n cron/hour > cron.log` * this function can be locally tested with `neko index.n cron/hour > cron.log`
*/ */
public function doHour() { public function doHour() {
app.event(HourlyCron); app.event(HourlyCron);
distribNotif(4, db.User.UserFlags.HasEmailNotif4h); // 4h before distribNotif(4, db.User.UserFlags.HasEmailNotif4h); // 4h before
@@ -62,9 +56,9 @@ class Cron extends Controller
// sendOrdersByProductWhenOrdersClose(); // sendOrdersByProductWhenOrdersClose();
} }
public function doDaily() { public function doDaily() {
if (!canRun()) return; if (!canRun())
return;
app.event(DailyCron); app.event(DailyCron);
@@ -78,7 +72,8 @@ class Cron extends Controller
var report = new StringBuf(); var report = new StringBuf();
report.add("<h1>" + App.config.NAME + " : ERRORS</h1>"); report.add("<h1>" + App.config.NAME + " : ERRORS</h1>");
for (e in errors) { for (e in errors) {
report.add("<div><pre>"+e.error + " at URL " + e.url + " ( user : " + (e.user!=null?e.user.toString():"none") + ", IP : " + e.ip + ")</pre></div><hr/>"); report.add("<div><pre>" + e.error + " at URL " + e.url + " ( user : " + (e.user != null ? e.user.toString() : "none") + ", IP : " + e.ip
+ ")</pre></div><hr/>");
} }
var m = new Mail(); var m = new Mail();
@@ -89,11 +84,11 @@ class Cron extends Controller
App.sendMail(m); App.sendMail(m);
} }
// DEMO CONTRATS deletion after 7 days ( see controller.Group.doCreate() ) // DEMO CONTRATS deletion after 7 days ( see controller.Group.doCreate() )
db.Contract.manager.delete($name == "Contrat AMAP Maraîcher Exemple" && $startDate < DateTools.delta(Date.now(), -1000.0 * 60 * 60 * 24 * 7)); db.Contract.manager.delete($name == "Contrat AMAP Maraîcher Exemple"
db.Contract.manager.delete($name == "Contrat Poulet Exemple" && $startDate < DateTools.delta(Date.now(), -1000.0 * 60 * 60 * 24 * 7)); && $startDate < DateTools.delta(Date.now(), -1000.0 * 60 * 60 * 24 * 7));
db.Contract.manager.delete($name == "Contrat Poulet Exemple"
&& $startDate < DateTools.delta(Date.now(), -1000.0 * 60 * 60 * 24 * 7));
// Old Messages cleaning // Old Messages cleaning
db.Message.manager.delete($date < DateTools.delta(Date.now(), -1000.0 * 60 * 60 * 24 * 30 * 6)); db.Message.manager.delete($date < DateTools.delta(Date.now(), -1000.0 * 60 * 60 * 24 * 30 * 6));
@@ -107,8 +102,6 @@ class Cron extends Controller
u.email2 = null; u.email2 = null;
u.update(); u.update();
}*/ }*/
} }
/** /**
@@ -117,7 +110,6 @@ class Cron extends Controller
* @param flag * @param flag
*/ */
function distribNotif(hour:Int, flag:db.User.UserFlags) { function distribNotif(hour:Int, flag:db.User.UserFlags) {
// trouve les distrib qui commencent dans le nombre d'heures demandé // trouve les distrib qui commencent dans le nombre d'heures demandé
// on recherche celles qui commencent jusqu'à une heure avant pour ne pas en rater // on recherche celles qui commencent jusqu'à une heure avant pour ne pas en rater
var from = DateTools.delta(Date.now(), 1000.0 * 60 * 60 * (hour - 1)); var from = DateTools.delta(Date.now(), 1000.0 * 60 * 60 * (hour - 1));
@@ -136,7 +128,8 @@ class Cron extends Controller
// Sys.print("distribNotif "+hour+" from "+from+" to "+to+"<br/>\n"); // Sys.print("distribNotif "+hour+" from "+from+" to "+to+"<br/>\n");
// on s'arrete immédiatement si aucune distibution trouvée // on s'arrete immédiatement si aucune distibution trouvée
if (distribs.length == 0) return; if (distribs.length == 0)
return;
// cherche plus tard si on a pas une "grappe" de distrib // cherche plus tard si on a pas une "grappe" de distrib
/*while (true) { /*while (true) {
@@ -170,25 +163,30 @@ class Cron extends Controller
} }
// toutes les distribs trouvées ont deja été traitées // toutes les distribs trouvées ont deja été traitées
if (distribs.length == 0) return; if (distribs.length == 0)
return;
// stocke cache // stocke cache
for (d in distribs) dist.push(d.id); for (d in distribs)
dist.push(d.id);
Cache.set(cacheId, dist, 24 * 60 * 60); Cache.set(cacheId, dist, 24 * 60 * 60);
// We have now the distribs we want to notify about. // We have now the distribs we want to notify about.
var distribsByContractId = new Map<Int, db.Distribution>(); var distribsByContractId = new Map<Int, db.Distribution>();
for (d in distribs) { for (d in distribs) {
if (d == null || d.contract==null) continue; if (d == null || d.contract == null)
continue;
distribsByContractId.set(d.contract.id, d); distribsByContractId.set(d.contract.id, d);
} }
// Boucle sur les distributions pour gerer le cas de plusieurs distributions le même jour sur le même contrat // Boucle sur les distributions pour gerer le cas de plusieurs distributions le même jour sur le même contrat
var orders = []; var orders = [];
for (d in distribs) { for (d in distribs) {
if (d == null || d.contract==null) continue; if (d == null || d.contract == null)
continue;
// get orders for both type of contracts // get orders for both type of contracts
for ( x in d.contract.getOrders(d)) orders.push(x); for (x in d.contract.getOrders(d))
orders.push(x);
} }
/* /*
@@ -203,9 +201,14 @@ class Cron extends Controller
}>(); }>();
for (o in orders) { for (o in orders) {
var x = users.get(o.user.id + "-" + o.product.contract.amap.id); var x = users.get(o.user.id + "-" + o.product.contract.amap.id);
if (x == null) x = {user:o.user,distrib:null,products:[],vendors:[]}; if (x == null)
x = {
user: o.user,
distrib: null,
products: [],
vendors: []
};
x.distrib = distribsByContractId.get(o.product.contract.id); x.distrib = distribsByContractId.get(o.product.contract.id);
x.products.push(o); x.products.push(o);
users.set(o.user.id + "-" + o.product.contract.amap.id, x); users.set(o.user.id + "-" + o.product.contract.amap.id, x);
@@ -214,7 +217,13 @@ class Cron extends Controller
// Prévenir également le deuxième user en cas des commandes alternées // Prévenir également le deuxième user en cas des commandes alternées
if (o.user2 != null) { if (o.user2 != null) {
var x = users.get(o.user2.id + "-" + o.product.contract.amap.id); var x = users.get(o.user2.id + "-" + o.product.contract.amap.id);
if (x == null) x = {user:o.user2,distrib:null,products:[],vendors:[]}; if (x == null)
x = {
user: o.user2,
distrib: null,
products: [],
vendors: []
};
x.distrib = distribsByContractId.get(o.product.contract.id); x.distrib = distribsByContractId.get(o.product.contract.id);
x.products.push(o); x.products.push(o);
users.set(o.user2.id + "-" + o.product.contract.amap.id, x); users.set(o.user2.id + "-" + o.product.contract.amap.id, x);
@@ -226,18 +235,25 @@ class Cron extends Controller
for (k in users.keys()) { for (k in users.keys()) {
var x = users.get(k); var x = users.get(k);
var total = 0.0; var total = 0.0;
for( o in x.products) total += o.quantity; for (o in x.products)
if(total==0.0) users.remove(k); total += o.quantity;
if (total == 0.0)
users.remove(k);
} }
// Dans le cas de l'ouverture de commande, ce sont tous les users qu'il faut intégrer // Dans le cas de l'ouverture de commande, ce sont tous les users qu'il faut intégrer
if ( db.User.UserFlags.HasEmailNotifOuverture == flag ) if (db.User.UserFlags.HasEmailNotifOuverture == flag) {
{
for (d in distribs) { for (d in distribs) {
var memberList = d.contract.amap.getMembers(); var memberList = d.contract.amap.getMembers();
for (u in memberList) { for (u in memberList) {
var x = users.get(u.id + "-" + d.contract.amap.id); var x = users.get(u.id + "-" + d.contract.amap.id);
if (x == null) x = {user:u,distrib:null,products:[],vendors:[]}; if (x == null)
x = {
user: u,
distrib: null,
products: [],
vendors: []
};
x.distrib = distribsByContractId.get(d.contract.id); x.distrib = distribsByContractId.get(d.contract.id);
x.vendors.push(d.contract.vendor); x.vendors.push(d.contract.vendor);
users.set(u.id + "-" + d.contract.amap.id, x); users.set(u.id + "-" + d.contract.amap.id, x);
@@ -247,16 +263,13 @@ class Cron extends Controller
} }
for (u in users) { for (u in users) {
if (u.user.flags.has(flag)) { if (u.user.flags.has(flag)) {
if (u.user.email != null) { if (u.user.email != null) {
var group = u.distrib.contract.amap; var group = u.distrib.contract.amap;
this.t = sugoi.i18n.Locale.init(u.user.lang); // switch to the user language this.t = sugoi.i18n.Locale.init(u.user.lang); // switch to the user language
var text; var text;
if ( db.User.UserFlags.HasEmailNotifOuverture == flag ) if (db.User.UserFlags.HasEmailNotifOuverture == flag) {
{
// order opening notif // order opening notif
text = t._("Opening of orders for the delivery of <b>::date::</b>", {date: view.hDate(u.distrib.date)}); text = t._("Opening of orders for the delivery of <b>::date::</b>", {date: view.hDate(u.distrib.date)});
text += "<br/>"; text += "<br/>";
@@ -266,11 +279,11 @@ class Cron extends Controller
text += "<li>" + v + "</li>"; text += "<li>" + v + "</li>";
} }
text += "</ul>"; text += "</ul>";
} else { } else {
// Distribution notif to the users // Distribution notif to the users
var d = u.distrib; var d = u.distrib;
text = t._("Do not forget the delivery on <b>::day::</b> from ::from:: to ::to::<br/>", {day:view.dDate(d.date),from:view.hHour(d.date),to:view.hHour(d.end)}); text = t._("Do not forget the delivery on <b>::day::</b> from ::from:: to ::to::<br/>",
{day: view.dDate(d.date), from: view.hHour(d.date), to: view.hHour(d.end)});
text += t._("Your products to collect :") + "<br/><ul>"; text += t._("Your products to collect :") + "<br/><ul>";
for (p in u.products) { for (p in u.products) {
text += "<li>" + p.quantity + " x " + p.product.getName(); text += "<li>" + p.quantity + " x " + p.product.getName();
@@ -294,29 +307,28 @@ class Cron extends Controller
try { try {
var m = new Mail(); var m = new Mail();
m.setSender(App.config.get("default_email"), "Cagette.net"); m.setSender(App.config.get("default_email"), "Cagette.net");
if(group.contact!=null) m.setReplyTo(group.contact.email, group.name); if (group.contact != null)
m.setReplyTo(group.contact.email, group.name);
m.addRecipient(u.user.email, u.user.getName()); m.addRecipient(u.user.email, u.user.getName());
if (u.user.email2 != null) m.addRecipient(u.user.email2); if (u.user.email2 != null)
m.addRecipient(u.user.email2);
m.setSubject(group.name + " : " + t._("Distribution on ::date::", {date: app.view.hDate(u.distrib.date)})); m.setSubject(group.name + " : " + t._("Distribution on ::date::", {date: app.view.hDate(u.distrib.date)}));
m.setHtmlBody(app.processTemplate("mail/message.mtt", {text: text, group: group})); m.setHtmlBody(app.processTemplate("mail/message.mtt", {text: text, group: group}));
App.sendMail(m, u.distrib.contract.amap); App.sendMail(m, u.distrib.contract.amap);
} catch (e:Dynamic) { } catch (e:Dynamic) {
app.logError(e); // email could be invalid app.logError(e); // email could be invalid
} }
} }
} }
} }
} }
/** /**
* Check if there is a multi-distrib to validate. * Check if there is a multi-distrib to validate.
* *
* Autovalidate it after 10 days * Autovalidate it after 10 days
*/ */
function distribValidationNotif() { function distribValidationNotif() {
var now = Date.now(); var now = Date.now();
var from = now.setHourMinute(now.getHours(), 0); var from = now.setHourMinute(now.getHours(), 0);
@@ -350,7 +362,8 @@ class Cron extends Controller
var html = t._("<p>Your distribution just finished, don't forget to <b>validate</b> it</p>"); var html = t._("<p>Your distribution just finished, don't forget to <b>validate</b> it</p>");
html += explain; html += explain;
html += t._("<p><a href='::distriburl::'>Click here to validate the distribution</a> (You must be connected to your group Cagette)", {distriburl:url}); html += t._("<p><a href='::distriburl::'>Click here to validate the distribution</a> (You must be connected to your group Cagette)",
{distriburl: url});
App.quickMail(d.contract.amap.contact.email, subj, html); App.quickMail(d.contract.amap.contact.email, subj, html);
} }
@@ -383,12 +396,12 @@ class Cron extends Controller
var html = t._("<p>Reminder: you have a delivery to validate.</p>"); var html = t._("<p>Reminder: you have a delivery to validate.</p>");
html += explain; html += explain;
html += t._("<p><a href='::distriburl::'>Click here to validate the delivery</a> (You must be connected to your Cagette group)", {distriburl:url}); html += t._("<p><a href='::distriburl::'>Click here to validate the delivery</a> (You must be connected to your Cagette group)",
{distriburl: url});
App.quickMail(d.contract.amap.contact.email, subj, html); App.quickMail(d.contract.amap.contact.email, subj, html);
} }
/* /*
* Autovalidate unvalidated distributions after 10 days * Autovalidate unvalidated distributions after 10 days
*/ */
@@ -408,17 +421,18 @@ class Cron extends Controller
print(d.toString()); print(d.toString());
service.PaymentService.validateDistribution(d); service.PaymentService.validateDistribution(d);
} }
// email // email
var ds = tools.ObjectListTool.deduplicateDistribsByKey(ds); var ds = tools.ObjectListTool.deduplicateDistribsByKey(ds);
for (d in ds) { for (d in ds) {
// var subj = d.contract.amap.name + t._(": Validation of the distribution of the ") + App.current.view.hDate(d.date); // var subj = d.contract.amap.name + t._(": Validation of the distribution of the ") + App.current.view.hDate(d.date);
var subj = t._("[::group::] Validation of the ::date:: distribution", {group: d.contract.amap.name, date: view.hDate(d.date)}); var subj = t._("[::group::] Validation of the ::date:: distribution", {group: d.contract.amap.name, date: view.hDate(d.date)});
var html = t._("<p>As you did not validate it manually after 10 days, <br/>the delivery of the ::deliveryDate:: has been validated automatically</p>", {deliveryDate:App.current.view.hDate(d.date)}); var html = t._("<p>As you did not validate it manually after 10 days, <br/>the delivery of the ::deliveryDate:: has been validated automatically</p>",
{
deliveryDate: App.current.view.hDate(d.date)
});
App.quickMail(d.contract.amap.contact.email, subj, html); App.quickMail(d.contract.amap.contact.email, subj, html);
} }
} }
/** /**
@@ -429,14 +443,15 @@ class Cron extends Controller
* (like "SELECT * FROM BufferedMail WHERE sdate IS NULL ORDER BY cdate DESC LIMIT 100 FOR UPDATE Lock wait timeout exceeded; try restarting transaction") * (like "SELECT * FROM BufferedMail WHERE sdate IS NULL ORDER BY cdate DESC LIMIT 100 FOR UPDATE Lock wait timeout exceeded; try restarting transaction")
*/ */
function sendEmailsfromBuffer() { function sendEmailsfromBuffer() {
print("<h3>Send Emails from Buffer</h3>"); App.log("Send Emails from Buffer");
// send // send
for (e in sugoi.db.BufferedMail.manager.search($sdate == null, {limit: 50, orderBy: -cdate}, false)) { for (e in sugoi.db.BufferedMail.manager.search($sdate == null, {limit: 50, orderBy: -cdate}, false)) {
e.lock(); e.lock();
if(e.isSent()) continue; if (e.isSent())
continue;
print('#${e.id} - ${e.title}'); App.log('Send Email id=#${e.id} - title=${e.title}');
e.finallySend(); e.finallySend();
Sys.sleep(0.1); Sys.sleep(0.1);
} }
@@ -459,7 +474,6 @@ class Cron extends Controller
* Email product report when orders close * Email product report when orders close
**/ **/
function sendOrdersByProductWhenOrdersClose() { function sendOrdersByProductWhenOrdersClose() {
var range = tools.DateTool.getLastHourRange(); var range = tools.DateTool.getLastHourRange();
// Sys.println("Time is "+Date.now()+"<br/>"); // Sys.println("Time is "+Date.now()+"<br/>");
// Sys.println('Find all distributions that have closed in the last hour from ${range.from} to ${range.to} \n<br/>'); // Sys.println('Find all distributions that have closed in the last hour from ${range.from} to ${range.to} \n<br/>');
@@ -467,10 +481,8 @@ class Cron extends Controller
for (d in db.Distribution.manager.search($orderEndDate >= range.from && $orderEndDate < range.to, false)) { for (d in db.Distribution.manager.search($orderEndDate >= range.from && $orderEndDate < range.to, false)) {
service.OrderService.sendOrdersByProductReport(d); service.OrderService.sendOrdersByProductReport(d);
} }
} }
function print(text) { function print(text) {
Sys.println(text + "<br/>"); Sys.println(text + "<br/>");
} }
+26 -8
View File
@@ -87,6 +87,17 @@ class Distribution extends Controller
} }
function getUser(uid:Int):db.User {
return db.User.manager.get(uid, false);
}
function getBasket(userId, placeId, date){
var user = getUser(userId);
var place = db.Place.manager.get(placeId, false);
return db.Basket.getOrCreate(user, place, date);
}
/** /**
* Attendance sheet to print ( mutidistrib ) * Attendance sheet to print ( mutidistrib )
*/ */
@@ -174,18 +185,25 @@ class Distribution extends Controller
var data = new Array<Dynamic>(); var data = new Array<Dynamic>();
for (o in orders3) { for (o in orders3) {
var basket = getBasket(o.userId,view.place.id,view.date);
data.push( { data.push( {
"name":o.userName, "NUM": basket.num,
"productName":o.productName, "ADHERENT":o.userName,
"price":view.formatNum(o.productPrice), "PRODUIT":o.productName,
"quantity":o.quantity, "QTÉ":o.quantity,
"fees":view.formatNum(o.fees), // "price":view.formatNum(o.productPrice),
"total":view.formatNum(o.total), // "fees":view.formatNum(o.fees),
"paid":o.paid // "total":view.formatNum(o.total),
// "paid":o.paid
}); });
} }
sugoi.tools.Csv.printCsvDataFromObjects(data, ["name", "productName", "price", "quantity","fees","total", "paid"],"Export-commandes-"+date.toString().substr(0,10)+"-Cagette"); sugoi.tools.Csv.printCsvDataFromObjects(data, ["NUM","ADHERENT", "PRODUIT", "QTÉ"],"Export-commandes-"+date.toString().substr(0,10)+"-Cagette");
//sugoi.tools.Csv.printCsvDataFromObjects(data, ["NUM","ADHERENT", "PRODUIT", "QTÉ","price","fees","total","paid"],"Export-commandes-"+date.toString().substr(0,10)+"-Cagette");
//sugoi.tools.Csv.printCsvDataFromObjects(data, ["NUM","ADHERENT", "PRODUIT", "price", "quantity","fees","total", "paid"],"Export-commandes-"+date.toString().substr(0,10)+"-Cagette");
return; return;
} }
} }
+50 -37
View File
@@ -1,22 +1,20 @@
package controller; package controller;
import db.Message; import db.Message;
import db.UserContract; import db.UserContract;
import sugoi.form.ListData; import sugoi.form.ListData;
import sugoi.form.elements.*; import sugoi.form.elements.*;
import sugoi.form.Form; import sugoi.form.Form;
class Messages extends Controller class Messages extends Controller {
{ public function new() {
public function new()
{
super(); super();
if (!app.user.canAccessMessages()) throw Redirect("/"); if (!app.user.canAccessMessages())
throw Redirect("/");
} }
@tpl("messages/default.mtt") @tpl("messages/default.mtt")
function doDefault() { function doDefault() {
var form = new Form("msg"); var form = new Form("msg");
var senderName = ""; var senderName = "";
@@ -25,7 +23,6 @@ class Messages extends Controller
if (App.current.session.data.whichUser == 1 && app.user.email2 != null) { if (App.current.session.data.whichUser == 1 && app.user.email2 != null) {
senderMail = app.user.email2; senderMail = app.user.email2;
senderName = app.user.firstName2 + " " + app.user.lastName2; senderName = app.user.firstName2 + " " + app.user.lastName2;
} else { } else {
senderMail = app.user.email; senderMail = app.user.email;
senderName = app.user.firstName + " " + app.user.lastName; senderName = app.user.firstName + " " + app.user.lastName;
@@ -39,22 +36,46 @@ class Messages extends Controller
form.addElement(new TextArea("text", t._("Message:"), "", false, null, "style='width:500px;height:350px;'")); form.addElement(new TextArea("text", t._("Message:"), "", false, null, "style='width:500px;height:350px;'"));
if (form.checkToken()) { if (form.checkToken()) {
var listId = form.getElement("list").value; var listId = form.getElement("list").value;
var dest = getSelection(listId); var dest = getSelection(listId);
var subject = form.getValueOf("subject");
App.log('about to send email <$subject> to listId=<$listId> to ${dest.length} recipient(s)');
// Sys.command("sh", [
// "-c",
// "sendemail -f no-reply@comptoirduvrac.re -s mail1.zourit.net:587 -t pvincent@comptoirduvrac.re -bcc 'pvincent974@gmail.com, pvincent974@laposte.net' -u TEST -m coucou -xu postmaster@comptoirduvrac.re -xp QqQeAPT6EpoK"
// ]);
var mails = []; var mails = [];
for (d in dest) { for (d in dest) {
if (d.email != null) mails.push(d.email); if (d.email != null)
if (d.email2 != null) mails.push(d.email2); mails.push(d.email);
if (d.email2 != null)
mails.push(d.email2);
} }
// send mail confirmation link // send mail confirmation link
var e = new sugoi.mail.Mail(); var e = new sugoi.mail.Mail();
e.setSubject(form.getValueOf("subject")); e.setSender(App.config.get("default_email"));
for ( x in mails) e.addRecipient(x); e.setSubject(subject);
var replyTo = form.getValueOf("senderMail");
var replyToName = form.getValueOf("senderName");
if (replyTo != null)
e.setReplyTo(replyTo, replyToName);
if (mails.length > 1) {
var cc = mails.join(",");
App.log('add header Cc: $cc');
e.setHeader("CC:", cc);
// for (x in mails)
// e.addRecipient(x);
e.addRecipient(e.getSender().email);
} else {
App.log('no header');
e.addRecipient(mails[0]);
}
e.setSender(App.config.get("default_email"),form.getValueOf("senderName"));
e.setReplyTo(form.getValueOf("senderMail"),form.getValueOf("senderName"));
// sender : default email ( explicitly tells that the server send an email on behalf of the user ) // sender : default email ( explicitly tells that the server send an email on behalf of the user )
// e.setHeader("Sender", App.config.get("default_email")); // e.setHeader("Sender", App.config.get("default_email"));
var text:String = form.getValueOf("text"); var text:String = form.getValueOf("text");
@@ -66,15 +87,16 @@ class Messages extends Controller
// store message // store message
var lm = new db.Message(); var lm = new db.Message();
lm.amap = app.user.amap; lm.amap = app.user.amap;
lm.recipients = Lambda.array(Lambda.map(e.getRecipients(), function(x) return x.email)); // lm.recipients = Lambda.array(Lambda.map(e.getRecipients(), function(x) return x.email));
lm.recipients = mails;
lm.title = e.getSubject(); lm.title = e.getSubject();
lm.date = Date.now(); lm.date = Date.now();
lm.body = e.getHtmlBody(); lm.body = e.getHtmlBody();
if (listId != null) lm.recipientListId = listId; if (listId != null)
lm.recipientListId = listId;
lm.sender = app.user; lm.sender = app.user;
lm.insert(); lm.insert();
throw Ok("/messages", t._("The message has been sent")); throw Ok("/messages", t._("The message has been sent"));
} }
@@ -83,15 +105,15 @@ class Messages extends Controller
if (app.user.isAmapManager()) { if (app.user.isAmapManager()) {
view.sentMessages = Message.manager.search($amap == app.user.amap && $recipientListId != null, {orderBy: -date, limit: 20}, false); view.sentMessages = Message.manager.search($amap == app.user.amap && $recipientListId != null, {orderBy: -date, limit: 20}, false);
} else { } else {
view.sentMessages = Message.manager.search($sender == app.user && $recipientListId!=null && $amap == app.user.amap , {orderBy:-date,limit:20}, false); view.sentMessages = Message.manager.search($sender == app.user && $recipientListId != null && $amap == app.user.amap, {orderBy: -date, limit: 20},
false);
} }
} }
@tpl("messages/message.mtt") @tpl("messages/message.mtt")
public function doMessage(msg:Message) { public function doMessage(msg:Message) {
if (!app.user.isAmapManager() && msg.sender.id != app.user.id)
if (!app.user.isAmapManager() && msg.sender.id != app.user.id) throw Error("/", t._("Non authorized access")); throw Error("/", t._("Non authorized access"));
view.list = getListName(msg.recipientListId); view.list = getListName(msg.recipientListId);
view.msg = msg; view.msg = msg;
@@ -106,7 +128,6 @@ class Messages extends Controller
}*/ }*/
view.status = s; view.status = s;
} }
function getLists():FormData<String> { function getLists():FormData<String> {
@@ -117,8 +138,8 @@ class Messages extends Controller
out.push({value: '3', label: t._("TEST: me + spouse")}); out.push({value: '3', label: t._("TEST: me + spouse")});
out.push({value: '4', label: t._("Members without contract/order")}); out.push({value: '4', label: t._("Members without contract/order")});
if(app.user.amap.hasMembership()) out.push( { value:'5', label:t._("Memberships to be renewed")} ); if (app.user.amap.hasMembership())
out.push({value: '5', label: t._("Memberships to be renewed")});
var contracts = db.Contract.getActiveContracts(app.user.amap, true); var contracts = db.Contract.getActiveContracts(app.user.amap, true);
for (c in contracts) { for (c in contracts) {
@@ -126,7 +147,6 @@ class Messages extends Controller
out.push({value: 'c' + c.id, label: label}); out.push({value: 'c' + c.id, label: label});
} }
return out; return out;
} }
/** /**
@@ -137,11 +157,11 @@ class Messages extends Controller
var l = getLists(); var l = getLists();
for (ll in l) { for (ll in l) {
if (ll.value == listId) return ll.label; if (ll.value == listId)
return ll.label;
} }
return null; return null;
} }
function getSelection(listId:String) { function getSelection(listId:String) {
@@ -153,7 +173,6 @@ class Messages extends Controller
var pids = Lambda.map(pids, function(x) return x.id); var pids = Lambda.map(pids, function(x) return x.id);
var up = db.UserContract.manager.search($productId in pids, false); var up = db.UserContract.manager.search($productId in pids, false);
var users = []; var users = [];
for (order in up) { for (order in up) {
if (!Lambda.has(users, order.user)) { if (!Lambda.has(users, order.user)) {
@@ -164,7 +183,6 @@ class Messages extends Controller
} }
} }
return users; return users;
} else { } else {
var out = []; var out = [];
switch (listId) { switch (listId) {
@@ -184,7 +202,8 @@ class Messages extends Controller
// ajouter les autres personnes ayant les droits Admin ou Gestion Adhérents ou Gestion Contrats // ajouter les autres personnes ayant les droits Admin ou Gestion Adhérents ou Gestion Contrats
for (ua in Lambda.array(db.UserAmap.manager.search($rights != null && $amap == app.user.amap, false))) { for (ua in Lambda.array(db.UserAmap.manager.search($rights != null && $amap == app.user.amap, false))) {
if (ua.hasRight(GroupAdmin) || ua.hasRight(Membership) || ua.hasRight(ContractAdmin())) { if (ua.hasRight(GroupAdmin) || ua.hasRight(Membership) || ua.hasRight(ContractAdmin())) {
if (!Lambda.has(users, ua.user)) users.push(ua.user); if (!Lambda.has(users, ua.user))
users.push(ua.user);
} }
} }
@@ -200,12 +219,6 @@ class Messages extends Controller
} }
return out; return out;
} }
} }
} }
+425
View File
@@ -0,0 +1,425 @@
package sugoi;
import sugoi.i18n.TemplateTranslator;
import sugoi.Web;
class BaseApp {
public var cnx : sys.db.Connection;
public var template : templo.Loader;
public var maintain : Bool;
public var session : sugoi.db.Session;
public var view : View;
public var user : db.User;
public var params : Map<String,String>;
public var cookieName : String;
public var cookieDomain : String;
public var uri : String;
public static var config: Config;
//public static var classPathes = sugoi.tools.Macros.getClassPathes();
public function new() {
if (config == null) {
loadConfig();
}
cookieName = "sid";
cookieDomain = "." + App.config.HOST;
#if plugins
if( false ) sugoi.plugin.PlugIn.copyTpl();
#end
// This macro generates translated templates for each langage
#if i18n_generation
if( false ) TemplateTranslator.parse("lang/master");
#end
}
public function loadConfig() {
App.config = BaseApp.config = new sugoi.Config();
}
public function loadTemplate( t : String ) {
templo.Loader.OPTIMIZED = App.config.DEBUG == false;
templo.Loader.BASE_DIR = App.config.TPL;
templo.Loader.TMP_DIR = App.config.TPL_TMP;
if ( t == null ) return null;
#if neko
return new templo.Loader(t, App.config.getBool("cachetpl"));
#else
return new templo.Loader(t);
#end
}
public function setTemplate( t : String ) {
template = t == null ? null : loadTemplate(t);
}
public function initLang( lang : String ) {
if (lang == null || lang == "") lang = config.LANG;
//Define template path
var path;
if (!App.config.DEBUG){
path = Web.getCwd() + "../lang/" + lang + "/";
}else{
path = Web.getCwd() + "../lang/master/";
}
App.config.TPL = path + "tpl/";
App.config.TPL_TMP = path + "tmp/";
//init system locale
if ( !Sys.setTimeLocale("en_US.UTF-8") ) {
Sys.setTimeLocale("en");
}
//init gettext translator
sugoi.i18n.Locale.init(lang);
return true;
}
function saveAndClose() {
if( cnx == null ) return;
if( session.sid != null )
session.update();
cnx.commit();
cnx.close();
untyped cnx.close = function() {}
untyped cnx.request = function(s) return null;
}
function executeTemplate( ?save ) {
view.init();
var result = template.execute(view);
if ( save ) saveAndClose();
#if php
//strange bug with templo in PHP
if (result.substr(0, 4) == "null") result = result.substr(4);
#end
Sys.print(result);
}
function onMeta( m : String, args : Array<Dynamic> ) {
switch( m ) {
case "tpl":
setTemplate(args[0]);
case "logged":
if ( user == null )
throw sugoi.ControllerAction.RedirectAction("/?__redirect="+Web.getURI());
case "admin":
if( user == null || !user.isAdmin() )
throw sugoi.ControllerAction.RedirectAction("/");
default:
}
}
/**
* Detect lang from HTTP headers
*/
function detectLang() {
var l = Web.getClientHeader("Accept-Language");
if( l != null )
for( l in l.split(",") ) {
l = l.split(";")[0];
l = l.split("-")[0];
l = StringTools.trim(l);
for( a in App.config.LANGS )
if( a == l )
return a;
}
return App.config.LANG;
}
/**
* Setup current app language
*/
function setupLang() {
//this app is monolingual and doesn't manage i18n
if (App.config.LANG == "master") return;
//lang is taken from user object or from HTTP headers
if ( session.lang == null || !Lambda.has(App.config.LANGS, session.lang) ){
session.lang = (user == null) ? detectLang() : user.lang;
}
//override if param is given
var lang = params.get("lang");
if ( lang != null && Lambda.has(App.config.LANGS, lang) ){
session.lang = lang;
}
//init lang
initLang(session.lang);
}
/**
* Get current application langage (2 letters lowercase)
*/
public function getLang(){
return (session != null && session.lang != null && session.lang != "") ? session.lang : App.config.LANG;
}
public function rollback() {
if( cnx != null ) cnx.rollback();
sys.db.Manager.cleanup();
if( user != null && session != null )
user = session.user;
// does not reset session
}
public function setCookie( oldCookie : String ){
if( session != null && session.sid != null && session.sid != oldCookie ) {
Web.setHeader("Set-Cookie", cookieName+"=" + session.sid + "; path=/;");
}
}
function mainLoop() {
params = Web.getParams();
//Get session
var sids = [];
var cookieSid = Web.getCookies().get(cookieName);
if( params.exists("sid") ) sids.push(params.get("sid"));
if( cookieSid != null ) sids.push(cookieSid);
session = sugoi.db.Session.init(sids);
//Check for maintenance
maintain = sugoi.db.Variable.getInt("maintain") != 0;
user = session.user;
//setup langage
setupLang();
if( maintain && ((user != null && user.isAdmin()) ) )
maintain = false;
setCookie(cookieSid);
if( maintain ) {
setTemplate("maintain.mtt");
executeTemplate();
return;
}
//dispatching
try {
uri = Web.getURI();
if ( StringTools.endsWith(uri, "/index.n") ) uri = uri.substr(0, -8);
//"before dispatch" callback
beforeDispatch();
var d = new haxe.web.Dispatch(uri, params);
d.onMeta = onMeta;
d.dispatch(new controller.Main());
} catch ( e : haxe.web.Dispatch.DispatchError ) {
//dispatch / routing error
if ( App.config.DEBUG ) {
#if neko
neko.Lib.rethrow(e);
#else
php.Lib.rethrow(e);
#end
}
cnx.rollback();
Web.redirect("/");
return;
} catch ( e : sugoi.ControllerAction) {
switch( e ) {
case RedirectAction(url):
Web.redirect(url);
template = null;
case ErrorAction(url, text), OkAction(url,text):
if( text == null ) {
text = url;
url = Web.getURI();
}
Web.redirect(url);
var error = switch(e) { case ErrorAction(_): true; default: false; };
if( error ) rollback();
if ( error ) {
session.addMessage(text,true);
}else {
session.addMessage(text);
}
template = null;
}
}
//Render template
if ( template == null ) {
saveAndClose();
} else {
executeTemplate(true); // will saveAndClose
}
}
/**
* Override this function if you want
* to insert some actions
*/
public function beforeDispatch() {
}
public function logError( e : Dynamic, ?stack : String ) {
var stack = if( stack != null ) stack else haxe.CallStack.toString(haxe.CallStack.exceptionStack());
var message = new StringBuf();
message.add(Std.string(e));
message.add("\n");
message.add(stack);
message.add("\n");
var e = new sugoi.db.Error();
e.url = Web.getURI();
e.ip = Web.getClientIP();
e.user = if( user != null ) user else null;
e.date = Date.now();
e.userAgent = Web.getClientHeader("User-Agent");
e.error = message.toString();
e.insert();
}
function errorHandler( e:Dynamic ) {
try {
var stack = haxe.CallStack.toString(haxe.CallStack.exceptionStack());
// ROLLBACK and LOG
if ( cnx != null ) {
cnx.rollback();
logError(e,stack);
}
//log also in a file, in case we don't have a valid connexion to DB
Web.logMessage(e+"\n" + stack);
maintain = true;
view = new View();
view.message = Std.string(e);
if ( App.config.DEBUG || (user != null && user.isAdmin()) ) {
view.stack = stack;
}
setTemplate("error.mtt");
executeTemplate(false);
} catch( e : Dynamic ) {
Sys.print("<pre>");
Sys.println("Error : "+try Std.string(e) catch( e : Dynamic ) "???");
Sys.println(haxe.CallStack.toString(haxe.CallStack.exceptionStack()));
try {
if( cnx != null )
sugoi.db.Error.manager.get(0,false);
} catch( e : Dynamic ) {
Sys.println("Initializing Database...");
sys.db.Admin.initializeDatabase();
Sys.println("Done");
}
Sys.print("</pre>");
}
}
/**
* init template engine
* and db connexion
*/
function init() {
maintain = App.config.getBool("maintain");
if( maintain ) {
view = new View();
setTemplate("maintain.mtt");
executeTemplate(false);
return false;
}
try {
var dbstr = App.config.get("database");
var dbreg = ~/([^:]+):\/\/([^:]+):([^@]*?)@([^:]+)(:[0-9]+)?\/(.*?)$/;
if( !dbreg.match(dbstr) )
throw "Configuration requires a valid database attribute, format is : mysql://user:password@host:port/dbname";
var port = dbreg.matched(5);
var dbparams = {
user:dbreg.matched(2),
pass:dbreg.matched(3),
host:dbreg.matched(4),
port:port == null ? 3306 : Std.parseInt(port.substr(1)),
database:dbreg.matched(6),
socket:null
};
cnx = sys.db.Mysql.connect(dbparams);
} catch( e : Dynamic ) {
errorHandler(e);
return false;
}
if( App.config.SQL_LOG )
cnx = new sugoi.tools.DebugConnection(cnx);
return true;
}
function cloneApp() {
// ensure that we have no variable initialized in app loop
var app = new App();
var bapp : BaseApp = app;
bapp.cnx = cnx;
bapp.view = new View();
App.current = app;
bapp.mainLoop();
}
function run() {
// Will close the connection
sys.db.Transaction.main(cnx, cloneApp, function(e) { var b : BaseApp = App.current; b.errorHandler(e); });
App.current = null;
}
function sendHeaders(){
Web.setHeader("Cache-Control", "no-store, no-cache, must-revalidate");
Web.setHeader("Pragma", "no-cache");
Web.setHeader("Expires", "-1");
Web.setHeader("P3P", "CP=\"ALL DSP COR NID CURa OUR STP PUR\"");
Web.setHeader("Content-Type", "text/html; Charset=UTF-8");
Web.setHeader("Expires", "Mon, 26 Jul 1997 05:00:00 GMT");
}
static function main() {
/**
* this macro will parse the code and generate the allTexts.pot file
* which will be used as a template for translation files (*.po and *.mo)
*/
#if i18n_parsing
if( false ) sugoi.i18n.GetText.parse(["src", "lang/master","js","common"], "www/lang/allTexts.pot");
#end
App.current = new App();
var a : BaseApp = App.current;
a.sendHeaders();
if( !a.init() ) {
a = null;
return;
}
a.run();
a = null;
#if neko
if ( App.config.getInt("cache", 0) == 1 ) {
neko.Web.cacheModule(App.main);
}
#end
}
}
+95
View File
@@ -0,0 +1,95 @@
package sugoi;
import sugoi.db.File;
import sugoi.Web;
import sugoi.ControllerAction;
@:autoBuild(sugoi.tools.Macros.buildController())
class BaseController {
var app : App;
var view : View;
public function new() {
app = App.current;
view = app.view;
}
function getParam( v : String ) {
return app.params.get(v);
}
function checkToken():Bool {
var token = haxe.crypto.Md5.encode(app.session.sid + App.config.KEY.substr(0,6));
view.token = token;
return app.params.get("token") == token;
}
function isAdmin() {
return app.user != null && app.user.isAdmin();
}
public function Redirect( url : String ) {
return RedirectAction(url);
}
public function Error( url : String, ?text : String ) {
return ErrorAction(url, text);
}
public function Ok( url : String, ?text : String ) {
return OkAction(url, text);
}
/**
* User uploaded images are stored in the db.File table.
* When there is an attempt to display an image like /file/***.jpg
* the .htaccess in /file/ redirects to this handler to generate the file from the DB
* @param fname
*/
function doFile( fname : String ) {
//get the file from DB
var fid = Std.parseInt(fname);
var f = File.manager.get(fid, false);
var ext = fname.substr( fname.lastIndexOf(".") );//.png
if( f == null ) {
Sys.print("404 - File not found '"+StringTools.htmlEscape(fname)+"' id #"+fid);
return;
}
if ( fname != File.makeSign(fid) + ext ){
Sys.print("404 - File signature do not match '"+fname+"' != '"+File.makeSign(fid)+ ext+"'");
return;
}
var path;
var ch;
try {
path = Web.getCwd()+"/file/"+File.makeSign(f.id)+ext;
ch = sys.io.File.write(path,true);
} catch( e : Dynamic ) {
Sys.sleep(0.1); // wait for another process to write ?
Web.redirect(Web.getURI()+"?retry=1");
return;
}
ch.write(f.data);
ch.close();
try {
// get mtime of current index.n
#if neko
var s = sys.FileSystem.stat(Web.getCwd() + "index.n");
#else
var s = sys.FileSystem.stat(Web.getCwd() + "index.php");
#end
var mtime = s.mtime.toString();
// set mtime of new file
var p = new sys.io.Process("touch",["-m","-d",mtime,path]);
p.exitCode();
}catch( e : Dynamic ){
}
Web.redirect(Web.getURI()+"?reload=1");
}
}
+163
View File
@@ -0,0 +1,163 @@
package sugoi;
import sugoi.db.Variable;
import sugoi.db.File;
class BaseView implements Dynamic {
var _vcache : Map<String,String>;
public function new() {
_vcache = new Map();
}
public function init() {
var app = App.current;
this.user = app.user;
this.session = app.session;
this.LANG = App.config.LANG;
this.HOST = App.config.HOST;
this.DATA_HOST = App.config.DATA_HOST;
this.DEBUG = App.config.DEBUG;
this.NAME = App.config.NAME;
this.isAdmin = app.user != null && app.user.isAdmin();
//Access basic functions in views
this.Std = Std;
this.Math = Math;
if ( App.config.SQL_LOG ) {
this.sqlLog = untyped app.cnx == null ? null : app.cnx.log;
}
}
function getMessage() {
var session = App.current.session;
if ( session == null ) return null;
if (session.messages == null) return null;
var n = session.messages.pop();
if( n == null ) return null;
return { text : n.text, error : n.error, next : session.messages.length > 0 };
}
function getMessages() {
var out = [];
var session = App.current.session;
if ( session == null ) return [];
if (session.messages == null) return [];
for ( m in session.messages) {
if( m == null ) continue;
out.push( { text : m.text, error : m.error } );
}
session.messages = [];
return out;
}
function urlEncode(str:String) {
return StringTools.urlEncode(str);
}
/**
* To safely print a string in javascript
* @param str
*/
public function escapeJS( str : String ) {
if (str == null) return "";
return str.split("\\").join("\\\\").split("'").join("\\'").split("\r").join("\\r").split("\n").join("\\n");
}
/**
* Get a value from the Variable table
* @param file
*/
function getVariable( file : String ) {
var v = _vcache.get(file);
if( v != null )
return v;
if( App.current.maintain )
return "";
v = Variable.get(file);
if( v == null ) v = "";
_vcache.set(file,v);
return v;
}
function getParam( p : String ) {
return App.current.params.get(p);
}
/**
* Return the url of a db.File record
*/
public function file( file : sugoi.db.File) {
if (file == null) throw "file is null";
return "/file/"+sugoi.db.File.makeSign(file.id)+"."+file.getExtension();
}
/**
* Try to print an HTML table from any kind of object
* @param data
*/
function table(data:Dynamic) {
return new sugoi.helper.Table("table table-bordered").toString(data);
}
/**
* newline to <br/>
* @param txt
*/
public function nl2br(txt:String):String {
if (txt == null) return "";
return txt.split("\n").join("<br/>");
}
public function _(str:String):String {
if (sugoi.i18n.Locale.texts != null) {
return sugoi.i18n.Locale.texts.get(str);
} else {
return str;
}
}
//same function with params ( templo doesnt manage optionnal params in functions )
public function __(str:String, params:Dynamic){
return sugoi.i18n.Locale.texts.get(str, params);
}
/*
#else
public function _(str:String):String {
neko.Web.logMessage("_call "+str);
return StringTools.rtrim( str.split("||")[0] );
}
//same function with params ( templo doesnt manage optionnal params in functions )
public function __(str:String, params:Dynamic):String {
neko.Web.logMessage("_call "+str);
str = StringTools.rtrim( str.split("||")[0] );
var list = str.split("::");
if(params != null) {
for (k in Reflect.fields(params)) {
str = StringTools.replace(str, "::" + k + "::", Reflect.field(params, k));
}
}
return str;
}
#end
*/
public function loopList(start:Int,end:Int):List<Int> {
var list = new List<Int>();
for (i in start...end) {
list.add(i);
}
return list;
}
}
+63
View File
@@ -0,0 +1,63 @@
package sugoi;
import sugoi.Web;
class Config {
public var PATH :String;
//public var json : Dynamic;
public var xml : Xml;
public var LANG :String;
public var LANGS :Array<String>;
public var TPL :String;
public var TPL_TMP :String;
public var DEBUG :Bool;
public var HOST :String;
public var NAME :String;
public var KEY :String;
public var DATA_HOST :String;
public var SQL_LOG :Bool;
public function new(?path:String) {
PATH = (path != null) ? path : Web.getCwd() + "../";
//json = haxe.Json.parse(sys.io.File.getContent(PATH + "config.json"));
xml = Xml.parse(sys.io.File.getContent(PATH + "config.xml")).firstElement();
LANG = get("lang");
LANGS = get("langs").split(";");
TPL = PATH + "lang/" + LANG + "/tpl/";
TPL_TMP = PATH + "lang/" + LANG + "/tmp/";
DEBUG = get("debug","0") == "1";
HOST = get("host");
NAME = get("name");
KEY = get("key");
DATA_HOST = get("dataHost","data."+HOST);
SQL_LOG = getBool("sqllog", false);
}
public function defined( val : String ) {
//return Reflect.field(xml,val) != null;
return xml.get(val) != null;
}
public function getBool( val : String, ?def ) {
var v = get(val);
if( v == null ) return def;
return( v == "1" || v == "true" );
}
public function get( val : String, ?def : String ) : String {
//var v = Reflect.field(xml,val);
var v = xml.get(val);
if( v == null )
v = def;
if( v == null )
throw "Missing config attribute : '"+val+"'";
return v;
}
public function getInt( val : String, ?def : Int ) : Int {
return Std.parseInt(get(val,Std.string(def)));
}
}
+7
View File
@@ -0,0 +1,7 @@
package sugoi;
enum ControllerAction {
RedirectAction( url : String );
ErrorAction( url : String, ?text : String );
OkAction( url : String, ?text : String );
}
+12
View File
@@ -0,0 +1,12 @@
package sugoi;
/**
* Shortcut to system class
*
* @author fbarbut<francois.barbut@gmail.com>
*/
#if php
typedef Web = php.Web;
#else
typedef Web = neko.Web;
#end
+127
View File
@@ -0,0 +1,127 @@
package sugoi.apis.facebook.server;
/**
*
* Call Facebook services thru the graph API
*
* @author fbarbut<francois.barbut@gmail.com>
*/
class FB
{
var app_id : String;
var app_secret : String;
var token : String;
public function new(fbToken, ?app_id, ?app_secret) {
this.token = fbToken;
this.app_id = app_id;
this.app_secret = app_secret;
}
public static function init(fbToken, ?app_id, ?app_secret) {
return new FB(fbToken, app_id, app_secret);
}
/**
* Publish a photo in the current user galery
* @param imgUrl
* @param text
* @return a json object
*/
public function publishPhoto(imgUrl:String,text:String):Dynamic {
//var c = new sugoi.apis.linux.Curl();
//c.setPostData("access_token", token);
//c.setPostData("url", imgUrl );
//var r = c.call("POST", "https://graph.facebook.com/v2.5/me/photos");
//var c = new sys.io.Process("curl", [
//"-X POST",
//"-d url="+imgUrl,
//"-d access_token=" + token,
//"https://graph.facebook.com/v2.5/me/photos"
//]);
var text = StringTools.urlEncode(text);
return call('curl -X POST -d url=$imgUrl -d access_token=$token -d caption="$text" https://graph.facebook.com/v2.5/me/photos');
}
/**
* Publish a story
* @doc https://developers.facebook.com/docs/sharing/opengraph
*
* @param action Like 'game.achieves', see https://developers.facebook.com/docs/reference/opengraph/action-type/games.achieves/
* @param ogObjectUrl An opengraph object representing the subject the action in done to
*/
public function publishStory(action:String, objectType:String,ogObjectUrl:String,?message:String) {
ogObjectUrl = StringTools.urlEncode(ogObjectUrl);
return call('curl -X POST -d access_token=$token -d "$objectType=$ogObjectUrl" -d fb:explicitly_shared=true '+(message!=null ? ' -d message="'+StringTools.urlDecode(message)+'"' : "") +' https://graph.facebook.com/v2.5/me/$action');
}
/**
* Get a long lived facebook token from a short lived one
* @doc https://developers.facebook.com/docs/facebook-login/access-tokens/expiration-and-extension
* @return token
*/
public function getLongLivedToken():String {
//WTF the result is not JSON but like "access_token=XXX&expires=5179521"
var req = 'curl -X GET "https://graph.facebook.com/oauth/access_token?grant_type=fb_exchange_token&client_id=$app_id&client_secret=$app_secret&fb_exchange_token=$token&redirect_uri=https://' + App.config.HOST + '"';
var r = call(req, false);
if (r.substr(0, 13) != "access_token=") throw r;
var r2 = r.split("&")[0].split("=")[1];
if (r == null) throw 'error while parsing $r';
return r2;
}
/**
* call via cURL
*/
function call(cmd:String,?isJson=true):Dynamic {
var c = new sys.io.Process(cmd,[]);
#if neko
var r = neko.Lib.stringReference(p.stdout.readAll());
#else
var r = c.stdout.readAll().toString();
#end
//error ?
if (r == null) {
#if neko
var r = neko.Lib.stringReference(c.stderr.readAll());
#else
var r = c.stderr.readAll().toString();
#end
}
c.exitCode();
if (r == null) throw "cUrl answer is null";
if (!isJson) return r;
var json:Dynamic = haxe.Json.parse(r);
if ( json == null ) throw "json result is null";
if (json.error==1 || json.error || json.error=="1") {
throw r;
}
return json;
}
}
+134
View File
@@ -0,0 +1,134 @@
package sugoi.apis.google;
/**
* Geocoding via Google maps API
*
* @doc https://developers.google.com/maps/documentation/geocoding/
* @author fbarbut
*/
/**
* { "results" : [
* { "address_components" : [
* { "long_name" : "1", "short_name" : "1", "types" : [ "street_number" ] },
* { "long_name" : "Place Saint Bénigne", "short_name" : "Pl. Saint Bénigne", "types" : [ "route" ] },
* { "long_name" : "Dijon", "short_name" : "Dijon", "types" : [ "locality", "political" ] },
* { "long_name" : "Côte-d'Or", "short_name" : "Côte-d'Or", "types" : [ "administrative_area_level_2", "political" ] },
* { "long_name" : "Bourgogne", "short_name" : "Bourgogne", "types" : [ "administrative_area_level_1", "political" ] },
* { "long_name" : "France", "short_name" : "FR", "types" : [ "country", "political" ] },
* { "long_name" : "21000", "short_name" : "21000", "types" : [ "postal_code" ] } ],
* "formatted_address" : "1 Pl. Saint Bénigne, 21000 Dijon, France",
* "geometry" : { "bounds" : { "northeast" : { "lat" : 47.32183939999999, "lng" : 5.0339407 }, "southwest" : { "lat" : 47.3218299, "lng" : 5.0339282 } }, "location" : { "lat" : 47.32183939999999, "lng" : 5.0339282 }, "location_type" : "RANGE_INTERPOLATED", "viewport" : { "northeast" : { "lat" : 47.3231836302915, "lng" : 5.035283430291502 }, "southwest" : { "lat" : 47.3204856697085, "lng" : 5.032585469708497 } } }, "partial_match" : true, "place_id" : "EikxIFBsLiBTYWludCBCw6luaWduZSwgMjEwMDAgRGlqb24sIEZyYW5jZQ", "types" : [ "street_address" ] } ], "status" : "OK" }
*/
typedef GeoCodingData = Array<{
address_components : Array<{long_name:String,short_name:String,types:Array<String>}>,
formatted_address : String, //Marché de Lerme, Pl. de Lerme, 33000 Bordeaux, France
geometry : {location:{lat:Float,lng:Float}}
}>
class GeoCode
{
//API KEY : https://developers.google.com/maps/documentation/geocoding/?hl=FR#api_key
public static var KEY = "";
public static var USE_CURL = true;
public function new(api_key) {
KEY = api_key;
}
/**
* address -> lat/lng
*
* @doc components filtering : https://developers.google.com/maps/documentation/geocoding/intro#ComponentFiltering
*/
public function geocode(address:String,?components:String):GeoCodingData {
var url = "https://maps.googleapis.com/maps/api/geocode/json?address=" + StringTools.urlEncode(address) + "&key=" + KEY;
if (components != null) url += "&components=" + StringTools.urlEncode(components);
var d = curlRequest("GET", url, null, null);
if (d == "" || d == null) throw "curl response is empty";
return onData(d);
}
/**
* Reverse geocoding : latitude/longitude -> address
* @param lat
* @param lng
*/
public function reverse(lat:Float, lng:Float) {
//clermont-ferrand : 45.783 3.083
var url = "https://maps.googleapis.com/maps/api/geocode/json?latlng=" + lat + "," + lng + "&key="+KEY;
//if(USE_CURL) {
//NEEDS CURL
var d = curlRequest("GET", url, null, null);
if(d == "" || d == null) throw "curl response is empty";
return onReverseData(d);
//}else {
////NEEDS HXSSL
//var req = new haxe.Http(url);
//req.onError = function(err:String) trace("error : " + err);
//req.onData = onData;
//req.request(false);
//}
}
public function onData(s:String) {
//trace("<pre>"+s+"</pre>");
var json = cast haxe.Json.parse(s);
if (json.status != "OK") throw "Google geocoding API Error : " + s;
var r : GeoCodingData = cast json.results;
return r;
}
public function onReverseData(s:String) {
//var out : GeoCodingData = [{address_components:[],formatted_address:null,}];
//
//var json = haxe.Json.parse(s);
//if(json.status != "OK") throw "Google geocoding API Error : " + s;
//var arr : Array<Dynamic> = cast json.results;
//var addrs : Array<Dynamic> = cast arr[0].address_components;
//
//for ( a in addrs) {
//var types : Array<String> = cast a.types ;
//var o = { long_name:Std.string(a.long_name), short_name:Std.string(a.short_name), types:types};
//out[0].address_components.push(o);
//}
//
//return out;
}
public function curlRequest( method: String, url : String, ?headers : Dynamic, postData : String ) : Dynamic {
var cParams = ["-X"+method,"--max-time","5"];
for( k in Reflect.fields(headers) ){
cParams.push("-H");
cParams.push(k+": "+Reflect.field(headers,k));
}
cParams.push(url);
if( postData != null ){
cParams.push("-d");
cParams.push(postData);
}
var p = new sys.io.Process("curl",cParams);
var str = neko.Lib.stringReference(p.stdout.readAll());
p.exitCode();
return str;
}
}
+26
View File
@@ -0,0 +1,26 @@
package sugoi.apis.google;
import sugoi.apis.linux.Curl;
/**
* Google ReCaptcha
* @doc https://www.google.com/recaptcha/
* @author fbarbut<francois.barbut@gmail.com>
*/
class Recaptcha
{
/**
* Calls the service and returns a JSON response (not parsed)
*/
public static function call(secret:String, token:String, ip:String):String {
var c = new Curl();
c.setPostData("secret", secret);
c.setPostData("response", token);
c.setPostData("remoteip", ip );
return c.call("POST", "https://www.google.com/recaptcha/api/siteverify", { } );
}
}
+109
View File
@@ -0,0 +1,109 @@
package sugoi.apis.linux;
/**
* cURL
*
* @doc https://en.wikipedia.org/wiki/CURL
*
* Call cURL as an external process.
* It's an easy way to call HTTPS services from neko and php.
* Be sure to have cURL installed on your system
*
* @author fbarbut<francois.barbut@gmail.com>
*/
class Curl
{
public var postData : Map<String,String>; //POST params
public var params : Array<String>; //curl CLI params
public var debugCommand : String; //store here the generated command
public function new()
{
postData = new Map<String,String>();
params = [];
}
public static function get() {
return new Curl();
}
public function setPostData(key:String, value:String) {
postData.set(key, value);
}
/**
* Execute CURL request
*
* @param method POST, GET or PUT
* @param url
* @param headers
* @param post A string sent as raw POST i.e a json request object
*/
public function call( method:String, url : String, ?headers : Map<String,String>,?post:String) : String {
//method GET or POST
if (post != null || Lambda.count(postData) > 0)
params.push("-X" + method);
//time out
params.push("--max-time");
params.push("20");
//headers
if (headers != null){
for( k in headers.keys() ){
params.push("-H");
//params.push("\""+haxe.Utf8.encode(k+": "+headers.get(k))+"\"");
params.push( k+": "+headers.get(k) );
}
}
params.push(url);
//POST params (key-values)
if( postData!=null && Lambda.count(postData) > 0 ){
params.push("-d");
var d = [];
for (k in postData.keys()) {
d.push( k + "=" + StringTools.urlEncode(postData.get(k)) );
//d.push( k + "=" + postData.get(k) );
}
params.push("\""+d.join("&")+"\"");
}
//if there is a POST payload ( i.e a JSON formatted request )
if (post != null) {
params.push("-d");
params.push(post);
//params.push("\""+StringTools.urlEncode(post)+"\"");
}
//params = params.map(function(s) return haxe.Utf8.encode(s));
debugCommand = "curl " + params.join(" ");
var p = new sys.io.Process("curl", params);
#if neko
var str = neko.Lib.stringReference(p.stdout.readAll());
#else
var str = p.stdout.readAll().toString();
#end
//error ?
if (str == null) {
#if neko
str = "Error : " + neko.Lib.stringReference(p.stderr.readAll());
#else
str = "Error : " + p.stderr.readAll().toString();
#end
}
p.exitCode();
return str;
}
}
+72
View File
@@ -0,0 +1,72 @@
package sugoi.apis.mailchimp;
import sugoi.apis.linux.Curl;
class Mailchimp
{
var lastError:String;
var dataCenter:String;
var apiKey:String;
var listId:String;
var serviceUrl : String;
public function new(apiKey:String, listId:String, ?dataCenter:String = null)
{
this.apiKey = apiKey;
this.listId = listId;
this.dataCenter = (dataCenter == null) ? apiKey.split("-")[1] : dataCenter;
serviceUrl = "https://"+dataCenter+".api.mailchimp.com/2.0/";
}
/**
* Access up to the previous 180 days of daily detailed aggregated activity stats for a given list.
* Does not include AutoResponder activity.
*
* @return [
* {"user_id":13422379,"day":"2013-07-25","emails_sent":0,"unique_opens":0,"recipient_clicks":0,"hard_bounce":0,"soft_bounce":0,"abuse_reports":0,"subs":1,"unsubs":0,"other_adds":0,"other_removes":0},
* {"user_id":13422379,"day":"2013-10-24","emails_sent":0,"unique_opens":0,"recipient_clicks":0,"hard_bounce":0,"soft_bounce":0,"abuse_reports":0,"subs":1,"unsubs":0,"other_adds":0,"other_removes":0},
* {"user_id":13422379,"day":"2013-11-24","emails_sent":0,"unique_opens":0,"recipient_clicks":0,"hard_bounce":0,"soft_bounce":0,"abuse_reports":0,"subs":1,"unsubs":0,"other_adds":0,"other_removes":0}
* ]
*/
public function getActivity() {
var curl = Curl.get();
return haxe.Json.parse( curl.call("POST", serviceUrl + "lists/activity/", { }, haxe.Json.stringify( { apikey:apiKey, id:listId } ) ) );
}
/**
* Subscribe (or update) a member to a list
*
* @see https://apidocs.mailchimp.com/api/2.0/lists/subscribe.php
* @return { euid => a519e7b675, leid => 59171769, email => francois.barbut@gmail.com }
*/
public function subscribe(listId:String, email: { email:String }, merge_vars: { FNAME:String, LNAME:String, mc_language:String},custom_tags:Dynamic, double_option:Bool, update_existing:Bool, send_welcome:Bool ) {
//merge custom tags with merge_vars
if (custom_tags != null) {
for (f in Reflect.fields(custom_tags)) {
Reflect.setField(merge_vars, f, Reflect.getProperty(custom_tags, f));
}
}
var data = {
apikey:this.apiKey,
id:listId,
email:email,
merge_vars:merge_vars,
double_option:double_option,
update_existing:update_existing,
send_welcome:send_welcome
};
var curl = Curl.get();
var res = curl.call("POST", serviceUrl + "lists/subscribe/", { }, haxe.Json.stringify(data));
//Sys.println(curl.params);
return haxe.Json.parse(res);
}
//public function batchSuscribe(listId:String,
}
+101
View File
@@ -0,0 +1,101 @@
package sugoi.apis.morning;
/**
* Morning Up Payment Service Connector
*
* @author fbarbut
* @date 2016-10-05
* @doc https://up.morning.com
*/
class MorningUp
{
var token : String;
public function new(token:String)
{
this.token = token;
}
public function createPayment(amount:Float,title:String,?type=null,order_id:String,back_url:String) {
var amount = Std.string(amount);
title = title.substr(0, 255);
var args = [
'-X',
'POST',
'-d',
'token=$token',
'-d',
'amount=$amount',
'-d',
'title=$title',
'-d',
'order_id=$order_id',
'-d',
'back_url=$back_url',
'https://up.morning.com/api/creer-un-paiement'
];
return call("curl", args);
}
public function paymentInfo(hash:String){
var args = [
/*'-X',
'GET',
'-d',
'token=$token',
'-d',
'hash=$hash', */
'https://up.morning.com/api/paiement-information?hash=$hash&token=$token'
];
return call("curl", args);
}
/**
* call via cURL
*/
function call(cmd:String,args:Array<String>,?isJson=true):Dynamic {
var p = new sys.io.Process(cmd, args);
var r:String = null;
#if neko
r = neko.Lib.stringReference(p.stdout.readAll());
#else
r = p.stdout.readAll().toString();
#end
//error ?
if (r == null || r == "") {
#if neko
r = neko.Lib.stringReference(p.stderr.readAll());
#else
r = p.stderr.readAll().toString();
#end
}
p.exitCode();
if (r == null) throw "cUrl answer is null";
if (r == "") throw "cUrl answer is an empty string";
if (!isJson) return r;
var json:Dynamic = null;
try{
json = haxe.Json.parse(r);
}catch (e:Dynamic){
throw 'Error while parsing JSON "$r" : "$e"';
}
if ( json == null ) throw "JSON result is null";
if (json.error==1 || json.error || json.error=="1") {
throw r;
}
return json;
}
}
+122
View File
@@ -0,0 +1,122 @@
package sugoi.db;
import sys.db.Types;
import sugoi.mail.IMail;
import sugoi.mail.IMailer;
/**
* DB Buffer for emails
*/
@:index(remoteId, sdate, cdate)
class BufferedMail extends sys.db.Object {
public var id:SId;
// email content
public var title:SString<256>;
public var htmlBody:SNull<SText>;
public var textBody:SNull<SText>;
public var headers:SData<Map<String, String>>;
public var sender:SData<{name:String, email:String, ?userId:Int}>;
public var recipients:SData<Array<{name:String, email:String, ?userId:Int}>>;
// utility fields
public var mailerType:SString<32>; // mailer used when sending for real
public var tries:SInt; // number of times we tried to send the mail
public var cdate:SDateTime; // creation date
public var sdate:SNull<SDateTime>; // sent date
public var rawStatus:SNull<SText>; // raw return from the smtp server or mandrill API
public var status:SNull<SData<MailerResult>>; // map of emails with api/smtp results
// custom datas
public var data:SNull<SData<Dynamic>>; // custom datas
public var remoteId:SNull<Int>; // custom remote Id (userId, groupId ...)
public function getMailerResultMessage(k:String):{failure:String, success:String} {
var t = sugoi.i18n.Locale.texts;
var out = {failure: null, success: null};
switch (status.get(k)) {
case tink.core.Outcome.Failure(f):
out.failure = switch (f) {
case GenericError(e): t._("Generic error: ") + e.toString();
case HardBounce: t._("Mailbox does not exist");
case SoftBounce: t._("Mailbox full or blocked");
case Spam: t._("Message considered as spam");
case Unsub: t._("This user unsubscribed");
case Unsigned: t._("Sender incorrect (Unsigned)");
};
case tink.core.Outcome.Success(s):
out.success = switch (s) {
case Sent: t._("Sent");
case Queued: t._("Queued");
};
}
return out;
}
public function new() {
super();
cdate = Date.now();
tries = 0;
}
public function isSent() {
return sdate != null;
}
/**
* Finally really send the message
*/
public function finallySend():Void {
if (isSent())
throw "already sent";
var conf = {
smtp_host: sugoi.db.Variable.get("smtp_host"),
smtp_port: sugoi.db.Variable.getInt("smtp_port"),
smtp_user: sugoi.db.Variable.get("smtp_user"),
smtp_pass: sugoi.db.Variable.get("smtp_pass")
};
App.log('choose mail from ${this.mailerType}');
var mailer:sugoi.mail.IMailer = switch (this.mailerType) {
case "mandrill":
new sugoi.mail.MandrillMailer().init(conf);
case "smtp":
new sugoi.mail.SendEmailMailer().init(conf);
default:
throw "Unknown mailer type : " + this.mailerType;
};
var m = new sugoi.mail.Mail();
for (k in this.headers.keys())
m.setHeader(k, headers[k]);
m.setSubject(this.title);
for (r in recipients)
m.setRecipient(r.email, r.name, r.userId);
m.setSender(sender.email, sender.name, sender.userId);
m.setHtmlBody(this.htmlBody);
m.setTextBody(this.textBody);
this.lock();
this.tries++;
try {
mailer.send(m, null, afterSendCb);
this.sdate = Date.now();
this.rawStatus = null;
} catch (e:Dynamic) {
this.sdate = null;
this.rawStatus = Std.string(e);
// App.current.logError( Std.string(e) );
}
this.update();
}
function afterSendCb(status:MailerResult) {
// App.current.logError(status);
this.status = status;
this.update();
}
}
+65
View File
@@ -0,0 +1,65 @@
package sugoi.db;
import sys.db.Types;
/**
* Key-Value temporary storage in a Memcached style
*/
@:id(name)
class Cache extends sys.db.Object
{
public var name : SString<128>;
public var value : SText;
public var expire : SDateTime;
public var cdate : SNull<SDateTime>;
public function new(){
super();
cdate = Date.now();
}
/**
* Read the value for key $id
*/
public static function get(id:String):Dynamic {
if (Std.random(1000) == 0) Cache.manager.delete($expire < Date.now());
var c = manager.get(id, true);
if (c == null) return null;
if (c.expire.getTime() < Date.now().getTime()) {
c.delete();
return null;
}
return haxe.Unserializer.run(c.value);
}
/**
* Set a value for key $id
* and optionnaly a lifetime in seconds
*/
public static function set(id:String, value:Dynamic,expireInSeconds:Float) {
var c = manager.get(id, true);
var niou = false;
if (c == null) {
niou = true;
c = new Cache();
c.name = id;
}
c.value = haxe.Serializer.run(value);
c.expire = DateTools.delta(Date.now(), expireInSeconds*1000.0);
if (niou) {
c.insert();
}else {
c.update();
}
}
/**
* Delete a value for key $id
*/
public static function destroy(id:String) {
var c = manager.get(id, true);
if (c != null) c.delete();
}
}
+16
View File
@@ -0,0 +1,16 @@
package sugoi.db;
import sys.db.Types;
class Error extends sys.db.Object {
public var id : SId;
public var date : SDateTime;
public var error : SText;
@:relation(uid) public var user : SNull<db.User>;
public var ip : SNull<SString<15>>;
public var userAgent : SNull<SString<256>>;
public var url : SNull<STinyText>;
}
+77
View File
@@ -0,0 +1,77 @@
package sugoi.db;
import sys.db.Manager;
import sys.db.Types;
/**
* Store files in DB
*/
class File extends sys.db.Object {
public var id : SId;
public var name : STinyText; //filename
public var cdate : SNull<SDateTime>; //creation datetime
public var data : SBinary;
@:skip
static var CACHE = [];
override public function new(){
super();
cdate = Date.now();
}
/**
* Get the file name related to this File record.
* Usually files should be generated in /file/
*/
public static function makeSign( id : Int ) {
if( id == null )
return "";
var s = CACHE[id];
if( s != null ) return s;
s = id+"_"+haxe.crypto.Md5.encode(id + App.config.get('key'));
CACHE[id] = s;
return s;
}
public override function toString() {
return "#" + id + " " + name;
}
/**
* Creates a File record
* from string data (typically sent from a form) and a file name
*/
public static function create(stringData:String, ?fileName=""):File {
var bytes = new haxe.io.StringInput(stringData).readAll();
return createFromBytes(bytes, fileName);
}
public static function createFromBytes(b:haxe.io.Bytes, ?fileName=""):File {
#if neko
var f = new File();
f.name = fileName;
f.data = b;
f.insert();
return f;
#else
//there is a bug in PHP, we do it manually
var hexa = b.toHex();
Manager.cnx.request("INSERT INTO File (name,data) VALUES ('"+fileName+"', 0x"+hexa+")");
return File.manager.select($name==fileName);
#end
}
public function getExtension():String {
if (name == null || name=="") return "jpg";
return name.split(".")[1];
}
}
+135
View File
@@ -0,0 +1,135 @@
package sugoi.db;
import sys.db.Types;
import db.User;
#if neko
import neko.Web;
#else
import php.Web;
#end
@:id(sid)
@:index(uid,unique)
class Session extends sys.db.Object {
public var sid : SString<32>;
public var ip : SString<15>;
public var lang : SString<2>;
public var messages : SData<Array<{ error : Bool, text : String }>>;
public var lastTime : SDateTime;
public var createTime : SDateTime;
#if neko
public var sdata : SNekoSerialized;
#else
public var sdata : SText;
#end
@:skip public var data : Dynamic;
//public var uid : SNull<SInt>;
@:relation(uid) public var user : SNull<db.User>;
public function new() {
super();
messages = [];
data = {};
}
/**
* Stores a message in session
*/
public function addMessage( text : String, ?error=false ) {
messages.push({ error : error, text : text });
}
public function setUser( u : User ):Void {
//remove any previous session for this user
manager.delete($uid==u.id);
lang = u.lang;
user = u;
update();
App.current.user = u;
}
public override function update() {
#if neko
sdata = neko.Lib.serialize(data);
#else
sdata = haxe.Serializer.run(data);
#end
lastTime = Date.now();
super.update();
}
private static function get( sid:String ):Session {
if ( sid == null ) return null;
var s = manager.get(sid,true);
if ( s == null ) return null;
try {
#if neko
s.data = neko.Lib.localUnserialize(s.sdata);
#else
s.data = haxe.Unserializer.run(s.sdata);
#end
}catch (e:Dynamic) {
s.data = null;
}
return s;
}
public static function init( sids : Array<String> ) {
for( sid in sids ) {
var s = get(sid);
if( s != null ) return s;
}
var ip = Web.getClientIP();
var s = new Session();
s.ip = ip;
s.createTime = Date.now();
s.lastTime = Date.now();
s.sid = generateId();
var count = 20;
while( try { s.insert(); false; } catch( e : Dynamic ) true ) {
s.sid = generateId();
// prevent infinite loop in SQL error
if( count-- == 0 ) {
s.insert();
break;
}
}
return s;
}
/**
* Generate a random 32 chars string
*/
public static var S = "abcdefjhijklmnopqrstuvwxyABCDEFJHIJKLMNOPQRSTUVWXYZ0123456789";
public static function generateId():String {
var id = "";
for ( x in 0...32 ) {
id += S.substr(Std.random(S.length),1);
}
return id;
}
/**
* Delete sessions older than 1 month
*/
public static function clean() {
manager.delete($lastTime < DateTools.delta(Date.now(),-1000.0*60*60*24*30));
}
}
+48
View File
@@ -0,0 +1,48 @@
package sugoi.db;
import sys.db.Types;
@:id(name)
class Variable extends sys.db.Object {
public var name : SString<50>;
public var value : SString<50>;
public static function get( name ) {
var v = manager.get(name,false);
return v == null ? null : v.value;
}
public static function set(name, val:Dynamic) {
var v = Variable.manager.get(name,true);
if (v==null) {
v = new Variable();
v.name = name;
v.value = Std.string(val);
v.insert();
}
else {
v.value = Std.string(val);
v.update();
}
}
public static function increment(name, ?inc=1) {
var v = Variable.manager.get(name,true);
if (v==null) {
v = new Variable();
v.name = name;
v.value = Std.string(inc);
v.insert();
}
else {
v.value = Std.string(Std.parseInt(v.value)+1);
v.update();
}
}
public static function getInt( name ) {
var v = manager.get(name,false);
return v == null ? 0 : Std.parseInt(v.value);
}
}
+29
View File
@@ -0,0 +1,29 @@
package sugoi.form;
class FieldSet
{
public var name:String;
public var form:Form;
public var label:String;
public var visible:Bool;
public var elements:Array<FormElement<Dynamic>>;
public function new(?name:String = "", ?label:String = "", ?visible:Bool = true)
{
this.name = name;
this.label = label;
this.visible = visible;
elements = [];
}
public function getOpenTag()
{
return "<fieldset id=\""+form.name+"_"+name+"\" name=\""+form.name+"_"+name+"\" class=\""+(visible?"":"fieldsetNoDisplay")+"\" ><legend>" + label + "</legend>";
}
public function getCloseTag()
{
return "</fieldset>";
}
}
+578
View File
@@ -0,0 +1,578 @@
package sugoi.form;
import haxe.crypto.Md5;
import sugoi.form.elements.Input;
import sugoi.i18n.translator.ITranslator;
import sugoi.form.elements.*;
import sugoi.Web;
import sys.db.Types;
import sys.db.Object;
import sys.db.Manager;
import sys.db.TableInfos;
enum FormMethod
{
GET;
POST;
}
class Form
{
public var id:String;
public var name:String;
public var action:String;
public var method:FormMethod;
public var elements:Array<FormElement<Dynamic>>;
public var fieldsets:Map<String,FieldSet>;
public var forcePopulate:Bool; //the form is populated by web params if isValid() is called
public var submitButton:FormElement<String>;
private var extraErrors:List<String>;
public var requiredClass:String;
public var requiredErrorClass:String;
public var invalidErrorClass:String;
public var labelRequiredIndicator:String;
public var defaultClass : String;
public var multipart:Bool;
public static var translator : ITranslator;
//submit button
public var submitButtonLabel:String;
public var autoGenSubmitButton:Bool; //add a submit button automatically
//conf
public static var USE_TWITTER_BOOTSTRAP = true;
public static var USE_DATEPICKER = true; //http://eonasdan.github.io/bootstrap-datetimepicker/
public var toString : Void->String; //you can change the way the form is rendered
public function new(name:String, ?action:String, ?method:FormMethod)
{
requiredClass = "formRequired";
requiredErrorClass = "formRequiredError";
invalidErrorClass = "formInvalidError";
labelRequiredIndicator = " *";
defaultClass = Form.USE_TWITTER_BOOTSTRAP ? "form-horizontal":"";
forcePopulate = true;
multipart = false;
autoGenSubmitButton = true;
this.id = name;
this.name = name;
if (action == null) {
this.action = Web.getURI();
}else {
this.action = action;
}
this.method = (method == null) ? FormMethod.POST : method;
elements = new Array();
extraErrors = new List();
fieldsets = new Map();
addFieldset("__default", new FieldSet("__default", "Default", false));
addElement(new CSRFProtection());
toString = render;
}
/**
* Adds a form element to the form
* @param element
* @param ?fieldSetKey Add it to a specific fieldset
* @param ?index which index do u want to push it
* @return
*/
public function addElement(element:FormElement<Dynamic>,?index:Int, ?fieldSetKey:String = "__default"):FormElement<Dynamic>
{
element.parentForm = this;
if (index != null) {
var out = elements.slice(0, index);
out = out.concat([element]);
out = out.concat(elements.slice(index));
elements = out;
}else {
elements.push(element);
}
// add it to a group if requested
if (fieldSetKey != null){
if (!fieldsets.exists(fieldSetKey)) throw "No fieldset '" + fieldSetKey + "' exists in '" + name + "' form.";
fieldsets.get(fieldSetKey).elements.push(element);
}
//if ( Std.is(element, RichtextWym) )
//wymEditorCount++;
return element;
}
public function removeElement(element:FormElement<Dynamic>):Bool
{
if ( elements.remove(element) )
{
element.parentForm= null;
for ( fs in fieldsets )
{
fs.elements.remove(element);
}
//if ( Std.is(element, RichtextWym) )
//wymEditorCount--;
return true;
}
return false;
}
public function setSubmitButton(el:FormElement<String>):FormElement<String>
{
submitButton = el;
submitButton.parentForm = this;
return el;
}
public function addFieldset(fieldSetKey:String, fieldSet:FieldSet)
{
fieldSet.form = this;
fieldsets.set(fieldSetKey, fieldSet);
}
public function getFieldsets():Map<String,FieldSet>
{
return fieldsets;
}
public function getLabel( elementName : String ) : String
{
return getElement( elementName ).getLabel();
}
public function getElement(name:String):FormElement<Dynamic> {
if (name == null || name=='') throw "Element name is null";
for (element in elements){
if (element.name == name) return element;
}
return null;
}
public function removeElementByName(name:String) {
var e = getElement(name);
if (e != null) removeElement(e);
}
/**
* Get the typed value of a form element.
* The value can be of any type !
*
* @param elementName
* @return
*/
public function getValueOf(elementName:String):Dynamic {
return getElement(elementName).value;
}
public function getElementTyped<T>(name:String, type:Class<T>):T{
var o:T = cast(getElement(name));
return o;
}
/**
* return datas contained in current form elements
* @return
*/
public function getData():Map<String,Dynamic>
{
var data = new Map<String,Dynamic>();
for (element in getElements())
{
if (element.name == null) throw "Element has no name : "+element.toString();
data.set( element.name,element.getValue() );
}
return data;
}
/**
* return datas in an anonymous object
* @return
*/
public function getDatasAsObject():Dynamic {
var data = { };
for ( el in elements) {
Reflect.setField(data, el.name, el.value);
}
return data;
}
/**
* populate Form from anonymous object or if null from web params.
* @param custom
*/
public function populate(?custom:Dynamic){
if (custom != null) {
//from object
for (element in getElements()) {
var n = element.name;
var v = Reflect.field(custom, n);
if (v != null)
element.value = v;
}
} else {
for (element in getElements()) {
//populate from web params
element.populate();
}
}
}
/**
* update a spod object from the content of the form
* @param data
* @param obj
*/
public function toSpod(obj:sys.db.Object) {
if (!isValid()) throw "submitted form should be valid";
var data = getData();
//if not new object, lock it
var id = Std.parseInt(data.get("id"));
if (id == 0) id = null;
if (id != null) {
obj.lock();
}
for (f in data.keys()) {
//check if field was in the original form
if (this.getElement(f) == null) throw "field '"+f+"' was not in the original form";
var v = data.get(f);
if (f == "id") continue;
//Values are already cleaned by each form elements when populated
/*if (Std.is(v, String)) {
v = StringTools.trim(v);
if (v == "") v = null;
}*/
//Debug : trace(f + " -> " + v+"<br>");
try{
Reflect.setProperty(obj, f, v);
}catch (e:Dynamic){
throw "Error '" + e+"' while setting value " + v + " to " + f;
}
}
}
/**
* Generate a form from any object
* @param obj
*/
public static function fromObject(obj:Dynamic) {
var form = new Form('fromObj');
for (f in Reflect.fields(obj)) {
var val = Reflect.field(obj, f);
if (val == "") val = null;
form.addElement(new sugoi.form.elements.StringInput(f, f, val));
}
return form;
}
/*
* Generate a form from a spod object
*/
public static function fromSpod(obj:sys.db.Object) {
//generate a form name
var cl = Type.getClass(obj);
var name = Type.getClassName(cl);
var form = new Form("form"+Md5.encode(name));
var ti = new TableInfos(Type.getClassName(Type.getClass(obj)));
//translator
//var t = Form.translator;
var t = new Map<String,String>();
if (Reflect.hasField(cl, "getLabels")){
t = Reflect.callMethod(cl, Reflect.getProperty(cl,"getLabels"),[]);
}
var label = function(s) return if (t.get(s) == null) s else t.get(s);
//get metas of this object
var metas = haxe.rtti.Meta.getFields(Type.getClass(obj));
//loop on db object fields to create form elements
for (f in ti.fields) {
var e : FormElement<Dynamic>;
//field value
var v :Dynamic = Reflect.field(obj, f.name);
//trace( "field " + f.name+" of " + obj + " is " + v+"<br/>");
//meta of this field
var meta :Dynamic = Reflect.field(metas, f.name);
//trace(f.name+"=>" + meta + "<br/>");
//hide this field in forms
if (meta!=null && Reflect.hasField(meta,'hideInForms')) {
continue;
}
//check if its a foreign key
var rl = Lambda.filter(ti.relations, function(r) return r.key == f.name );
var isNull = ti.nulls.get(f.name);
//foreign keys
if (rl.length > 0 ) {
var r = rl.first();
//trace(f.name + ' is a key for ' + r.key + "/"+r.prop);
var objects = new List();
meta = Reflect.field(metas, r.prop);
if (meta != null) {
//trace(r.prop+"=>" + meta + "<br/>");
if (meta.formPopulate != null) {
//If @formPopulate() meta is set, use this function to populate select box.
objects = Reflect.callMethod(obj, Reflect.field(obj,Std.string(meta.formPopulate[0])) , []);
}
//if @hideInForms meta is set, hide the fields in the form
if (meta!=null && Reflect.hasField(meta,'hideInForms')) {
continue;
}
}else {
//get all available values
objects = r.manager.all(false).map(function(d) {
return {
label : d.toString(),
value : Reflect.field(d,r.manager.table_keys[0])
};
});
}
e = new IntSelect(f.name, label(r.prop), Lambda.array(objects),v, !isNull);
}else {
//not foreign key
switch (f.type) {
case DId, DUId:
e = new IntInput(f.name, "id", v, false);
untyped e.inputType = ITHidden;
case DEncoded:
e = new StringInput(f.name, label(f.name), v);
case DFlags(fl, auto):
e = new Flags(f.name,label(f.name), Lambda.array(fl), Std.parseInt(v));
case DTinyInt, DUInt, DSingle, DInt:
e = new IntInput(f.name, label(f.name) , v , !isNull);
case DFloat:
e = new FloatInput(f.name, label(f.name), v, !isNull );
case DBool :
e = new Checkbox(f.name, label(f.name), Std.string(v) == 'true');
case DString(n):
e = new StringInput(f.name,label(f.name), v, !isNull ,null,"maxlength="+n);
case DTinyText, DSmallText, DText, DSerialized:
e = new TextArea(f.name, label(f.name), v,!isNull);
case DTimeStamp, DDateTime:
if (USE_DATEPICKER) {
//WTF bugfix : the type is correct (Date) but is null when traced in DatePicker
var d :Date = cast v;
e = new DatePicker(f.name, label(f.name), d);
untyped e.format = "LLLL";
}else {
e = new DateInput(f.name, label(f.name), v);
}
case DDate :
if (USE_DATEPICKER) {
//trace(f.name+" => " + v);
//trace(Type.getClassName(Type.getClass(v)));
//WTF bugfix : the type is correct (Date) but is null when traced in DatePicker
var d :Date = cast v;
e = new DatePicker(f.name, label(f.name), d);
untyped e.format = "LL";
}else {
e = new DateDropdowns(f.name, label(f.name), v);
}
case DEnum(name):
e = new sugoi.form.elements.Enum(f.name, label(f.name), name, Std.parseInt(v), !isNull);
default :
e = new StringInput(f.name, label(f.name) , "unknown field type : "+f.type+", value : "+v);
}
}
form.addElement(e);
}
return form;
}
public function clearData()
{
for (element in getElements()){
element.value = null;
}
}
/**
* Prints form open tag <form ...>
*/
public function getOpenTag():String
{
//if there is a file input in the form, make it multipart
for ( e in elements) {
if (Type.getClass(e) == sugoi.form.elements.FileUpload || Type.getClass(e) == sugoi.form.elements.ImageUpload){
multipart = true;
break;
}
}
return '<form id="' + id + '" class="'+defaultClass+'" name="' + name + '" method="' + method +'" action="' + action +'" ' + (multipart?'enctype="multipart/form-data"':'') + ' >';
}
/**
* Prints form close tag ...</form>
*/
public function getCloseTag():String
{
var s = new StringBuf();
s.add('<div style="clear:both; height:0px;">&nbsp;</div>');
s.add('<input type="hidden" name="' + name + '_formSubmitted" value="true" /></form>');
return s.toString();
}
public function isValid():Bool
{
if (!isSubmitted()) return false;
populate();
var valid = true;
for (element in getElements()){
//trace(element.name+" -> "+element.value+" : "+element.isValid()+"<br>");
element.filter();
if (!element.isValid()) valid = false;
}
if (extraErrors.length > 0) valid = false;
return valid;
}
public function checkToken() {
return isValid();
}
public function addError(error:String)
{
extraErrors.add(error);
}
public function getErrorsList():List<String>
{
isValid();
var errors:List<String> = new List();
for(e in extraErrors)
errors.add(e);
for (element in getElements())
for (error in element.getErrors())
errors.add(error);
return errors;
}
public function getElements():Array<FormElement<Dynamic>>
{
return elements;
}
public function isSubmitted():Bool
{
//if (multipart){
//var req = sugoi.tools.Utils.getMultipart(1024 * 1024 * 12);
//for ( r in req.keys() ) App.current.params.set(r, req.get(r));
//}
return App.current.params.get(name + "_formSubmitted") == "true";
}
public function getSubmittedValue():String
{
return App.current.params.get(name + "_formSubmitted");
}
public function getErrors():String
{
if (!isSubmitted())
return "";
var s:StringBuf = new StringBuf();
var errors = getErrorsList();
if (errors.length > 0)
{
if (USE_TWITTER_BOOTSTRAP) s.add('<div class="alert alert-danger">');
s.add("<ul class=\"formErrors\" >");
for (error in errors)
{
s.add("<li>"+error+"</li>");
}
s.add("</ul>");
if (USE_TWITTER_BOOTSTRAP) s.add('</div>');
}
return s.toString();
}
/**
* Render form's HTML
*/
public function render()
{
var s:StringBuf = new StringBuf();
s.add(getOpenTag());
//errors
if (isSubmitted())
s.add(getErrors());
for (element in getElements())
if (element != submitButton && element.internal == false)
s.add("\t"+element.getFullRow()+"\n");
//submit button
if (submitButton != null) {
submitButton.parentForm = this;
}else if(autoGenSubmitButton){
submitButton = new Submit('submit', submitButtonLabel != null ? submitButtonLabel : 'OK');
submitButton.parentForm = this;
}
if(submitButton!=null) s.add(submitButton.getFullRow());
s.add(getCloseTag());
return s.toString();
}
}
+242
View File
@@ -0,0 +1,242 @@
package sugoi.form;
import sugoi.form.filters.IFilter;
import sugoi.form.validators.Validator;
using StringTools;
class FormElement<T>
{
public var parentForm:Form;
public var name:String;
public var label:String;
public var description:String;
//value can be any type : Int, Float, Enum...
public var value:T;
public var required:Bool;
public var errors:List<String>;
public var attributes:String;
public var active:Bool;
public var cssClass:String;
public var inited:Bool;
public var internal:Bool;
public var validators:List<Validator<T>>;
public var filters:List<IFilter<T>>;
public function new()
{
active = true;
errors = new List();
validators = new List();
filters = new List();
inited = false;
internal = false;
}
/**
* apply all linked filter to the data
*/
public function filter() {
for ( f in filters) {
value = f.filter(value);
}
return value;
}
/**
* Checks if the current value of the elements is valid
*/
public function isValid():Bool
{
errors.clear();
if (!active) return true;
if ( value == null && required ) {
//required field is empty
errors.add("<span class=\"formErrorsField\">\"" + ((label != null && label != "") ? label : name) + "\"</span> ne doit pas être vide.");
return false;
}
if (value!=null) {
//check validity
if (!validators.isEmpty()){
for (validator in validators)
{
if (!validator.isValid(value)) return false;
}
}
return true;
}
return true;
}
public function init(){
inited = true;
}
public function addValidator(validator:Validator<T>){
validators.add(validator);
}
public function addFilter(filter:IFilter<T>) {
filters.add(filter);
}
/**
* Fill the element with a value taken from the web params
*/
public function populate():Void
{
if (!inited) init();
var n = parentForm.name + "_" + name;
var v = App.current.params.get(n);
value = getTypedValue(v);
//Debug
//trace("value of " + name +"("+n+") is " + v + ", typed :"+ value+"<br/>");
}
/**
* From string (web param) to typed value.
* This method is in charge of cleaning the input which may be unsafe ( triming, escaping ...)
*/
public function getTypedValue(str:String):T{
throw "getTypedValue() function not implemented in \""+name+"\"";
}
public function getErrors():List<String>
{
isValid();
for (val in validators)
for(err in val.errors)
errors.add("<span class=\"formErrorsField\">" + label + "</span> : " + err);
return errors;
}
/**
* Render the element in HTML
*/
public function render():String
{
if (!inited) init();
return Std.string(value);
}
public function remove():Bool
{
if ( parentForm!= null ){
return parentForm.removeElement(this);
}
return false;
}
/**
* renders the element with label+tr+td...
*/
public function getFullRow():String {
var s = new StringBuf();
if(Form.USE_TWITTER_BOOTSTRAP) s.add('<div class="form-group">\n');
s.add(getLabel());
s.add("<div class='col-sm-8'>" + this.render() + "</div>");
if (Form.USE_TWITTER_BOOTSTRAP) s.add('</div>\n');
return s.toString();
}
public function getType():String
{
return Std.string(Type.getClass(this));
}
/**
* Get CSS classes for the form element label
*/
public function getLabelClasses() : String
{
var css = "";
if (Form.USE_TWITTER_BOOTSTRAP) css = "col-sm-4 control-label";
var requiredSet = false;
if (required) {
css += " "+parentForm.requiredClass;
if (parentForm.isSubmitted() && required && value == null) {
css += " "+parentForm.requiredErrorClass;
requiredSet = true;
}
}
if(!requiredSet && parentForm.isSubmitted() && !isValid()){
css += " "+parentForm.invalidErrorClass;
}
//if ( cssClass != null )
//css += ( css == "" ) ? cssClass : " " + cssClass;
return css;
}
public function getLabel():String
{
var n = parentForm.name + "_" + name;
return "<label for=\"" + n + "\" class=\""+getLabelClasses()+"\" id=\"" + n + "__Label\">" + label +(required?parentForm.labelRequiredIndicator:'') +"</label>";
}
/**
* Return CSS classes of the element
*/
public function getClasses() : String
{
var css = ( cssClass != null ) ? cssClass : parentForm.defaultClass;
if ( required && parentForm.isSubmitted() )
{
if ( value == null )
css += " " + parentForm.requiredErrorClass;
if ( !isValid() )
css += " " + parentForm.invalidErrorClass;
}
if(css == null) css = "";
return css.trim();
}
public function getErrorClasses()
{
var css = "";
if ( required && parentForm.isSubmitted() )
{
if ( value == null )
css += " " + parentForm.requiredErrorClass;
if ( !isValid() )
css += " " + parentForm.invalidErrorClass;
}
return css.trim();
}
private inline function safeString(s:Dynamic) {
return s == null ? "" : Std.string(s).htmlEscape().split('"').join("&quot;");
}
/**
* Renders the element in HTML
*/
public function toString() :String
{
return render();
}
/**
* get element value with the correct type
*/
public function getValue():T{
return value;
}
}
+5
View File
@@ -0,0 +1,5 @@
enum FormMethod
{
GET;
POST;
}
+6
View File
@@ -0,0 +1,6 @@
package sugoi.form;
interface Formatter
{
function format(data:Dynamic):String;
}
+127
View File
@@ -0,0 +1,127 @@
package sugoi.form;
typedef FormData<T> = Array<{label:String,value:T}>;
class ListData
{
public static function getDateElement( low : Int, high : Int, ?labels : Array<String> ) : FormData<Int>
{
var data = [];
if ( labels != null ){
for ( i in low ... high + 1 )
data.push( { label:labels[i-1], value:i } );
}else{
for ( i in low ... high + 1 ){
var n = Std.string(i);
data.push( { label:((i < 10) ? "0" + n : n), value: i } );
}
}
return data;
}
public static function getMinutes():FormData<Int> {
var data = [];
for ( i in 0...12) {
var x = i * 5;
data.push( {label: (x<10) ? "0"+Std.string(x) : Std.string(x) ,value: x } );
}
return data;
}
public static function fromArray(arr:Array<Dynamic>) {
var data = [];
for (a in arr) {
data.push( {key:Std.string(a),value:Std.string(a) } );
}
return data;
}
public static function getDays(?reverse = true):Array<{label:String,value:Int}>
{
var data= [];
for (i in 1...31+1) {
data.push( { label:Std.string(i), value:i } );
}
return(data);
}
//public static var months_short = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
public static var months_short = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
//public static var months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
public static var months = ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Aout", "Septembre", "Octobre", "Novembre", "Décembre"];
/**
* Get months list
*/
public inline static function getMonths(?short = false):Array<{label:String,value:Int}>
{
var input = short ? months_short : months;
var out = [];
var c = 1;
for ( i in input) {
out.push( { label:i, value:c } );
c++;
}
return out;
}
/**
* get years list
*/
public static function getYears(from:Int, to:Int, ?reverse = true):Array<{label:String,value:Int}>
{
var data = [];
if (reverse){
for (i in 0...(to-from+1)) {
var n = to - i;
data.push( { label:Std.string(n), value:n } );
}
}else {
for (i in 0...(to-from+1)) {
var n = from + i;
data.push( { label:Std.string(n), value:n } );
}
}
return(data);
}
/*public static function getLetters(uppercase=false){
if (uppercase) return(array(a=>"A", b=>"B", c=>"C", d=>"D", e=>"E", f=>"F", g=>"G", h=>"H", i=>"I", j=>"J", k=>"K", l=>"L", m=>"M", n=>"N", o=>"O", p=>"P", q=>"Q", r=>"R", s=>"S", t=>"T", u=>"U", v=>"V", w=>"W", x=>"X", y=>"Y", z=>"Z"));
return(array(a=>"a", b=>"b", c=>"c", d=>"d", e=>"e", f=>"f", g=>"g", h=>"h", i=>"i", j=>"j", k=>"k", l=>"l", m=>"m", n=>"n", o=>"o", p=>"p", q=>"q", r=>"r", s=>"s", t=>"t", u=>"u", v=>"v", w=>"w", x=>"x", y=>"y", z=>"z"));
}*/
public static function hashToList(hash:Map<String,String>, ?startCounter:Int=0):List<Dynamic>
{
var data:List<Dynamic> = new List();
for (key in hash.keys())
{
data.add( { key:key, value:hash.get(key) } );
}
return data;
}
public static function arrayToList(array:Array<String>, ?startCounter:Int=0):List<Dynamic>
{
var data:List<Dynamic> = new List();
var c = startCounter;
for (v in array)
{
data.add( { key:c, value:v } );
c++;
}
return data;
}
public static function flatArraytoList(array:Array<String>):List<Dynamic>
{
var data:List<Dynamic> = new List();
for (i in array) data.add( { key:i, value:i } );
return data;
}
}
+26
View File
@@ -0,0 +1,26 @@
package sugoi.form;
class Rules
{
public function new()
{
}
public static function isNumber(element:FormElement)
{
}
public static function greaterThan(element:FormElement, value)
{
}
public static function lessThan(element:FormElement, value)
{
}
}
+63
View File
@@ -0,0 +1,63 @@
package sugoi.form.elements;
import form.Form;
import form.FormElement;
class Button extends FormElement
{
public var type:ButtonType;
//public function new(name:String, label:String, ?value:String = "Submit", ?type:ButtonType = null)
public function new(name:String, label:String, ?value:String = null, ?type:ButtonType = null)
{
super();
this.name = name;
this.label = label;
this.value = value;
this.type = (type == null) ? ButtonType.SUBMIT : type;
}
override public function isValid():Bool
{
return true;
}
override public function render() :String
{
return "<button type=\"" + type + "\" class=\"" + getClasses() +"\" value=\"" + value + "\" " + attributes + " name=\"" +parentForm.name + "_" +name + "\" id=\"" +parentForm.name + "_" +name + "\" >" +label + "</button>";
}
public function toString() :String
{
return render();
}
override public function getLabel():String
{
var n = parentForm.name + "_" + name;
return "<label for=\"" + n + "\" ></label>";
}
override public function getPreview():String
{
return "<tr><td></td><td>" + this.render() + "<td></tr>";
}
override public function populate():Void
{
super.populate();
var n = parentForm.name + "_" + name;
if ( App.current.params.exists(n) )
parentForm.submittedButtonName = name;
}
}
enum ButtonType
{
SUBMIT;
BUTTON;
RESET;
}
+44
View File
@@ -0,0 +1,44 @@
package sugoi.form.elements;
import sugoi.form.FormElement;
import sugoi.form.elements.Input;
#if neko
import neko.Web;
#else
import php.Web;
#end
/**
* creates a hidden token in forms to avoid CSRF
*/
class CSRFProtection extends StringInput
{
public function new()
{
value = haxe.crypto.Md5.encode(App.current.session.sid + App.config.KEY.substr(0, 5));
super("token","", value, true);
inputType = ITHidden;
}
override public function isValid() {
if (value == null) throw "empty token";
var valid = Web.getParams().get(parentForm.name + "_" + name) == value;
if (!valid) {
errors.add("Bad token");
}
return valid;
}
override public function getFullRow() {
return render();
}
override public function render() {
return "<input type=\"hidden\" value=\"" + value + "\" " + attributes + " name=\"" +parentForm.name + "_" +name + "\" id=\"" +parentForm.name + "_" +name + "\" />";
}
}
+45
View File
@@ -0,0 +1,45 @@
package sugoi.form.elements;
import sugoi.form.Form;
import sugoi.form.FormElement;
class Checkbox extends FormElement<Bool>
{
public function new(name:String, label:String, ?checked:Bool=false, ?required:Bool=false, ?attibutes:String="")
{
super();
this.name = name;
this.label = label;
this.value = checked;
this.required = required;
this.attributes = attibutes;
}
override public function render():String
{
var n = parentForm.name + "_" +name;
var checkedStr = value ? "checked" : "";
return "<input type=\"checkbox\" id=\"" + n + "\" name=\"" + n + "\" class=\"" + getClasses() + "\" value=\"true\" " + checkedStr + " />";
}
override public function getTypedValue(str:String):Bool
{
return str == "1" || str == "true";
}
override public function isValid():Bool
{
errors.clear();
if ( required && value == null )
{
errors.add("Please check '" + ((label != null && label != "") ? label : name) + "'");
return false;
}
return true;
}
}
+104
View File
@@ -0,0 +1,104 @@
package sugoi.form.elements;
import sugoi.form.Form;
import sugoi.form.FormElement;
import sugoi.form.Formatter;
import sugoi.form.ListData;
/**
* Manage an array of string with a checkbox group
*/
class CheckboxGroup extends FormElement<Array<String>>
{
public var data:Array<Dynamic>;
public var selectMessage:String;
public var labelLeft:Bool;
public var verticle:Bool;
public var labelRight:Bool;
public var formatter:Formatter;
public var columns:Int;
public function new(name:String, label:String,data:FormData<String>, ?selected:Array<String>, ?verticle:Bool=true, ?labelRight:Bool=true)
{
super();
this.name = name;
this.label = label;
this.data = data;
this.value = selected != null ? selected : new Array();
this.verticle = verticle;
this.labelRight = labelRight;
columns = 1;
}
override public function populate()
{
var v = Web.getParamValues(parentForm.name + "_" + name);
if (parentForm.isSubmitted())
{
value = (v != null) ? v : [];
} else {
if (v != null) value = v;
}
}
override public function render():String
{
var s = "";
var n = parentForm.name + "_" +name;
var tagCss = getClasses();
var labelCss = getLabelClasses();
var c = 0;
var datas = Lambda.array(data);
if (datas != null)
{
var rowsPerColumn = Math.ceil(datas.length / columns);
s = "<table><tr>";
for (i in 0...columns)
{
s += "<td valign=\"top\">\n";
s += "<table>\n";
for (j in 0...rowsPerColumn)
{
if (c >= datas.length) break;
s += "<tr>";
var row:Dynamic = datas[c];
var checkbox = "<input type=\"checkbox\" class=\"" + tagCss + "\" name=\""+n+"[]\" id=\""+n+c+"\" value=\"" + row.value + "\" " + (value != null ? Lambda.has(value, row.value) ? "checked":"":"") +" ></input>\n";
var label;
if (formatter != null){
label = "<label for=\"" + n + c + "\" class=\""+''/*labelCss*/+"\" >" + formatter.format(row.label) +"</label>\n";
//}else if(Form.translator!=null){
//label = "<label for=\"" + n + c + "\" class=\"" + ''/*labelCss*/+"\" >" + Form.translator._(row.label) +"</label>\n";
}else {
label = "<label for=\"" + n + c + "\" class=\"" + ''/*labelCss*/+"\" >" + row.label +"</label>\n";
}
if (labelRight)
{
s += "<td>" + checkbox + "</td>\n";
s += "<td>&nbsp;" + label + "</td>\n";
} else {
s += "<td>" + label + "&nbsp;</td>\n";
s += "<td>" + checkbox + "</td>\n";
}
s += "</tr>";
c++;
}
s += "</table>";
s += "</td>";
}
s += "</tr></table>\n";
}
return s;
}
}
+142
View File
@@ -0,0 +1,142 @@
package sugoi.form.elements;
import sugoi.Web;
import sugoi.form.FormElement;
import sugoi.form.validators.Validator;
import sugoi.form.ListData;
/**
* A list of selectBox for day + month + year
*/
class DateDropdowns extends FormElement<Date>
{
public var maxOffset:Int;
public var minOffset:Int;
//public var date : Date; //valeur typée à la place de value:Dynamic
public var yearMin:Int;
public var yearMax:Int;
private var daySelector:Selectbox<Int>;
private var monthSelector:Selectbox<Int>;
private var yearSelector:Selectbox<Int>;
public function new(name:String, label:String, ?_value:Date, ?required:Bool=false, yearMin:Int=1950, yearMax:Int=null, ?validators:Array<Validator<Date>>, ?attibutes:String="")
{
super();
this.name = name;
this.label = label;
if (_value == null) {
value = Date.now();
}else {
value = _value;
}
this.required = required;
this.attributes = attibutes;
this.yearMin = yearMin;
this.yearMax = yearMax;
maxOffset = null;
minOffset = null;
var day :Int = null;
var month :Int = null;
var year :Int = null;
if (value != null)
{
day = value.getDate();
month = (value.getMonth()+1);
year = value.getFullYear();
}
var t = sugoi.form.Form.translator;
daySelector = new IntSelect(name+"_day", t._("day"),ListData.getDays(),day,true);
monthSelector = new IntSelect(name+"_month", t._("month"),ListData.getMonths(),month,true);
yearSelector = new IntSelect(name+"_year", t._("year"), ListData.getYears(Date.now().getFullYear()-3, Date.now().getFullYear()+3, true), year, true);
daySelector.internal = monthSelector.internal = yearSelector.internal = true;
//if (Form.USE_TWITTER_BOOTSTRAP) {
//daySelector.cssClass = "input-mini";
//}
//trace("date : " + date);
}
public function shortLabels()
{
daySelector.nullMessage = "-D-";
monthSelector.nullMessage = "-M-";
yearSelector.nullMessage = "-Y-";
monthSelector.data = ListData.getMonths(true);
}
override public function init()
{
super.init();
parentForm.addElement(daySelector);
parentForm.addElement(monthSelector);
parentForm.addElement(yearSelector);
}
override public function populate()
{
var day = Std.parseInt(App.current.params.get(parentForm.name + "_" + daySelector.name));
var month = Std.parseInt(App.current.params.get(parentForm.name + "_" + monthSelector.name));
var year = Std.parseInt(App.current.params.get(parentForm.name + "_" + yearSelector.name));
value = (day != null && month != null && year != null ) ? new Date(year, month - 1, day, 0, 0, 0) : null;
}
override public function isValid():Bool
{
/*var valid = super.isValid();
if ( required && valid )
{
var n = form.name + "_" + name;
var day = Std.parseInt(App.current.params.get(n));
var month = Std.parseInt(App.current.params.get(n));
var year = Std.parseInt(App.current.params.get(n));
if (day == null || month == null || year == null )
{
errors.add("<span class=\"formErrorsField\">" + ((label != null && label != "") ? label : name) + "</span> is an invalid date.");
return false;
}
return true;
}
return valid;*/
return true;
}
override public function render():String
{
super.render();
if (value != null)
{
try{
var v:Date = cast value;
daySelector.value = v.getDate();
monthSelector.value = v.getMonth()+1;
yearSelector.value = v.getFullYear();
}catch(e:Dynamic){}
}
return '<div class="row">
<div class="col-xs-2">'+daySelector.render()+'</div>
<div class="col-xs-6">'+monthSelector.render()+'</div>
<div class="col-xs-4">'+yearSelector.render()+'</div>
</div>';
}
}
+72
View File
@@ -0,0 +1,72 @@
package sugoi.form.elements;
import sugoi.form.FormElement;
import sugoi.form.validators.Validator;
import sugoi.form.ListData;
/**
* date selectBox : day month year hour minutes
*/
class DateInput extends DateDropdowns
{
private var hourSelector:Selectbox<Int>;
private var minuteSelector:Selectbox<Int>;
public function new(name:String, label:String, ?value:Date, ?required:Bool=false, yearMin:Int=1950, yearMax:Int=null, ?validators:Array<Validator<Date>>, ?attibutes:String="")
{
super(name, label, value, required, yearMin, yearMax, validators, attibutes);
var t = sugoi.form.Form.translator;
hourSelector = new Selectbox<Int>(name+"_hour" , t._("hour") , ListData.getDateElement(0,23), value.getHours(),true,"-",'title="Hour"');
minuteSelector = new Selectbox<Int>(name+"_minute", t._("minute"), ListData.getDateElement(0, 59), value.getMinutes(), true, "-", 'title="Minute"');
if (Form.USE_TWITTER_BOOTSTRAP) {
hourSelector.cssClass = "form-control";
minuteSelector.cssClass = "form-control";
}
}
override public function isValid() {
return true;
}
override public function render():String
{
hourSelector.parentForm = this.parentForm;
minuteSelector.parentForm = this.parentForm;
var s = super.render() + " : ";
if (value != null){
var v:Date = cast value;
hourSelector.value = v.getHours();
minuteSelector.value = v.getMinutes();
}
s += hourSelector.render() + " h ";
s += minuteSelector.render() + " m ";
return s;
}
override public function populate()
{
//super.populate();
var n = parentForm.name + "_" + hourSelector.name;
var v = App.current.params.get(n);
var params = App.current.params;
if (v != null)
{
var minute = Std.parseInt(params.get(parentForm.name + "_" + minuteSelector.name));
var hour = Std.parseInt(params.get(parentForm.name + "_" + hourSelector.name));
var day = Std.parseInt(params.get(parentForm.name + "_" + daySelector.name));
var month = Std.parseInt(params.get(parentForm.name + "_" + monthSelector.name));
var year = Std.parseInt(params.get(parentForm.name + "_" + yearSelector.name));
value = new Date(year, month - 1, day, hour, minute, 0);
}
}
}
+118
View File
@@ -0,0 +1,118 @@
package sugoi.form.elements;
import sugoi.Web;
import sugoi.form.FormElement;
import sugoi.form.validators.Validator;
import sugoi.form.ListData;
/**
* DatePicker for Bootstrap 3
*
* You'll need to install some additionnal js librairies (moment.js, jquery)
* more info at : http://eonasdan.github.io/bootstrap-datetimepicker/
*/
class DatePicker extends FormElement<Date>
{
public var maxOffset:Int;
public var minOffset:Int;
public var yearMin:Int;
public var yearMax:Int;
private var daySelector:Selectbox<Int>;
private var monthSelector:Selectbox<Int>;
private var yearSelector:Selectbox<Int>;
public var format : String; //moment.js format
public function new(name:String, label:String, ?v:Date, ?required:Bool=false, yearMin:Int=1950, yearMax:Int=null, ?validators:Array<Validator<Date>>, ?attibutes:String="")
{
super();
this.name = name;
this.label = label;
format = 'LLLL';
if (v == null) {
this.value = Date.now();
}else {
this.value = v;
}
//trace(value);
this.required = required;
this.attributes = attibutes;
this.yearMin = yearMin;
this.yearMax = yearMax;
maxOffset = null;
minOffset = null;
var day = "";
var month = "";
var year = "";
if (value != null)
{
day = ""+value.getDate();
month = ""+(value.getMonth()+1);
year = ""+value.getFullYear();
}
}
override public function populate()
{
//data is stored as float in the html form element
var d = App.current.params.get(parentForm.name + "_" + name);
//trace(parentForm.name + "_" + name+"="+d);
//value = Date.fromTime(Std.parseFloat(d));
value = Date.fromString(d);
}
override public function isValid():Bool
{
return true;
}
override public function render():String
{
//component init date
//var d = value.getFullYear() +"-" + (value.getMonth() + 1) + "-" + value.getDate() + " " + value.getHours() + ":" + value.getMinutes()+":00";
var d = value.toString();
var defaultDate = 'moment("' + d + '", "YYYY-MM-DD HH:mm:ss")';
return "
<div class='input-group date' id='datetimepicker-"+name+"'>
<span class='input-group-addon'>
<span class='glyphicon glyphicon-calendar'></span>
</span>
<input type='text' class='form-control' />
</div>
<input type='hidden' name='"+parentForm.name+"_"+name+"' id='datetimepickerdata-"+name+"' value='"+d+"'/>
<script type='text/javascript'>
$(function () {
$('#datetimepicker-"+name+"').datetimepicker(
{
locale:'fr',
format:'"+this.format+"',
defaultDate:"+defaultDate+"
}
);
//stores the date in mysql format in a hidden input element
$('#datetimepicker-"+name+"').on('dp.change',function(e){
var d = $('#datetimepicker-"+name+"').data('DateTimePicker').date();//moment.js obj
//fix 2038 date overflow bug https://en.wikipedia.org/wiki/Year_2038_problem
if(d.year()>2037) d.year(2037);
console.log(d.toString());
$('#datetimepickerdata-"+name+"').val( d.format('YYYY-MM-DD HH:mm:ss'));
});
});
</script>";
}
}
@@ -0,0 +1,130 @@
/*
* Copyright (c) 2008, TouchMyPixel & contributors
* Original author : Matt Benton <matt@touchmypixel.com>
* Contributers: Tarwin Stroh-Spijer
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE TOUCH MY PIXEL & CONTRIBUTERS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE TOUCH MY PIXEL & CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
package form.elements;
import form.Form;
import form.FormElement;
import poko.Poko;
enum EmbeddedVideoService
{
youtube;
vimeo;
}
typedef EmbeddedVideoConfig =
{
var videoID:String;
var width:Int;
var height:Int;
}
typedef VimeoConfig =
{ > EmbeddedVideoConfig,
var color:String;
var showPortrait:Bool;
var showTitle:Bool;
var showByline:Bool;
}
class EmbeddedVideoOptions extends FormElement
{
// Type of video service
public var service:EmbeddedVideoService;
/**
* Common options
*/
//public var videoID:String;
//public var width:Int;
//public var height:Int;
/**
* Vimeo options
*/
// Can be Blue, Orange, Lime, Fuschia, White or #RRGGBB
//public var color:String;
//public var showPortrait:Bool;
//public var showTitle:Bool;
//public var showByline:Bool;
public var vimeo (default, null) : VimeoConfig;
public function new(name:String, label:String, service:EmbeddedVideoService)
{
super();
this.name = name;
this.label = label;
this.service = service;
}
override public function render():String
{
var n = form.name + "_" + name;
if ( service == EmbeddedVideoService.vimeo )
{
var color = new Input(n + "Color", "Color", "Blue");
}
return null;
}
public function toString() :String
{
return render();
}
override public function populate():Void
{
}
}
/*var date:Date = cast value;
var year = date.getFullYear();
var month = date.getMonth();
var day = date.getDate();
var l = new List();
var s = "";
var elYear = new Selectbox(form, "1", ListData.getYears(1990, 2000, true), Std.string(year), false, "");
var elMonth = new Selectbox(form, "2", ListData.getMonths(), Std.string(year), false);
var elDay = new Selectbox(form, "3", ListData.getDays() , Std.string(year), false);
form.addElement(name + "[]", elYear);
form.addElement(name + "[]", elMonth);
form.addElement(name + "[]", elDay);
s += elYear.toString();
s += elMonth.toString();
s += elDay.toString();
form.initElements();
*/
+128
View File
@@ -0,0 +1,128 @@
package sugoi.form.elements;
import sugoi.form.Form;
import sugoi.form.FormElement;
import sugoi.form.Formatter;
import sugoi.form.elements.Flags;
class Enum extends FormElement<Int>
{
public var enumName:String;
public var selectMessage:String;
public var labelLeft:Bool;
public var verticle:Bool;
public var labelRight:Bool;
var checked : Array<Bool>;
public var columns:Int;
/**
*
* @param name
* @param label
* @param data list of enums
* @param value int (enum index)
* @param ?verticle
* @param ?labelRight
*/
public function new(name:String, label:String, enumName:String, value:Int, ?required=false, ?verticle:Bool=false, ?labelRight:Bool=true)
{
super();
this.name = name;
this.label = label;
this.enumName = enumName;
this.verticle = verticle;
this.labelRight = labelRight;
//trace("value = " + value);
if (required && value == null){
this.value = /*Type.resolveEnum(enumName).createByIndex(0)*/0;
}else{
this.value = value;
}
columns = 1;
}
override function getTypedValue(str:String):Int {
if (str == null) return null;
str = StringTools.trim(str);
if (str == "") {
return null;
}else{
return Std.parseInt(str);
}
}
override function getValue(){
if (value == null) return null;
return Type.resolveEnum(enumName).createByIndex(value);
}
override public function render():String
{
var s = "";
var n = parentForm.name + "_" +name;
var tagCss = getClasses();
//no label css otherwise the style col-sm-4 will be added, and we dont want that
// as its for the left column labels
//var labelCss = getLabelClasses();
var c = 0;
var array = Type.allEnums(Type.resolveEnum(enumName));
var rowsPerColumn = Math.ceil(array.length / columns);
s = "<table style='margin-bottom:8px;'><tr>";
for (i in 0...columns)
{
s += "<td valign=\"top\">\n";
s += "<table>\n";
for (j in 0...rowsPerColumn)
{
if (c >= array.length) break;
s += "<tr>";
var row:Dynamic = array[c];
var checked = value == Type.enumIndex(row);
var checkbox = "<input type=\"radio\" class=\"" + tagCss + "\" name=\""+n+"\" id=\""+n+row+"\" value=\""+Type.enumIndex(row)+"\" " + (checked? "checked":"") +" ></input>\n";
var label;
var t = Form.translator;
if (t == null){
label = "<label for=\"" + n + c + "\" >" + Std.string(row) +"</label>\n";
}else{
label = "<label for=\"" + n + c + "\" >" + t._(Std.string(row)) +"</label>\n";
}
if (labelRight)
{
s += "<td style='vertical-align:middle;padding-right: 8px;'>" + checkbox + "</td> \n";
s += "<td style='vertical-align:middle;'>" + label + "</td>\n";
} else {
s += "<td style='vertical-align:middle;padding-right: 8px;'>" + label + "</td> \n";
s += "<td style='vertical-align:middle;'>" + checkbox + "</td>\n";
}
s += "</tr>";
c++;
}
s += "</table>";
s += "</td>";
}
s += "</tr></table>\n";
return s;
}
}
+87
View File
@@ -0,0 +1,87 @@
package sugoi.form.elements;
import haxe.crypto.Md5;
import haxe.Timer;
import sugoi.form.Form;
import sys.io.File;
/**
* Manage an <input type="file" /> element.
*/
class FileUpload extends FormElement<haxe.io.Bytes>
{
public var fileName: String;
public var maxSize : Int; //Max file size in Mb
public function new(name:String, label:String, ?value:haxe.io.Bytes, ?required:Bool=false, toFolder:String=null, ?keepFullFileName:Bool=true )
{
super();
this.name = name;
this.label = label;
this.value = value;
this.required = required;
fileName = null;
maxSize = 6;
}
override public function getTypedValue(s:String):haxe.io.Bytes
{
var request = sugoi.tools.Utils.getMultipart(1024 * 1024 * maxSize);
//trace(request.toString());
var strData = request.get(parentForm.name + "_" + name);
fileName = request.get(parentForm.name + "_" + name+"_filename");
return new haxe.io.StringInput(strData).readAll();
}
override public function render():String
{
var n = parentForm.name + "_" +name;
//var path = toFolder.substr((Sys.getCwd() + "tmp/").length);
//var path = toFolder;
var str:String = "";
//str += '<span class="fileName">'+getOriginalFileName()+'</span><br/>';
str += '<input type="file" name="' + n + '" id="' + n + '" ' + attributes + ' />';
//if (!required && value != '' && value != null) str += '[ <a href="#" onclick="document.getElementById(\'' + n + '__delete\').value = \'1\'; return false;">remove</a> ]';
//str += '<input type="hidden" name="' + n + '__previous" id="' + n + '__previous" value="'+value+'"/>';
//str += '<input type="hidden" name="' + n + '__delete" id="' + n + '__delete" value="0"/>';
return str;
}
/**
* Contains MD5 and original filename.
*/
//public function getFileName()
//{
//if (keepFullFileName)
//{
//var s = Std.string(value);
//return s.substr(s.lastIndexOf("/") + 1);
//} else {
//return value;
//}
//}
/**
* Orginal filename.
*/
//public function getOriginalFileName()
//{
//if (keepFullFileName)
//{
//var s = Std.string(value);
//return s.substr(s.lastIndexOf("/") + 33);
//} else {
//return Std.string(value).substr(33);
//}
//}
}
+175
View File
@@ -0,0 +1,175 @@
package sugoi.form.elements;
import sugoi.form.Form;
import sugoi.form.FormElement;
import sugoi.form.Formatter;
#if php
import php.Web;
#else
import neko.Web;
#end
enum FakeFlag {
Flag1;
Flag2;
Flag3;
Flag4;
Flag5;
Flag6;
Flag7;
Flag8;
Flag9;
Flag10;
Flag11;
Flag12;
Flag13;
Flag14;
Flag15;
Flag16;
Flag17;
Flag18;
Flag19;
Flag20;
Flag21;
Flag22;
Flag23;
Flag24;
Flag25;
Flag26;
Flag27;
Flag28;
Flag29;
Flag30;
Flag31;
Flag32;
}
/**
* Manage flags stored in an Int , various flags are defined by an Enum
*/
class Flags<T> extends FormElement<Int>
{
public var data:Array<String>;
public var selectMessage:String;
public var labelLeft:Bool;
public var verticle:Bool;
public var labelRight:Bool;
var checked : Array<Bool>;
public var columns:Int;
/**
*
* @param name
* @param label
* @param data list of enums
* @param value int
* @param ?verticle
* @param ?labelRight
*/
public function new(name:String, label:String, data:Array<String>, value:Int, ?verticle:Bool=true, ?labelRight:Bool=true)
{
super();
this.name = name;
this.label = label;
this.data = data;
this.value = value;
this.verticle = verticle;
this.labelRight = labelRight;
if (value == null) value = 0;
checked = [];
var i = 0;
for( f in data) {
checked.push( value & (1 << i) != 0 );
i++;
}
columns = 1;
}
override public function populate()
{
var v = Web.getParamValues(parentForm.name + "_" + name);
value = 0;
if (v != null) {
//App.log("flags populate : " + v );
var val = new haxe.EnumFlags<FakeFlag>();
//var i = 0;
for (vv in v) {
val.set( FakeFlag.createByIndex(Std.parseInt(vv)) );
//i++;
}
value = val.toInt();
}
//if (form.isSubmitted()){
//value = (v != null) ? v : new Array();
//} else {
//if (v != null) value = v;
//}
}
override public function render():String
{
var s = "";
var n = parentForm.name + "_" +name;
var tagCss = getClasses();
var labelCss = getLabelClasses();
var c = 0;
var array = Lambda.array(data);
if (array != null)
{
//trace("L" + array.length);
var rowsPerColumn = Math.ceil(array.length / columns);
s = "<table><tr>";
for (i in 0...columns)
{
s += "<td valign=\"top\">\n";
s += "<table>\n";
for (j in 0...rowsPerColumn)
{
if (c >= array.length) break;
s += "<tr>";
var row:Dynamic = array[c];
var checkbox = "<input type=\"checkbox\" class=\"" + tagCss + "\" name=\""+n+"[]\" id=\""+n+c+"\" value=\""+c+"\" " + (checked[c]? "checked":"") +" ></input>\n";
var label;
var t = Form.translator;
label = "<label for=\"" + n + c + "\" class=\""+''/*labelCss*/+"\" > " + t._(row) +"</label>\n";
if (labelRight)
{
s += "<td style='vertical-align:middle;'>" + checkbox + "</td>\n";
s += "<td style='vertical-align:middle;'>" + label + "</td>\n";
} else {
s += "<td style='vertical-align:middle;'>" + label + "</td>\n";
s += "<td style='vertical-align:middle;'>" + checkbox + "</td>\n";
}
s += "</tr>";
c++;
}
s += "</table>";
s += "</td>";
}
s += "</tr></table>\n";
}
return s;
}
}
+24
View File
@@ -0,0 +1,24 @@
package sugoi.form.elements;
import sugoi.form.filters.FloatFilter;
class FloatInput extends Input<Float>
{
public function new(name, label, value, ?required=false){
super(name, label, value, required);
}
override public function getTypedValue(str:String):Float{
var f = new FloatFilter();
var n = f.filterString(str);
if (n==null && this.required){
return 0.0;
}else{
return n;
}
}
}
+19
View File
@@ -0,0 +1,19 @@
package sugoi.form.elements;
/**
* ...
* @author fbarbut
*/
class FloatSelect extends Selectbox<Float>
{
override function getTypedValue(str:String):Float{
str = StringTools.trim(str);
if (str == "" || str==null) {
return null;
}else{
return Std.parseFloat(str);
}
}
}
+92
View File
@@ -0,0 +1,92 @@
package sugoi.form.elements;
import sugoi.Web;
import sugoi.form.FormElement;
import sugoi.form.validators.Validator;
import sugoi.form.ListData;
class HourDropDowns extends FormElement<Date>
{
var hourSelector:Selectbox<Int>;
var minuteSelector:Selectbox<Int>;
public function new(name:String, label:String, ?_value:Date, ?required:Bool=false,?attributes="")
{
super();
this.name = name;
this.label = label;
if (_value == null) {
value = Date.now();
}else {
value = _value;
}
this.required = required;
this.attributes = attributes;
var hours = 0;
var minutes = 0;
if (value != null)
{
hours = value.getHours();
minutes = value.getMinutes();
}
var t = sugoi.form.Form.translator;
hourSelector = new IntSelect(name+"_hour", t._("hour"), ListData.getDateElement(0, 23), value.getHours(), true, "-", 'title="Hour"');
minuteSelector = new IntSelect(name+"_minute", t._("minute"), ListData.getMinutes(), value.getMinutes(), true, "-", 'title="Minute"');
hourSelector.internal = minuteSelector.internal = true;
if (Form.USE_TWITTER_BOOTSTRAP) {
minuteSelector.cssClass = "form-control";
hourSelector.cssClass = "form-control";
}
}
override public function init()
{
super.init();
parentForm.addElement(hourSelector);
parentForm.addElement(minuteSelector);
}
override public function populate()
{
var hour = Std.parseInt(App.current.params.get(parentForm.name + "_" + hourSelector.name));
var minute = Std.parseInt(App.current.params.get(parentForm.name + "_" + minuteSelector.name));
var now = Date.now();
value = (hour!= null && minute != null) ? new Date(now.getFullYear(),now.getMonth(), now.getDay(), hour, minute, 0) : null;
}
override public function isValid():Bool
{
return super.isValid();
}
override public function render():String{
super.render();
var s = "<span class='form-inline'>";
if (value != null){
try{
var v:Date = cast value;
hourSelector.value = v.getHours();
minuteSelector.value = v.getMinutes();
}catch(e:Dynamic){}
}
s += hourSelector.render();
s += " : ";
s += minuteSelector.render();
return s+"</span>";
}
}
+30
View File
@@ -0,0 +1,30 @@
package sugoi.form.elements;
/**
* Use this to fill some custom HTML between form elements
*
* @author fbarbut<francois.barbut@gmail.com>
*/
class Html extends sugoi.form.FormElement<String>
{
var html : String;
public function new(name:String,html:String,?label="")
{
this.name = name;
this.html = html;
this.label = label;
super();
}
override public function render()
{
return html;
}
override public function getTypedValue(str:String):String
{
return null;
}
}
+76
View File
@@ -0,0 +1,76 @@
package sugoi.form.elements;
import sugoi.form.Form;
import sys.io.File;
/**
* Manage an <input type="file" /> element for uploading images.
*/
class ImageUpload extends FormElement<haxe.io.Bytes>
{
public var fileName: String;
public var maxSize : Int; // Max file size in Mb
public var previewMaxSize : Int; // Image preview max size in pixels
public var url : String;
public var text : String;
/**
* @param name
* @param label
* @param url file URL for previewing the image
* @param required
*/
public function new(name:String, label:String, ?url:String, ?required:Bool=false )
{
super();
this.name = "upload_"+name;
this.label = label;
this.url = url;
this.required = required;
fileName = null;
maxSize = 6;
previewMaxSize = 200;
}
override public function getTypedValue(s:String):haxe.io.Bytes
{
var request = sugoi.tools.Utils.getMultipart(1024 * 1024 * maxSize);
var strData = request.get(parentForm.name + "_" + name + "_data");
if (strData != null && strData != ""){
fileName = request.get(parentForm.name + "_" + name + "_data_filename");
return new haxe.io.StringInput(strData).readAll();
}
return null;
}
public function hasDeleteAction():Bool{
var n = parentForm.name + "_" +name;
return (App.current.params.get(n + "_delete") == "1");
}
/**
* Renders an input + image preview + delete btn
*/
override public function render():String
{
var n = parentForm.name + "_" +name;
var str = new StringBuf();
str.add('<div class="imageUpload" style="position: relative;">');
if (url!= null)str.add('<img id="' + n + '_preview" src="$url" class="img-thumbnail" style="max-width:'+previewMaxSize+'px;max-height:'+previewMaxSize+'px;float:right;">');
str.add('<input class="btn btn-default" type="file" name="' + n + '_data" id="' + n + '" ' + attributes + ' />');
if (text != null) str.add('<p>$text</p>');
if (!required && url!= null){
str.add('<a style="position:absolute;right:3px;top:3px;" href="#" class="btn btn-default btn-xs" onclick="document.getElementById(\'' + n + '_delete\').value = \'1\';document.getElementById(\'' + n + '_preview\').style.display=\'none\';this.style.display=\'none\';return false;">');
str.add('<span class="glyphicon glyphicon-remove" alt="Remove"></span>');
str.add('</a>');
}
str.add('<input type="hidden" name="' + n + '_delete" id="' + n + '_delete" value="0"/>');
str.add('</div>');
return str.toString();
}
}
+99
View File
@@ -0,0 +1,99 @@
package sugoi.form.elements;
import sugoi.form.Form;
import sugoi.form.FormElement;
import sugoi.form.validators.*;
import sugoi.form.Formatter;
using StringTools;
enum InputType{
ITText;
ITPassword;
ITHidden;
ITColor; //http://caniuse.com/#feat=input-color
}
class Input<T> extends FormElement<T>
{
public var password(get,set):Bool;
public var disabled:Bool;
public var showLabelAsDefaultValue:Bool;
public var printRequired:Bool;
public var formatter:Formatter;
public var inputType : InputType;
public function new(name:String, label:String, ?value:T, ?required=false, ?validators:Array<Validator<T>>, ?attributes="")
{
super();
this.name = name;
this.label = label;
this.value = value;
this.required = required;
this.attributes = attributes;
if (validators != null)
{
for (i in validators)
{
this.validators.add(i);
}
}
this.password = false;
this.disabled = false;
inputType = ITText;
printRequired = false;
if(Form.USE_TWITTER_BOOTSTRAP) cssClass = "form-control";
}
public function get_password(){
return inputType == ITPassword;
}
public function set_password(v:Bool){
if (v){
inputType = ITPassword;
}else{
inputType = ITText;
}
return v;
}
override public function render():String
{
var n = parentForm.name + "_" +name;
var tType = switch(inputType){
case ITHidden: "hidden";
case ITPassword : "password" ;
case ITText : "text" ;
case ITColor : "color";
}
return "<input class=\""+ getClasses() +"\" type=\""+tType+"\" name=\""+n+"\" id=\""+n+"\" value=\"" +safeString(value)+ "\" "+attributes+" "+ (disabled?"disabled":"")+"/>" + ((required && parentForm.isSubmitted() && printRequired)?" required":"") ;
}
override public function getTypedValue(str:String):T{
if (str == "" || str==null) {
return null;
}
return cast StringTools.trim(str);
}
/**
* render label + field
*/
override public function getFullRow():String {
if (this.inputType == ITHidden){
return this.render();
}else{
return super.getFullRow();
}
}
}
+39
View File
@@ -0,0 +1,39 @@
package sugoi.form.elements;
class IntInput extends Input<Int>
{
public function new(name, label, value, ?required=false){
super(name, label, value, required);
}
override public function getTypedValue(str:String):Int{
if(str!=null) str = StringTools.trim(str);
if (str == "" || str==null) {
if (this.required){
return 0;
}else{
return null;
}
}else{
var v = Std.parseInt(str);
if (v == null){
if (this.required){
return 0;
}else{
return null;
}
}else{
return v;
}
}
}
}
+21
View File
@@ -0,0 +1,21 @@
package sugoi.form.elements;
/**
* ...
* @author fbarbut
*/
class IntSelect extends Selectbox<Int>
{
override function getTypedValue(str:String):Int{
if (str != null) str = StringTools.trim(str);
if (str==null || str=="") {
return null;
}else{
return Std.parseInt(str);
}
}
}
+11
View File
@@ -0,0 +1,11 @@
/**
* ...
* @author Tonypee
*/
package form.elements;
typedef KeyVal = {
var key:String;
var value:Dynamic;
}
+22
View File
@@ -0,0 +1,22 @@
package form;
class Label
{
public var forElement:FormElement;
public var value:String;
public function new()
{
}
override public function render():String
{
}
public function populate(data:String)
{
value = data;
}
}
+63
View File
@@ -0,0 +1,63 @@
package sugoi.form.elements;
import sugoi.form.Form;
import sugoi.form.FormElement;
class RadioGroup extends FormElement<String>
{
public var data:ListData.FormData<String>;
public var selectMessage:String;
public var labelLeft:Bool;
public var labelRight:Bool;
public var vertical:Bool;
public function new(name:String, label:String, ?data:Array<{label:String,value:String}>, ?selected:String, ?defaultValue:String, ?vertical:Bool=true, ?labelRight:Bool=true,?required=false)
{
super();
this.name = name;
this.label = label;
this.data = data != null ? data : [];
this.value = selected != null ? selected : defaultValue;
this.vertical = vertical;
this.labelRight = labelRight;
this.required = required;
}
public function addOption(label:String, value:String)
{
data.push( { label:label, value:value } );
}
override public function render():String
{
var s = "";
var n = parentForm.name + "_" +name;
var c = 0;
if (data != null)
{
for (row in data)
{
var vClass = vertical ? " radioItemVertical" : " radioItemHorizontal";
s += '<div class="radioItem'+vClass+'">';
var radio = "<input type=\"radio\" name=\""+n+"\" id=\""+n+c+"\" value=\"" + row.value + "\" " + (row.value == Std.string(value) ? "checked":"") +" />\n";
var label = "<label for=\"" + n+c + "\" >" + row.label +"</label>";
s += labelRight ? radio + " "+label+" ": label+" "+radio+" ";
s += '</div>';
//if (verticle) s += "<br />";
c++;
}
}
return s;
}
override function getTypedValue(str:String){
if(str==null) return null;
str = StringTools.trim(str);
return (str == "") ? return null : str;
}
}
+43
View File
@@ -0,0 +1,43 @@
package sugoi.form.elements;
import sugoi.form.Form;
import sugoi.form.FormElement;
class Readonly<T> extends FormElement<T>
{
public var display:Bool;
public function new(name:String, label:String, ?value:T, ?required:Bool = false, ?display:Bool = false, ?attributes:String = "")
{
super();
this.name = name;
this.label = label;
this.value = value;
this.required = required;
this.display = display;
this.attributes = attributes;
}
override public function render():String
{
var n = parentForm.name + "_" + name;
var str:StringBuf = new StringBuf();
str.add("<input type=\"hidden\" name=\"" + n + "\" id=\"" + n + "\" value=\"" +value + "\"/>");
if (display) {
str.add(value);
}
return str.toString();
}
override public function getTypedValue(str:String):T
{
if (str == "" || str==null) {
return null;
}
return cast StringTools.trim(str);
}
}
+105
View File
@@ -0,0 +1,105 @@
package form.elements;
/*
* TinyMCE rich text editor
*
*/
import form.Form;
import form.FormElement;
class Richtext extends FormElement
{
public var width:Float;
public var height:Float;
public var content_css:String;
public var mode:RichtextMode;
public function new(name:String, label:String, ?value:String, ?required:Bool=false, ?attibutes:String="")
{
super();
this.name = name;
this.label = label;
this.value = value;
this.required = required;
this.attributes = attibutes;
width = 300;
height = 300;
content_css = "css/cms/richtext_default.css";
mode = RichtextMode.SIMPLE;
}
override public function render():String
{
var n = form.name + "_" +name;
if(content_css != "") content_css += "?" + Date.now().getTime();
var str:StringBuf = new StringBuf();
str.add("\n <textarea name=\"" + n + "\" id=\"" + n + "\" >"+value+"</textarea>");
str.add("\n <script> ");
str.add("\n tinyMCE.init({ ");
str.add("\n mode : \"exact\", ");
str.add("\n elements : \""+n+"\", ");
str.add("\n theme : \""+(mode == RichtextMode.SIMPLE ? "simple" : "advanced")+"\", ");
str.add("\n width : \""+width+"\", ");
str.add("\n height : \""+height+"\", ");
str.add("\n content_css : \"" + content_css + "\", ");
switch(mode)
{
case SIMPLE:
case FORMAT:
str.add("\n plugins : \"advlink,inlinepopups,paste\", ");
str.add("\n theme_advanced_buttons1 : \"bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,outdent,indent,|,link,unlink,|,pastetext,cleanup,removeformat\", ");
str.add("\n theme_advanced_buttons2 : \"\", ");
str.add("\n theme_advanced_toolbar_location : \"top\", ");
str.add("\n theme_advanced_statusbar_location : \"bottom\", ");
str.add("\n theme_advanced_resizing : true, ");
case SIMPLE_TABLES:
str.add("\n plugins : \"table,advlink,inlinepopups,paste\", ");
str.add("\n theme_advanced_buttons1 : \"bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,|,outdent,indent,|,link,unlink,|,pastetext,cleanup,removeformat\", ");
str.add("\n theme_advanced_buttons2 : \"table,tablecontrols\", ");
str.add("\n theme_advanced_buttons3 : \"\", ");
str.add("\n theme_advanced_toolbar_location : \"top\", ");
str.add("\n theme_advanced_statusbar_location : \"bottom\", ");
str.add("\n theme_advanced_resizing : true, ");
case ADVANCED:
str.add("\n theme_advanced_styles: \"Small 1=small1\", ");
str.add("\n plugins : \"safari,pagebreak,style,layer,table,save,advhr,advimage,advlink,emotions,iespell,inlinepopups,insertdatetime,preview,media,searchreplace,print,contextmenu,paste,directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,template\", ");
str.add("\n theme_advanced_buttons1 : \"bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,styleselect,formatselect,fontselect,fontsizeselect,|,forecolor\", ");
str.add("\n theme_advanced_buttons2 : \"cut,copy,paste,pastetext,pasteword,|,search,replace,|,bullist,numlist,|,outdent,indent,blockquote,|,undo,redo,|,link,unlink,anchor,image,cleanup,code\", ");
str.add("\n theme_advanced_buttons3 : \"tablecontrols,|,hr,removeformat,visualaid,|,sub,sup,|,charmap,emotions,iespell,media,advhr,|,print,|,ltr,rtl,|,fullscreen\", ");
str.add("\n theme_advanced_buttons4 : \"insertlayer,moveforward,movebackward,absolute,|,styleprops,|,cite,abbr,acronym,del,ins,attribs,|,visualchars,nonbreaking,template,pagebreak\", ");
str.add("\n theme_advanced_toolbar_location : \"top\", ");
str.add("\n theme_advanced_toolbar_align : \"left\", ");
str.add("\n theme_advanced_statusbar_location : \"bottom\", ");
str.add("\n theme_advanced_resizing : false, ");
}
str.add("\n file_browser_callback : 'myFileBrowser', ");
str.add("\n }); ");
str.add("\n </script>\n ");
if (!isValid()) str.add(" required");
return str.toString();
}
public function toString() :String
{
return render();
}
}
enum RichtextMode
{
SIMPLE;
FORMAT;
SIMPLE_TABLES;
ADVANCED;
}
+92
View File
@@ -0,0 +1,92 @@
/*
* WYMEditor ( XHTML wysiwig editor )
* http://files.wymeditor.org/wymeditor-1.0.0b2/examples/
*/
package form.elements;
import form.Form;
import form.FormElement;
class RichtextWym extends FormElement
{
public var width:Float;
public var height:Float;
public var allowImages:Bool;
public var allowTables:Bool;
public var editorStyles:String;
public var containersItems:String;
public var classesItems:String;
public function new(name:String, label:String, ?value:String, ?required:Bool=false, ?attibutes:String="")
{
super();
this.name = name;
this.label = label;
this.value = value;
this.required = required;
this.attributes = attibutes;
width = 500;
height = 300;
allowImages = true;
allowTables = false;
editorStyles = "";
containersItems = "";
classesItems = "";
}
override public function render():String
{
var n = form.name + "_" +name;
editorStyles = StringTools.replace(editorStyles, "\n", " ");
editorStyles = StringTools.replace(editorStyles, "\r", " ");
var str:StringBuf = new StringBuf();
str.add("\n <textarea name=\"" + n + "\" id=\"" + n + "\" >" + value + "</textarea>");
str.add("<script type=\"text/javascript\">");
str.add("jQuery(function() {");
str.add(" jQuery('#" + n + "').wymeditor({");
str.add("logoHtml: '',");
//str.add(" stylesheet: './css/site.css',");
str.add("editorStyles: [\""+editorStyles+"\"],");
str.add("postInit: function(wym) {");
str.add(" jQuery(wym._box).find(wym._options.containersSelector).removeClass('wym_dropdown').addClass('wym_panel').find('h2 > span').remove();");
str.add(" jQuery(wym._box).find(wym._options.iframeSelector).css('height', '"+height+"px').css('width', '"+width+"px');");
str.add("},");
str.add("toolsItems: [");
str.add(" {'name': 'Bold', 'title': 'Strong', 'css': 'wym_tools_strong'}, ");
str.add(" {'name': 'Italic', 'title': 'Emphasis', 'css': 'wym_tools_emphasis'},");
str.add(" {'name': 'CreateLink', 'title': 'Link', 'css': 'wym_tools_link'},");
str.add(" {'name': 'Unlink', 'title': 'Unlink', 'css': 'wym_tools_unlink'},");
if(allowImages) str.add("{'name': 'InsertImage', 'title': 'Image', 'css': 'wym_tools_image'},");
str.add(" {'name': 'InsertOrderedList', 'title': 'Ordered_List', 'css': 'wym_tools_ordered_list'},");
str.add(" {'name': 'InsertUnorderedList', 'title': 'Unordered_List', 'css': 'wym_tools_unordered_list'},");
if(allowTables) str.add("{'name': 'InsertTable', 'title': 'Table', 'css': 'wym_tools_table'},");
str.add(" {'name': 'Paste', 'title': 'Paste_From_Word', 'css': 'wym_tools_paste'},");
str.add(" {'name': 'Undo', 'title': 'Undo', 'css': 'wym_tools_undo'},");
str.add(" {'name': 'Redo', 'title': 'Redo', 'css': 'wym_tools_redo'},");
str.add(" {'name': 'ToggleHtml', 'title': 'HTML', 'css': 'wym_tools_html'}");
str.add("],");
str.add("containersItems: [" + containersItems + "],");
if (classesItems != "") {
str.add("classesItems: [" + classesItems + "],");
}else {
str.add("classesHtml: '',");
}
str.add("postInitDialog: function (wym, wdw) { if(wymeditor_filebrowser != null) wymeditor_filebrowser(wym, wdw); }");
str.add(" });");
str.add("});");
str.add("</script>");
if (!isValid()) str.add(" required");
return str.toString();
}
public function toString() :String
{
return render();
}
}
+50
View File
@@ -0,0 +1,50 @@
package sugoi.form.elements;
import sugoi.form.Form;
import sugoi.form.FormElement;
class Selectbox<T> extends FormElement<T>
{
public var data:Array<{label:String,value:T}>;
public var nullMessage:String;
public var onChange:String;
public var size:Int;
public var multiple:Bool;
public function new(name:String, label:String, ?data:Array<{label:String,value:T}>, ?selected:T, required:Bool=false, ?nullMessage="-", ?attributes="")
{
super();
this.name = name;
this.label = label;
this.data = data != null ? data: new Array();
this.value = selected;
this.required = required;
this.nullMessage = nullMessage;
this.attributes = attributes;
size = 1;
multiple = false;
onChange = "";
if(Form.USE_TWITTER_BOOTSTRAP) cssClass = "form-control";
}
override public function render():String
{
var s = "";
var n = parentForm.name;
n += "_" +name;
s += '\n<select name="' + n + '" id="' + n + '" '+attributes+' class="'+ getClasses() +'" onChange="'+onChange+'" size="'+size+'" '+(multiple ? "multiple" : "")+'/>';
if (nullMessage != "")
s += "<option value=\"\" " + (Std.string(value) == "" ? "selected":"") + ">" + nullMessage + "</option>";
if (data != null){
for (row in data) {
s += "<option value=\"" + Std.string(row.value) + "\" " + (Std.string(row.value) == Std.string(value) ? "selected":"") + ">" + Std.string(row.label) + "</option>";
}
}
s += "</select>";
return s;
}
}
+20
View File
@@ -0,0 +1,20 @@
package sugoi.form.elements;
class StringInput extends Input<String>
{
override public function getTypedValue(str:String):String{
if (str != null)
str = StringTools.trim(str);
if (str == "" || str==null) {
return null;
}else{
return str;
}
}
}
+19
View File
@@ -0,0 +1,19 @@
package sugoi.form.elements;
/**
* ...
* @author fbarbut
*/
class StringSelect extends Selectbox<String>
{
override function getTypedValue(str:String){
str = StringTools.trim(str);
if (str == "" || str==null) {
return null;
}else{
return str;
}
}
}
+36
View File
@@ -0,0 +1,36 @@
package sugoi.form.elements;
import sugoi.form.Form;
import sugoi.form.FormElement;
class Submit extends FormElement<String>
{
public function new(name:String, value:String)
{
super();
this.name = name;
this.value = value;
}
override public function isValid():Bool
{
return true;
}
override public function render() :String
{
if (Form.USE_TWITTER_BOOTSTRAP) cssClass = "btn btn-primary";
var s = "<input type=\"submit\" class=\"" + getClasses() +"\" value=\"" + value + "\" " + attributes + " name=\"" +parentForm.name + "_" +name + "\" id=\"" +parentForm.name + "_" +name + "\" />";
return s;
}
override public function getFullRow():String
{
return "<div class='col-sm-4'></div><div class='col-sm-8'>" + this.render() + "</div>";
}
}
+39
View File
@@ -0,0 +1,39 @@
package sugoi.form.elements;
import sugoi.form.elements.Input;
import sugoi.form.Form;
import sugoi.form.validators.*;
class TextArea extends StringInput
{
public var height:Int;
public function new(name:String, label:String, ?value:String, ?required:Bool=false, ?validators:Array<Validator<String>>, ?attributes:String)
{
super(name, label, value, required, validators, attributes);
}
override public function render():String
{
var n = parentForm.name + "_" +name;
//if (showLabelAsDefaultValue && value == label){
//addValidator(new BoolValidator(false, "Not valid"));
//}
if ((value == null || value == "") && showLabelAsDefaultValue) {
value = label;
}
var s = "";
if (required && parentForm.isSubmitted() && printRequired) s += "required<br />";
s += "<textarea class=\""+ getClasses() +"\" name=\"" + n + "\" id=\"" + n + "\" " + attributes + " >" + safeString(value) + "</textarea>";
return s;
}
}
+9
View File
@@ -0,0 +1,9 @@
package sugoi.form.filters;
class Filter
{
public function new() {
}
}
+28
View File
@@ -0,0 +1,28 @@
package sugoi.form.filters;
/**
* Converts a String to a Float
*/
class FloatFilter extends Filter implements IFilter<Float>
{
public function new()
{
super();
}
public function filter(f:Float):Float{
return f;
}
public function filterString(n:String):Float {
if (n == null || n=="") return null;
n = StringTools.trim(n);
n = StringTools.replace(n, ",", ".");
var f = Std.parseFloat(n);
if( Math.isNaN(f) ) f = null;
return f;
}
}
+10
View File
@@ -0,0 +1,10 @@
package sugoi.form.filters;
interface IFilter<T>
{
public function filter(data:T):T;
public function filterString(data:String):T;
}
@@ -0,0 +1,29 @@
package sugoi.form.validators;
import sugoi.form.validators.Validator;
class BoolValidator extends Validator<Bool>
{
public var errorNotValid:String;
public var valid:Bool;
public function new(valid:Bool, ?error:String)
{
super();
this.valid = valid;
if (error != null) {
errorNotValid = error;
}else {
errorNotValid = "Not valid.";
}
}
override public function isValid(value):Bool
{
if (!valid)
errors.push(errorNotValid);
return valid;
}
}
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2008, TouchMyPixel & contributors
* Original author : Tony Polinelli <tonyp@touchmypixel.com>
* Contributers: Tarwin Stroh-Spijer
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE TOUCH MY PIXEL & CONTRIBUTERS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE TOUCH MY PIXEL & CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
package sugoi.form.validators;
import sugoi.form.Form;
import sugoi.form.FormElement;
import sugoi.form.Validator;
class CustomValidator extends Validator
{
public var validationFunction : Dynamic->Bool;
public var errorNotValid:String;
public function new( validationFunction : Dynamic->Bool, ?errorMessage:String = null )
{
super();
this.validationFunction = validationFunction;
this.errorNotValid = errorMessage;
}
override public function isValid( value : Dynamic ) : Bool
{
super.isValid( value );
var valid = false;
if ( validationFunction != null )
valid = validationFunction( value);
if (!valid)
errors.add(errorNotValid);
return valid;
}
}
@@ -0,0 +1,162 @@
/*
* Copyright (c) 2008, TouchMyPixel & contributors
* Original author : Tony Polinelli <tonyp@touchmypixel.com>
* Contributers: Tarwin Stroh-Spijer
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE TOUCH MY PIXEL & CONTRIBUTERS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE TOUCH MY PIXEL & CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
package sugoi.form.validators;
//import sugoi.form.Validator;
//import poko.utils.StringTools2;
//import site.cms.common.DateTimeMode;
class DateTimeValidator extends Validator
{
//public static var EMAIL_REGEX : EReg = "([0-9]{4})[-\.[:space:]]([0-9]{2})[-\.[:space:]]([0-9]{2})";
public var format:EReg;
public var minDate:Date;
public var maxDate:Date;
public var errorDateOutOfRange:String;
public var errorDateNotValid:String;
public var errorDateNotExist:String;
public function new( ?mode : DateTimeMode = null, ?minDate:Date, ?maxDate:Date )
{
super();
if ( mode == DateTimeMode.date )
{
format = new EReg("[0-9]{4}-[0-9]{2}-[0-9]{2}", null);
errorDateNotValid = "Is not in the correct format. YYYY-MM-DD is required.";
}
else if ( mode == DateTimeMode.time )
{
format = new EReg("[0-9]{2}:[0-9]{2}:[0-9]{2}", null);
errorDateNotValid = "Is not in the correct format. HH:MM:SS is required.";
}
else
{
format = new EReg("[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}", null);
errorDateNotValid = "Is not in the correct format. YYYY-MM-DD HH:MM:SS is required.";
}
errorDateOutOfRange = "Must be between %s and %s";
if (minDate != null) this.minDate = minDate;
if (maxDate != null) this.maxDate = maxDate;
}
override public function isValid(value:Dynamic):Bool
{
var valid = true;
var d:Date;
// value is date
if (Type.getClass(value) == Date){
d = value;
// value conforms to format, convert to date
}else if (Type.getClass(value) == String && format.match(value)) {
d = Date.fromString(value);
}else {
errors.add(errorDateNotValid);
return false;
}
// check date range
if (minDate != null){
if (d.getTime() < minDate.getTime())
valid = false;
}
if (maxDate != null){
if (d.getTime() > maxDate.getTime())
valid = false;
}
// date must be out of range if invalid at this point
if (!valid)
errors.add(StringTools2.printf(errorDateOutOfRange, [dateOnly(minDate), dateOnly(maxDate)]));
return valid;
}
private function dateOnly(d:Date):String
{
return StringTools.lpad(Std.string(d.getFullYear()), "0", 4) + "-" + StringTools.lpad(Std.string(d.getMonth()), "0", 2) + "-" + StringTools.lpad(Std.string(d.getDate()), "0", 2);
}
}
/*
var v:DateValidator = new DateValidator(Date.fromString("1981-12-09"), Date.fromString("2030-11-11"));
// -------------------------------------------------
// check input
trace("<br /><br />INPUT<br />");
v.reset();
trace(true);
trace(v.isValid("1981-12-09"));
trace(v.errors);
v.reset();
trace(false);
trace(v.isValid("x981-12-09"));
trace(v.errors);
v.reset();
trace(false);
trace(v.isValid("1985-90-90"));
trace(v.errors);
// -------------------------------------------------
// check ranges
trace("<br /><br />RANGES<br />");
v.reset();
trace(true);
trace(v.isValid(Date.fromString("1981-12-09")));
trace(v.errors);
v.reset();
trace(false);
trace(v.isValid(Date.fromString("1981-12-08")));
trace(v.errors);
v.reset();
trace(true);
trace(v.isValid(Date.now()));
trace(v.errors);
v.reset();
trace(true);
trace(v.isValid(Date.fromString("2030-11-11")));
trace(v.errors);
v.reset();
trace(false);
trace(v.isValid(Date.fromString("2030-11-12")));
trace(v.errors);
*/
+147
View File
@@ -0,0 +1,147 @@
/*
* Copyright (c) 2008, TouchMyPixel & contributors
* Original author : Tony Polinelli <tonyp@touchmypixel.com>
* Contributers: Tarwin Stroh-Spijer
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE TOUCH MY PIXEL & CONTRIBUTERS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE TOUCH MY PIXEL & CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
package sugoi.form.validators;
import sugoi.form.Validator;
import poko.utils.StringTools2;
class DateValidator extends Validator
{
//public static var EMAIL_REGEX : EReg = "([0-9]{4})[-\.[:space:]]([0-9]{2})[-\.[:space:]]([0-9]{2})";
public var format:EReg;
public var minDate:Date;
public var maxDate:Date;
public var errorDateOutOfRange:String;
public var errorDateNotValid:String;
public var errorDateNotExist:String;
public function new(?minDate:Date, ?maxDate:Date)
{
super();
format = new EReg("[0-9]{4}-[0-9]{2}-[0-9]{2}", null);
errorDateOutOfRange = "Must be between %s and %s";
errorDateNotValid = "Is not in the correct format. YYYY-MM-DD is required.";
if (minDate != null) this.minDate = minDate;
if (maxDate != null) this.maxDate = maxDate;
}
override public function isValid(value:Dynamic):Bool
{
var valid = true;
var d:Date;
// value is date
if (Type.getClass(value) == Date){
d = value;
// value conforms to format, convert to date
}else if (Type.getClass(value) == String && format.match(value)) {
d = Date.fromString(value);
}else {
errors.add(errorDateNotValid);
return false;
}
// check date range
if (minDate != null){
if (d.getTime() < minDate.getTime())
valid = false;
}
if (maxDate != null){
if (d.getTime() > maxDate.getTime())
valid = false;
}
// date must be out of range if invalid at this point
if (!valid)
errors.add(StringTools2.printf(errorDateOutOfRange, [dateOnly(minDate), dateOnly(maxDate)]));
return valid;
}
private function dateOnly(d:Date):String
{
return StringTools.lpad(Std.string(d.getFullYear()), "0", 4) + "-" + StringTools.lpad(Std.string(d.getMonth()), "0", 2) + "-" + StringTools.lpad(Std.string(d.getDate()), "0", 2);
}
}
/*
var v:DateValidator = new DateValidator(Date.fromString("1981-12-09"), Date.fromString("2030-11-11"));
// -------------------------------------------------
// check input
trace("<br /><br />INPUT<br />");
v.reset();
trace(true);
trace(v.isValid("1981-12-09"));
trace(v.errors);
v.reset();
trace(false);
trace(v.isValid("x981-12-09"));
trace(v.errors);
v.reset();
trace(false);
trace(v.isValid("1985-90-90"));
trace(v.errors);
// -------------------------------------------------
// check ranges
trace("<br /><br />RANGES<br />");
v.reset();
trace(true);
trace(v.isValid(Date.fromString("1981-12-09")));
trace(v.errors);
v.reset();
trace(false);
trace(v.isValid(Date.fromString("1981-12-08")));
trace(v.errors);
v.reset();
trace(true);
trace(v.isValid(Date.now()));
trace(v.errors);
v.reset();
trace(true);
trace(v.isValid(Date.fromString("2030-11-11")));
trace(v.errors);
v.reset();
trace(false);
trace(v.isValid(Date.fromString("2030-11-12")));
trace(v.errors);
*/
@@ -0,0 +1,40 @@
package sugoi.form.validators;
import sugoi.form.validators.Validator;
class EmailValidator extends Validator<String>
{
public var errorNotValid:String;
public static var emailRegex = ~/^[^()<>@,;:\\"\[\]\s[:cntrl:]]+@[A-Z0-9][A-Z0-9-]*(\.[A-Z0-9][A-Z0-9-]*)*\.(xn--[A-Z0-9]+|[A-Z]{2,8})$/i;
public function new()
{
super();
#if js
errorNotValid = "Not a valid email address";
#else
errorNotValid = switch(App.current.getLang()){
case "fr" : "Adresse email invalide";
default : "Not a valid email address";
};
#end
}
override public function isValid(value:Dynamic):Bool
{
super.isValid(value);
var valid = emailRegex.match(Std.string(value));
if (!valid)
errors.add(errorNotValid);
return valid;
}
public inline static function check(value:String):Bool
{
var val = new EmailValidator();
return val.isValid(value);
}
}
+105
View File
@@ -0,0 +1,105 @@
/*
* Copyright (c) 2008, TouchMyPixel & contributors
* Original author : Tony Polinelli <tonyp@touchmypixel.com>
* Contributers: Tarwin Stroh-Spijer
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE TOUCH MY PIXEL & CONTRIBUTERS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE TOUCH MY PIXEL & CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
package sugoi.form.validators;
import sugoi.form.Validator;
import poko.utils.StringTools2;
class ListValidator extends Validator
{
public var list:Array<Dynamic>;
public var mode:ListValidatorMode;
public var errorAllow:String;
public var errorDeny:String;
public function new(?mode:ListValidatorMode)
{
super();
errorAllow = "Only the values %s are allowed.";
// this is used for the complete list of denied values
//errorDeny = "The values %s are not allowed.";
errorDeny = "The value '%s' is not allowed.";
this.mode = mode != null ? mode : ListValidatorMode.ALLOW;
}
override public function isValid(value:Dynamic):Bool
{
super.isValid(value);
var valueExists = Lambda.has(list, value);
var valid = (mode == ListValidatorMode.ALLOW) ? valueExists : !valueExists;
if (!valid) {
// this one returns a list of denied values, which is nice, but though thought it might be a security risk somehow?
//errors.push(StringTools2.printf(mode == ListValidatorMode.ALLOW ? errorAllow : errorDeny, [joinAsSentence(list, "'")]));
if (mode == ListValidatorMode.ALLOW) {
errors.push(StringTools2.printf(errorAllow, [joinAsSentence(list, "'")]));
}else {
errors.push(StringTools2.printf(errorDeny, [value]));
}
}
return valid;
}
private function joinAsSentence(a:Array<Dynamic>, ?wrapWith:String):String
{
if (wrapWith != null) {
for(i in 0...a.length)
a[i] = wrapWith + a[i] + wrapWith;
}
var e = a.pop();
var s = a.join(", ") + " and " + e;
return s;
}
}
enum ListValidatorMode
{
ALLOW;
DENY;
}
/*
var v:ListValidator = new ListValidator(ListValidatorMode.ALLOW);
var a:Array<Dynamic> = ["good", "bad", "stupid"];
v.list = a;
trace(v.isValid("good"));
trace(v.errors);
v.reset();
v.list = a;
trace(v.isValid("bad"));
trace(v.errors);
v.reset();
v.list = a;
trace(v.isValid("ugly"));
trace(v.errors);
v.reset();
*/
@@ -0,0 +1,113 @@
/*
* Copyright (c) 2008, TouchMyPixel & contributors
* Original author : Tony Polinelli <tonyp@touchmypixel.com>
* Contributers: Tarwin Stroh-Spijer
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE TOUCH MY PIXEL & CONTRIBUTERS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE TOUCH MY PIXEL & CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
package sugoi.form.validators;
import sugoi.form.Validator;
import poko.utils.StringTools2;
class NumberValidator extends Validator
{
public var isInt:Bool;
public var min:Float;
public var max:Float;
public var errorNumber:String;
public var errorInt:String;
public var errorMin:String;
public var errorMax:String;
public function new(min:Float=0, max:Float=999999999999, isInt:Bool=false)
{
super();
this.min = min;
this.max = max;
this.isInt = isInt;
errorNumber = "Must be a number";
errorInt = "Must be an integer";
errorMin = "Minimum number %s";
errorMax = "Maximum number %s";
}
override public function isValid(value:Dynamic):Bool
{
super.isValid(value);
var valid = true;
var f = Std.parseFloat(Std.string(value));
var i = Std.int(f);
if (Math.isNaN(f))
{
errors.add(errorNumber);
valid = false;
}else{
if (isInt && i != f) {
errors.add(errorInt);
valid = false;
}
var n:Float = isInt ? i : f;
if (n < min) {
errors.add(StringTools2.printf(errorMin, [min]));
valid = false;
}else if (n > max) {
errors.add(StringTools2.printf(errorMax, [max]));
valid = false;
}
}
return valid;
}
}
/*
var v:NumberValidator = new NumberValidator();
v.isInt = false;
v.min = -5;
v.max = 10.55;
trace(v.validate(5));
trace(v.errors);
v.reset();
trace(v.validate( -6));
trace(v.errors);
v.reset();
trace(v.validate( -3));
trace(v.errors);
v.reset();
trace(v.validate(11.5));
trace(v.errors);
v.reset();
*/
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2008, TouchMyPixel & contributors
* Original author : Tony Polinelli <tonyp@touchmypixel.com>
* Contributers: Tarwin Stroh-Spijer
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE TOUCH MY PIXEL & CONTRIBUTERS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE TOUCH MY PIXEL & CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
package sugoi.form.validators;
import sugoi.form.Form;
import sugoi.form.FormElement;
import sugoi.form.Validator;
import poko.utils.StringTools2;
class RegexValidator extends Validator
{
public var regex:EReg;
public var regexOptions:String;
public var errorRegex:String;
public function new(regex:EReg, ?errorMessage:String=null )
{
super();
this.regex = regex;
errorRegex = (errorMessage != null) ? errorMessage : "Regex Failed";
}
override public function isValid(value:Dynamic):Bool
{
super.isValid(value);
var valid:Bool = true;
if (!regex.match(Std.string(value)))
{
errors.add(StringTools2.printf(errorRegex, [regex]));
valid = false;
}
return valid;
}
}
@@ -0,0 +1,139 @@
/*
* Copyright (c) 2008, TouchMyPixel & contributors
* Original author : Tony Polinelli <tonyp@touchmypixel.com>
* Contributers: Tarwin Stroh-Spijer
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE TOUCH MY PIXEL & CONTRIBUTERS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE TOUCH MY PIXEL & CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
package sugoi.form.validators;
import sugoi.form.Validator;
import poko.utils.StringTools2;
import EReg;
class StringValidator extends Validator
{
public var minChars:Int;
public var maxChars:Int;
public var charList:String;
public var mode:StringValidatorMode;
public var regex:EReg;
public var regexError:String;
public var errorMinChars:String;
public var errorMaxChars:String;
public var errorDenyChars:String;
public var errorAllowChars:String;
public function new(?minChars:Int=0, ?maxChars:Int=999999, ?charList:String="", ?mode:StringValidatorMode, ?regex:EReg = null, ?regexError:String)
{
super();
errorMinChars = "Must be at least %s characters long";
errorMaxChars = "Must be less than %s characters long";
errorDenyChars = "Cannot contain the characters '%s'";
errorAllowChars = "Must contain only the characers '%s'";
this.minChars = minChars;
this.maxChars = maxChars;
this.charList = charList;
this.mode = mode;
if (this.mode == null) this.mode = StringValidatorMode.ALLOW;
this.regex = regex;
this.regexError = regexError != null ? regexError : "Doesn't match required input.";
errors = new List();
}
override public function isValid(value:Dynamic):Bool
{
super.isValid(value);
var valid = true;
var s = Std.string(value);
if (minChars != null && minChars > 0 && s.length < minChars)
{
valid = false;
errors.add(StringTools2.printf(errorMinChars, [ minChars]));
}
if (maxChars != null && maxChars > 0 && s.length > maxChars)
{
valid = false;
errors.add(StringTools2.printf(errorMaxChars, [maxChars]));
}
if (charList.length > 0)
{
switch(mode)
{
case StringValidatorMode.ALLOW:
for (i in 0...s.length)
{
var letter = s.charAt(i);
if (charList.indexOf(letter) == -1)
{
valid = false;
// errors.add(StringTools2.printf(errorAllowChars, [StringTools2.toSentenceList(charList)]));
errors.add(StringTools2.printf(errorAllowChars, [charList]));
break;
}
}
case StringValidatorMode.DENY:
for (i in 0...s.length)
{
var letter = s.charAt(i);
if (charList.indexOf(letter) != -1)
{
valid = false;
//errors.add(StringTools2.printf(errorDenyChars, [StringTools2.toSentenceList(charList)]));
errors.add(StringTools2.printf(errorDenyChars, [charList]));
break;
}
}
}
}
if (regex != null)
{
if (!regex.match(s))
{
valid = false;
errors.add(regexError);
}
}
return valid;
}
}
enum StringValidatorMode
{
ALLOW;
DENY;
}
+23
View File
@@ -0,0 +1,23 @@
package sugoi.form.validators;
class Validator<T>
{
public var errors:List<String>;
public function new()
{
errors = new List();
}
public function isValid(value:T):Bool
{
errors.clear();
return true;
}
public function reset()
{
errors.clear();
}
}
+102
View File
@@ -0,0 +1,102 @@
package mt.net.helper;
import mt.Compat;
/**
* BreadCrumb (fil d'ariane)
*
* For breadcrumb translation,
* create "breadcrumb_*" entries in the translation file for each section.
*
* @author fbarbut
**/
class BreadCrumb{
public var lang : String;
public var sections : Array<String>; /* raw sections like "game/tetris/highscores" */
public var translation : Hash<String>; /* translated sections */
public function new(uri:String,?translator:String->String) {
sections = [];
translation = new Hash<String>();
//remove GET params
uri = uri.split('?')[0];
if( uri.substr(0,1) == "/" )
uri = uri.substr(1);
var uri = uri.split('/');
/*modify*/
for (i in 0...uri.length) {
var u = uri[i];
if(u == null)
continue;
if(Std.parseInt(u) != null) {
uri[i] = null;
continue;
}
if(u == 'index.n' || u == '') {
u = uri[i] = "home";
continue;
}
}
/*remove nulls*/
uri = Lambda.array(Lambda.filter(uri,function(e) return e!=null));
sections = uri;
/*translation*/
if(translator != null) {
for(s in sections) {
var name = translator("breadcrumb_" + s);
if(name != "#breadcrumb_" + s + "#") {
translation.set(s, name);
}else {
//no translation
translation.set(s, s);
}
}
}
}
public function toString(?mode=1, ?format:String):Dynamic {
switch(mode) {
case 1 :
/* raw sections */
return sections;
case 2 :
/* translated sections */
return getTranslatedSections();
}
return null;
//if(format != null) {
//format = StringTools.replace(format, "::rank::", Std.string(rank));
//format = StringTools.replace(format, "::suffix::", Std.string(suffix));
//return format;
//}else {
//return rank + suffix;
//}
}
public function getTranslatedSections() {
var out = [];
for(s in sections) {
out.push(translation.get(s));
}
return out;
}
}
+21
View File
@@ -0,0 +1,21 @@
package mt.net.helper;
class Helper {
/**
* Init all helpers in applicatio view.
*/
public static function init(context:Dynamic, viewHelpers:Array<Dynamic>) {
/* gros paté (c) ncannasse */
for(vh in viewHelpers) {
var methodName = Type.getClassName(Type.getClass(vh)).split(".").pop().toLowerCase();
var toString = Reflect.field(vh, "toString");
var nargs : Int = untyped $nargs(toString);
Reflect.setField(context, methodName , Reflect.makeVarArgs(function(p:Array<Dynamic>) {
while( p.length < nargs ) p.push(null);
return Reflect.callMethod(vh, toString, p);
}));
}
}
}
+113
View File
@@ -0,0 +1,113 @@
package mt.net.helper;
/**
* Ordinal number helper
*
* @author fbarbut
**/
class Ordinal /*implements IHelper*/{
public var lang : String;
public static var AVAILABLE_LANGS = ['fr','en','es'];
public function new(_lang:String) {
lang = _lang.toLowerCase();
if(!Lambda.has(AVAILABLE_LANGS, lang)) lang = 'en';
}
/**
* Get output, like "1st" or "2nd"
* @param rank number/rank starting from 1
* @param ?sex 1:male,2:female, 3: neutral (german)
* @param ?format text format like "::rank::<span>::suffix::</span>"
*/
public function toString(rank:Int, ?sex:Int, ?format:String):String {
var suffix = "";
if(sex == null) sex = 1;
switch(lang) {
case "fr":
if(rank == 1) {
suffix = (sex==1)?"er":"ère";
}else {
suffix = "ème";
}
case "es":
//http://reglasdeortografia.com/numerales.html
suffix = (sex == 1)?"o":"a";
case "de":
switch(sex) {
case 1 :
suffix = "ter";
case 2 :
suffix = "te";
default :
suffix = "tes";
}
default:
//"en":
if(rank == 1) {
suffix = "st";
}else if(rank == 2) {
suffix = "nd";
}else if(rank == 3) {
suffix = "rd";
}else {
suffix = "th";
}
/*DE
*
* Karsten :
Hihi. Un peu compliqué pour l'Allemand comme les suffix/mots dépendend de nombre exacte. ...en plus il y un troisième sexe.
Je propose tu utilise ce modèle qui fonctionne toujours: > X : X.
Voici le modèle complète :
> masculin:
> X = 1 : Erster
> X = 2 : Zweiter
> X = 3 : Dritter
> X = 4-19 : Xter
> X = 20-1000 : Xster
... + : Les derniers 3 chiffres décident de nouveau (comme indiqué anvant)
p.ex. 232456 = Y[232]X[456]
> neutre :
> X = 1 : Erstes
> X = 2 : Zweites
> X = 3 : Drittes
> X = 4-19 : Xtes
> X = 20-1000 : Xstes
... + : Les derniers 3 chiffres décident de nouveau (comme indiqué anvant)
p.ex. 232456 = Y[232]X[456]
> feminin :
> X = 1 : Erste
> X = 2 : Zweite
> X = 3 : Dritte
> X = 4-19 : Xte
> X = 20-1000 : Xste
... + : Les derniers 3 chiffres décident de nouveau (comme indiqué anvant)
p.ex. 232456 = Y[232]X[456]>
* */
}
if(format != null) {
format = StringTools.replace(format, "::rank::", Std.string(rank));
format = StringTools.replace(format, "::suffix::", Std.string(suffix));
return format;
}else {
return rank + suffix;
}
}
}
+8
View File
@@ -0,0 +1,8 @@
C'est quoi un helper ( ou view helper ? )
C'est un petit composant qui permet de rendre en html des éléments complexes, difficiles à rendre en macro templo.
Utiliser Helper.init() avec le Context d'une application et une liste de helpers pour les rendres disponible dans le Context de l'application web.
Inspiré de http://framework.zend.com/manual/1.12/en/zend.view.helpers.html
+166
View File
@@ -0,0 +1,166 @@
package sugoi.helper;
/**
* Simple class to print a HTML table
*
* @author fbarbut
*
* It should be able to print a lot of structures :
* - a spod request result
* - arrays, lists
* - anonymous objects
*
* usage :
* var t = new Table();
* t.title = "the table";
* t.setContent( [{toto:1,tata:2},{toto:4,tata:7}] );
* neko.Lib.print( t.toString(); );
**/
class Table{
//table content
public var title : String;
public var head : Array<String>;
public var content : Array<Array<Dynamic>>;
//table CSS class
public var tableCSSClass : String;
public function new(?defaultCss:String) {
if(defaultCss != null) tableCSSClass = defaultCss;
}
public function toString(?param:Dynamic):String {
if(param != null) {
setContent(param);
return go();
}else {
return "null";
}
}
public function go():String {
var output = "";
output += "<table " + (tableCSSClass != null?"class=\"" + tableCSSClass + "\"":"") +">";
if (title != null) {
var length = 0;
for(row in content) {
for(cell in row) {
length++;
}
break;
}
//alors là je comprends pas du tout du tout du tout pourquoi content[0].length ne marche pas...
//output += content[0]+ " "+Type.getClass(content[0])+" "+content[0].length ;
output += "<tr><th colspan='"+length+"'>" + title + "</th></tr>";
}
if (head != null) {
output += "<tr>";
for ( cell in head ) {
output += "<th>" + cell + "</th>";
}
output += "</tr>";
}
for (row in content) {
output += "<tr>";
for (cell in row) {
output += "<td>"+cell+"</td>";
}
output += "</tr>";
}
output += "</table>";
return output;
}
/**
* Iterable -> Array
*/
function fromIterableToArray<T>(iterable) {
var out = [];
var iterable : Iterable<T> = cast iterable;
for (el in iterable) {
out.push(el);
}
return out;
}
/**
* Reflectable -> Array
*/
function fromReflectableToArray<T>(reflectable) {
var out = [];
//get fields
for (field in Reflect.fields(reflectable) ) {
if(!Reflect.isFunction(Reflect.field(reflectable,field)) && field!="__cache__" && field!="__lock")
out.push(Reflect.field(reflectable,field));
}
return out;
}
public function toArray<T>(c:Dynamic) {
if (c.iterator!=null) { //Reflect.hasField(c, "iterator")
//trace("fromIterableToArray "+c);
return fromIterableToArray(c);
}else {
//trace("fromReflectableToArray "+c);
return fromReflectableToArray(c);
}
}
public function setContent<T>(c:Dynamic) {
//header
head = [];
//content
content = [];
var row = [];
//on essaye de voir si il y a une deuxieme dimension au tableau (ex : liste d'objets )
try{
for (obj in toArray(c) ) {
row = [];
for (prop in toArray(obj)) {
//get header
if(head.length==0){
for (prop in Reflect.fields(obj)) {
//if the field is not a function, lets add it :
if(!Reflect.isFunction(Reflect.field(obj,prop)) && prop!="__cache__")
head.push(prop);
}
}
row.push(prop);
}
content.push(row);
}
}catch(e :Dynamic) {
content = [];
head = ["field","value"];
//a priori c'est un objet simple pas une liste , donc on liste ses propriétés
for (field in Reflect.fields(c) ) {
var row = [];
if(!Reflect.isFunction(Reflect.field(c, field)) && field != "__cache__" && field != "__lock") {
row.push(field);
row.push(Reflect.field(c, field));
}
content.push(row);
}
}
return content;
}
}
+558
View File
@@ -0,0 +1,558 @@
package sugoi.i18n;
import haxe.macro.Expr;
import haxe.macro.Context;
/**
* @author tpfeiffer<thomas.pfeiffer@gmail.com>
*/
#if (potools || macro)
typedef POData = Array<POEntry>;
typedef POEntry = {
?msgid: String,
?msgstr: String,
id: Null<String>,
str: Null<String>,
?cTranslator: String,
?cExtracted: String,
?cRef: String,
?cFlags: String,
?cPrevious : String,
?cComment: String,
}
#end
typedef LocaleString = String;
class GetText {
public var texts : Map<String,LocaleString>;
public function new() {}
public function toString() {
return "GetText";
}
public function untranslated(str:Dynamic) : LocaleString {
return cast Std.string(str);
}
public macro function _(ethis:Expr, estr:ExprOf<String>, ?params:ExprOf<Dynamic>) {
var str = switch(estr.expr) {
case EConst(CString(s)) : s;
default : Context.error("Constant string expected here!", estr.pos);
}
var odd = false;
var hasVars = false;
var strVars = [];
for(s in str.split("::")) {
odd = !odd;
if( !odd ) {
hasVars = true;
strVars.push(s);
}
}
switch( params.expr ) {
case EObjectDecl(fields) :
var vmap = new Map();
for(f in fields) {
if( str.indexOf("::"+f.field+"::")<0 )
Context.error("Variable "+f.field+" not found in the string!", f.expr.pos);
vmap.set(f.field, true);
//useless when there's no obfuscation
//f.field = "_"+f.field;
}
for(k in strVars)
if( !vmap.exists(k) )
Context.error("String requires field "+k, params.pos);
params = { expr:EObjectDecl(fields), pos:params.pos };
case EConst(CIdent("null")) :
if( hasVars )
Context.error("Missing params: "+strVars.join(", "), params.pos);
default :
Context.error("Anonymous object expected here!", params.pos);
}
return macro $ethis.get($estr,$params);
}
@:noCompletion public function get(str:String, ?params:Dynamic) : LocaleString {
if(texts == null){
//fail silently
texts = new Map<String,LocaleString>();
}
str = StringTools.rtrim( str.split("||")[0] );
if(texts.exists(str)) {
str = texts.get(str);
}
var list = str.split("::");
if (params != null) {
for (k in Reflect.fields(params)) {
str = StringTools.replace(str, "::" + k + "::", Reflect.field(params, k));
}
}
return new LocaleString(str);
}
public function readMo(data:haxe.io.Bytes) {
var r = new MoReader(data);
texts = r.parse();
r = null;
}
public function emptyDictionary() {
texts = new Map();
}
/**
* Parses all the project to generate the POT file
*/
macro public static function parse(codePath:Array<String>, potFilePath:String, ?refPoFilePath:String) {
Sys.println("[GetText] Parsing source code...");
var data : POData = [];
data.push(POTools.mkHeaders([
"MIME-Version" => "1.0",
"Content-Type" => "text/plain; charset=UTF-8",
"Content-Transfer-Encoding" => "8bit"
]));
var strMap : Map<String,Bool> = new Map();
for( path in codePath )
explore(path, data, strMap);
Sys.println("[GetText] Saving POT file..." + potFilePath);
POTools.exportFile( potFilePath, data );
if( refPoFilePath != null ) {
if( !sys.FileSystem.exists(refPoFilePath) ) {
Sys.println("[GetText] Warning: File not found: "+refPoFilePath );
} else {
Sys.println("[GetText] Saving Translated-POT file...");
POTools.exportTranslatedFile(potFilePath,refPoFilePath,data);
}
}
Sys.println("[GetText] Done.");
return macro {}
}
#if macro
static function explore(folder:String, data:POData, strMap:Map<String,Bool>) {
for ( f in sys.FileSystem.readDirectory(folder) ) {
// Parse sub folders
if( sys.FileSystem.isDirectory(folder+"/"+f) ) {
explore(folder+"/"+f, data, strMap);
continue;
}
// Ignore non-sourcecode
var isHaxeFile = f.substr(f.length - 3) == ".hx";
var isTemplateFile = f.substr(f.length - 4) == ".mtt";
if( !(isHaxeFile || isTemplateFile) )
continue;
Sys.println('explore $folder/$f');
// Read lines
var c = sys.io.File.getContent(folder + "/" + f);
// Test it: http://regexr.com/
//var strReg = ~/_\([ ]*"((?:[^"\\]+|\\.)*)"[ ]*\)/igm;
var strReg = ~/_\([ ]*"((?:[^"\\]+|\\.)*)"[ ]*(?:,[ ]*{[()\[\].,:\w\s]*})?\)/igm;
var out = strReg.map(c, function(e) {
var fullStr = e.matched(0);
var str = e.matched(1);
//Sys.println("str matched:"+str);
// Ignore commented strings
var i = str.indexOf("//");
var matchPos = strReg.matchedPos().pos;
//Sys.println("i="+i+" matchPos="+matchPos);
//TODO required??
//if( i >= 0 && i < matchPos )
// return "";
var cleanedStr = str;
// Translator comment
var comment : String = null;
if( cleanedStr.indexOf("||") >= 0 ) {
var parts = cleanedStr.split("||");
if( parts.length != 2 ) {
Sys.println("Malformed translator comment");
throw "Malformed translator comment";
return "";
}
comment = StringTools.trim(parts[1]);
cleanedStr = cleanedStr.substr(0,cleanedStr.indexOf("||"));
cleanedStr = StringTools.rtrim(cleanedStr);
}
var n = e.matchedPos().pos;
//Sys.println("match line : "+n);
// New entry found
if( !strMap.exists(cleanedStr) ) {
//Sys.println("register key : "+cleanedStr);
strMap.set(cleanedStr, true);
data.push({
id : cleanedStr,
str : "",
cRef : folder+"/"+f+":"+n,
cExtracted : comment,
});
} else {
var previous = Lambda.find(data, function(e) return e.id == cleanedStr);
if( previous != null )
previous.cRef += " "+folder+"/"+f+":"+n;
}
//return Locale.texts.get(cleanedStr);
//return fullStr;
Sys.println("out = "+cleanedStr);
return cleanedStr;
});
}
}
#end
}
/**
* GNU GetText MO file reader
* @doc https://www.gnu.org/software/gettext/manual/html_node/MO-Files.html
*/
class MoReader
{
private var original_table_offset:UInt;
private var translated_table_offset:UInt;
private var hash_num_entries:UInt;
private var hash_offset:UInt;
private var data:haxe.io.BytesInput;
static var MAGIC:UInt = 0x950412DE;
static var MAGIC2:UInt = 0xDE120495;
public function new(data:haxe.io.Bytes):Void
{
this.data = new haxe.io.BytesInput(data);
}
public function parse():Map<String,LocaleString>
{
var d = data;
var header : UInt = d.readInt32();
if(header != MAGIC && header != MAGIC2) {
throw "Bad MO file header : " + header;
}
var revision:UInt = d.readInt32();
if (revision > 1){
throw "Bad MO file format revision : "+revision;
}
var num_strings:UInt = d.readInt32();
original_table_offset= d.readInt32();
translated_table_offset = d.readInt32();
hash_num_entries= d.readInt32();
hash_offset= d.readInt32();
var texts : Map<String,LocaleString> = new Map();
for (i in 1...num_strings)
texts.set( getOriginalString(i), getTranslatedString(i) );
return texts;
}
function getTranslatedString(index:Int):LocaleString {
return getString(translated_table_offset + 8 * index );
}
function getOriginalString(index:Int):String {
return getString(original_table_offset + 8 * index );
}
function getString(offset:UInt):LocaleString {
data.position = offset;
var length :UInt = data.readInt32();
var pos :UInt = data.readInt32();
data.position = pos;
return new LocaleString( data.readString(length) );
}
}
#if (potools || macro)
class POTools {
public static function parseFile( path : String ) : POData {
return parse( sys.io.File.getContent(path) );
}
public static function parse( data : String ) : POData {
var arr : POData = [];
var e : POEntry = cast { };
var lnum = -1;
for ( line in data.split("\n") ) {
lnum++;
// Remove CR before LF
if ( line.length > 0 && line.substr( -1, 1) == "\r" )
line = line.substr(0, line.length - 1);
if( line.length == 0 ){
arr.push(e);
e = cast {};
continue;
}
var f = line.charCodeAt(0);
if( f == '"'.code ){
if( e.msgstr != null ){
e.msgstr += "\n"+line;
e.str += getString(line,lnum);
}else if( e.msgid != null ){
e.msgid += "\n"+line;
e.id += getString(line,lnum);
}else{
throw "Parse error line "+lnum;
}
}else if( f == '#'.code ){
var p = line.charCodeAt(1);
switch( p ){
case ':'.code:
var s = line.substr(3);
if( e.cRef == null )
e.cRef = s;
else
e.cRef += "\n"+s;
case '.'.code:
var s = line.substr(3);
if( e.cExtracted == null )
e.cExtracted = s;
else
e.cExtracted += "\n"+s;
case ','.code:
var s = line.substr(3);
if( e.cFlags == null )
e.cFlags = s;
else
e.cFlags += "\n"+s;
case '|'.code:
var s = line.substr(3);
if( e.cPrevious == null )
e.cPrevious = s;
else
e.cPrevious += "\n"+s;
case '~'.code:
var s = line.substr(3);
if( e.cComment == null )
e.cComment = s;
else
e.cComment += "\n"+s;
case ' '.code:
var s = line.substr(2);
if( e.cTranslator == null )
e.cTranslator = s;
else
e.cTranslator += "\n"+s;
default:
throw "Parse error line "+lnum;
}
}else if( StringTools.startsWith(line, "msgid ") ){
e.msgid = line;
e.id = getString(line,lnum);
}else if( StringTools.startsWith(line, "msgstr ") ){
e.msgstr = line;
e.str = getString(line,lnum);
}else{
throw "Parse error line "+lnum;
}
}
return arr;
}
public static function mkHeaders( headers : Map<String,String> ) : POEntry {
var str = 'msgstr ""';
for( k in headers.keys() )
str += '\n"'+k+': '+headers.get(k)+'\\n"';
return {
id: "",
str: null,
msgstr: str,
};
}
public static function exportFile( filePath : String, data : POData ){
var fp = sys.io.File.write(filePath, true);
export(fp,data);
fp.close();
}
public static function exportTranslatedFile( potFilePath:String, refPoFilePath:String, data:POData ){
var refData = parseFile( refPoFilePath );
var expData : POData = [];
var mref = new Map<String,{change: Bool, ref: POEntry, refid: Array<String>}>();
for( entry in data ){
if( entry.id == "" ){
expData.push( entry );
continue;
}
var refEntry = Lambda.find(refData,function(e) return e.id == entry.id);
var change = false;
var refid = null;
if( refEntry != null && refEntry.str != "" ){
change = refEntry.str != refEntry.id;
if( entry.cExtracted != null )
refid = entry.cExtracted+"\n\n--------\n"+refEntry.msgid;
else
refid = "--------\n"+refEntry.msgid;
entry.msgid = null;
entry.id = refEntry.str;
}
if( mref.exists(entry.id) ){
var mEntry = mref.get( entry.id );
if( refid != null ){
mEntry.refid.push(refid);
if( change )
mEntry.change = true;
}
}else{
var r = refid==null ? [] : [refid];
mref.set( entry.id, {change: change, ref: entry, refid: r} );
expData.push( entry );
}
}
for( o in mref ){
if( !o.change )
continue;
o.ref.cExtracted = o.refid.join("\n");
}
exportFile(potFilePath.split(".pot").join("-translated.pot"), expData);
}
public static function export( out: haxe.io.Output, data : POData ){
var ids = new Map<String,Bool>();
for( e in data ){
if( e.cTranslator != null )
out.writeString("# "+e.cTranslator.split("\n").join("\n# ")+"\n");
if( e.cExtracted != null )
out.writeString("#. "+e.cExtracted.split("\n").join("\n#. ")+"\n");
if( e.cRef != null )
out.writeString("#: "+e.cRef.split("\n").join("\n#: ")+"\n");
if( e.cFlags != null )
out.writeString("#, "+e.cFlags.split("\n").join("\n#, ")+"\n");
if( e.cPrevious != null )
out.writeString("#| "+e.cPrevious.split("\n").join("\n#| ")+"\n");
if( e.cComment != null )
out.writeString("#~ "+e.cComment.split("\n").join("\n#~ ")+"\n");
var id = null;
if( e.msgid != null ){
out.writeString(e.msgid+"\n");
id = getMultiString(e.msgid,0);
}else if( e.id != null ){
e.msgid = "msgid "+wrapQuote(e.id);
out.writeString(e.msgid+"\n");
id = e.id;
}
if( id != null ){
#if gettext_warning
if( ids.exists(id) )
Sys.println("Warning: duplicate id in pot: "+id);
#end
ids.set(id,true);
}
if( e.msgstr != null ){
out.writeString(e.msgstr+"\n");
}else if( e.str != null ){
e.msgstr = "msgstr "+wrapQuote(e.str);
out.writeString(e.msgstr+"\n");
}
out.writeString("\n");
}
out.writeString("\n");
}
static var REG_STRING = ~/"((\\"|[^"]+)*)"$/;
static function getString( line : String, lnum:Int ){
if( !REG_STRING.match(line) )
throw "Parse error line "+lnum+ "("+line+")";
return REG_STRING.matched(1);
}
public static function getMultiString( lines : String, lnum ) {
return lines.split("\n").map(function(s) return getString(s,lnum++)).join("");
}
public static function wrapQuote( str : String ){
var arr = [str];
var i = 0;
while( arr[i].length > 72 ){
var s = arr[i];
var cut = s.indexOf(" ",72);
if( cut < 0 || cut >= s.length -1 )
break;
arr[i] = s.substr(0,cut+1);
arr[i+1] = s.substr(cut+1);
i++;
}
if( arr.length > 1 )
arr.unshift("");
return arr.map(quote).join("\n");
}
public static function quote( str : String ){
return '"'+str+'"';
}
public static function unescape( str : String ){
return str.split('\\"').join('"').split("\\n").join("\n").split("\\\\").join("\\");
}
public static function escape( str : String ){
return str.split("\\").join("\\\\").split("\n").join("\\n").split('"').join('\\"');
}
public static function cloneEntry( e : POEntry ) : POEntry {
var ne : POEntry = {
id: e.id,
str: e.str,
};
if( e.msgid != null ) ne.msgid = e.msgid;
if( e.msgstr != null ) ne.msgstr = e.msgstr;
if( e.cTranslator != null ) ne.cTranslator = e.cTranslator;
if( e.cExtracted != null ) ne.cExtracted = e.cExtracted;
if( e.cRef != null ) ne.cRef = e.cRef;
if( e.cFlags != null ) ne.cFlags = e.cFlags;
if( e.cPrevious != null ) ne.cPrevious = e.cPrevious;
if( e.cComment != null ) ne.cComment = e.cComment;
return ne;
}
}
#end
+75
View File
@@ -0,0 +1,75 @@
package sugoi.i18n;
/**
* @author tpfeiffer<thomas.pfeiffer@gmail.com>
*/
class Locale
{
static public var texts : GetText;
/**
* Cache gettext objects in case of many language switches
* (like in a cron action which lead to send many emails in various languages)
*/
static public var cache = new Map<String,GetText>();
public static function init(lang:String,?callback:GetText->Void):GetText
{
//Load mo file in various runtimes : macro, js and serverside
#if macro
var filePath = sugoi.Web.getCwd() + "www/" + fileName(lang);
if( !sys.FileSystem.exists(filePath) ){
//Create MO file from PO file. "Gettext" package should be installed.
var cmd = 'msgfmt -o $filePath '+filePath.substr(0,filePath.length-3)+'.po';
Sys.println(cmd);
var p = new sys.io.Process(cmd);
p.exitCode();
}
var file = sys.io.File.getBytes( filePath );
texts = new GetText();
texts.readMo(file);
return texts;
#elseif js
var file : haxe.io.Bytes = null;
var r = new js.html.XMLHttpRequest();
r.responseType = js.html.XMLHttpRequestResponseType.ARRAYBUFFER;
r.onreadystatechange = function(e:js.html.Event){
if (r.readyState == js.html.XMLHttpRequest.DONE){
file = haxe.io.Bytes.ofData(r.response);
texts = new GetText();
texts.readMo(file);
callback(texts);
}
}
r.open('GET', '/'+fileName(lang), true);
r.send();
return null;
#else
/*var texts = cache.get(lang);
trace(texts);
if (texts == null){*/
var file = null;
try{
file = sys.io.File.getBytes(sugoi.Web.getCwd() + "/" + fileName(lang));
}catch(e:Dynamic){
//fail safely
App.current.session.addMessage("Cannot read translation file : "+Std.string(e));
}
texts = new GetText();
if(file!=null) texts.readMo(file);
/*cache.set(lang,texts);
}*/
return texts;
#end
}
inline static function fileName(lang:String)
{
return "lang/texts_" +lang+ ".mo";
}
}
+133
View File
@@ -0,0 +1,133 @@
package sugoi.i18n;
import haxe.macro.Expr;
import haxe.macro.Context;
/**
* Computes translated templates from master templates
*
* @author tpfeiffer<thomas.pfeiffer@gmail.com>
*/
class TemplateTranslator
{
macro public static function parse(path:String)
{
var langs = new sugoi.Config(neko.Web.getCwd()).LANGS;
for( lang in langs ) {
Sys.println(lang + " : Generating template files");
Locale.init(lang);
translateTemplates(lang, path);
//translationForJs(lang);
}
return macro {}
}
#if macro
/*static public function translationForJs(lang:String){
var out = new StringBuf();
var v = "";
out.add("var texts = [];\n");
for ( k in Locale.texts.texts.keys()){
v = Locale.texts.get(k);
k = StringTools.replace(k, '\"', '\\"');
v = StringTools.replace(v, '\"', '\\"');
out.add('texts["$k"] = "$v";\n');
}
var path = sugoi.Web.getCwd() + "www/js/texts_" + lang + ".js";
sys.io.File.saveContent(path, out.toString());
Sys.println(lang +" : Save js translation file (" + path + ")");
}*/
static public function translateTemplates(lang:String, folder:String)
{
//Sys.println('$lang : $folder');
//var strReg = ~/(::_\("([^"]*)"\)::)+/ig;
//var strReg = ~/(::_\([ ]*"([^"]+)+"[ ]*\)::)+/ig;
var strReg = ~/_\([ ]*"((?:[^"\\]+|\\.)*)"[ ]*(?:,[ ]*{[.,:\w\s\(\)]*})?\)/igm;
for ( f in sys.FileSystem.readDirectory(folder) ) {
// Parse sub folders
if(sys.FileSystem.isDirectory(folder+"/"+f) ) {
//create target directory
var langPath = StringTools.replace(folder+"/"+f, "master", lang);
sys.FileSystem.createDirectory(langPath);
translateTemplates(lang, folder+"/"+f);
continue;
}
var isTemplateFile = f.substr(f.length - 4) == ".mtt";
if( !isTemplateFile )
continue;
var filePath = StringTools.replace(folder+"/"+f, "master", lang);
Sys.println(lang + " : " + filePath);
var c = sys.io.File.getContent(folder + "/" + f);
var out = "";
try{
out = strReg.map(c, function(e) {
var str = e.matched(1);
//Sys.println("str matched:"+str);
// Ignore commented strings
//var i = str.indexOf("//");
//if( i >= 0 && i < strReg.matchedPos().pos )
// return "";
var cleanedStr = str;
// Translator comment
var comment : String = null;
if( cleanedStr.indexOf("||") >= 0 ) {
var parts = cleanedStr.split("||");
if( parts.length!=2 ) {
throw "Malformed translator comment";
return "";
}
comment = StringTools.trim(parts[1]);
cleanedStr = cleanedStr.substr(0,cleanedStr.indexOf("||"));
cleanedStr = StringTools.rtrim(cleanedStr);
}
//Sys.println(e.matched(0)+" replace "+str+" by "+Locale.texts.get(cleanedStr));
var output = StringTools.replace( e.matched(0), str, Locale.texts.get(cleanedStr) );
//Sys.println("output:"+output);
return output;
//return Locale.texts.get(cleanedStr);
/*
function getVars(ereg:EReg, input:String, index:Int = 0):Array<String> {
var matches = [];
while (ereg.match(input)) {
matches.push(ereg.matched(index));
input = ereg.matchedRight();
}
return matches;
}
var eregVars = ~/(?:::([^:]+)::)/i;
var aVars = getVars(eregVars, strTmp,1);
var sVars = aVars.map(function(v) { return v+":"+v; });
var variables = '{'+sVars.join(",")+'}';
var contentWithVars = StringTools.replace(e.matched(1), str, strTmp+","+variables);
return StringTools.replace(contentWithVars, "::_", "::__");
*/
});
}catch (e:Dynamic){
throw "Error in " + f + " : " + e;
}
//copy the file to the correct new folder
var langFile = sys.io.File.write(filePath, false);
out = StringTools.replace(out, "\r", "");//for an unknown reason, there was double newlines
langFile.writeString(out);
langFile.flush();
langFile.close();
}
}
#end
}
+178
View File
@@ -0,0 +1,178 @@
package sugoi.i18n.translator;
/**
* GetText (*.po/*.mo) translator
* @doc https://www.gnu.org/software/gettext/manual/html_node/MO-Files.html
* @author fbarbut
*/
class GetText implements sugoi.i18n.translator.ITranslator{
public var texts : Map<String,String>;
public function toString() {
return "#GetText";
}
public function _(str:String, ?params : Dynamic):String {
if(texts == null) throw "no data in dictionnary";
if(texts.exists(str)) str = texts.get(str);
var list = str.split("::");
var n = 0;
if(params!=null){
for (k in Reflect.fields(params)){
str = StringTools.replace(str, "::" + k.substr(1) + "::", Reflect.field(params, k));
}
}
return str;
}
public function loadMoFile(d:haxe.io.Bytes) {
var r = new MoReader(d);
texts = r.parse();
r = null;
}
public function new()
{
}
/**
* parse all the project source code and generate the *.pot file
*/
macro public static function parse(codePath:String, potFilePath:String) {
Sys.println(codePath);
var path = codePath;
var data = new Map<String,{path:String}>();
explore(path, data);
writePotFile(data,potFilePath);
return macro { }; //returns an empty expression
}
#if macro
/**
* Generates the POT (PO template) file which contains all the keys to translate
*/
static function writePotFile(data:Map<String,{path:String}>, potFilePath:String ) {
var pot = new StringBuf();
var d = null;
for(k in data.keys()) {
d = data.get(k);
pot.add( "#"+d.path+"\n" );
pot.add( "msgid \""+k+"\"\n" );
pot.add( "msgstr \"\"\n\n" );
}
sys.io.File.saveContent(potFilePath, pot.toString());
}
/**
* function recursive qui analyse les dossiers/fichiers
* @param folder
* @param pot
*/
static function explore(folder:String, data) {
Sys.println("parsing "+folder);
for(f in sys.FileSystem.readDirectory(folder) ) {
if(f.substr(f.length - 3) == ".hx") {
var c = sys.io.File.getContent(folder+"/"+f);
var reg = ~/t\._\("([^"]+)"\)/i; // match t._("truc"). Le point et la parenthese sont escapés.
for( line in c.split("\n")) {
if(line == "") continue;
var r = reg.match(line);
if(r) {
data.set(reg.matched(1),{path:folder+"/"+f});
}
}
}else if( sys.FileSystem.isDirectory(folder+"/"+f) ) {
explore(folder+"/"+f,data);
}
}
}
#end
}
/**
* GNU GetText MO file reader
*/
class MoReader
{
private var original_table_offset:UInt;
private var translated_table_offset:UInt;
private var hash_num_entries:UInt;
private var hash_offset:UInt;
private var data:haxe.io.BytesInput;
static var MAGIC:UInt = 0x950412DE;
static var MAGIC2:UInt = 0xDE120495;
public function new(data:haxe.io.Bytes):Void
{
this.data = new haxe.io.BytesInput(data);
}
public function parse():Map<String,String>
{
var d = data;
var header : UInt = d.readInt32();
if(header != MAGIC && header != MAGIC2) {
throw "Bad MO file header : " + header;
}
var revision:UInt = d.readInt32();
if (revision > 1){
throw "Bad MO file format revision : "+revision;
}
var num_strings:UInt = d.readInt32();
original_table_offset= d.readInt32();
translated_table_offset = d.readInt32();
hash_num_entries= d.readInt32();
hash_offset= d.readInt32();
var texts = new Map();
for (i in 1...num_strings)
{
texts.set(getOriginalString(i), getTranslatedString(i));
}
return texts;
}
function getTranslatedString(index:Int):String
{
return getString(translated_table_offset + 8 * index );
}
function getOriginalString(index:Int):String
{
return getString(original_table_offset + 8 * index );
}
function getString(offset:UInt):String
{
data.position = offset;
var length :UInt = data.readInt32();
var pos :UInt = data.readInt32();
data.position = pos;
return data.readString(length);
}
}
+21
View File
@@ -0,0 +1,21 @@
package sugoi.i18n.translator;
/**
* Text translation interface
*
* @author fbarbut
*/
interface ITranslator{
/**
* Get translated text
*
* @param t Key to translate
* @param ?params Params to inject in string
*/
public function _( t : String, ?params : Dynamic ):String;
public function getStrings():Map<String,String>;
}
+45
View File
@@ -0,0 +1,45 @@
package sugoi.i18n.translator;
/**
* Simple Map<String,String> based Translator
*
* @author fbarbut
*/
class TMap implements ITranslator{
var texts : Map<String,String> = null;
var lang : String;
public function new(arr: Map<String,String>, lang:String) {
this.lang = lang;
texts = arr;
}
public function _(key:String, ?data:Dynamic):String {
if (key == null) throw "key is null";
var str = texts.get(key);
if(str == null) {
//App.log("key \""+key+"\" not found in "+texts);
str = key;
}
if(data!=null){
//var list = str.split("::");
for (k in Reflect.fields(data)){
str = StringTools.replace(str, "::" + k.substr(1) + "::", Reflect.field(data, k));
}
}
return str;
}
public function getStrings(){
return texts;
}
}

Some files were not shown because too many files have changed in this diff Show More