Compare commits

...
72 Commits
Author SHA1 Message Date
pvincent fc5e1cc4d2 tagged as 1.14.2 2022-12-10 12:55:47 +04:00
pvincent 5db8f5f1f3 mentions légales 2022-12-10 12:55:23 +04:00
pvincent 0c45dffc1e tagged as 1.14.1 2022-11-29 10:52:35 +04:00
pvincent 5bc2ad1517 view misses div class row 2022-11-29 10:52:17 +04:00
pvincent a6eed18ac1 tagged as 1.14.0 2022-11-29 10:29:24 +04:00
pvincent 67bbb02a2f footer reuse 2022-11-29 10:29:03 +04:00
pvincent 8fe3153c10 tagged as 1.13.6 2022-11-25 09:55:46 +04:00
pvincent de0f10425b Main redirect if group 1 exists 2022-11-25 09:55:38 +04:00
pvincent 4bd7c3d174 tagged as 1.13.5 2022-11-23 11:05:10 +04:00
pvincent ad1ae04624 logo france relance 2022-11-23 11:04:54 +04:00
pvincent dad6babe97 tagged as 1.13.4 2022-11-22 12:00:13 +04:00
pvincent 240c63d9fd wiki update 2022-11-22 12:00:02 +04:00
pvincent d6675758ff tagged as 1.13.3 2022-11-21 22:54:40 +04:00
pvincent 61ce852dbc package json fixed 2022-11-21 22:54:30 +04:00
pvincent 789a4eb7ec tagged as 1.13.2 2022-11-21 22:32:06 +04:00
pvincent 2b0f3557fc no more www/file in git 2022-11-21 22:31:58 +04:00
pvincent 10688b9892 tagged as 1.13.1 2022-11-21 22:22:36 +04:00
pvincent c86eafa130 ok 2022-11-21 22:22:27 +04:00
pvincent b2b7e88e3c by default /group/1 2022-11-21 22:21:56 +04:00
pvincent c078d7cf16 tagged as 1.12.0 2022-11-16 09:11:06 +04:00
pvincent f2c6c05a00 bandeau_region via config.xml 2022-11-16 09:09:36 +04:00
pvincent c163807c96 tagged as 1.11.9 2022-08-24 10:04:36 +04:00
pvincent 5cbc81a07a file/.htaccess required 2022-08-24 10:04:25 +04:00
pvincent 22a78d1fef tagged as 1.11.8 2022-08-23 22:05:07 +04:00
pvincent 6eb02e94ce makefile 2022-08-23 22:04:57 +04:00
pvincent 5116f6b60d tagged as 1.11.7 2022-08-23 21:56:32 +04:00
pvincent 9edfa4f40e package.json 2022-08-23 21:54:01 +04:00
pvincent 4b9fd45d92 tagged as 1.11.6 2022-08-23 21:49:31 +04:00
pvincent 42b07fea6a lang owend by www-data 2022-08-23 21:49:19 +04:00
pvincent 04a526e728 tagged as 1.11.5 2022-08-22 10:59:40 +04:00
pvincent e28efeab7e re-enable config.xml 2022-08-22 10:59:30 +04:00
pvincent bc939a5bdb tagged as 1.11.4 2022-08-22 10:48:54 +04:00
pvincent 4a052ea707 no more config.xml first time 2022-08-22 10:48:41 +04:00
pvincent f139fcfefb Update 'README.md' 2021-12-03 20:18:54 +04:00
pvincent 347a81dc66 Update 'README.md' 2021-08-31 21:34:26 +04:00
pvincent d142fc1eac Update 'README.md' 2021-08-31 21:22:59 +04:00
pvincent e039ed4fe5 better readme 2021-08-31 20:04:52 +04:00
pvincent 44d2248ba5 screenshot 2021-08-31 19:54:35 +04:00
pvincent 34e7527442 screenshot 2021-08-31 19:15:28 +04:00
pvincent ecffe9bb4e tagged as 1.11.3 2021-08-30 23:22:46 +04:00
pvincent 6e7382633f css footer margin 0 2021-08-30 23:22:19 +04:00
pvincent bb50c92230 tagged as 1.11.2 2021-08-30 22:49:49 +04:00
pvincent 98dffaebed fix config missing key 2021-08-30 22:49:34 +04:00
pvincent ca6aceffbc tagged as 1.11.1 2021-08-30 21:48:13 +04:00
pvincent c4ea83cca9 better README 2021-08-30 21:47:25 +04:00
pvincent 6c4aa6d9ea tagged as 1.11.0 2021-08-30 21:10:58 +04:00
pvincent cdfe52715f simpler config + new logo 2021-08-30 21:10:33 +04:00
pvincent 8bd119a818 maintain 2021-08-30 18:30:00 +04:00
pvincent 0854d556fe cagettepéi 2021-08-30 17:44:14 +04:00
pvincent 278f69e7d9 tagged as 1.10.2 2021-08-29 21:31:33 +04:00
pvincent 8699728593 version cagettepei 2021-08-29 21:31:16 +04:00
pvincent b14688417d tagged as 1.10.1 2021-08-28 16:41:12 +04:00
pvincent ea4da3c270 reply-to working 2021-08-28 16:40:37 +04:00
pvincent ef7b09454c cagettepéi 2021-08-28 11:05:37 +04:00
pvincent c23e3533d8 reply-to missing yet 2021-08-28 01:16:54 +04:00
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
137 changed files with 12156 additions and 9698 deletions
+4 -5
View File
@@ -1,7 +1,7 @@
*.n
*.php
config.xml
/package-lock.json
package-lock.json
www/plugin/*
tmp/*
*node_modules*
@@ -25,15 +25,14 @@ lang/es/tmp/*.n
www/lang/*.mo
www/file/*
www/js/app.js
www/js/libs.js
www/file/*
www/css/.sass-cache
www/css/*.css
!www/css/bootstrap.min.css
/package-lock.json
config-dev.xml
config-prod.xml
+1 -1
View File
@@ -1 +1 @@
1.7.1
1.14.2
+8 -6
View File
@@ -20,7 +20,7 @@ install_dev:
#set config file to debug=1
#@sed -e 's?debug=.*?debug="1"?g' --in-place config.xml
#install haxe dependencies in .haxelib
haxelib newrepo
haxelib newrepo
haxelib -always install cagette.hxml
haxelib -always install cagetteJs.hxml
#template tools
@@ -41,13 +41,13 @@ install_dev:
#compile backend to Neko
compile:
@if [ $(PLUGINS) = 1 ]; then \
echo "compile Cagette.net with plugins"; \
echo "compile CagettePéi plugins"; \
haxe cagetteAllPlugins.hxml; \
else \
echo "compile CagettePéi core"; \
haxe cagette.hxml; \
fi
#compile SASS files to CSS
css:
@@ -64,16 +64,18 @@ frontend:
chown www-data:www-data tmp
#update POT file from source
i18n:
i18n:
haxe potGeneration.hxml
#compile templates in each language, required for production env.
templates:
templates:
mkdir -p lang/master/tmp
haxe cagette.hxml -D i18n_generation
@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 lang
ctemplates:
(cd lang/$(LANG)/tpl; temploc2 -macros macros.mtt -output ../tmp/ *.mtt */*.mtt */*/*.mtt)
+66 -48
View File
@@ -1,67 +1,82 @@
CAGETTE PEI
===========
Fourche du logiciel [Cagette.net](https://github.com/CagetteNet) à partir de la version 0.9.2 Novembre 2018
Cagette Péi est un logiciel libre qui favorise les circuits courts.
Il a été adapté pour les besoins des AMAPs sur le marché réunionnais.
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.
> <french>
> Fourche du logiciel [Cagette.net](https://github.com/CagetteNet) à partir de la version 0.9.2 Novembre 2018. Certaines portions de code n'étant plus disponibles en téléchargement à partir de cette date, le logiciel a été repris, adapté puis réactualisé. Pour plus d'information, contact@artcode.re
> </french>
## INSTALL FROM SCRATCH (debian-buster)
![Screenshot](www/img/screenshot.png)
* apt install -y curl
* curl -sL 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
## PREREQUISITE MARIADB
```bash
apt install -y 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
```
## INSTALL FROM SCRATCH
Rk: Works on any Debian (>=Buster) compatible server!
```
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 sendemail libio-socket-ssl-perl libnet-ssleay-perl
cd /var/www
git clone https://git.artcode.re/cagetters/cagettepei.git
cd cagettepei
make install ENV=dev
vim /etc/apache2/sites-available/cagettepei.conf
```
```
<VirtualHost *:80>
ServerName cagettepei
DirectoryIndex index.n
DocumentRoot /var/www/cagettepei/www/
DocumentRoot /var/www/cagettepei/www
DirectoryIndex index.n
CustomLog ${APACHE_LOG_DIR}/cagettepei/access.log combined
</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
```bash
sed -i '/<Directory \/var\/www\/>/,/<\/Directory>/ s/AllowOverride None/AllowOverride All/' /etc/apache2/apache2.conf
a2dissite 000-default.conf
a2ensite cagettepei.conf
a2enmod rewrite
mkdir -p /var/log/apache2/cagettepei
systemctl restart apache2
```
## CONFIGURATION
* cd /var/www/cagettepei
* vim config.xml
```bash
cd /var/www/cagettepei
vim config.xml
```
```
<config
host="<DOMAIN.TLD>"
key="<SECRET_PHRASE>"
name = "CagettePéi"
host = "<DOMAIN.TLD>"
database = "mysql://cagettepei:cagettepei@localhost/cagettepei"
key = "<SECRET_PHRASE>"
default_email = "<EMAIL>"
webmaster_email = "<EMAIL>"
database="mysql://cagettepei:cagettepei@localhost/cagettepei"
smtp_host="localhost"
smtp_port="25"
smtp_user=""
smtp_pass=""
debug = "0"
sqllog = "0"
maintain = "0"
lang="fr"
langs="fr"
langnames="Français"
maintain="0"
debug="0"
sqllog="0"
cache="1"
cachetpl="0"
cache = "1"
cachetpl = "0"
/>
```
@@ -70,16 +85,19 @@ 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
```
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
```
echo "update User set pass=MD5('${SECRET}cagettepei') where id=1" | mysql -h localhost -u cagettepei -pcagettepei cagettepei
```
SYSTEMD-TIMERS
--------------
@@ -96,7 +114,7 @@ SYSTEMD-TIMERS
SELECTOR=$1
for i in /var/www/cagettepei-* ; do
for i in /var/www/cagettepei* ; do
if [[ -d $i ]]; then
cd $i/www
echo "cron-$SELECTOR in: $i"
-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
+2 -1
View File
@@ -21,9 +21,10 @@
-lib tink_streams:0.2.1
-lib tink_syntaxhub:0.4.3
-lib tink_typecrawler:0.7.0
-lib thx.text:0.2.1
## GIT
-lib sugoi:git:https://git.artcode.re/cagetters/sugoi.git
# -lib sugoi:git:https://git.artcode.re/cagetters/sugoi.git
-lib record-macros:git:https://git.artcode.re/cagetters/record-macros.git
-lib dbadmin:git:https://git.artcode.re/cagetters/dbadmin.git
+3 -8
View File
@@ -1,19 +1,14 @@
<config
database = "mysql://<USER>:<PASSWORD>@<HOST>/<DBNAME>"
host = "<URL without http>"
name = "<HTML head title>"
default_email = "<EMAIL>"
webmaster_email = "<EMAIL>"
key = "<SECRET_PHRASE>"
maintain="0"
cache="1"
cachetpl="0"
debug="0"
sqllog="0"
maintain="0"
lang="fr"
langs="fr"
langnames="Français"
cache="1"
cachetpl="0"
/>
+48 -52
View File
@@ -1,4 +1,5 @@
package react.user;
import react.ReactComponent;
import react.ReactMacro.jsx;
import Common;
@@ -19,23 +20,21 @@ typedef LoginBoxState = {
* Login Box
* @author fbarbut
*/
class LoginBox extends react.ReactComponentOfPropsAndState<LoginBoxProps,LoginBoxState>
{
class LoginBox extends react.ReactComponentOfPropsAndState<LoginBoxProps, LoginBoxState> {
public function new(props:LoginBoxProps) {
if (props.redirectUrl == null)
props.redirectUrl = "/";
if (props.message == "")
props.message = null;
super(props);
this.state = {email: "", password: "", error: null};
}
public function new(props:LoginBoxProps)
{
if (props.redirectUrl == null) props.redirectUrl = "/";
if (props.message == "") props.message = null;
super(props);
this.state = {email:"", password:"", error:null};
function setError(err:String) {
this.setState(cast {error: err});
}
function setError(err:String){
this.setState(cast {error:err});
}
override public function render(){
override public function render() {
return jsx('<div onKeyPress=$onKeyPress>
<$Error error="${state.error}" />
<$Message message="${props.message}" />
@@ -62,78 +61,75 @@ class LoginBox extends react.ReactComponentOfPropsAndState<LoginBoxProps,LoginBo
<!--
<hr/>
<p className="text-center">
<b>C\'est votre première visite sur Cagette.net ?</b>&nbsp;&nbsp;
<b>C\'est votre première visite sur CagettePéi ?</b>&nbsp;&nbsp;
<a onClick={registerBox} className="btn btn-default"><span className="glyphicon glyphicon-chevron-right"></span> S\'inscrire</a>
</p>
-->
</div>');
}
/**
* @doc https://facebook.github.io/react/docs/forms.html
*/
function onChange(e:js.html.Event){
function onChange(e:js.html.Event) {
e.preventDefault();
var name :String = untyped e.target.name;
var value :String = untyped /*(e.target.value == "") ? null :*/ e.target.value;
var name:String = untyped e.target.name;
var value:String = untyped /*(e.target.value == "") ? null :*/ e.target.value;
Reflect.setField(state, name, value);
this.setState(this.state);
}
/**
* displays a registerBox
*/
public function registerBox(){
public function registerBox() {
var body = js.Browser.document.querySelector('#myModal .modal-body');
ReactDOM.unmountComponentAtNode( body );
ReactDOM.unmountComponentAtNode(body);
js.Browser.document.querySelector("#myModal .modal-title").innerHTML = "Inscription";
ReactDOM.render(jsx('<$RegisterBox redirectUrl="${props.redirectUrl}" phoneRequired="${props.phoneRequired}"/>'), body );
ReactDOM.render(jsx('<$RegisterBox redirectUrl="${props.redirectUrl}" phoneRequired="${props.phoneRequired}"/>'), body);
}
public function submit(?e:js.html.Event){
if (state.email == ""){
public function submit(?e:js.html.Event) {
if (state.email == "") {
setError("Veuillez saisir votre email");
return;
}
if (state.password == ""){
if (state.password == "") {
setError("Veuillez saisir votre mot de passe");
return;
}
//lock button
var el: js.html.Element = null;
if(e!=null){
// lock button
var el:js.html.Element = null;
if (e != null) {
el = cast e.target;
el.classList.add("disabled");
}
var req = new haxe.Http("/api/user/login");
req.addParameter("email", state.email);
req.addParameter("password", state.password);
req.addParameter("redirecturl", props.redirectUrl);
req.onData = req.onError = function(d){
req.onData = req.onError = function(d) {
var d = req.responseData;
if(e!=null) el.classList.remove("disabled");
if (e != null)
el.classList.remove("disabled");
var d = haxe.Json.parse(d);
if (Reflect.hasField(d, "error")) setError(d.error.message);
if (Reflect.hasField(d, "success")) js.Browser.window.location.href = props.redirectUrl;
if (Reflect.hasField(d, "error"))
setError(d.error.message);
if (Reflect.hasField(d, "success"))
js.Browser.window.location.href = props.redirectUrl;
}
req.request(true);
}
function onKeyPress(e:js.html.KeyboardEvent){
if(e.key=="Enter") submit();
function onKeyPress(e:js.html.KeyboardEvent) {
if (e.key == "Enter")
submit();
}
}
}
+1 -1
View File
@@ -1,7 +1,7 @@
::use 'admin/design.mtt'::
<h3>Console d'administration de Cagette.net</h3>
<h3>Console d'administration de CagettePéi</h3>
<p>
::hDate(now)::
</p>
+1 -1
View File
@@ -11,7 +11,7 @@
::end::
</title>
<!-- Cagette CSS -->
<!-- CagettePéi CSS -->
<link rel="stylesheet" type="text/css" href="/css/::getVariable('css')::/style.css"/>
<!--
<link rel="stylesheet" type="text/css" href="/css/::getVariable('css')::/shop.css"/>
+34 -171
View File
@@ -1,5 +1,5 @@
::use 'base.mtt'::
::if getParam('__redirect')!=null::
<!-- need to login to go to this page-->
<script>$(function(){
@@ -13,18 +13,14 @@
::if groupName!=null && noGroup!=true::
<h1>::groupName::</h1>
::else::
<div style="margin-bottom:22px;" >
<a href="https://git.artcode.re/cagetters/cagette" target="_blank">
<img src="/img/logo3.png" alt="::NAME::" />
</a>
</div>
<h1>::_("Cagette.net")::</h1>
::end::
</div>
<div class="col-md-4 accountBlock">
::if user != null::
<div class="col-md-4 accountBlock">
::if user != null::
<div class="dropdown pull-right text-right userName" style="margin-top:16px;">
<a class="" style="margin-right:16px;" href="/user/choose?show=1">
<span class="glyphicon glyphicon-chevron-left"></span>::_("Change group")::
</a>
@@ -33,46 +29,46 @@
<span class="glyphicon glyphicon-user"></span>
::user.getName()::
::if user.firstName2!=null::
::_("et"):: ::user.lastName2:: ::user.firstName2::
::_("et"):: ::user.lastName2:: ::user.firstName2::
::end::
</a>
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownMenu1">
<li><a href="/user/logout"><span class="glyphicon glyphicon-log-out"></span> ::_("Log out")::</a></li>
</ul>
</div>
</div>
::else::
<div class="pull-right text-right">
<a href="/user/login" class="btn btn-default btn-sm"><span class="glyphicon glyphicon-user"></span> ::_("Log in")::</a>
<a class="btn btn-default btn-sm" href="/group/map"><span class="glyphicon glyphicon-search"></span> ::_("Look for another group")::</a>
<a class="btn btn-default btn-sm" href="/group/map"><span class="glyphicon glyphicon-search"></span> ::_("Look for another group")::</a>
</div>
::end::
</div>
</div>
</div>
::if user != null && noGroup!=true::
<nav class="navbar navbar-default" ::cond user.getAmap()!=null::>
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#cagette-navbar">
<span class="sr-only">Toggle navigation</span>
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
</div>
</button>
</div>
<div class="collapse navbar-collapse" id="cagette-navbar">
<ul class="nav navbar-nav navbar-left">
<li ::attr class if(category=="home") 'active' ::><a href="/">::_("Home")::</a></li>
<li ::attr class if(category=="contract" ) 'active' ::><a href="/contract">::_("My account")::</a></li>
<li ::attr class if(category=="amap") 'active' ::><a href="/amap">::_("Farmers")::</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li id="member" ::attr class if(category=="members") 'active' ::>
<a href="/member" ::cond user.canAccessMembership()::>::_("Members")::</a></li>
@@ -85,13 +81,13 @@
<li ::cond user.isAdmin():: ::attr class if(category=="admin") 'active' ::>
<a href="/admin" >::_("Admin")::</a></li>
</ul>
</div>
</nav>
::end::
<!-- Notifications -->
<div class="row">
<div id="content" class="col-md-12">
@@ -102,166 +98,33 @@
::else::
<span class="glyphicon glyphicon-ok"></span>
::end::
::raw notification.text::
::raw notification.text::
</div>
::end::
</div>
</div>
</div>
<!-- extra notification block for plugins -->
::if extraNotifBlock!=null::
::raw extraNotifBlock::
::end::
<div class="row">
::raw __content__::
</div>
</div>
<!--<div class="row">
<div class="col-md-12 alert-danger text-center" style="border-radius: 8px;margin:8px 0;padding:8px 0;">
<span class="glyphicon glyphicon-cog"></span>
Attention : Cagette.net sera indisponible le <b>mardi 7 août de 9:00 à 11:00</b>.
Attention : CagettePéi sera indisponible le <b>mardi 7 août de 9:00 à 11:00</b>.
<br/>
Nous déménageons sur un nouveau serveur pour plus de rapidité et de fiabilité.
</div>
</div>-->
<div class="row" id="footer">
<div class="col-md-9" >
<a href="https://git.artcode.re/cagetters/cagette" target="_blank">
<img src="/img/logo.png" alt="::_("Cagette.net")::"/>
</a>
</div>
::use 'footer.mtt'::
::end::
<div class="col-md-3" >
::_("HELP")::
<ul>
<li>
<a href="http://www.cagette.net/wiki" target="_blank">::_("Documentation")::</a>
</li>
<li>
<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>
</ul>
<!--<p style="position: absolute;display: block;top: 96px;width: 700px;">
<b>::raw _("Vous êtes producteur ?")::</b>
::raw _("Découvrez nos formations gratuites pour utiliser <b>Cagette Pro</b> ")::
&nbsp;&nbsp;<a href="http://alilo.fr/formations-producteurs" target="_blank" class="btn btn-xs btn-info">C'est parti !</a>
</p>-->
</div>
<!--
<div class="col-md-3" >
::_("FOLLOW US")::
<ul class="cagsocialmedia">
<li class="cagfb">
<a title="Facebook" href="https://www.facebook.com/cagette" target="_blank"> <i class="fa fa-facebook"></i></a>
</li>
<li class="cagtwitter">
<a title="Twitter" href="https://twitter.com/Cagettenet" target="_blank"> <i class="fa fa-twitter"></i></a>
</li>
<li class="cagyoutube">
<a title="Youtube" href="https://www.youtube.com/channel/UC3cvGxAUrbN9oSZmr1oZEaw" target="_blank"> <i class="fa fa-youtube"></i></a>
</li>
<li style="background-color:#333;">
<a title="Github" href="https://github.com/bablukid/cagette" target="_blank"> <i class="fa fa-github"></i></a>
</li>
</ul>
</div>
-->
<!--
<div class="col-md-3">
::_("CONTACT US")::
<ul>
<li>
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
<input type="hidden" name="cmd" value="_s-xclick"/>
<input type="hidden" name="hosted_button_id" value="S9KT7FQS7P622"/>
<input type="image" src="https://www.paypalobjects.com/fr_FR/FR/i/btn/btn_donate_LG.gif" border="0" name="submit" alt="PayPal, le réflexe sécurité pour payer en ligne"/>
<img alt="" border="0" src="https://www.paypalobjects.com/fr_FR/i/scr/pixel.gif" width="1" height="1"/>
</form>
</li>
</ul>
</div>
-->
</div>
<div class="row" id="subfooter">
<!--
::raw _("Cagette.net is made by <a href='https://www.alilo.fr' target='_blank'><img src='/img/alilo-trans-black.png' /></a>")::<br/>
-->
<span>version ::VERSION::</span>
</div>
<div class="row">
<div class="col-md-12">
<!-- SQL LOG -->
<table class="sqlLog table table-bordered" ::cond sqlLog::>
::foreach r sqlLog::
<tr ::attr class if(r.bad) 'badSql'::>
<td>::r.t::ms</td>
<td>::r.length::</td>
<td><a href="#" onclick="javascript:alert('::r.explain::');return false;">Exp</a></td>
<td><a href="#" onclick="javascript:alert('::r.stack::');return false;">Stack</a></td>
<td>::r.sql::</td>
</tr>
::end::
</table>
</div>
</div>
::if (false && user!=null && user.isAmapManager())::
<script>
// Include the UserVoice JavaScript SDK (only needed once on a page)
UserVoice=window.UserVoice||[];(function(){var uv=document.createElement('script');uv.type='text/javascript';uv.async=true;uv.src='//widget.uservoice.com/sayZ1eIZLfSZUa3IwslHCw.js';var s=document.getElementsByTagName('script')[0];s.parentNode.insertBefore(uv,s)})();
//
// UserVoice Javascript SDK developer documentation:
// https://www.uservoice.com/o/javascript-sdk
//
// Set colors
UserVoice.push(['set', {
accent_color: '#6aba2e',
trigger_color: 'white',
trigger_background_color: '#e2753a'
}]);
// Identify the user and pass traits
// To enable, replace sample data with actual user traits and uncomment the line
UserVoice.push(['identify', {
//email: 'john.doe@example.com', // Users email address
//name: 'John Doe', // Users real name
//created_at: 1364406966, // Unix timestamp for the date the user signed up
//id: 123, // Optional: Unique id of the user (if set, this should not change)
//type: 'Owner', // Optional: segment your users by type
//account: {
// id: 123, // Optional: associate multiple users with a single account
// name: 'Acme, Co.', // Account name
// created_at: 1364406966, // Unix timestamp for the date the account was created
// monthly_rate: 9.99, // Decimal; monthly rate of the account
// ltv: 1495.00, // Decimal; lifetime value of the account
// plan: 'Enhanced' // Plan name for the account
//}
}]);
// Add default trigger to the bottom-right corner of the window:
UserVoice.push(['addTrigger', { mode: 'smartvote', trigger_position: 'bottom-right' }]);
// Or, use your own custom trigger:
//UserVoice.push(['addTrigger', '#id', { mode: 'smartvote' }]);
// Autoprompt for Satisfaction and SmartVote (only displayed under certain conditions)
UserVoice.push(['autoprompt', {}]);
</script>
::end::
::end::
+56
View File
@@ -0,0 +1,56 @@
<div class="row" id="footer">
<div class="col-md-2" >
<img width="120" style="border: white 5px solid;" src="/img/logo-mini.jpg" alt="::_("Cagette.net")::" title="Logo ::_("Cagette.net")::"/>
</div>
<div class="col-md-7" style="padding-top:1em">
Cagette Péi est un logiciel libre qui favorise les circuits courts.<br/>
Il a été adapté pour les besoins des AMAPs sur le marché réunionnais.<br/>
</div>
<div class="col-md-3" >
::_("HELP")::
<ul>
<li>
<a href="https://wiki.cagette.net/doku.php" target="_blank">::_("Documentation")::</a>
</li>
<li>
<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>
</ul>
</div>
</div>
<div class="row" id="subfooter">
<p><a href="/mentions">MENTIONS LÉGALES</a></p>
<p>
version cagettepei-<a target="_blank" href="https://git.artcode.re/cagetters/cagettepei/src/tag/::VERSION::">::VERSION::</a>
</p>
::if BANDEAU_REGION::
<img src='/img/bandeau_region_france_relance.png' alt='bandeau Région Réunion' width='640'></img>
::end::
</div>
<div class="row">
<div class="col-md-12">
<!-- SQL LOG -->
<table class="sqlLog table table-bordered" ::cond sqlLog::>
::foreach r sqlLog::
<tr ::attr class if(r.bad) 'badSql'::>
<td>::r.t::ms</td>
<td>::r.length::</td>
<td><a href="#" onclick="javascript:alert('::r.explain::');return false;">Exp</a></td>
<td><a href="#" onclick="javascript:alert('::r.stack::');return false;">Stack</a></td>
<td>::r.sql::</td>
</tr>
::end::
</table>
</div>
</div>
+49 -52
View File
@@ -1,6 +1,6 @@
::use 'base.mtt'::
<div class="row header" style="margin-top:10px;">
<div class="col-md-8 col-md-offset-2" style="margin-top:12px;">
<!-- Notifications -->
@@ -13,19 +13,19 @@
::else::
<span class="glyphicon glyphicon-ok"></span>
::end::
::raw notification.text::
::raw notification.text::
</div>
::end::
</div>
</div>
</div>
<div class="article">
<div class="row">
<div class="col-md-4">
<p>
::if group._image!=null::
::if group.extUrl!=null && group.extUrl!=""::
@@ -36,21 +36,21 @@
::end::
</p>
<p>
::if isMember::
<!-- connected and member of the group-->
<a href="/user/choose?amap=::group.id::" class="btn btn-primary">
<span class="glyphicon glyphicon-chevron-right"></span>
::_("Go to my account")::
</a>
::else::
<h4>Inscriptions :</h4>
::switch group._regOption::
::case::
<p>
::_("Contact the person in charge in order to subscribe to this group and get to know membership conditions.")::
@@ -71,7 +71,7 @@
::_("Subscription on waiting list")::
</a>
</p>
::case::
<p>
<a href="/group/register/::group.id::" class="btn btn-primary">
@@ -84,12 +84,12 @@
::_("This group is full and do not accept any more subscription.")::
</p>
::end::
::end::
</p>
::if user==null::
<hr />
<h4>::_("Already member? Log in:")::</h4>
@@ -97,26 +97,26 @@
<a href="#" onclick="_.loginBox('/group/::group.id::')" class="btn btn-default btn-xs"><span class="glyphicon glyphicon-user"></span> ::_("Log in")::</a>
</div>
::end::
</div>
<div class="col-md-8">
<h2>
::group.name::
</h2>
<p>
::if group.txtIntro!=null && group.txtIntro!=""::
::raw nl2br(group.txtIntro)::
::end::
</p>
<p>
<h3> ::_("Next deliveries")::</h3>
::foreach d group.getDeliveries(4)::
<div style="display:inline-block;margin-right:8px;vertical-align:top;">
::set s = getDate( d.date )::
::if isToday(d.date)::
@@ -125,15 +125,15 @@
$$date(::s.dow::,::s.d::,::s.m::,::s.y::,::d._place::)
::end::
</div>
::end::
</p>
<hr/>
<p ::cond group._contact!=null::>
<h3>::_("Contact")::</h3>
<i class="icon-user"></i>
<b>::group._contact.firstName:: ::group._contact.lastName::</b><br/>
<span ::cond group._contact.email!=null::>
@@ -144,52 +144,49 @@
<span class="glyphicon glyphicon-phone-alt"></span> ::group._contact.phone::<br/>
</span>
</p>
<hr/>
<p>
<h3>::_("Suppliers and products")::</h3>
<p>
::_("Non-complete extract of available products:")::
</p>
::foreach c contracts::
<p>
<b>::c.name::</b><br/>
::c._vendor.name::, $$nullSafe(::c._vendor.zipCode::) $$nullSafe(::c._vendor.city::)
<div class="row">
::foreach p c.getProductsPreview(6)::
<div class="col-md-2" data-toggle="tooltip" data-placement="top" title="::p.name::" >
<div style="background-image:url('::p.getImage()::')" class="productImg"/>
<div class="col-md-2" data-toggle="tooltip" data-placement="top" title="::p.name::" >
<div style="background-image:url('::p.getImage()::')" class="productImg"/>
</div>
::end::
::end::
</div>
</p>
</p>
::end::
</p>
</div>
</div>
<div class="col-md-4">
</div>
</div>
</div>
<div class="col-md-12">
<div id="footer">
<a href="::_("http://www.cagette.net")::">::_("Cagette.net, the free software of food direct-selling")::</a>.
</div>
</div>
</div>
::use 'footer.mtt'::
::end::
::end::
+2 -2
View File
@@ -1,4 +1,4 @@
::use 'design.mtt'::
<!--::use 'design.mtt'::-->
<div class="text-center" style="height:400px;width:100%;">
<h1>
<span class="glyphicon glyphicon-cog"></span>
@@ -14,4 +14,4 @@
</div>
::end::
<!--::end::-->
@@ -0,0 +1,62 @@
::use 'design.mtt'::
<div class="col-md-9">
<div class="article">
<h4>Mentions Légales</h4>
<table class="table table-bordered table-condensed">
<tbody><tr>
<th>Champ</th>
<th>Valeur</th>
</tr>
<tr>
<td>Adresse de courrier électronique</td>
<td>contact@mangue-et-solidaire.re</td>
</tr>
<tr>
<td>Numéro Siren</td>
<td>879178739</td>
</tr>
<tr>
<td>Numéro Siret</td>
<td>87917873900022</td>
</tr>
<tr>
<td>Code NAF / APE</td>
<td>4711B</td>
</tr>
<tr>
<td>Forme juridique et capital</td>
<td>Association Loi 1901</td>
</tr>
<tr>
<td>Responsable éditorial</td>
<td>Yann Jacob</td>
</tr>
<tr>
<td>Hébergeur</td>
<td><a href="www.artcode.re">Artcode</a></td>
</tr>
</tbody>
</table>
</div>
</div>
::end::
+1 -1
View File
@@ -29,7 +29,7 @@
<div class="col-md-9">
<div class="article">
<h3>::_("Send an e-mail")::</h3>
::raw form::
</div>
</div>
-6188
View File
File diff suppressed because it is too large Load Diff
+1 -51
View File
@@ -1,51 +1 @@
{
"name": "CagettePei",
"description": "le logiciel libre du circuit court",
"license": "AGPL-3.0",
"dependencies": {
"bootstrap": "^3.3.4",
"eonasdan-bootstrap-datetimepicker": "^4.7.14",
"geolib": "^2.0.24",
"haxe-modular": "^0.7.0",
"jquery": "^2.2.4",
"leaflet": "^1.3.1",
"react": "^15.6.1",
"react-bootstrap-typeahead": "1.4.2",
"react-datetime": "^2.9.0",
"react-dom": "^15.6.1",
"react-leaflet": "^1.8.0",
"react-places-autocomplete": "^6.0.2",
"react-router": "^4.2.0",
"react-router-dom": "^4.2.2"
},
"devDependencies": {
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"babelify": "^7.3.0",
"browserify": "^14.4.0",
"cross-env": "^5.1.1",
"node-sass": "^4.5.3",
"npm-run-all": "^4.1.2",
"uglify-js": "^3.1.9",
"watchify": "^3.9.0"
},
"scripts": {
"build": "npm-run-all --parallel build:*",
"build:js": "haxe cagetteJs.hxml",
"build:sass": "node-sass www/css -o www/css",
"watch:sass": "npm run build:sass -- --watch",
"build2": "babel src -d lib",
"libs:dev": "browserify js/libs/libs.js -o www/js/libs.js -d",
"libs:prod": "cross-env NODE_ENV=production browserify js/libs/libs.js | uglifyjs -c -m > www/js/libs.js",
"test": "haxe testsJS.hxml && node js/test/tests.js",
"prepush": "npm test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/bablukid/cagette.git"
},
"bugs": {
"url": "https://github.com/bablukid/cagette/issues"
},
"version": "1.7.1"
}
{"name": "cagette-pei", "description": "le logiciel libre du circuit court", "license": "AGPL-3.0", "dependencies": {"bootstrap": "^3.3.4", "eonasdan-bootstrap-datetimepicker": "^4.7.14", "geolib": "^2.0.24", "haxe-modular": "^0.7.0", "jquery": "^2.2.4", "leaflet": "^1.3.1", "react": "^15.6.1", "react-bootstrap-typeahead": "1.4.2", "react-datetime": "^2.9.0", "react-dom": "^15.6.1", "react-leaflet": "^1.8.0", "react-places-autocomplete": "^6.0.2", "react-router": "^4.2.0", "react-router-dom": "^4.2.2"}, "devDependencies": {"babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", "babelify": "^7.3.0", "browserify": "^14.4.0", "cross-env": "^5.1.1", "node-sass": "^4.5.3", "npm-run-all": "^4.1.2", "uglify-js": "^3.1.9", "watchify": "^3.9.0"}, "scripts": {"build": "npm-run-all --parallel build:*", "build:js": "haxe cagetteJs.hxml", "build:sass": "node-sass www/css -o www/css", "watch:sass": "npm run build:sass -- --watch", "build2": "babel src -d lib", "libs:dev": "browserify js/libs/libs.js -o www/js/libs.js -d", "libs:prod": "cross-env NODE_ENV=production browserify js/libs/libs.js | uglifyjs -c -m > www/js/libs.js", "test": "haxe testsJS.hxml && node js/test/tests.js", "prepush": "npm test"}, "repository": {"type": "git", "url": "git+https://github.com/bablukid/cagette.git"}, "bugs": {"url": "https://github.com/bablukid/cagette/issues"}, "version": "1.14.2"}
+129 -127
View File
@@ -1,110 +1,106 @@
import db.User;
import thx.semver.Version;
import Common;
class App extends sugoi.BaseApp {
public static var current : App = null;
public static var t : sugoi.i18n.translator.ITranslator;
class App extends sugoi.BaseApp {
public static var current:App = null;
public static var t:sugoi.i18n.translator.ITranslator;
public static var config = sugoi.BaseApp.config;
public var eventDispatcher :hxevents.Dispatcher<Event>;
public var plugins : Array<sugoi.plugin.IPlugIn>;
/**
* Version management
* @doc https://github.com/fponticelli/thx.semver
*/
//public static var VERSION = ([0,9,2] : Version).withPre("july");
//public static var VERSION = ([0,9,2] : Version).withPre(MyMacros.getGitShortSHA(), MyMacros.getGitCommitDate());
public static var VERSION = "cagette-péi " + MyMacros.getGitCommitDate();
public function new(){
public var eventDispatcher:hxevents.Dispatcher<Event>;
public var plugins:Array<sugoi.plugin.IPlugIn>;
public static var VERSION = "cagette-pei"; // empty yet, cf.BaseApp.loadConfig
public function new() {
super();
}
public static function main() {
App.t = sugoi.form.Form.translator = new sugoi.i18n.translator.TMap(getTranslationArray(), "fr");
sugoi.BaseApp.main();
}
/**
* Init plugins and event dispatcher just before launching the app
*/
override public function mainLoop() {
eventDispatcher = new hxevents.Dispatcher<Event>();
plugins = [];
//internal plugins
// internal plugins
plugins.push(new plugin.Tutorial());
//optionnal plugins
// optionnal plugins
#if plugins
plugins.push( new hosted.HostedPlugIn() );
plugins.push( new pro.ProPlugIn() );
plugins.push( new connector.ConnectorPlugIn() );
plugins.push( new pro.LemonwayEC() );
plugins.push( new who.WhoPlugIn() );
plugins.push(new hosted.HostedPlugIn());
plugins.push(new pro.ProPlugIn());
plugins.push(new connector.ConnectorPlugIn());
plugins.push(new pro.LemonwayEC());
plugins.push(new who.WhoPlugIn());
#end
super.mainLoop();
}
public function getCurrentGroup(){
if (session == null) return null;
if (session.data == null ) return null;
public function getCurrentGroup() {
if (session == null)
return null;
if (session.data == null)
return null;
var a = session.data.amapId;
if (a == null) {
return null;
}else {
return db.Amap.manager.get(a,false);
} else {
return db.Amap.manager.get(a, false);
}
}
override function beforeDispatch() {
//send "current page" event
event( Page(this.uri) );
// send "current page" event
event(Page(this.uri));
super.beforeDispatch();
}
public function getPlugin(name:String):sugoi.plugin.IPlugIn {
for (p in plugins) {
if (p.getName() == name) return p;
if (p.getName() == name)
return p;
}
return null;
}
public static function log(t:Dynamic) {
if(App.config.DEBUG) {
neko.Web.logMessage(Std.string(t)); //write in Apache error log
#if weblog
Weblog.log(t); //write en Weblog console (https://lib.haxe.org/p/weblog/)
#end
}
public static function log(t:Dynamic, ?infos:haxe.PosInfos) {
var message:String = '[${infos.fileName}:${infos.lineNumber} ${infos.className}#${infos.methodName}] ';
message += Std.string(t);
// trace(message);
if (neko.Web.isModNeko)
neko.Web.logMessage(message);
else
Sys.println(message);
}
public function event(e:Event) {
if(e==null) return null;
if (e == null)
return null;
this.eventDispatcher.dispatch(e);
return e;
}
/**
* Translate DB objects fields in forms
*/
public static function getTranslationArray() {
//var t = sugoi.i18n.Locale.texts;
var out = new Map<String,String>();
//out.set("firstName", t._("First name") );
//out.set("lastName", t._("Last name"));
// var t = sugoi.i18n.Locale.texts;
var out = new Map<String, String>();
// out.set("firstName", t._("First name") );
// out.set("lastName", t._("Last name"));
out.set("firstName2", "Prénom du conjoint");
out.set("lastName2", "Nom du conjoint");
out.set("email2", "e-mail du conjoint");
//out.set("pass", t._("Password") );
//out.set("address1", t._("address") );
//out.set("address2", t._("address") );
// out.set("pass", t._("Password") );
// out.set("address1", t._("address") );
// out.set("address2", t._("address") );
out.set("zipCode", "code postal");
out.set("city", "commune");
out.set("phone", "téléphone");
@@ -123,24 +119,24 @@ class App extends sugoi.BaseApp {
out.set("txtHome", "Texte en page d'accueil pour les adhérents connectés");
out.set("txtDistrib", "Texte à faire figurer sur les listes d'émargement lors des distributions");
out.set("extUrl", "URL du site du groupe.");
out.set("distributor1", "Distributeur 1");
out.set("distributor2", "Distributeur 2");
out.set("distributor3", "Distributeur 3");
out.set("distributor4", "Distributeur 4");
out.set("distributorNum", "Nbre de distributeurs nécessaires (de 0 à 4)");
out.set("startDate", "Date de début");
out.set("endDate", "Date de fin");
out.set("orderStartDate", "Date ouverture des commandes");
out.set("orderEndDate", "Date fermeture des commandes");
out.set("openingHour", "Heure d'ouverture");
out.set("closingHour", "Heure de fermeture");
out.set("date", "Date de distribution");
out.set("active", "actif");
out.set("orderEndDate", "Date fermeture des commandes");
out.set("openingHour", "Heure d'ouverture");
out.set("closingHour", "Heure de fermeture");
out.set("date", "Date de distribution");
out.set("active", "actif");
out.set("contact", "Reponsable");
out.set("vendor", "Producteur");
out.set("text", "Texte");
@@ -170,7 +166,7 @@ class App extends sugoi.BaseApp {
out.set("pname", "Produit");
out.set("organic", "Agriculture biologique");
out.set("hasFloatQt", "Autoriser quantités \"à virgule\"");
out.set("membershipRenewalDate", "Adhésions : Date de renouvellement");
out.set("membershipPrice", "Adhésions : Coût de l'adhésion");
out.set("UsersCanOrder", "Les adhérents peuvent saisir leur commande en ligne");
@@ -193,12 +189,12 @@ class App extends sugoi.BaseApp {
out.set("ref", "Référence");
out.set("linkText", "Intitulé du lien");
out.set("linkUrl", "URL du lien");
out.set("Amap", "AMAP");
out.set("GroupedOrders", "Groupement d'achat");
out.set("ProducerDrive", "Collectif de producteurs");
out.set("FarmShop", "Vente à la ferme");
out.set("regOption", "Inscription de nouveaux adhérents");
out.set("Closed", "Fermé : Le coordinateur ajoute les nouveaux adhérents");
out.set("WaitingList", "Liste d'attente");
@@ -206,102 +202,108 @@ class App extends sugoi.BaseApp {
out.set("Full", "Complet : Le groupe n'accepte plus de nouveaux adhérents");
out.set("percent", "Pourcentage");
out.set("pinned", "Mets en avant les produits");
out.set("CagetteNetwork", "Me lister dans l'annuaire des groupes Cagette.net");
out.set("CagetteNetwork", "Me lister dans l'annuaire des groupes");
out.set("unitType", "Unité");
out.set("qt", "Quantité");
out.set("Unit", "Pièce");
out.set("Kilogram", "Kilogrammes");
out.set("Gram", "Grammes");
out.set("Litre", "Litres");
out.set("Litre", "Litres");
out.set("htPrice", "Prix H.T");
out.set("amount", "Montant");
out.set("HasPayments", "Gestion des paiements");
out.set("byMember", "Par adhérent");
out.set("byProduct", "Par produit");
out.set("variablePrice", "Variable price based on weight");
return out;
}
public function populateAmapMembers() {
public function populateAmapMembers() {
return user.amap.getMembersFormElementData();
}
public static function getMailer():sugoi.mail.IMailer {
var mailer : sugoi.mail.IMailer = new sugoi.mail.BufferedMailer();
if(App.config.DEBUG){
var typeOfMailer = sugoi.db.Variable.get("mailer");
App.log('type of mailer is <$typeOfMailer>');
//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>");
throw sugoi.ControllerAction.ErrorAction("/",msg);
}
if (typeOfMailer == null) {
var msg = sugoi.i18n.Locale.texts._("Please configure the email settings in a <href='/admin/emails'>this section</a>");
throw sugoi.ControllerAction.ErrorAction("/", msg);
}
if (sugoi.db.Variable.get("mailer") == "mandrill"){
//Buffered emails with Mandrill
untyped mailer.defineFinalMailer("mandrill");
}else{
//Buffered emails with SMTP
untyped mailer.defineFinalMailer("smtp");
}
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") {
// Buffered emails with Mandrill
untyped mailer.defineFinalMailer("mandrill");
} else {
// Buffered emails with SMTP
untyped mailer.defineFinalMailer("smtp");
}
return mailer;
}
/**
* Send an email
*/
public static function sendMail(m:sugoi.mail.Mail, ?group:db.Amap, ?listId:String, ?sender:db.User){
if (group == null) group = App.current.user == null ? null:App.current.user.getAmap();
current.event(SendEmail(m));
var params = group==null ? null : {remoteId:group.id};
public static function sendMail(m:sugoi.mail.Mail, ?group:db.Amap, ?listId:String, ?sender:db.User) {
if (group == null)
group = App.current.user == null ? null : App.current.user.getAmap();
getMailer().send(m,params,function(o){});
current.event(SendEmail(m));
var params = group == null ? null : {remoteId: group.id};
App.log('about to send email with subject=<${m.getSubject()}> ...');
getMailer().send(m, params, function(o) {});
}
public static function quickMail(to:String, subject:String, html:String,?group:db.Amap){
var e = new sugoi.mail.Mail();
public static function quickMail(to:String, subject:String, html:String, ?group:db.Amap) {
App.log('quickMail subject=$subject');
var e = new sugoi.mail.Mail();
e.setSubject(subject);
e.setRecipient(to);
e.setSender(App.config.get("default_email"),"Cagette.net");
var html = App.current.processTemplate("mail/message.mtt", {text:html,group:group});
e.setRecipient(to);
e.setSender(App.config.get("default_email"), t._("CagettePéi"));
var html = App.current.processTemplate("mail/message.mtt", {text: html, group: group});
e.setHtmlBody(html);
App.sendMail(e);
}
/**
* process a template and returns the generated string
* @param tpl
* @param ctx
*/
public function processTemplate(tpl:String, ctx:Dynamic):String {
Reflect.setField(ctx, 'HOST', App.config.HOST);
Reflect.setField(ctx, 'hDate', App.current.view.hDate);
//i18n functions
// i18n functions
ctx._ = App.current.view._;
ctx.__ = App.current.view.__;
var tpl = loadTemplate(tpl);
var html = tpl.execute(ctx);
var html = tpl.execute(ctx);
#if php
if ( html.substr(0, 4) == "null") html = html.substr(4);
if (html.substr(0, 4) == "null")
html = html.substr(4);
#end
return html;
}
}
+292 -300
View File
@@ -1,4 +1,5 @@
package controller;
import db.UserContract;
import sugoi.form.elements.DateDropdowns;
import sugoi.form.elements.Input;
@@ -7,145 +8,139 @@ import sugoi.form.Form;
import db.Contract;
import Common;
import plugin.Tutorial;
using Std;
import service.OrderService;
class Contract extends Controller
{
public function new()
{
class Contract extends Controller {
public function new() {
super();
}
@tpl("contract/view.mtt")
public function doView(c:db.Contract) {
view.category = 'amap';
view.c = c;
}
/**
* "my account" page
*/
@tpl("contract/default.mtt")
function doDefault() {
//Create the list of links to change the language
var langs = App.config.get("langs").split(";");
var langNames = App.config.get("langnames").split(";");
var i=0;
// Create the list of links to change the language
var langs = App.config.get("langs", "fr").split(";");
var langNames = App.config.get("langnames", "Français").split(";");
var i = 0;
var langLinks = "";
for (lang in langs)
{
for (lang in langs) {
langLinks += "<li><a href=\"?lang=" + langs[i] + "\">" + langNames[i] + "</a></li>";
i++;
}
view.langLinks = langLinks;
view.langText = langNames[langs.indexOf(app.session.lang)];
//change account lang
if (app.params.exists("lang") && app.user!=null){
// change account lang
if (app.params.exists("lang") && app.user != null) {
app.user.lock();
app.user.lang = app.params.get("lang");
app.user.update();
}
var ua = db.UserAmap.get(app.user, app.user.amap);
if (ua == null) throw Error("/", t._("You are not a member of this group"));
if (ua == null)
throw Error("/", t._("You are not a member of this group"));
var constOrders = null;
var varOrders = new Map<String,Array<db.UserContract>>();
var a = App.current.user.amap;
var varOrders = new Map<String, Array<db.UserContract>>();
var a = App.current.user.amap;
var oneMonthAgo = DateTools.delta(Date.now(), -1000.0 * 60 * 60 * 24 * 30);
//constant orders
var contracts = db.Contract.manager.search($type == db.Contract.TYPE_CONSTORDERS && $amap == a && $endDate > oneMonthAgo, false);
// constant orders
var contracts = db.Contract.manager.search($type == db.Contract.TYPE_CONSTORDERS && $amap == a && $endDate > oneMonthAgo, false);
constOrders = [];
for ( c in contracts){
for (c in contracts) {
var orders = app.user.getOrdersFromContracts([c]);
if (orders.length == 0) continue;
constOrders.push({contract:c, orders:service.OrderService.prepare(orders) });
if (orders.length == 0)
continue;
constOrders.push({contract: c, orders: service.OrderService.prepare(orders)});
}
//variable orders, grouped by date
// variable orders, grouped by date
var contracts = db.Contract.manager.search($type == db.Contract.TYPE_VARORDER && $amap == a && $endDate > oneMonthAgo, false);
for (c in contracts) {
var ds = c.getDistribs(false);
for (d in ds) {
//store orders in a stringmap like "2015-01-01" => [order1,order2,...]
// store orders in a stringmap like "2015-01-01" => [order1,order2,...]
var k = d.date.toString().substr(0, 10);
var orders = app.user.getOrdersFromDistrib(d);
if (orders.length > 0) {
if (!varOrders.exists(k)) {
varOrders.set(k, Lambda.array(orders));
}else {
} else {
var z = varOrders.get(k).concat(Lambda.array(orders));
varOrders.set(k, z);
}
}
}
}
}
//final structure
var varOrders2 = new Array<{date:Date,orders:Array<UserOrder>}>();
for ( k in varOrders.keys()) {
// final structure
var varOrders2 = new Array<{date:Date, orders:Array<UserOrder>}>();
for (k in varOrders.keys()) {
var d = new Date(k.split("-")[0].parseInt(), k.split("-")[1].parseInt() - 1, k.split("-")[2].parseInt(), 0, 0, 0);
var orders = service.OrderService.prepare( Lambda.list(varOrders[k]) );
varOrders2.push({date:d,orders:orders});
var orders = service.OrderService.prepare(Lambda.list(varOrders[k]));
varOrders2.push({date: d, orders: orders});
}
//sort by date desc
// sort by date desc
varOrders2.sort(function(b, a) {
return Math.round(a.date.getTime()/1000)-Math.round(b.date.getTime()/1000);
return Math.round(a.date.getTime() / 1000) - Math.round(b.date.getTime() / 1000);
});
view.varOrders = varOrders2;
view.constOrders = constOrders;
// tutorials
if (app.user.isAmapManager()) {
//actions
if (app.params.exists('startTuto') ) {
//start a tuto
// actions
if (app.params.exists('startTuto')) {
// start a tuto
app.user.lock();
var t = app.params.get('startTuto');
app.user.tutoState = {name:t,step:0};
var t = app.params.get('startTuto');
app.user.tutoState = {name: t, step: 0};
app.user.update();
}
//tuto state
var tutos = new Array<{name:String,completion:Float,key:String}>();
for ( k in Tutorial.all().keys() ) {
// tuto state
var tutos = new Array<{name:String, completion:Float, key:String}>();
for (k in Tutorial.all().keys()) {
var t = Tutorial.all().get(k);
var completion = null;
if (app.user.tutoState!=null && app.user.tutoState.name == k) completion = app.user.tutoState.step / t.steps.length;
tutos.push( { name:t.name, completion:completion , key:k } );
if (app.user.tutoState != null && app.user.tutoState.name == k)
completion = app.user.tutoState.step / t.steps.length;
tutos.push({name: t.name, completion: completion, key: k});
}
view.tutos = tutos;
}
//should be able to stop tuto in any case
// should be able to stop tuto in any case
if (app.params.exists('stopTuto')) {
//stopped tuto from a tuto window
// stopped tuto from a tuto window
app.user.lock();
app.user.tutoState = null;
app.user.update();
app.user.update();
view.stopTuto = true;
}
checkToken();
view.userAmap = ua;
}
@@ -155,29 +150,30 @@ class Contract extends Controller
*/
@tpl("form.mtt")
function doEdit(c:db.Contract) {
view.category = 'contractadmin';
if (!app.user.isContractManager(c)) throw Error('/', t._("Forbidden action"));
if (!app.user.isContractManager(c))
throw Error('/', t._("Forbidden action"));
view.title = t._("Edit contract ::contractName::",{contractName:c.name});
view.title = t._("Edit contract ::contractName::", {contractName: c.name});
var group = c.amap;
var currentContact = c.contact;
var form = Form.fromSpod(c);
form.removeElement( form.getElement("amapId") );
form.removeElement(form.getElement("amapId"));
form.removeElement(form.getElement("type"));
form.getElement("userId").required = true;
app.event(EditContract(c,form));
app.event(EditContract(c, form));
if (form.checkToken()) {
form.toSpod(c);
c.amap = group;
//checks & warnings
if (c.hasPercentageOnOrders() && c.percentageValue==null) throw Error("/contract/edit/"+c.id, t._("If you would like to add fees to the order, define a rate (%) and a label."));
// checks & warnings
if (c.hasPercentageOnOrders() && c.percentageValue == null)
throw Error("/contract/edit/" + c.id, t._("If you would like to add fees to the order, define a rate (%) and a label."));
if (c.hasStockManagement()) {
for (p in c.getProducts()) {
if (p.stock == null) {
@@ -186,73 +182,72 @@ class Contract extends Controller
}
}
}
//no stock mgmt for constant orders
if (c.hasStockManagement() && c.type==db.Contract.TYPE_CONSTORDERS) {
// no stock mgmt for constant orders
if (c.hasStockManagement() && c.type == db.Contract.TYPE_CONSTORDERS) {
c.flags.unset(ContractFlags.StockManagement);
app.session.addMessage(t._("Managing stock is not available for CSA contracts"), true);
}
c.update();
//update rights
if ( c.contact != null && (currentContact==null || c.contact.id!=currentContact.id) ) {
// update rights
if (c.contact != null && (currentContact == null || c.contact.id != currentContact.id)) {
var ua = db.UserAmap.get(c.contact, app.user.amap, true);
ua.giveRight(ContractAdmin(c.id));
ua.giveRight(Messages);
ua.giveRight(Membership);
ua.update();
//remove rights to old contact
// remove rights to old contact
if (currentContact != null) {
var x = db.UserAmap.get(currentContact, c.amap, true);
if (x != null) {
x.removeRight(ContractAdmin(c.id));
x.update();
x.update();
}
}
}
throw Ok("/contractAdmin/view/"+c.id, t._("Contract updated"));
throw Ok("/contractAdmin/view/" + c.id, t._("Contract updated"));
}
view.form = form;
}
@tpl("contract/insertChoose.mtt")
function doInsertChoose() {
//checkToken();
// checkToken();
}
/**
* Créé un nouveau contrat
*/
@tpl("form.mtt")
function doInsert(?type:Int) {
if (!app.user.canManageAllContracts()) throw Error('/', t._("Forbidden action"));
if (type == null) throw Redirect('/contract/insertChoose');
view.title = if (type == db.Contract.TYPE_CONSTORDERS)t._("Create a contract with fixed orders") else t._("Create a contract with variable orders");
if (!app.user.canManageAllContracts())
throw Error('/', t._("Forbidden action"));
if (type == null)
throw Redirect('/contract/insertChoose');
view.title = if (type == db.Contract.TYPE_CONSTORDERS) t._("Create a contract with fixed orders") else t._("Create a contract with variable orders");
var c = new db.Contract();
var form = Form.fromSpod(c);
form.removeElement( form.getElement("amapId") );
form.removeElement(form.getElement("amapId"));
form.removeElement(form.getElement("type"));
form.getElement("userId").required = true;
if (form.checkToken()) {
form.toSpod(c);
c.amap = app.user.amap;
//trace(app.user.amap);
//trace(c.amap);
// trace(app.user.amap);
// trace(c.amap);
c.type = type;
c.insert();
//right
// right
if (c.contact != null) {
var ua = db.UserAmap.get(c.contact, app.user.amap, true);
ua.giveRight(ContractAdmin(c.id));
@@ -260,51 +255,52 @@ class Contract extends Controller
ua.giveRight(Membership);
ua.update();
}
throw Ok("/contractAdmin/view/"+c.id, t._("New contract created"));
throw Ok("/contractAdmin/view/" + c.id, t._("New contract created"));
}
view.form = form;
}
/**
* Delete a contract (... and its products, orders & distributions)
*/
function doDelete(c:db.Contract) {
if (!app.user.canManageAllContracts()) throw Error("/contractAdmin", t._("You don't have the authorization to remove a contract"));
if (!app.user.canManageAllContracts())
throw Error("/contractAdmin", t._("You don't have the authorization to remove a contract"));
if (checkToken()) {
c.lock();
//check if there is orders in this contract
// check if there is orders in this contract
var products = c.getProducts();
var orders = db.UserContract.manager.search($productId in Lambda.map(products, function(p) return p.id));
var qt = 0.0;
for ( o in orders) qt += o.quantity; //there could be "zero c qt" orders
for (o in orders)
qt += o.quantity; // there could be "zero c qt" orders
if (qt > 0) {
throw Error("/contractAdmin", t._("You cannot delete this contract because some orders are linked to it."));
}
//remove admin rights and delete contract
if(c.contact!=null){
// remove admin rights and delete contract
if (c.contact != null) {
var ua = db.UserAmap.get(c.contact, c.amap, true);
if (ua != null) {
ua.removeRight(ContractAdmin(c.id));
ua.update();
}
ua.update();
}
}
app.event(DeleteContract(c));
c.delete();
throw Ok("/contractAdmin", t._("Contract deleted"));
}
throw Error("/contractAdmin", t._("Token error"));
}
/**
* Make an order by contract ( standard mode )
* The form is prepopulated if orders have already been made.
@@ -314,305 +310,301 @@ class Contract extends Controller
*
*/
@tpl("contract/order.mtt")
function doOrder(c:db.Contract ) {
//checks
if (app.user.amap.hasPayments()) throw Redirect("/contract/orderAndPay/" + c.id);
if (app.user.amap.hasShopMode()) throw Redirect("/shop");
if (!c.isUserOrderAvailable()) throw Error("/", t._("This contract is not opened for orders"));
function doOrder(c:db.Contract) {
// checks
if (app.user.amap.hasPayments())
throw Redirect("/contract/orderAndPay/" + c.id);
if (app.user.amap.hasShopMode())
throw Redirect("/shop");
if (!c.isUserOrderAvailable())
throw Error("/", t._("This contract is not opened for orders"));
var distributions = [];
// If its a varying contract, we display a column by distribution
if (c.type == db.Contract.TYPE_VARORDER) {
distributions = db.Distribution.getOpenToOrdersDeliveries(c);
}else{
} else {
distributions = [null];
}
//list of distribs with a list of product and optionnaly an order
var userOrders = new Array< {distrib:db.Distribution,datas:Array<{order:db.UserContract,product:db.Product}>} >();
// list of distribs with a list of product and optionnaly an order
var userOrders = new Array<{distrib:db.Distribution, datas:Array<{order:db.UserContract, product:db.Product}>}>();
var products = c.getProducts();
if ( c.type == db.Contract.TYPE_VARORDER ){
for ( d in distributions){
if (c.type == db.Contract.TYPE_VARORDER) {
for (d in distributions) {
var datas = [];
for ( p in products) {
var ua = { order:null, product:p };
var order = db.UserContract.manager.select($user == app.user && $productId == p.id && $distributionId==d.id, true);
if (order != null) ua.order = order;
for (p in products) {
var ua = {order: null, product: p};
var order = db.UserContract.manager.select($user == app.user && $productId == p.id && $distributionId == d.id, true);
if (order != null)
ua.order = order;
datas.push(ua);
}
userOrders.push({distrib:d,datas:datas});
userOrders.push({distrib: d, datas: datas});
}
}else{
} else {
var datas = [];
for ( p in products) {
var ua = { order:null, product:p };
for (p in products) {
var ua = {order: null, product: p};
var order = db.UserContract.manager.select($user == app.user && $productId == p.id, true);
if (order != null) ua.order = order;
if (order != null)
ua.order = order;
datas.push(ua);
}
userOrders.push({distrib:null,datas:datas});
userOrders.push({distrib: null, datas: datas});
}
//form check
// form check
if (checkToken()) {
//get dsitrib if needed
//var distrib : db.Distribution = null;
//if (c.type == db.Contract.TYPE_VARORDER) {
//distrib = db.Distribution.manager.get(Std.parseInt(app.params.get("distribution")), false);
//}
// get dsitrib if needed
// var distrib : db.Distribution = null;
// if (c.type == db.Contract.TYPE_VARORDER) {
// distrib = db.Distribution.manager.get(Std.parseInt(app.params.get("distribution")), false);
// }
for (k in app.params.keys()) {
if (k.substr(0, 1) != "d") continue;
if (k.substr(0, 1) != "d")
continue;
var qt = app.params.get(k);
if (qt == "") continue;
if (qt == "")
continue;
var pid = null;
var did = null;
try{
pid = Std.parseInt(k.split("-")[1].substr(1));
did = Std.parseInt(k.split("-")[0].substr(1));
}catch (e:Dynamic){trace("unable to parse key "+k); }
//find related element in userOrders
try {
pid = Std.parseInt(k.split("-")[1].substr(1));
did = Std.parseInt(k.split("-")[0].substr(1));
} catch (e:Dynamic) {
trace("unable to parse key " + k);
}
// find related element in userOrders
var uo = null;
for ( x in userOrders){
if (x.distrib!=null && x.distrib.id != did) {
for (x in userOrders) {
if (x.distrib != null && x.distrib.id != did) {
continue;
}else{
for (a in x.datas){
if (a.product.id == pid){
} else {
for (a in x.datas) {
if (a.product.id == pid) {
uo = a;
break;
}
}
}
}
if (uo == null) throw t._("Could not find the product ::produ:: and delivery ::deliv::", {produ:pid, deliv:did});
if (uo == null)
throw t._("Could not find the product ::produ:: and delivery ::deliv::", {produ: pid, deliv: did});
var q = 0.0;
if (uo.product.hasFloatQt ) {
if (uo.product.hasFloatQt) {
var param = StringTools.replace(qt, ",", ".");
q = Std.parseFloat(param);
}else {
} else {
q = Std.parseInt(qt);
}
if (uo.order != null) {
if (uo.order != null) {
OrderService.edit(uo.order, q);
}else {
} else {
OrderService.make(app.user, q, uo.product, did);
}
}
throw Ok("/contract/order/"+c.id, t._("Your order has been updated"));
throw Ok("/contract/order/" + c.id, t._("Your order has been updated"));
}
view.c = view.contract = c;
view.userOrders = userOrders;
}
/**
* Make an order by contract ( standard mode ) + payment process
*/
@tpl("contract/orderAndPay.mtt")
function doOrderAndPay(c:db.Contract ) {
//checks
if (!app.user.amap.hasPayments()) throw Redirect("/contract/order/" + c.id);
if (app.user.amap.hasShopMode()) throw Redirect("/");
if (!c.isUserOrderAvailable()) throw Error("/", t._("This contract is not opened for orders"));
function doOrderAndPay(c:db.Contract) {
// checks
if (!app.user.amap.hasPayments())
throw Redirect("/contract/order/" + c.id);
if (app.user.amap.hasShopMode())
throw Redirect("/");
if (!c.isUserOrderAvailable())
throw Error("/", t._("This contract is not opened for orders"));
var distributions = [];
/* If its a varying contract, we display a column by distribution*/
if (c.type == db.Contract.TYPE_VARORDER) {
distributions = db.Distribution.getOpenToOrdersDeliveries(c);
}
//list of distribs with a list of product and optionnaly an order
var userOrders = new Array< {distrib:db.Distribution,datas:Array<{order:db.UserContract,product:db.Product}>} >();
// list of distribs with a list of product and optionnaly an order
var userOrders = new Array<{distrib:db.Distribution, datas:Array<{order:db.UserContract, product:db.Product}>}>();
var products = c.getProducts();
for ( d in distributions){
for (d in distributions) {
var datas = [];
for ( p in products) {
var ua = { order:null, product:p };
var order : db.UserContract = null;
for (p in products) {
var ua = {order: null, product: p};
var order:db.UserContract = null;
if (c.type == db.Contract.TYPE_VARORDER) {
order = db.UserContract.manager.select($user == app.user && $productId == p.id && $distributionId==d.id, true);
}else {
order = db.UserContract.manager.select($user == app.user && $productId == p.id && $distributionId == d.id, true);
} else {
order = db.UserContract.manager.select($user == app.user && $productId == p.id, true);
}
if (order != null) ua.order = order;
if (order != null)
ua.order = order;
datas.push(ua);
}
userOrders.push({distrib:d,datas:datas});
userOrders.push({distrib: d, datas: datas});
}
//form check
// form check
if (checkToken()) {
//get distrib if needed
// get distrib if needed
var distrib = null;
if (c.type == db.Contract.TYPE_VARORDER) {
distrib = db.Distribution.manager.get(Std.parseInt(app.params.get("distribution")), false);
}
var orders : OrderInSession = {products:[],userId:app.user.id,total:0};
var orders:OrderInSession = {products: [], userId: app.user.id, total: 0};
for (k in app.params.keys()) {
if (k.substr(0, 1) != "d") continue;
if (k.substr(0, 1) != "d")
continue;
var qt = app.params.get(k);
if (qt == "") continue;
if (qt == "")
continue;
var pid = null;
var did = null;
try{
try {
pid = Std.parseInt(k.split("-")[1].substr(1));
did = Std.parseInt(k.split("-")[0].substr(1));
}catch (e:Dynamic){
trace("unable to parse key "+k);
} catch (e:Dynamic) {
trace("unable to parse key " + k);
}
//find related element in userOrders
// find related element in userOrders
var uo = null;
for ( x in userOrders){
if (x.distrib!=null && x.distrib.id != did) {
for (x in userOrders) {
if (x.distrib != null && x.distrib.id != did) {
continue;
}else{
for (a in x.datas){
if (a.product.id == pid){
} else {
for (a in x.datas) {
if (a.product.id == pid) {
uo = a;
break;
}
}
}
}
if (uo == null) throw t._("Could not find the product ::produ:: and delivery ::deliv::", {produ:pid, deliv:did});
//quantity
var q = 0.0;
if (uo.product.hasFloatQt ) {
if (uo == null)
throw t._("Could not find the product ::produ:: and delivery ::deliv::", {produ: pid, deliv: did});
// quantity
var q = 0.0;
if (uo.product.hasFloatQt) {
var param = StringTools.replace(qt, ",", ".");
q = Std.parseFloat(param);
}else {
} else {
q = Std.parseInt(qt);
}
orders.products.push({productId:pid, quantity:q, distributionId:did});
orders.products.push({productId: pid, quantity: q, distributionId: did});
var p = db.Product.manager.get(pid, false);
orders.total += p.getPrice() * q;
}
App.current.session.data.order = orders;
//Go to payments page
// Go to payments page
if (c.type == db.Contract.TYPE_CONSTORDERS) {
throw Ok("/contract/order/"+c.id, t._("Your CSA order has been saved"));
}else{
throw Ok("/contract/order/" + c.id, t._("Your CSA order has been saved"));
} else {
throw Ok("/transaction/pay/", t._("In order to save your order, please choose a means of payment."));
}
}
view.c = view.contract = c;
view.userOrders = userOrders;
view.userOrders = userOrders;
}
/**
* A user edit an order for a multidistrib.
*/
@tpl("contract/orderByDate.mtt")
function doEditOrderByDate(date:Date) {
if (app.user.amap.hasPayments()) {
//when payments are active, the user cannot modify his order
// when payments are active, the user cannot modify his order
throw Redirect("/");
}
// cannot edit order if date is in the past
if (Date.now().getTime() > date.getTime()) {
var msg = t._("This delivery has already taken place, you can no longer modify the order.");
if (app.user.isContractManager()) msg += t._("<br/>As the manager of the contract you can modify the order from this page: <a href='/contractAdmin'>Management of contracts</a>");
if (app.user.isContractManager())
msg += t._("<br/>As the manager of the contract you can modify the order from this page: <a href='/contractAdmin'>Management of contracts</a>");
throw Error("/contract", msg);
}
// Il faut regarder le contrat de chaque produit et verifier si le contrat est toujours ouvert à la commande.
// Il faut regarder le contrat de chaque produit et verifier si le contrat est toujours ouvert à la commande.
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 cids = Lambda.map(app.user.amap.getActiveContracts(true), function(c) return c.id);
var distribs = db.Distribution.manager.search(($contractId in cids) && $date >= d1 && $date <=d2 , false);
var orders = db.UserContract.manager.search($userId==app.user.id && $distributionId in Lambda.map(distribs,function(d)return d.id) );
var distribs = db.Distribution.manager.search(($contractId in cids) && $date >= d1 && $date <= d2, false);
var orders = db.UserContract.manager.search($userId == app.user.id
&& $distributionId in Lambda.map(distribs, function(d) return d.id));
view.orders = service.OrderService.prepare(orders);
view.date = date;
//form check
// form check
if (checkToken()) {
var orders_out = [];
for (k in app.params.keys()) {
var param = app.params.get(k);
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 order = Lambda.find(orders, function(uo) return uo.product.id == pid);
if (order == null) throw t._("Error, could not find the order");
if (order == null)
throw t._("Error, could not find the order");
var q = 0.0;
if (order.product.hasFloatQt ) {
if (order.product.hasFloatQt) {
param = StringTools.replace(param, ",", ".");
q = Std.parseFloat(param);
}else {
} else {
q = Std.parseInt(param);
}
var quantity = Math.abs( q==null?0:q );
if ( order.distribution.canOrderNow() ) {
//met a jour la commande
var quantity = Math.abs(q == null ? 0 : q);
if (order.distribution.canOrderNow()) {
// met a jour la commande
var o = OrderService.edit(order, quantity);
if(o!=null) orders_out.push( o );
}
if (o != null)
orders_out.push(o);
}
}
}
app.event(MakeOrder(orders_out));
throw Ok("/contract", t._("Your order has been updated"));
}
}
File diff suppressed because it is too large Load Diff
+257 -245
View File
@@ -1,144 +1,137 @@
package controller;
import sugoi.db.Cache;
import sugoi.Web;
import sugoi.mail.Mail;
import Common;
using Lambda;
using tools.DateTool;
class Cron extends Controller
{
class Cron extends Controller {
public function doDefault() {}
public function doDefault()
{
}
/**
* CLI only en prod
*/
function canRun() {
if (App.current.user != null && App.current.user.isAdmin()){
if (App.current.user != null && App.current.user.isAdmin()) {
return true;
}else if (App.config.DEBUG) {
} else if (App.config.DEBUG) {
return true;
}else {
} else {
if (Web.isModNeko) {
Sys.print("only CLI.");
return false;
}else {
} else {
return true;
}
}
}
public function doMinute() {
public function doMinute() {
print("Cron.doMinute is called");
if (!canRun()) return;
if (!canRun())
return;
app.event(MinutelyCron);
sendEmailsfromBuffer();
}
/**
* Hourly Cron
*
* this function can be locally tested with `neko index.n cron/hour > cron.log`
*/
public function doHour() {
app.event(HourlyCron);
distribNotif(4,db.User.UserFlags.HasEmailNotif4h); //4h before
distribNotif(24,db.User.UserFlags.HasEmailNotif24h); //24h before
distribNotif(0, db.User.UserFlags.HasEmailNotifOuverture); //on command open
distribNotif(4, db.User.UserFlags.HasEmailNotif4h); // 4h before
distribNotif(24, db.User.UserFlags.HasEmailNotif24h); // 24h before
distribNotif(0, db.User.UserFlags.HasEmailNotifOuverture); // on command open
distribValidationNotif();
//sendOrdersByProductWhenOrdersClose();
// sendOrdersByProductWhenOrdersClose();
}
public function doDaily() {
if (!canRun()) return;
if (!canRun())
return;
app.event(DailyCron);
//ERRORS MONITORING
// ERRORS MONITORING
var n = Date.now();
var yest24h = new Date(n.getFullYear(), n.getMonth(), n.getDate(), 0, 0, 0);
var yest0h = DateTools.delta(yest24h, -1000 * 60 * 60 * 24);
var errors = sugoi.db.Error.manager.search( $date < yest24h && $date > yest0h );
var errors = sugoi.db.Error.manager.search($date < yest24h && $date > yest0h);
if (errors.length > 0) {
var report = new StringBuf();
report.add("<h1>" + App.config.NAME + " : ERRORS</h1>");
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();
m.setSender(App.config.get("default_email"),"Cagette.net");
m.setSender(App.config.get("default_email"), t._("Cagette.net"));
m.addRecipient(App.config.get("webmaster_email"));
m.setSubject(App.config.NAME+" Errors");
m.setHtmlBody( app.processTemplate("mail/message.mtt", { text:report.toString() } ) );
m.setSubject(App.config.NAME + " Errors");
m.setHtmlBody(app.processTemplate("mail/message.mtt", {text: report.toString()}));
App.sendMail(m);
}
//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 Poulet Exemple" && $startDate < DateTools.delta(Date.now(), -1000.0 * 60 * 60 * 24 * 7));
//Old Messages cleaning
// 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 Poulet Exemple"
&& $startDate < DateTools.delta(Date.now(), -1000.0 * 60 * 60 * 24 * 7));
// Old Messages cleaning
db.Message.manager.delete($date < DateTools.delta(Date.now(), -1000.0 * 60 * 60 * 24 * 30 * 6));
//DB cleaning : I dont know how, but some people have empty string emails...
// DB cleaning : I dont know how, but some people have empty string emails...
/*for ( u in db.User.manager.search($email == "", true)){
u.email = Std.random(9999) + "@cagette.net";
u.update();
}
for ( u in db.User.manager.search($email2 == "", true)){
u.email2 = null;
u.update();
u.email = Std.random(9999) + "@cagette.net";
u.update();
}
for ( u in db.User.manager.search($email2 == "", true)){
u.email2 = null;
u.update();
}*/
}
/**
* Send email notifications to users before a distribution
* @param hour
* @param flag
*/
function distribNotif(hour:Int, flag:db.User.UserFlags) {
//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
var from = DateTools.delta(Date.now(), 1000.0 * 60 * 60 * (hour-1));
var to = DateTools.delta(Date.now(), 1000.0 * 60 * 60 * hour);
//if (App.config.DEBUG) from = DateTools.delta(from, 1000.0 * 60 * 60 * 24 * -30);
// 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
var from = DateTools.delta(Date.now(), 1000.0 * 60 * 60 * (hour - 1));
var to = DateTools.delta(Date.now(), 1000.0 * 60 * 60 * hour);
// if (App.config.DEBUG) from = DateTools.delta(from, 1000.0 * 60 * 60 * 24 * -30);
// dans le cas HasEmailNotifOuverture la date à prendre est le orderStartDate
// et non pas date qui est la date de la distribution
var distribs;
if ( db.User.UserFlags.HasEmailNotifOuverture == flag )
distribs = db.Distribution.manager.search( $orderStartDate >= from && $orderStartDate <= to , false);
if (db.User.UserFlags.HasEmailNotifOuverture == flag)
distribs = db.Distribution.manager.search($orderStartDate >= from && $orderStartDate <= to, false);
else
distribs = db.Distribution.manager.search( $date >= from && $date <= to , false);
//Sys.print("distribNotif "+hour+" from "+from+" to "+to+"<br/>\n");
//on s'arrete immédiatement si aucune distibution trouvée
if (distribs.length == 0) return;
//cherche plus tard si on a pas une "grappe" de distrib
distribs = db.Distribution.manager.search($date >= from && $date <= to, false);
// Sys.print("distribNotif "+hour+" from "+from+" to "+to+"<br/>\n");
// on s'arrete immédiatement si aucune distibution trouvée
if (distribs.length == 0)
return;
// cherche plus tard si on a pas une "grappe" de distrib
/*while (true) {
var extraDistribs ;
if ( db.User.UserFlags.HasEmailNotifOuverture != flag )
@@ -154,10 +147,10 @@ class Cron extends Controller
break;
}
}*/
//on vérifie dans le cache du jour que ces distrib n'ont pas deja été traitées lors d'un cron précédent
var cacheId = Date.now().toString().substr(0, 10)+Std.string(flag);
var dist :Array<Int> = sugoi.db.Cache.get(cacheId);
// on vérifie dans le cache du jour que ces distrib n'ont pas deja été traitées lors d'un cron précédent
var cacheId = Date.now().toString().substr(0, 10) + Std.string(flag);
var dist:Array<Int> = sugoi.db.Cache.get(cacheId);
if (dist != null) {
for (d in Lambda.array(distribs)) {
if (Lambda.exists(dist, function(x) return x == d.id)) {
@@ -165,115 +158,135 @@ class Cron extends Controller
distribs.remove(d);
}
}
}else {
} else {
dist = [];
}
//toutes les distribs trouvées ont deja été traitées
if (distribs.length == 0) return;
//stocke cache
for (d in distribs) dist.push(d.id);
// toutes les distribs trouvées ont deja été traitées
if (distribs.length == 0)
return;
// stocke cache
for (d in distribs)
dist.push(d.id);
Cache.set(cacheId, dist, 24 * 60 * 60);
//We have now the distribs we want to notify about.
var distribsByContractId = new Map<Int,db.Distribution>();
for (d in distribs) {
if (d == null || d.contract==null) continue;
// We have now the distribs we want to notify about.
var distribsByContractId = new Map<Int, db.Distribution>();
for (d in distribs) {
if (d == null || d.contract == null)
continue;
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
var orders = [];
for (d in distribs) {
if (d == null || d.contract==null) continue;
//get orders for both type of contracts
for ( x in d.contract.getOrders(d)) orders.push(x);
// Boucle sur les distributions pour gerer le cas de plusieurs distributions le même jour sur le même contrat
var orders = [];
for (d in distribs) {
if (d == null || d.contract == null)
continue;
// get orders for both type of contracts
for (x in d.contract.getOrders(d))
orders.push(x);
}
/*
* Group orders by users-group to receive separate emails by groups for the same user.
* Map key is $userId-$groupId
*/
var users = new Map <String,{
*/
var users = new Map<String, {
user:db.User,
distrib:db.Distribution,
products:Array<db.UserContract>,
vendors:Array<db.Vendor>
vendors:Array<db.Vendor>
}>();
for (o in orders) {
var x = users.get(o.user.id+"-"+o.product.contract.amap.id);
if (x == null) x = {user:o.user,distrib:null,products:[],vendors:[]};
var x = users.get(o.user.id + "-" + o.product.contract.amap.id);
if (x == null)
x = {
user: o.user,
distrib: null,
products: [],
vendors: []
};
x.distrib = distribsByContractId.get(o.product.contract.id);
x.products.push(o);
users.set(o.user.id+"-"+o.product.contract.amap.id, x);
//trace (o.userId+"-"+o.product.contract.amap.id, x);Sys.print("<br/>\n");
x.products.push(o);
users.set(o.user.id + "-" + o.product.contract.amap.id, x);
// trace (o.userId+"-"+o.product.contract.amap.id, x);Sys.print("<br/>\n");
// Prévenir également le deuxième user en cas des commandes alternées
if (o.user2 != null) {
var x = users.get(o.user2.id+"-"+o.product.contract.amap.id);
if (x == null) x = {user:o.user2,distrib:null,products:[],vendors:[]};
x.distrib = distribsByContractId.get(o.product.contract.id);
x.products.push(o);
users.set(o.user2.id+"-"+o.product.contract.amap.id, x);
//trace (o.user2.id+"-"+o.product.contract.amap.id, x);Sys.print("<br/>\n");
}
if (o.user2 != null) {
var x = users.get(o.user2.id + "-" + o.product.contract.amap.id);
if (x == null)
x = {
user: o.user2,
distrib: null,
products: [],
vendors: []
};
x.distrib = distribsByContractId.get(o.product.contract.id);
x.products.push(o);
users.set(o.user2.id + "-" + o.product.contract.amap.id, x);
// trace (o.user2.id+"-"+o.product.contract.amap.id, x);Sys.print("<br/>\n");
}
}
//remove zero qt orders
for( k in users.keys()){
// remove zero qt orders
for (k in users.keys()) {
var x = users.get(k);
var total = 0.0;
for( o in x.products) total += o.quantity;
if(total==0.0) users.remove(k);
for (o in x.products)
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
if ( db.User.UserFlags.HasEmailNotifOuverture == flag )
{
for (d in distribs) {
if (db.User.UserFlags.HasEmailNotifOuverture == flag) {
for (d in distribs) {
var memberList = d.contract.amap.getMembers();
for (u in memberList) {
var x = users.get(u.id+"-"+d.contract.amap.id);
if (x == null) x = {user:u,distrib:null,products:[],vendors:[]};
var x = users.get(u.id + "-" + d.contract.amap.id);
if (x == null)
x = {
user: u,
distrib: null,
products: [],
vendors: []
};
x.distrib = distribsByContractId.get(d.contract.id);
x.vendors.push(d.contract.vendor);
users.set(u.id+"-"+d.contract.amap.id, x);
//print(u.id+"-"+d.contract.amap.id, x);
users.set(u.id + "-" + d.contract.amap.id, x);
// print(u.id+"-"+d.contract.amap.id, x);
}
}
}
for ( u in users) {
if (u.user.flags.has(flag) ) {
for (u in users) {
if (u.user.flags.has(flag)) {
if (u.user.email != null) {
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;
if ( db.User.UserFlags.HasEmailNotifOuverture == flag )
{
//order opening notif
text = t._("Opening of orders for the delivery of <b>::date::</b>", {date:view.hDate(u.distrib.date)});
if (db.User.UserFlags.HasEmailNotifOuverture == flag) {
// order opening notif
text = t._("Opening of orders for the delivery of <b>::date::</b>", {date: view.hDate(u.distrib.date)});
text += "<br/>";
text += t._("The following suppliers are involved :");
text += "<br/><ul>";
for ( v in u.vendors) {
for (v in u.vendors) {
text += "<li>" + v + "</li>";
}
text += "</ul>";
}else{
//Distribution notif to the users
} else {
// Distribution notif to the users
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>";
for ( p in u.products) {
text += "<li>"+p.quantity+" x "+p.product.getName();
for (p in u.products) {
text += "<li>" + p.quantity + " x " + p.product.getName();
// Gerer le cas des contrats en alternance
if (p.user2 != null) {
text += " " + t._("alternated with") + " ";
@@ -286,141 +299,142 @@ class Cron extends Controller
}
text += "</ul>";
}
if (u.distrib.isDistributor(u.user)) {
text += t._("<b>Warning: you are in charge of the delivery ! Do not forget to print the attendance sheet.</b>");
}
try{
try {
var m = new Mail();
m.setSender(App.config.get("default_email"), "Cagette.net");
if(group.contact!=null) m.setReplyTo(group.contact.email, group.name);
m.setSender(App.config.get("default_email"), t._("Cagette.net"));
if (group.contact != null)
m.setReplyTo(group.contact.email, group.name);
m.addRecipient(u.user.email, u.user.getName());
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.setHtmlBody( app.processTemplate("mail/message.mtt", { text:text,group:group } ) );
App.sendMail(m , u.distrib.contract.amap);
}catch (e:Dynamic){
app.logError(e); //email could be invalid
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.setHtmlBody(app.processTemplate("mail/message.mtt", {text: text, group: group}));
App.sendMail(m, u.distrib.contract.amap);
} catch (e:Dynamic) {
app.logError(e); // email could be invalid
}
}
}
}
}
/**
* Check if there is a multi-distrib to validate.
*
* Autovalidate it after 10 days
*/
function distribValidationNotif(){
function distribValidationNotif() {
var now = Date.now();
var from = now.setHourMinute( now.getHours(), 0 );
var to = now.setHourMinute( now.getHours()+1 , 0);
var from = now.setHourMinute(now.getHours(), 0);
var to = now.setHourMinute(now.getHours() + 1, 0);
var explain = t._("<p>This step is important in order to:</p>");
explain += t._("<ul><li>Update orders if delivered quantities are different from ordered quantities</li>");
explain += t._("<li>Confirm the reception of payments (checks, cash, transfers) in order to mark orders as 'paid'</li></ul>");
/*
* warn administrator if a distribution just ended
*/
var ds = db.Distribution.manager.search( !$validated && ($end >= from) && ($end < to) , false);
for ( d in Lambda.array(ds)){
if ( d.contract.type != db.Contract.TYPE_VARORDER ){
*/
var ds = db.Distribution.manager.search(!$validated && ($end >= from) && ($end < to), false);
for (d in Lambda.array(ds)) {
if (d.contract.type != db.Contract.TYPE_VARORDER) {
ds.remove(d);
}else if ( !d.contract.amap.hasPayments() ){
} else if (!d.contract.amap.hasPayments()) {
ds.remove(d);
}
}
var ds = tools.ObjectListTool.deduplicateDistribsByKey(ds);
var view = App.current.view;
for ( d in ds ){
for (d in ds) {
// var subj = "["+d.contract.amap.name+"] " + t._("Validation of the ::date:: distribution",{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 url = "http://" + App.config.HOST + "/distribution/validate/"+d.date.toString().substr(0,10)+"/"+d.place.id;
var subj = t._("[::group::] Validation of the ::date:: distribution", {group: d.contract.amap.name, date: view.hDate(d.date)});
var url = "http://" + App.config.HOST + "/distribution/validate/" + d.date.toString().substr(0, 10) + "/" + d.place.id;
var html = t._("<p>Your distribution just finished, don't forget to <b>validate</b> it</p>");
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);
}
/*
* warn administrator if a distribution ended 3 days ago
*/
var from = now.setHourMinute( now.getHours() , 0 ).deltaDays(-3);
var to = now.setHourMinute( now.getHours()+1 , 0).deltaDays(-3);
//warn administrator if a distribution just ended
var ds = db.Distribution.manager.search( !$validated && ($end >= from) && ($end < to) , false);
for ( d in Lambda.array(ds)){
if ( d.contract.type != db.Contract.TYPE_VARORDER ){
*/
var from = now.setHourMinute(now.getHours(), 0).deltaDays(-3);
var to = now.setHourMinute(now.getHours() + 1, 0).deltaDays(-3);
// warn administrator if a distribution just ended
var ds = db.Distribution.manager.search(!$validated && ($end >= from) && ($end < to), false);
for (d in Lambda.array(ds)) {
if (d.contract.type != db.Contract.TYPE_VARORDER) {
ds.remove(d);
}else if ( !d.contract.amap.hasPayments() ){
} else if (!d.contract.amap.hasPayments()) {
ds.remove(d);
}
}
var ds = tools.ObjectListTool.deduplicateDistribsByKey(ds);
for ( d in ds ){
for (d in ds) {
// var subj = d.contract.amap.name + t._(": Validation of the delivery 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 url = "http://" + App.config.HOST + "/distribution/validate/"+d.date.toString().substr(0,10)+"/"+d.place.id;
var subj = t._("[::group::] Validation of the ::date:: distribution", {group: d.contract.amap.name, date: view.hDate(d.date)});
var url = "http://" + App.config.HOST + "/distribution/validate/" + d.date.toString().substr(0, 10) + "/" + d.place.id;
var html = t._("<p>Reminder: you have a delivery to validate.</p>");
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);
}
/*
* Autovalidate unvalidated distributions after 10 days
*/
var from = now.setHourMinute( now.getHours() , 0 ).deltaDays( 0 - db.Distribution.DISTRIBUTION_VALIDATION_LIMIT );
var to = now.setHourMinute( now.getHours() + 1 , 0).deltaDays( 0 - db.Distribution.DISTRIBUTION_VALIDATION_LIMIT );
*/
var from = now.setHourMinute(now.getHours(), 0).deltaDays(0 - db.Distribution.DISTRIBUTION_VALIDATION_LIMIT);
var to = now.setHourMinute(now.getHours() + 1, 0).deltaDays(0 - db.Distribution.DISTRIBUTION_VALIDATION_LIMIT);
print('AUTOVALIDATION');
print('Find distributions from $from to $to');
var ds = db.Distribution.manager.search( !$validated && ($end >= from) && ($end < to) , true);
for ( d in Lambda.array(ds)){
if ( d.contract.type != db.Contract.TYPE_VARORDER ){
var ds = db.Distribution.manager.search(!$validated && ($end >= from) && ($end < to), true);
for (d in Lambda.array(ds)) {
if (d.contract.type != db.Contract.TYPE_VARORDER) {
ds.remove(d);
}else if ( !d.contract.amap.hasPayments() ){
} else if (!d.contract.amap.hasPayments()) {
ds.remove(d);
}
}
for ( d in ds){
for (d in ds) {
print(d.toString());
service.PaymentService.validateDistribution(d);
}
//email
// email
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 = 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 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)
});
App.quickMail(d.contract.amap.contact.email, subj, html);
}
}
/**
* Send emails from buffer.
*
@@ -428,50 +442,48 @@ class Cron extends Controller
* you should consider the right amount of emails to send each minute in order to avoid overlaping and getting in concurrency problems.
* (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(){
print("<h3>Send Emails from Buffer</h3>");
//send
for( e in sugoi.db.BufferedMail.manager.search($sdate==null,{limit:50,orderBy:-cdate},false) ){
function sendEmailsfromBuffer() {
App.log("Send Emails from Buffer");
// send
for (e in sugoi.db.BufferedMail.manager.search($sdate == null, {limit: 50, orderBy: -cdate}, false)) {
e.lock();
if(e.isSent()) continue;
print('#${e.id} - ${e.title}');
if (e.isSent())
continue;
App.log('Send Email id=#${e.id} - title=${e.title}');
e.finallySend();
Sys.sleep(0.1);
}
//delete old emails
var threeMonthsAgo = DateTools.delta(Date.now(), -1000.0*60*60*24*30*3);
// delete old emails
var threeMonthsAgo = DateTools.delta(Date.now(), -1000.0 * 60 * 60 * 24 * 30 * 3);
sugoi.db.BufferedMail.manager.delete($cdate < threeMonthsAgo);
//emails that cannot be sent
for( e in sugoi.db.BufferedMail.manager.search($tries>100,{limit:50,orderBy:-cdate},true) ){
if(e.sender.email != App.config.get("default_email")){
var str = t._("Sorry, the email entitled <b>::title::</b> could not be sent.",{title:e.title});
App.quickMail(e.sender.email,t._("Email not sent"),str);
}
// emails that cannot be sent
for (e in sugoi.db.BufferedMail.manager.search($tries > 100, {limit: 50, orderBy: -cdate}, true)) {
if (e.sender.email != App.config.get("default_email")) {
var str = t._("Sorry, the email entitled <b>::title::</b> could not be sent.", {title: e.title});
App.quickMail(e.sender.email, t._("Email not sent"), str);
}
e.delete();
}
}
}
/**
* Email product report when orders close
**/
function sendOrdersByProductWhenOrdersClose(){
**/
function sendOrdersByProductWhenOrdersClose() {
var range = tools.DateTool.getLastHourRange();
// 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/>');
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);
}
}
function print(text){
Sys.println( text + "<br/>" );
function print(text) {
Sys.println(text + "<br/>");
}
}
+89 -89
View File
@@ -14,66 +14,66 @@ class Group extends controller.Controller
*/
@tpl('group/view.mtt')
function doDefault(group:db.Amap){
if (group.regOption == db.Amap.RegOption.Open) {
app.session.data.amapId = group.id;
throw Redirect("/");
}
view.group = group;
view.contracts = group.getActiveContracts();
view.pageTitle = group.name;
group.getMainPlace(); //just to update cache
if (app.user != null){
view.isMember = Lambda.has(app.user.getAmaps(), group);
}
}
/**
* Register to a waiting list.
* the user can be logged or not !
*/
@tpl('form.mtt')
function doList(group:db.Amap){
//checks
if (group.regOption != db.Amap.RegOption.WaitingList) throw Redirect("/group/" + group.id);
if (app.user != null) {
try{
WaitingListService.canRegister(app.user,group);
}catch(e:tink.core.Error){
}catch(e:tink.core.Error){
throw Error("/group/" + group.id,e.message);
}
}
//build form
var form = new sugoi.form.Form("reg");
var form = new sugoi.form.Form("reg");
if (app.user == null){
form.addElement(new StringInput("userFirstName", t._("Your firstname"),"",true));
form.addElement(new StringInput("userLastName", t._("Your lastname") ,"",true));
form.addElement(new StringInput("userEmail", t._("Your e-mail"), "", true));
form.addElement(new StringInput("userEmail", t._("Your e-mail"), "", true));
}
form.addElement(new sugoi.form.elements.TextArea("msg", t._("Leave a message")));
form.addElement(new sugoi.form.elements.TextArea("msg", t._("Leave a message"),"",false));
if (form.isValid()){
try{
if (app.user == null){
var f = form;
var user = service.UserService.softRegistration(f.getValueOf("userFirstName"),f.getValueOf("userLastName"), f.getValueOf("userEmail") );
db.User.login(user, user.email);
}
db.User.login(user, user.email);
}
WaitingListService.registerToWl(app.user,group,form.getValueOf("msg"));
throw Ok("/group/" + group.id,t._("Your subscription to the waiting list has been recorded. You will receive an e-mail as soon as your request is processed.") );
}catch(e:tink.core.Error){
throw Error("/group/list/" + group.id,e.message);
}
}
view.title = t._("Subscription to \"::groupeName::\" waiting list", {groupeName:group.name});
view.form = form;
view.form = form;
}
/**
@@ -82,27 +82,27 @@ class Group extends controller.Controller
function doListCancel(group:db.Amap){
try{
WaitingListService.removeFromWl(app.user,group);
}catch(e:tink.core.Error){
}catch(e:tink.core.Error){
throw Error("/group/" + group.id,e.message);
}
throw Ok("/group/" + group.id,t._("You've been removed from the waiting list"));
}
/**
* Register direclty in an open group
*
*
* the user can be logged or not !
@tpl('form.mtt')
function doRegister(group:db.Amap){
if (group.regOption != db.Amap.RegOption.Open) throw Redirect("/group/" + group.id);
if (app.user != null){
if ( db.UserAmap.manager.select($amapId == group.id && $user == app.user) != null) throw Error("/group/" + group.id, t._("You are already member of this group."));
if (app.user != null){
if ( db.UserAmap.manager.select($amapId == group.id && $user == app.user) != null) throw Error("/group/" + group.id, t._("You are already member of this group."));
}
var form = new sugoi.form.Form("reg");
var form = new sugoi.form.Form("reg");
form.submitButtonLabel = t._("Join the group");
form.addElement(new sugoi.form.elements.Html("html",t._("Confirm your subscription to \"::groupName::\"", {groupName:group.name})));
if (app.user == null){
@@ -110,15 +110,15 @@ class Group extends controller.Controller
form.addElement(new StringInput("userLastName", t._("Your lastname"), "", true));
var em = new StringInput("userEmail", t._("Your e-mail"), "", true);
em.addValidator(new EmailValidator());
form.addElement(em);
form.addElement(new StringInput("address", t._("Address"), "", true));
form.addElement(new StringInput("zipCode", t._("Zip code"), "", true));
form.addElement(new StringInput("city", t._("City"), "", true));
form.addElement(new StringInput("phone", t._("Phone"), "", true));
form.addElement(em);
form.addElement(new StringInput("address", t._("Address"), "", true));
form.addElement(new StringInput("zipCode", t._("Zip code"), "", true));
form.addElement(new StringInput("city", t._("City"), "", true));
form.addElement(new StringInput("phone", t._("Phone"), "", true));
}
if (form.isValid()){
if (app.user == null){
var f = form;
var user = new db.User();
@@ -129,92 +129,92 @@ class Group extends controller.Controller
user.zipCode = f.getValueOf("zipCode");
user.city = f.getValueOf("city");
user.phone = f.getValueOf("phone");
if ( db.User.getSameEmail(user.email).length > 0 ) {
throw Ok("/user/login",t._("You already subscribed to Cagette.net, please log in on this page"));
}
user.insert();
user.insert();
app.session.setUser(user);
}
}
var w = new db.UserAmap();
w.user = app.user;
w.amap = group;
w.insert();
throw Ok("/user/choose", t._("Your subscription has been taken into account"));
}
view.title = t._("Subscription to \"::groupName::\"", {groupName:group.name});
view.form = form;
}*/
/**
* create a new group
*/
@tpl("form.mtt")
function doCreate() {
view.title = t._("Create a new Cagette Group");
var f = new sugoi.form.Form("c");
f.addElement(new StringInput("name", t._("Name of your group"), "", true));
//group type
var data = [
{label:t._("CSA"),value:"0"},
{label:t._("Grouped orders"),value:"1"},
{label:t._("Farmers collective"),value:"2"},
{label:t._("Farm shop"),value:"3"},
];
];
var gt = new sugoi.form.elements.RadioGroup("type", t._("Group type"), data ,"1","1",true,true,true);
f.addElement(gt);
if (f.checkToken()) {
var user = app.user;
var g = new db.Amap();
g.name = f.getValueOf("name");
g.contact = user;
var type:db.Amap.GroupType = Type.createEnumIndex(db.Amap.GroupType, Std.parseInt(f.getValueOf("type")) );
switch(type){
case null :
case null :
throw "unknown group type";
case db.Amap.GroupType.Amap :
case db.Amap.GroupType.Amap :
g.flags.set(db.Amap.AmapFlags.HasMembership);
g.regOption = db.Amap.RegOption.WaitingList;
case db.Amap.GroupType.GroupedOrders :
g.flags.set(db.Amap.AmapFlags.ShopMode);
g.flags.set(db.Amap.AmapFlags.HasMembership);
g.regOption = db.Amap.RegOption.WaitingList;
case db.Amap.GroupType.ProducerDrive :
case db.Amap.GroupType.ProducerDrive :
g.flags.set(db.Amap.AmapFlags.ShopMode);
g.regOption = db.Amap.RegOption.Open;
g.flags.set(db.Amap.AmapFlags.PhoneRequired);
case db.Amap.GroupType.FarmShop :
case db.Amap.GroupType.FarmShop :
g.flags.set(db.Amap.AmapFlags.ShopMode);
g.regOption = db.Amap.RegOption.Open;
g.flags.set(db.Amap.AmapFlags.PhoneRequired);
}
g.groupType = type;
g.insert();
var ua = new db.UserAmap();
ua.user = user;
ua.amap = g;
ua.rights = [db.UserAmap.Right.GroupAdmin,db.UserAmap.Right.Membership,db.UserAmap.Right.Messages,db.UserAmap.Right.ContractAdmin(null)];
ua.insert();
//example datas
var place = new db.Place();
place.name = t._("Market square");
@@ -222,7 +222,7 @@ class Group extends controller.Controller
place.city = "St Martin de la Cagette";
place.amap = g;
place.insert();
//contrat AMAP
var vendor = new db.Vendor();
vendor.amap = g;
@@ -230,8 +230,8 @@ class Group extends controller.Controller
vendor.zipCode = "000";
vendor.city = "Langon";
vendor.email = "jean@cagette.net";
vendor.insert();
vendor.insert();
if (type == Amap){
var contract = new db.Contract();
contract.name = t._("CSA contract Vegetables - Example");
@@ -244,32 +244,32 @@ class Group extends controller.Controller
contract.contact = user;
contract.distributorNum = 2;
contract.insert();
var p = new db.Product();
p.name = t._("Big basket of vegetables");
p.price = 15;
p.organic = true;
p.contract = contract;
p.insert();
var p = new db.Product();
p.name = t._("Small basket of vegetables");
p.price = 10;
p.organic = true;
p.contract = contract;
p.insert();
OrderService.make(user, 1, p, null, true);
var d = new db.Distribution();
d.contract = contract;
d.date = DateTools.delta(Date.now(), 1000.0 * 60 * 60 * 24 * 14);
d.end = DateTools.delta(d.date, 1000.0 * 60 * 90);
d.place = place;
d.insert();
}
//contrat variable
var vendor = new db.Vendor();
vendor.amap = g;
@@ -277,8 +277,8 @@ class Group extends controller.Controller
vendor.zipCode = "000";
vendor.city = t._("Bazas");
vendor.email = "galinette@cagette.net";
vendor.insert();
vendor.insert();
var contract = new db.Contract();
contract.name = t._("Chicken Contract - Example");
contract.description = t._("Example of contract with variable orders. It is allowed to order something else at every delivery.");
@@ -291,7 +291,7 @@ class Group extends controller.Controller
contract.distributorNum = 2;
contract.flags.set(db.Contract.ContractFlags.UsersCanOrder);
contract.insert();
var egg = new db.Product();
egg.name = t._("12 eggs");
egg.price = 5;
@@ -299,7 +299,7 @@ class Group extends controller.Controller
egg.organic = true;
egg.contract = contract;
egg.insert();
var p = new db.Product();
p.name = t._("Chicken");
//p.type = 2;
@@ -307,7 +307,7 @@ class Group extends controller.Controller
p.organic = true;
p.contract = contract;
p.insert();
var d = new db.Distribution();
d.contract = contract;
d.orderStartDate = Date.now();
@@ -316,36 +316,36 @@ class Group extends controller.Controller
d.end = DateTools.delta(d.date, 1000.0 * 60 * 90);
d.place = place;
d.insert();
OrderService.make(user, 2, egg, d.id);
OrderService.make(user, 1, p, d.id);
App.current.session.data.amapId = g.id;
app.session.data.newGroup = true;
throw Redirect("/");
}
view.form= f;
}
/**
Displays a google map in a popup
**/
@tpl('group/place.mtt')
public function doPlace(place:db.Place){
view.place = place;
//build adress for google maps
var addr = "";
if (place.address1 != null) addr += place.address1;
if (place.address2 != null) addr += ", " + place.address2;
if (place.zipCode != null) addr += " " + place.zipCode;
if (place.city != null) addr += " " + place.city;
view.addr = view.escapeJS(addr);
}
/**
* Groups map
*/
@@ -353,16 +353,16 @@ class Group extends controller.Controller
public function doMap(?args:{?lat:Float,?lng:Float,?address:String}){
view.container = "container-fluid";
//if no param is sent, focus on Paris
if (args == null || (args.address == null && args.lat == null && args.lng == null)){
args = {lat:48.855675, lng:2.3472365};
}
view.lat = args.lat;
view.lng = args.lng;
view.address = args.address;
view.address = args.address;
}
}
+85 -89
View File
@@ -1,4 +1,5 @@
package controller;
import db.Distribution;
import db.UserContract;
import haxe.Json;
@@ -9,149 +10,147 @@ import Common;
import tools.ArrayTool;
class Main extends Controller {
/**
* public pages
* public pages
*/
function doGroup(d:haxe.web.Dispatch){
function doGroup(d:haxe.web.Dispatch) {
d.dispatch(new controller.Group());
}
/**
Group homepage
Group homepage
**/
@tpl("home.mtt")
function doDefault() {
view.category = 'home';
var group = app.getCurrentGroup();
if ( app.user!=null && group == null) {
var group = app.getCurrentGroup();
if (app.user != null && group == null) {
throw Redirect("/user/choose");
}else if (app.user == null && (group==null || group.regOption!=db.Amap.RegOption.Open) ) {
throw Redirect("/user/login");
} else if (app.user == null && (group == null || group.regOption != db.Amap.RegOption.Open)) {
if (db.Amap.manager.get(1, false) != null)
throw Redirect("/group/1");
else
throw Redirect("/user/login");
}
view.amap = group;
//contract with open orders
// contract with open orders
var openContracts = Lambda.filter(group.getActiveContracts(), function(c) return c.isUserOrderAvailable());
view.openContracts = openContracts;
//register to become "distributor"
view.contractsWithDistributors = app.user==null ? [] : Lambda.filter(app.user.amap.getActiveContracts(), function(c) return c.distributorNum > 0);
//freshly created group
// register to become "distributor"
view.contractsWithDistributors = app.user == null ? [] : Lambda.filter(app.user.amap.getActiveContracts(), function(c) return c.distributorNum > 0);
// freshly created group
view.newGroup = app.session.data.newGroup == true;
var n = Date.now();
var now = new Date(n.getFullYear(), n.getMonth(), n.getDate(), 0, 0, 0);
var in3Month = DateTools.delta(now, 1000.0 * 60 * 60 * 24 * 30 * 3);
var distribs = MultiDistrib.getFromTimeRange(group,now,in3Month);
var distribs = MultiDistrib.getFromTimeRange(group, now, in3Month);
view.distribs = distribs;
//view functions
// view functions
view.getWhosTurn = function(orderId:Int, distrib:Distribution) {
return db.UserContract.manager.get(orderId, false).getWhosTurn(distrib);
}
//event for additionnal blocks on home page
// event for additionnal blocks on home page
var e = Blocks([], "home");
app.event(e);
view.blocks = e.getParameters()[0];
//message if phone is required
if(app.user!=null && app.user.amap.flags.has(db.Amap.AmapFlags.PhoneRequired) && app.user.phone==null){
app.session.addMessage(t._("Members of this group should provide a phone number. <a href='/account/edit'>Please click here to update your account</a>."),true);
// message if phone is required
if (app.user != null && app.user.amap.flags.has(db.Amap.AmapFlags.PhoneRequired) && app.user.phone == null) {
app.session.addMessage(t._("Members of this group should provide a phone number. <a href='/account/edit'>Please click here to update your account</a>."),
true);
}
}
//login and stuff
// login and stuff
function doUser(d:Dispatch) {
d.dispatch(new controller.User());
}
function doCron(d:Dispatch) {
d.dispatch(new controller.Cron());
}
/**
* JSON REST API Entry point
*/
function doApi(d:Dispatch) {
try {
d.dispatch(new controller.Api());
}catch (e:tink.core.Error){
//manage tink Errors (service errors)
} catch (e:tink.core.Error) {
// manage tink Errors (service errors)
sugoi.Web.setReturnCode(e.code);
Sys.print(Json.stringify( {error:{code:e.code,message:e.message,stack:e.exceptionStack}} ));
}catch (e:Dynamic){
//manage other errors
sugoi.Web.setReturnCode(500);
var stack = if ( App.config.DEBUG ) haxe.CallStack.toString(haxe.CallStack.exceptionStack()) else "";
Sys.print(Json.stringify({error: {code: e.code, message: e.message, stack: e.exceptionStack}}));
} catch (e:Dynamic) {
// manage other errors
sugoi.Web.setReturnCode(500);
var stack = if (App.config.DEBUG) haxe.CallStack.toString(haxe.CallStack.exceptionStack()) else "";
App.current.logError(e, stack);
Sys.print(Json.stringify( {error:{code:500,message : Std.string(e), stack:stack }} ));
Sys.print(Json.stringify({error: {code: 500, message: Std.string(e), stack: stack}}));
}
}
@tpl("cssDemo.mtt")
function doCssdemo() {
view.category = 'home';
}
@tpl("form.mtt")
function doInstall(d:Dispatch) {
d.dispatch(new controller.Install());
}
function doP(d:Dispatch) {
/*
* Invalid array access
Stack (ADMIN|DEBUG)
* Invalid array access
Stack (ADMIN|DEBUG)
Called from C:\HaxeToolkit\haxe\std/haxe/web/Dispatch.hx line 463
Called from controller/Main.hx line 117
*
var plugin = d.parts.shift();
for ( p in App.plugins) {
var n = Type.getClassName(Type.getClass(p)).toLowerCase();
n = n.split(".").pop();
if (plugin == n) {
d.dispatch( p.getController() );
return;
Called from C:\HaxeToolkit\haxe\std/haxe/web/Dispatch.hx line 463
Called from controller/Main.hx line 117
*
var plugin = d.parts.shift();
for ( p in App.plugins) {
var n = Type.getClassName(Type.getClass(p)).toLowerCase();
n = n.split(".").pop();
if (plugin == n) {
d.dispatch( p.getController() );
return;
}
}
}
throw Error("/","Plugin '"+plugin+"' introuvable.");
*/
throw Error("/","Plugin '"+plugin+"' introuvable.");
*/
d.dispatch(new controller.Plugin());
}
@logged
function doMember(d:Dispatch) {
view.category = 'members';
d.dispatch(new controller.Member());
}
@logged
function doStats(d:Dispatch) {
App.log('doStats called');
view.category = 'stats';
d.dispatch(new Stats());
}
function doMentions(d:Dispatch) {
App.log('do Mentions called');
view.category = 'mentions';
d.dispatch(new MentionsLegales());
}
@logged
function doAccount(d:Dispatch) {
view.category = 'account';
@@ -163,31 +162,31 @@ Called from controller/Main.hx line 117
view.category = 'contractadmin';
d.dispatch(new controller.Vendor());
}
@logged
function doPlace(d:Dispatch) {
view.category = 'contractadmin';
d.dispatch(new controller.Place());
}
@logged
function doTransaction(d:Dispatch) {
view.category = 'members';
d.dispatch(new controller.Transaction());
}
@logged
function doDistribution(d:Dispatch) {
view.category = 'contractadmin';
d.dispatch(new controller.Distribution());
}
@logged
function doMembership(d:Dispatch) {
view.category = 'members';
d.dispatch(new controller.Membership());
}
function doShop(d:Dispatch) {
view.category = 'shop';
d.dispatch(new controller.Shop());
@@ -199,63 +198,60 @@ Called from controller/Main.hx line 117
view.place = place;
view.date = date;
}
@logged
function doProduct(d:Dispatch) {
view.category = 'contractadmin';
d.dispatch(new controller.Product());
}
@logged
function doAmap(d:Dispatch) {
view.category = 'amap';
d.dispatch(new controller.Amap());
}
@logged
function doContract(d:Dispatch) {
view.category = 'contract';
d.dispatch(new Contract());
}
@logged
function doContractAdmin(d:Dispatch) {
view.category = 'contractadmin';
d.dispatch(new ContractAdmin());
}
@logged
function doMessages(d:Dispatch) {
view.category = 'messages';
d.dispatch(new Messages());
}
@logged
function doAmapadmin(d:Dispatch) {
view.category = 'amapadmin';
d.dispatch(new AmapAdmin());
}
@logged
function doValidate(date:Date, place:db.Place, user:db.User, d:haxe.web.Dispatch){
function doValidate(date:Date, place:db.Place, user:db.User, d:haxe.web.Dispatch) {
var v = new controller.Validate();
v.date = date;
v.place = place;
v.user = user;
v.user = user;
d.dispatch(v);
}
@admin
function doAdmin(d:Dispatch) {
d.dispatch(new controller.admin.Admin());
}
@admin
function doDb(d:Dispatch) {
d.parts = []; //disable haxe.web.Dispatch
d.parts = []; // disable haxe.web.Dispatch
sys.db.Admin.handler();
}
}
+397 -393
View File
File diff suppressed because it is too large Load Diff
+10
View File
@@ -0,0 +1,10 @@
package controller;
class MentionsLegales extends Controller {
public function new() {
super();
}
@tpl("mentions_legales/default.mtt")
function doDefault() {}
}
+128 -127
View File
@@ -1,211 +1,212 @@
package controller;
import db.Message;
import db.UserContract;
import sugoi.form.ListData;
import sugoi.form.elements.*;
import sugoi.form.Form;
class Messages extends Controller
{
public function new()
{
class Messages extends Controller {
public function new() {
super();
if (!app.user.canAccessMessages()) throw Redirect("/");
if (!app.user.canAccessMessages())
throw Redirect("/");
}
@tpl("messages/default.mtt")
function doDefault() {
var form = new Form("msg");
var form = new Form("msg");
var senderName = "";
var senderMail = "";
if (App.current.session.data.whichUser == 1 && app.user.email2 != null) {
senderMail = app.user.email2;
senderName = app.user.firstName2 + " " + app.user.lastName2;
}else {
} else {
senderMail = app.user.email;
senderName = app.user.firstName + " " + app.user.lastName;
}
var lists = getLists();
form.addElement( new StringInput("senderName", t._("Sender name"),senderName,true));
form.addElement( new StringInput("senderMail", t._("Sender E-Mail"),senderMail,true));
form.addElement( new StringSelect("list", t._("Recipients"),lists,"1", true,null,"style='width:500px;'"));
form.addElement( new StringInput("subject", t._("Subject:"),"",false,null,"style='width:500px;'") );
form.addElement( new TextArea("text", t._("Message:"), "", false, null, "style='width:500px;height:350px;'") );
form.addElement(new StringInput("senderName", t._("Sender name"), senderName, true));
form.addElement(new StringInput("senderMail", t._("Sender E-Mail"), senderMail, true));
form.addElement(new StringSelect("list", t._("Recipients"), lists, "3", true, null, "style='width:500px;'"));
form.addElement(new StringInput("subject", t._("Subject:"), "", true, null, "style='width:500px;'"));
form.addElement(new TextArea("text", t._("Message:"), "", false, null, "style='width:500px;height:350px;'"));
if (form.checkToken()) {
var listId = form.getElement("list").value;
var dest = getSelection(listId);
var subject = form.getValueOf("subject");
App.log('about to send email <$subject> to listId=<$listId> to ${dest.length} recipient(s)');
var mails = [];
for ( d in dest) {
if (d.email != null) mails.push(d.email);
if (d.email2 != null) mails.push(d.email2);
for (d in dest) {
if (d.email != null)
mails.push(d.email);
if (d.email2 != null)
mails.push(d.email2);
}
//send mail confirmation link
var e = new sugoi.mail.Mail();
e.setSubject(form.getValueOf("subject"));
for ( x in mails) e.addRecipient(x);
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 )
//e.setHeader("Sender", App.config.get("default_email"));
var text :String = form.getValueOf("text");
var html = app.processTemplate("mail/message.mtt", { text:text,group:app.user.amap,list:getListName(listId) });
// send mail confirmation link
var e = new sugoi.mail.Mail();
e.setSender(App.config.get("default_email"), t._("Cagette.net"));
e.setSubject('[cagettepei] $subject');
var replyTo = form.getValueOf("senderMail");
var replyToName = form.getValueOf("senderName");
if (replyTo != null)
e.setReplyTo(replyTo, replyToName);
for (x in mails)
e.addRecipient(x);
var text:String = form.getValueOf("text");
if (text == null)
text = "";
var html = app.processTemplate("mail/message.mtt", {text: text, group: app.user.amap, list: getListName(listId)});
e.setHtmlBody(html);
App.sendMail(e,app.user.getAmap(),listId,app.user);
//store message
App.log('text=[$text]');
App.log('html= ${e.getHtmlBody()}');
App.sendMail(e, app.user.getAmap(), listId, app.user);
// store message
var lm = new db.Message();
lm.amap = app.user.amap;
lm.recipients = Lambda.array(Lambda.map(e.getRecipients(), function(x) return x.email));
lm.amap = app.user.amap;
// lm.recipients = Lambda.array(Lambda.map(e.getRecipients(), function(x) return x.email));
lm.recipients = mails;
lm.title = e.getSubject();
lm.date = Date.now();
lm.body = e.getHtmlBody();
if (listId != null) lm.recipientListId = listId;
if (listId != null)
lm.recipientListId = listId;
lm.sender = app.user;
lm.insert();
throw Ok("/messages", t._("The message has been sent"));
}
view.form = form;
if (app.user.isAmapManager()) {
view.sentMessages = Message.manager.search($amap == app.user.amap && $recipientListId!=null, {orderBy:-date,limit:20}, false);
}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($amap == app.user.amap && $recipientListId != null, {orderBy: -date, limit: 20}, false);
} else {
view.sentMessages = Message.manager.search($sender == app.user && $recipientListId != null && $amap == app.user.amap, {orderBy: -date, limit: 20},
false);
}
}
@tpl("messages/message.mtt")
public function doMessage(msg:Message) {
if (!app.user.isAmapManager() && msg.sender.id != app.user.id) throw Error("/", t._("Non authorized access"));
if (!app.user.isAmapManager() && msg.sender.id != app.user.id)
throw Error("/", t._("Non authorized access"));
view.list = getListName(msg.recipientListId);
view.msg = msg;
//make status easier to display
var s = new Array<{email:String,success:String,failure:String}>();
// make status easier to display
var s = new Array<{email:String, success:String, failure:String}>();
/*if (msg.status != null){
for ( k in msg.status.keys()) {
var r = msg.getMailerResultMessage(k);
s.push({email:k,success:r.success,failure:r.failure});
}
}*/
view.status = s;
}
function getLists() :FormData<String>{
function getLists():FormData<String> {
var out = [
{value:'1', label: t._("Everyone")},
{value:'2', label: t._("The board: persons in charge + contracts + memberships")},
{value: '1', label: t._("Everyone")},
{value: '2', label: t._("The board: persons in charge + contracts + memberships")},
];
out.push( { value:'3', label: t._("TEST: me + spouse") } );
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")} );
var contracts = db.Contract.getActiveContracts(app.user.amap,true);
for ( c in contracts) {
var label = t._("Subscribers") + " " + c.toString();
out.push({value:'c'+c.id,label:label});
out.push({value: '3', label: t._("TEST: me + spouse")});
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")});
var contracts = db.Contract.getActiveContracts(app.user.amap, true);
for (c in contracts) {
var label = t._("Subscribers") + " " + c.toString();
out.push({value: 'c' + c.id, label: label});
}
return out ;
return out;
}
/**
* get list name from id
* @param listId
*/
function getListName(listId:String) {
var l = getLists();
for (ll in l) {
if (ll.value == listId) return ll.label;
if (ll.value == listId)
return ll.label;
}
return null;
}
function getSelection(listId:String) {
if (listId.substr(0, 1) == "c") {
//contrats
// contrats
var contract = Std.parseInt(listId.substr(1));
var pids = db.Product.manager.search($contractId == contract, false);
var pids = Lambda.map(pids, function(x) return x.id);
var up = db.UserContract.manager.search($productId in pids, false);
var users = [];
for ( order in up) {
for (order in up) {
if (!Lambda.has(users, order.user)) {
users.push(order.user);
users.push(order.user);
}
if (order.user2 != null && !Lambda.has(users, order.user2)) {
users.push(order.user2);
}
}
}
return users;
}else {
} else {
var out = [];
switch(listId) {
case "1":
//tout le monde
out = Lambda.array(app.user.amap.getMembers());
case "2":
var users = [];
users.push(app.user.amap.contact);
for ( c in db.Contract.manager.search($amap == app.user.amap)) {
if (!Lambda.has(users, c.contact)) {
users.push(c.contact);
switch (listId) {
case "1":
// tout le monde
out = Lambda.array(app.user.amap.getMembers());
case "2":
var users = [];
users.push(app.user.amap.contact);
for (c in db.Contract.manager.search($amap == app.user.amap)) {
if (!Lambda.has(users, c.contact)) {
users.push(c.contact);
}
}
}
//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))) {
if (ua.hasRight(GroupAdmin) || ua.hasRight(Membership) || ua.hasRight(ContractAdmin())) {
if (!Lambda.has(users, ua.user)) users.push(ua.user);
}
}
out = users;
case "3":
//moi
return [app.user];
case "4":
return Lambda.array(db.User.getUsers_NoContracts());
case "5":
return Lambda.array(db.User.getUsers_NoMembership());
// 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))) {
if (ua.hasRight(GroupAdmin) || ua.hasRight(Membership) || ua.hasRight(ContractAdmin())) {
if (!Lambda.has(users, ua.user))
users.push(ua.user);
}
}
out = users;
case "3":
// moi
return [app.user];
case "4":
return Lambda.array(db.User.getUsers_NoContracts());
case "5":
return Lambda.array(db.User.getUsers_NoMembership());
}
return out;
}
}
}
}
+114 -129
View File
@@ -1,4 +1,5 @@
package controller;
import haxe.crypto.Md5;
import sugoi.form.elements.Input;
import sugoi.form.Form;
@@ -7,200 +8,192 @@ import sugoi.form.elements.StringInput;
import sugoi.form.validators.EmailValidator;
import ufront.mail.*;
class User extends Controller
{
public function new()
{
class User extends Controller {
public function new() {
super();
}
@tpl("user/default.mtt")
function doDefault() {
}
function doDefault() {}
@tpl("user/login.mtt")
function doLogin() {
if (App.current.user != null) {
throw Redirect('/');
}
//if its needed to redirect after login
if (app.params.exists("redirect")){
// if its needed to redirect after login
if (app.params.exists("redirect")) {
view.redirect = app.params.exists("redirect");
}else{
} else {
view.redirect = "/";
}
}
/**
* Choose which group to connect to.
*/
@logged
@tpl("user/choose.mtt")
function doChoose(?args: { amap:db.Amap } ) {
if (app.user == null) throw t._("You are not connected");
function doChoose(?args:{amap:db.Amap}) {
if (app.user == null)
throw t._("You are not connected");
var amaps = db.UserAmap.manager.search($user == app.user, false);
if (amaps.length == 1 && !app.params.exists("show")) {
//qu'une amap
// qu'une amap
app.session.data.amapId = amaps.first().amap.id;
throw Redirect('/');
}else{
view.noGroup = true; //force template to not display current group
} else {
view.noGroup = true; // force template to not display current group
}
if (args!=null && args.amap!=null) {
//select a group
var which = app.session.data==null ? 0 : app.session.data.whichUser ;
if (args != null && args.amap != null) {
// select a group
var which = app.session.data == null ? 0 : app.session.data.whichUser;
app.session.data.order = null;
app.session.data.newGroup = null;
app.session.data.amapId = args.amap.id;
app.session.data.whichUser = which;
throw Redirect('/');
}
view.amaps = amaps;
view.wl = db.WaitingList.manager.search($user == app.user, false);
#if plugins
view.pros = pro.db.PUserCompany.getCompanies(app.user);
#end
}
function doLogout() {
App.current.session.delete();
throw Redirect('/');
}
/**
* Ask for password renewal by mail
* when password is forgotten
*/
@tpl("user/forgottenPassword.mtt")
function doForgottenPassword(?key:String, ?u:db.User){
//STEP 1
function doForgottenPassword(?key:String, ?u:db.User) {
// STEP 1
var step = 1;
var error : String = null;
var error:String = null;
var url = "/user/forgottenPassword";
//ask for mail
// ask for mail
var askmailform = new Form("askemail");
askmailform.addElement(new StringInput("email", t._("Please key-in your E-Mail address"),null,true));
//change pass form
askmailform.addElement(new StringInput("email", t._("Please key-in your E-Mail address"), null, true));
// change pass form
var chpassform = new Form("chpass");
var pass1 = new StringInput("pass1", t._("Your new password"),null,true);
var pass1 = new StringInput("pass1", t._("Your new password"), null, true);
pass1.password = true;
chpassform.addElement(pass1);
var pass2 = new StringInput("pass2", t._("Again your new password"),null,true);
var pass2 = new StringInput("pass2", t._("Again your new password"), null, true);
pass2.password = true;
chpassform.addElement(pass2);
var uid = new IntInput("uid","uid", u == null?null:u.id);
var uid = new IntInput("uid", "uid", u == null ? null : u.id);
uid.inputType = ITHidden;
chpassform.addElement(uid);
if (askmailform.isValid()) {
//STEP 2
//send password renewal email
// STEP 2
// send password renewal email
step = 2;
var email :String = askmailform.getValueOf("email");
var email:String = askmailform.getValueOf("email");
var user = db.User.manager.select(email == $email, false);
//could be user 2
if(user==null) user = db.User.manager.select(email == $email2, false);
//user not found
if (user == null) throw Error(url, t._("This E-mail is not linked to a known account"));
//create token
var token = haxe.crypto.Md5.encode("chp"+Std.random(1000000000));
// could be user 2
if (user == null)
user = db.User.manager.select(email == $email2, false);
// user not found
if (user == null)
throw Error(url, t._("This E-mail is not linked to a known account"));
// create token
var token = haxe.crypto.Md5.encode("chp" + Std.random(1000000000));
sugoi.db.Cache.set(token, user.id, 60 * 60 * 24 * 30);
var m = new sugoi.mail.Mail();
m.setSender(App.config.get("default_email"), t._("Cagette.net"));
m.setRecipient(email, user.name);
m.setSubject( "["+App.config.NAME+"] : "+t._("Password change"));
m.setHtmlBody( app.processTemplate('mail/forgottenPassword.mtt', { user:user, link:'http://' + App.config.HOST + '/user/forgottenPassword/'+token+"/"+user.id }) );
App.sendMail(m);
m.setSender(App.config.get("default_email"), t._("Cagette.net"));
m.setRecipient(email, user.name);
m.setSubject("[" + App.config.NAME + "] " + t._("Password change"));
m.setHtmlBody(app.processTemplate('mail/forgottenPassword.mtt',
{user: user, link: 'http://' + App.config.HOST + '/user/forgottenPassword/' + token + "/" + user.id}));
App.sendMail(m);
}
if (key != null && u!=null) {
//check key and propose to change pass
if (key != null && u != null) {
// check key and propose to change pass
step = 3;
if ( u.id == sugoi.db.Cache.get(key) ) {
if (u.id == sugoi.db.Cache.get(key)) {
view.form = chpassform;
}else {
} else {
error = t._("Invalid request");
}
}
if (chpassform.isValid()) {
//change pass
// change pass
step = 4;
if ( chpassform.getValueOf("pass1") == chpassform.getValueOf("pass2")) {
var uid = Std.parseInt( chpassform.getValueOf("uid") );
if (chpassform.getValueOf("pass1") == chpassform.getValueOf("pass2")) {
var uid = Std.parseInt(chpassform.getValueOf("uid"));
var user = db.User.manager.get(uid, true);
var pass = chpassform.getValueOf("pass1");
user.setPass(pass);
user.update();
var m = new sugoi.mail.Mail();
m.setSender(App.config.get("default_email"), t._("Cagette.net"));
m.setRecipient(user.email, user.name);
if(user.email2!=null) m.setRecipient(user.email2, user.name);
m.setSubject( "["+App.config.NAME+"] : "+t._("New password confirmed"));
m.setSender(App.config.get("default_email"), t._("Cagette.net"));
m.setRecipient(user.email, user.name);
if (user.email2 != null)
m.setRecipient(user.email2, user.name);
m.setSubject("[" + App.config.NAME + "] : " + t._("New password confirmed"));
var emails = [user.email];
if(user.email2!=null) emails.push(user.email2);
if (user.email2 != null)
emails.push(user.email2);
var params = {
user:user,
emails:emails.join(", "),
password:pass,
NAME:App.config.NAME
user: user,
emails: emails.join(", "),
password: pass,
NAME: App.config.NAME
}
m.setHtmlBody( app.processTemplate('mail/newPasswordConfirmed.mtt', params) );
App.sendMail(m);
}else {
m.setHtmlBody(app.processTemplate('mail/newPasswordConfirmed.mtt', params));
App.sendMail(m);
} else {
error = t._("You must key-in two times the same password");
}
}
if (step == 1) {
view.form = askmailform;
}
view.step = step;
view.error = error;
}
/**
* generate a custom key for transactionnal emails, valid during the current day
*/
//function getKey(m:db.User) {
//return haxe.crypto.Md5.encode(App.config.get("key")+m.email+(Date.now().getDate())).substr(0,12);
//}
// function getKey(m:db.User) {
// return haxe.crypto.Md5.encode(App.config.get("key")+m.email+(Date.now().getDate())).substr(0,12);
// }
@logged
@tpl("form.mtt")
function doDefinePassword(?key:String, ?u:db.User){
if (app.user.isFullyRegistred()) throw Error("/", t._("You already have a password"));
function doDefinePassword(?key:String, ?u:db.User) {
if (app.user.isFullyRegistred())
throw Error("/", t._("You already have a password"));
var form = new Form("definepass");
var pass1 = new StringInput("pass1", t._("Your new password"));
@@ -209,47 +202,39 @@ class User extends Controller
pass2.password = true;
form.addElement(pass1);
form.addElement(pass2);
if (form.isValid()) {
if ( form.getValueOf("pass1") == form.getValueOf("pass2")) {
if (form.getValueOf("pass1") == form.getValueOf("pass2")) {
app.user.lock();
app.user.setPass(form.getValueOf("pass1"));
app.user.update();
throw Ok('/', t._("Congratulations, your account is now protected by a password."));
}else {
form.addError( t._("You must key-in two times the same password"));
} else {
form.addError(t._("You must key-in two times the same password"));
}
}
view.form = form;
view.title = t._("Create a password for your account");
}
/**
* landing page when coming from an invitation
* @param k
*/
public function doValidate(k:String ) {
var uid = Std.parseInt(sugoi.db.Cache.get("validation" + k));
if (uid == null || uid==0) throw Error('/user/login', t._("Your invitation is invalid or expired ($k)"));
public function doValidate(k:String) {
var uid = Std.parseInt(sugoi.db.Cache.get("validation" + k));
if (uid == null || uid == 0)
throw Error('/user/login', t._("Your invitation is invalid or expired ($k)"));
var user = db.User.manager.get(uid, true);
db.User.login(user, user.email);
var groups = user.getAmaps();
if(groups.length>0) app.session.data.amapId = groups.first().id;
if (groups.length > 0)
app.session.data.amapId = groups.first().id;
sugoi.db.Cache.destroy("validation" + k);
throw Ok("/user/definePassword", t._("Congratulations ::userName::, your account is validated!", {userName:user.getName()}));
throw Ok("/user/definePassword", t._("Congratulations ::userName::, your account is validated!", {userName: user.getName()}));
}
}
}
+77 -108
View File
@@ -1,138 +1,122 @@
package controller.admin;
import haxe.web.Dispatch;
import Common;
class Admin extends Controller {
public function new() {
super();
view.category = 'admin';
//trigger a "Nav" event
// trigger a "Nav" event
var nav = new Array<Link>();
var e = Nav(nav,"admin");
var e = Nav(nav, "admin");
app.event(e);
view.nav = e.getParameters()[0];
}
@tpl("admin/default.mtt")
function doDefault() {
view.now = Date.now();
}
@tpl("admin/emails.mtt")
function doEmails() {
var browse = function(index:Int, limit:Int) {
return sugoi.db.BufferedMail.manager.search($sdate==null,{limit:[index,limit],orderBy:-cdate},false);
return sugoi.db.BufferedMail.manager.search($sdate == null, {limit: [index, limit], orderBy: -cdate}, false);
}
var count = sugoi.db.BufferedMail.manager.count($sdate==null);
view.browser = new sugoi.tools.ResultsBrowser(count,10,browse);
var count = sugoi.db.BufferedMail.manager.count($sdate == null);
view.browser = new sugoi.tools.ResultsBrowser(count, 10, browse);
view.num = count;
}
@tpl("form.mtt")
function doSmtp() {
var f = new sugoi.form.Form("emails");
var data = [
{label:"SMTP",value:"smtp"},
{label:"Mandrill API",value:"mandrill"},
];
var mailer = sugoi.db.Variable.get("mailer")==null ? "smtp" : sugoi.db.Variable.get("mailer");
var host = sugoi.db.Variable.get("smtp_host")==null ? App.config.get("smtp_host") : sugoi.db.Variable.get("smtp_host");
var port = sugoi.db.Variable.get("smtp_port")==null ? App.config.get("smtp_port") : sugoi.db.Variable.get("smtp_port");
var user = sugoi.db.Variable.get("smtp_user")==null ? App.config.get("smtp_user") : sugoi.db.Variable.get("smtp_user");
var pass = sugoi.db.Variable.get("smtp_pass")==null ? App.config.get("smtp_pass") : sugoi.db.Variable.get("smtp_pass");
f.addElement(new sugoi.form.elements.StringSelect("mailer", "Mailer", data, mailer ));
var data = [{label: "SMTP", value: "smtp"}, {label: "Mandrill API", value: "mandrill"},];
var mailer = sugoi.db.Variable.get("mailer") == null ? "smtp" : sugoi.db.Variable.get("mailer");
var host = sugoi.db.Variable.get("smtp_host") == null ? "<SMTP_SERVER>" : sugoi.db.Variable.get("smtp_host");
var port = sugoi.db.Variable.get("smtp_port") == null ? "587" : sugoi.db.Variable.get("smtp_port");
var user = sugoi.db.Variable.get("smtp_user") == null ? "" : sugoi.db.Variable.get("smtp_user");
var pass = sugoi.db.Variable.get("smtp_pass") == null ? "" : sugoi.db.Variable.get("smtp_pass");
f.addElement(new sugoi.form.elements.StringSelect("mailer", "Mailer", data, mailer));
f.addElement(new sugoi.form.elements.StringInput("smtp_host", "host", host));
f.addElement(new sugoi.form.elements.StringInput("smtp_port", "port", port));
f.addElement(new sugoi.form.elements.StringInput("smtp_user", "user", user));
f.addElement(new sugoi.form.elements.StringInput("smtp_pass", "pass", pass));
if (f.isValid()){
for ( k in ["mailer","smtp_host","smtp_port","smtp_user","smtp_pass"]){
if (f.isValid()) {
for (k in ["mailer", "smtp_host", "smtp_port", "smtp_user", "smtp_pass"]) {
sugoi.db.Variable.set(k, f.getValueOf(k));
}
throw Ok("/admin/emails", t._("Configuration updated") );
throw Ok("/admin/emails", t._("Configuration updated"));
}
view.title = t._("Email service configuration");
view.form = f;
}
function doPlugins(d:Dispatch) {
d.dispatch(new controller.admin.Plugins());
}
@tpl("admin/taxo.mtt")
function doTaxo(){
function doTaxo() {
view.categ = db.TxpCategory.manager.all();
}
/**
* Display errors logged in DB
*/
@tpl("admin/errors.mtt")
function doErrors( args:{?user: Int, ?like: String, ?empty:Bool} ) {
function doErrors(args:{?user:Int, ?like:String, ?empty:Bool}) {
view.now = Date.now();
view.u = args.user!=null ? db.User.manager.get(args.user,false) : null;
view.like = args.like!=null ? args.like : "";
view.u = args.user != null ? db.User.manager.get(args.user, false) : null;
view.like = args.like != null ? args.like : "";
var sql = "";
if( args.user!=null ) sql += " AND uid="+args.user;
//if( args.like!=null && args.like != "" ) sql += " AND error like "+sys.db.Manager.cnx.quote("%"+args.like+"%");
if (args.user != null)
sql += " AND uid=" + args.user;
// if( args.like!=null && args.like != "" ) sql += " AND error like "+sys.db.Manager.cnx.quote("%"+args.like+"%");
if (args.empty) {
sys.db.Manager.cnx.request("truncate table Error");
}
var errorsStats = sys.db.Manager.cnx.request("select count(id) as c, DATE_FORMAT(date,'%y-%m-%d') as day from Error where date > NOW()- INTERVAL 1 MONTH "+sql+" group by day order by day").results();
var errorsStats = sys.db.Manager.cnx.request("select count(id) as c, DATE_FORMAT(date,'%y-%m-%d') as day from Error where date > NOW()- INTERVAL 1 MONTH "
+ sql
+ " group by day order by day")
.results();
view.errorsStats = errorsStats;
view.browser = new sugoi.tools.ResultsBrowser(
sugoi.db.Error.manager.unsafeCount("SELECT count(*) FROM Error WHERE 1 "+sql),
20,
function(start, limit) { return sugoi.db.Error.manager.unsafeObjects("SELECT * FROM Error WHERE 1 "+sql+" ORDER BY date DESC LIMIT "+start+","+limit,false); }
);
view.browser = new sugoi.tools.ResultsBrowser(sugoi.db.Error.manager.unsafeCount("SELECT count(*) FROM Error WHERE 1 " + sql), 20,
function(start, limit) {
return sugoi.db.Error.manager.unsafeObjects("SELECT * FROM Error WHERE 1 " + sql + " ORDER BY date DESC LIMIT " + start + "," + limit, false);
});
}
@tpl("admin/graph.mtt")
function doGraph(?key:String,?year:Int,?month:Int){
function doGraph(?key:String, ?year:Int, ?month:Int) {
var from = new Date(year, month, 1, 0, 0, 0);
var to = new Date(year, month + 1, 0, 23, 59, 59);
var from = new Date(year,month,1,0,0,0);
var to = new Date(year,month+1,0,23,59,59);
if(app.params.exists("recompute")){
switch(key){
if (app.params.exists("recompute")) {
switch (key) {
case "basket":
for( d in 1...to.getDate()){
var _from = new Date(year,month,d,0,0,0);
var _to = new Date(year,month,d,23,59,59);
var value = db.Basket.manager.count($cdate>=_from && $cdate<=_to);
var g = db.Graph.record(key,value, _from );
for (d in 1...to.getDate()) {
var _from = new Date(year, month, d, 0, 0, 0);
var _to = new Date(year, month, d, 23, 59, 59);
var value = db.Basket.manager.count($cdate >= _from && $cdate <= _to);
var g = db.Graph.record(key, value, _from);
// trace(value,_from,g);
}
}
}
var data = db.Graph.getRange(key,from,to);
var data = db.Graph.getRange(key, from, to);
view.data = data;
view.from = from;
view.to = to;
@@ -140,84 +124,69 @@ class Admin extends Controller {
var averageValue = 0.0;
var total = 0.0;
for( d in data) total += d.value;
averageValue = total/data.length;
for (d in data)
total += d.value;
averageValue = total / data.length;
view.total = total;
view.averageValue = averageValue;
}
function doFixDistribValidation() {
function doFixDistribValidation() {
Sys.println("===== Liste des distributions ayant été re-validées ====<br>");
//Get the current group
// Get the current group
var group = app.user.amap;
//Get all the contracts for this given group
var contractIds = Lambda.map(group.getContracts(),function(x) return x.id);
//Get all the validated distributions for this given group
var validatedDistribs = db.Distribution.manager.search( ($contractId in contractIds) && $validated == true, {orderBy:date}, false);
for (distrib in validatedDistribs){
// Get all the contracts for this given group
var contractIds = Lambda.map(group.getContracts(), function(x) return x.id);
// Get all the validated distributions for this given group
var validatedDistribs = db.Distribution.manager.search(($contractId in contractIds) && $validated == true, {orderBy: date}, false);
for (distrib in validatedDistribs) {
service.PaymentService.validateDistribution(distrib);
Sys.println(distrib.toString() + "<br>");
}
Sys.println("===== Fin de la liste ====");
}
function doCheckDistribValidation() {
Sys.println("===== Liste des distributions validées ayant des opérations/paiements non validés ====<br>");
//Get the current group
// Get the current group
var group = app.user.amap;
//Get all the contracts for this given group
var contractIds = Lambda.map(group.getContracts(),function(x) return x.id);
//Get all the validated distributions for this given group
var validatedDistribs = db.Distribution.manager.search( ($contractId in contractIds) && $validated == true, {orderBy:date}, false);
for (distrib in validatedDistribs){
for (user in distrib.getUsers()){
// Get all the contracts for this given group
var contractIds = Lambda.map(group.getContracts(), function(x) return x.id);
// Get all the validated distributions for this given group
var validatedDistribs = db.Distribution.manager.search(($contractId in contractIds) && $validated == true, {orderBy: date}, false);
for (distrib in validatedDistribs) {
for (user in distrib.getUsers()) {
var basket = db.Basket.get(user, distrib.place, distrib.date);
if (basket == null || basket.isValidated()) continue;
for (order in basket.getOrders()){
if (basket == null || basket.isValidated())
continue;
for (order in basket.getOrders()) {
if (!order.paid) {
Sys.println(order.distribution.toString() + "<br>");
Sys.println(order.toString() + "<br>");
}
}
}
var operation = basket.getOrderOperation(false);
if (operation != null){
if (operation != null) {
if (operation.pending) {
Sys.println(distrib.toString() + "<br>");
Sys.println(operation.toString() + "<br>");
}
for ( payment in basket.getPayments()){
if (payment.pending){
for (payment in basket.getPayments()) {
if (payment.pending) {
Sys.println(distrib.toString() + "<br>");
Sys.println(payment.toString() + "<br>");
}
}
}
}
}
}
Sys.println("===== Fin de la liste ====");
}
}
+22 -29
View File
@@ -1,46 +1,39 @@
package form;
import sugoi.form.elements.RadioGroup;
import Common;
class ColorRadioGroup extends RadioGroup
{
public function new(name:String, label:String,selected:String)
{
class ColorRadioGroup extends RadioGroup {
public function new(name:String, label:String, selected:String) {
var data = [];
var i = 0;
for (c in db.CategoryGroup.COLORS) {
data.push( { value:Std.string(c), label:Std.string(i) } );
data.push({value: Std.string(c), label: Std.string(i)});
i++;
}
super(name, label, data, selected, "1", false, true);
}
override public function render():String
{
override public function render():String {
var s = "";
var n = parentForm.name + "_" +name;
var n = parentForm.name + "_" + name;
var c = 0;
if (data != null)
{
for (row in data)
{
var radio = "<input type=\"radio\" name=\""+n+"\" id=\""+n+c+"\" value=\"" + row.label + "\" " + (row.label == Std.string(value) ? "checked":"") +" />\n";
var img = "<div style='margin-right:16px;width:32px;height:32px;background:"+App.current.view.intToHex(Std.parseInt(row.value))+";'></div>";
s += "<label for=\"" + n+c + "\" class='checkbox' style='display: inline-block;'>"+radio + " "+img+" </label>";
if (data != null) {
for (row in data) {
var radio = "<input type=\"radio\" name=\"" + n + "\" id=\"" + n + c + "\" value=\"" + row.label + "\" "
+ (row.label == Std.string(value) ? "checked" : "") + " />\n";
var img = "<div style='margin-right:16px;width:32px;height:32px;background:"
+ App.current.view.intToHex(Std.parseInt(row.value))
+ ";'></div>";
s += "<label for=\"" + n + c + "\" class='checkbox' style='display: inline-block;'>" + radio + " " + img + " </label>";
c++;
}
}
}
return s;
}
}
}
+238 -238
View File
@@ -1,4 +1,5 @@
package service;
import Common;
import tink.core.Error;
@@ -6,10 +7,8 @@ import tink.core.Error;
* Order Service
* @author web-wizard,fbarbut
*/
class OrderService
{
static function canHaveFloatQt(product:db.Product):Bool{
class OrderService {
static function canHaveFloatQt(product:db.Product):Bool {
return product.hasFloatQt || product.wholesale || product.variablePrice;
}
@@ -20,42 +19,45 @@ class OrderService
* @param productId
*/
public static function make(user:db.User, quantity:Float, product:db.Product, ?distribId:Int, ?paid:Bool, ?user2:db.User, ?invert:Bool):db.UserContract {
var t = sugoi.i18n.Locale.texts;
if(product.contract.type==db.Contract.TYPE_VARORDER && distribId==null) throw "You have to provide a distribId";
if(quantity==null) throw "Quantity is null";
if (product.contract.type == db.Contract.TYPE_VARORDER && distribId == null)
throw "You have to provide a distribId";
if (quantity == null)
throw "Quantity is null";
//quantity
if ( !canHaveFloatQt(product) ){
if( !tools.FloatTool.isInt(quantity) ) {
throw new tink.core.Error(t._("Error : product \"::product::\" quantity should be integer",{product:product.name}));
// quantity
if (!canHaveFloatQt(product)) {
if (!tools.FloatTool.isInt(quantity)) {
throw new tink.core.Error(t._("Error : product \"::product::\" quantity should be integer", {product: product.name}));
}
}
//multiweight : make one row per product
if (product.multiWeight && quantity > 1.0){
if (product.multiWeight && quantity != Math.abs(quantity)) throw t._("multi-weighing products should be ordered only with integer quantities");
// multiweight : make one row per product
if (product.multiWeight && quantity > 1.0) {
if (product.multiWeight && quantity != Math.abs(quantity))
throw t._("multi-weighing products should be ordered only with integer quantities");
var o = null;
for ( i in 0...Math.round(quantity)){
for (i in 0...Math.round(quantity)) {
o = make(user, 1, product, distribId, paid, user2, invert);
}
}
return o;
}
//checks
if (quantity <= 0) return null;
//check for previous orders on the same distrib
// checks
if (quantity <= 0)
return null;
// check for previous orders on the same distrib
var prevOrders = new List<db.UserContract>();
if (distribId == null) {
prevOrders = db.UserContract.manager.search($product==product && $user==user, true);
}else {
prevOrders = db.UserContract.manager.search($product==product && $user==user && $distributionId==distribId, true);
prevOrders = db.UserContract.manager.search($product == product && $user == user, true);
} else {
prevOrders = db.UserContract.manager.search($product == product && $user == user && $distributionId == distribId, true);
}
//Create order object
// Create order object
var o = new db.UserContract();
o.product = product;
o.quantity = quantity;
@@ -66,214 +68,214 @@ class OrderService
o.user = user;
if (user2 != null) {
o.user2 = user2;
if (invert != null) o.flags.set(InvertSharedOrder);
if (invert != null)
o.flags.set(InvertSharedOrder);
}
if (paid != null) o.paid = paid;
if (distribId != null) o.distribution = db.Distribution.manager.get(distribId);
//cumulate quantities if there is a similar previous order
if (paid != null)
o.paid = paid;
if (distribId != null)
o.distribution = db.Distribution.manager.get(distribId);
// cumulate quantities if there is a similar previous order
if (prevOrders.length > 0 && !product.multiWeight) {
for (prevOrder in prevOrders) {
//if (!prevOrder.paid) {
o.quantity += prevOrder.quantity;
prevOrder.delete();
//}
// if (!prevOrder.paid) {
o.quantity += prevOrder.quantity;
prevOrder.delete();
// }
}
}
//create a basket object
if (distribId != null){
// create a basket object
if (distribId != null) {
var dist = o.distribution;
var basket = db.Basket.getOrCreate(user, dist.place, dist.date);
o.basket = basket;
var basket = db.Basket.getOrCreate(user, dist.place, dist.date);
o.basket = basket;
}
o.insert();
//Stocks
// Stocks
if (o.product.stock != null) {
var c = o.product.contract;
if (c.hasStockManagement()) {
//trace("stock for "+quantity+" x "+product.name);
// trace("stock for "+quantity+" x "+product.name);
if (o.product.stock == 0) {
if (App.current.session != null) {
App.current.session.addMessage(t._("There is no more '::productName::' in stock, we removed it from your order", {productName:o.product.name}), true);
App.current.session.addMessage(t._("There is no more '::productName::' in stock, we removed it from your order",
{productName: o.product.name}), true);
}
o.quantity -= quantity;
if ( o.quantity <= 0 ) {
if (o.quantity <= 0) {
o.delete();
return null;
return null;
}
}else if (o.product.stock - quantity < 0) {
} else if (o.product.stock - quantity < 0) {
var canceled = quantity - o.product.stock;
o.quantity -= canceled;
o.update();
if (App.current.session != null) {
var msg = t._("We reduced your order of '::productName::' to quantity ::oQuantity:: because there is no available products anymore", {productName:o.product.name, oQuantity:o.quantity});
var msg = t._("We reduced your order of '::productName::' to quantity ::oQuantity:: because there is no available products anymore",
{productName: o.product.name, oQuantity: o.quantity});
App.current.session.addMessage(msg, true);
}
o.product.lock();
o.product.stock = 0;
o.product.update();
App.current.event(StockMove({product:o.product, move:0 - (quantity - canceled) }));
}else {
App.current.event(StockMove({product: o.product, move: 0 - (quantity - canceled)}));
} else {
o.product.lock();
o.product.stock -= quantity;
o.product.update();
App.current.event(StockMove({product:o.product, move:0 - quantity}));
o.product.update();
App.current.event(StockMove({product: o.product, move: 0 - quantity}));
}
}
}
}
return o;
}
/**
* Edit an existing order (quantity)
*/
public static function edit(order:db.UserContract, newquantity:Float, ?paid:Bool , ?user2:db.User,?invert:Bool) {
public static function edit(order:db.UserContract, newquantity:Float, ?paid:Bool, ?user2:db.User, ?invert:Bool) {
var t = sugoi.i18n.Locale.texts;
order.lock();
//quantity
if (newquantity == null) newquantity = 0;
if ( !canHaveFloatQt(order.product) ){
if( !tools.FloatTool.isInt(newquantity) ) {
throw new tink.core.Error(t._("Error : product \"::product::\" quantity should be integer",{product:order.product.name}));
// quantity
if (newquantity == null)
newquantity = 0;
if (!canHaveFloatQt(order.product)) {
if (!tools.FloatTool.isInt(newquantity)) {
throw new tink.core.Error(t._("Error : product \"::product::\" quantity should be integer", {product: order.product.name}));
}
}
//paid
// paid
if (paid != null) {
order.paid = paid;
}else {
if (order.quantity < newquantity) order.paid = false;
} else {
if (order.quantity < newquantity)
order.paid = false;
}
//shared order
if (user2 != null){
order.user2 = user2;
if (invert == true) order.flags.set(InvertSharedOrder);
if (invert == false) order.flags.unset(InvertSharedOrder);
}else{
// shared order
if (user2 != null) {
order.user2 = user2;
if (invert == true)
order.flags.set(InvertSharedOrder);
if (invert == false)
order.flags.unset(InvertSharedOrder);
} else {
order.user2 = null;
order.flags.unset(InvertSharedOrder);
}
//stocks
var e : Event = null;
// stocks
var e:Event = null;
if (order.product.stock != null) {
var c = order.product.contract;
if (c.hasStockManagement()) {
if (newquantity < order.quantity) {
//on commande moins que prévu : incrément de stock
if (c.hasStockManagement()) {
if (newquantity < order.quantity) {
// on commande moins que prévu : incrément de stock
order.product.lock();
order.product.stock += (order.quantity-newquantity);
e = StockMove({product:order.product, move:0 - (order.quantity-newquantity) });
}else {
//on commande plus que prévu : décrément de stock
order.product.stock += (order.quantity - newquantity);
e = StockMove({product: order.product, move: 0 - (order.quantity - newquantity)});
} else {
// on commande plus que prévu : décrément de stock
var addedquantity = newquantity - order.quantity;
if (order.product.stock - addedquantity < 0) {
//stock is not enough, reduce order
// stock is not enough, reduce order
newquantity = order.quantity + order.product.stock;
if( App.current.session!=null) App.current.session.addMessage(t._("We reduced your order of '::productName::' to quantity ::oQuantity:: because there is no available products anymore", {productName:order.product.name, oQuantity:newquantity}), true);
e = StockMove({product:order.product, move: 0 - order.product.stock });
if (App.current.session != null)
App.current.session.addMessage(t._("We reduced your order of '::productName::' to quantity ::oQuantity:: because there is no available products anymore",
{
productName: order.product.name,
oQuantity: newquantity
}), true);
e = StockMove({product: order.product, move: 0 - order.product.stock});
order.product.lock();
order.product.stock = 0;
}else{
//stock is big enough
} else {
// stock is big enough
order.product.lock();
order.product.stock -= addedquantity;
e = StockMove({ product:order.product, move: 0 - addedquantity });
}
}
order.product.update();
}
e = StockMove({product: order.product, move: 0 - addedquantity});
}
}
order.product.update();
}
}
//update order
// update order
if (newquantity == 0) {
order.quantity = 0;
order.quantity = 0;
order.paid = true;
order.update();
}else {
} else {
order.quantity = newquantity;
order.update();
}
order.update();
}
App.current.event(e);
App.current.event(e);
return order;
}
/**
* Delete an order
*/
public static function delete(order:db.UserContract) {
var t = sugoi.i18n.Locale.texts;
if(order==null) throw new Error(t._("This order has already been deleted."));
order.lock();
if (order.quantity == 0) {
if (order == null)
throw new Error(t._("This order has already been deleted."));
order.lock();
if (order.quantity == 0) {
var contract = order.product.contract;
var user = order.user;
//Amap Contract
if ( contract.type == db.Contract.TYPE_CONSTORDERS ) {
// Amap Contract
if (contract.type == db.Contract.TYPE_CONSTORDERS) {
order.delete();
if( contract.amap.hasPayments() ){
if (contract.amap.hasPayments()) {
var orders = contract.getUserOrders(user);
if( orders.length == 0 ){
if (orders.length == 0) {
var operation = db.Operation.findCOrderTransactionFor(contract, user);
if(operation!=null) operation.delete();
if (operation != null)
operation.delete();
}
}
}
else { //Variable orders contract
//Get the basket for this user
} else { // Variable orders contract
// Get the basket for this user
var place = order.distribution.place;
var basket = db.Basket.get(user, place, order.distribution.date);
if( contract.amap.hasPayments() ){
var orders = basket.getOrders();
//Check if it is the last order, if yes then delete the related operation
if( orders.length == 1 && orders.first().id==order.id ){
var operation = db.Operation.findVOrderTransactionFor(order.distribution.getKey(), user, place.amap);
if(operation!=null) operation.delete();
}
if (contract.amap.hasPayments()) {
var orders = basket.getOrders();
// Check if it is the last order, if yes then delete the related operation
if (orders.length == 1 && orders.first().id == order.id) {
var operation = db.Operation.findVOrderTransactionFor(order.distribution.getKey(), user, place.amap);
if (operation != null)
operation.delete();
}
}
order.delete();
}
}
else {
} else {
throw new Error(t._("Deletion not possible: quantity is not zero."));
}
}
/**
@@ -285,21 +287,20 @@ class OrderService
var view = App.current.view;
var t = sugoi.i18n.Locale.texts;
for (o in orders) {
var x : UserOrder = cast { };
var x:UserOrder = cast {};
x.id = o.id;
x.userId = o.user.id;
x.userName = o.user.getCoupleName();
x.userEmail = o.user.email;
//shared order
if (o.user2 != null){
// shared order
if (o.user2 != null) {
x.userId2 = o.user2.id;
x.userName2 = o.user2.getCoupleName();
x.userEmail2 = o.user2.email;
}
//deprecated
// deprecated
x.productId = o.product.id;
x.productRef = o.product.ref;
x.productQt = o.product.qt;
@@ -308,161 +309,160 @@ class OrderService
x.productImage = o.product.getImage();
x.productHasFloatQt = o.product.hasFloatQt;
x.productHasVariablePrice = o.product.variablePrice;
//new way
// new way
x.product = o.product.infos();
x.product.price = o.productPrice;//do not use current price, but price of the order
x.product.price = o.productPrice; // do not use current price, but price of the order
x.quantity = o.quantity;
//smartQt
if (x.quantity == 0.0){
// smartQt
if (x.quantity == 0.0) {
x.smartQt = t._("Canceled");
}else if(x.productHasFloatQt || x.productHasVariablePrice || o.product.wholesale){
} else if (x.productHasFloatQt || x.productHasVariablePrice || o.product.wholesale) {
x.smartQt = view.smartQt(x.quantity, x.productQt, x.productUnit);
}else{
} else {
x.smartQt = Std.string(x.quantity);
}
//product name.
if ( x.productHasVariablePrice || x.productQt==null || x.productUnit==null ){
x.productName = o.product.name;
}else{
x.productName = o.product.name + " " + view.formatNum(x.productQt) +" "+ view.unit(x.productUnit,x.productQt>1);
// product name.
if (x.productHasVariablePrice || x.productQt == null || x.productUnit == null) {
x.productName = o.product.name;
} else {
x.productName = o.product.name + " " + view.formatNum(x.productQt) + " " + view.unit(x.productUnit, x.productQt > 1);
}
x.subTotal = o.quantity * o.productPrice;
var c = o.product.contract;
if ( o.feesRate!=0 ) {
x.fees = x.subTotal * (o.feesRate/100);
if (o.feesRate != 0) {
x.fees = x.subTotal * (o.feesRate / 100);
x.percentageName = c.percentageName;
x.percentageValue = o.feesRate;
x.total = x.subTotal + x.fees;
}else {
} else {
x.total = x.subTotal;
}
//flags
// flags
x.paid = o.paid;
x.invertSharedOrder = o.flags.has(InvertSharedOrder);
x.contractId = c.id;
x.contractName = c.name;
x.canModify = o.canModify();
x.canModify = o.canModify();
out.push(x);
}
return sort(out);
}
/**
* Confirms an order : create real orders from tmp orders in session
* @param order
*/
public static function confirmSessionOrder(tmpOrder:OrderInSession){
public static function confirmSessionOrder(tmpOrder:OrderInSession) {
var orders = [];
var user = db.User.manager.get(tmpOrder.userId);
for (o in tmpOrder.products){
for (o in tmpOrder.products) {
o.product = db.Product.manager.get(o.productId);
orders.push( make(user, o.quantity, o.product, o.distributionId) );
orders.push(make(user, o.quantity, o.product, o.distributionId));
}
App.current.event(MakeOrder(orders));
App.current.session.data.order = null;
App.current.session.data.order = null;
return orders;
}
/**
* Send an order-by-products report to the coordinator
*/
public static function sendOrdersByProductReport(d:db.Distribution){
public static function sendOrdersByProductReport(d:db.Distribution) {
var t = sugoi.i18n.Locale.texts;
var m = new sugoi.mail.Mail();
m.addRecipient(d.contract.contact.email , d.contract.contact.getName());
m.setSender(App.config.get("default_email"),"Cagette.net");
m.addRecipient(d.contract.contact.email, d.contract.contact.getName());
m.setSender(App.config.get("default_email"), t._("Cagette.net"));
m.setSubject('[${d.contract.amap.name}] Distribution du ${App.current.view.dDate(d.date)} (${d.contract.name})');
var orders = service.ReportService.getOrdersByProduct(d);
var html = App.current.processTemplate("mail/ordersByProduct.mtt", {
contract:d.contract,
distribution:d,
orders:orders,
formatNum:App.current.view.formatNum,
currency:App.current.view.currency,
dDate:App.current.view.dDate,
hHour:App.current.view.hHour,
group:d.contract.amap
} );
var html = App.current.processTemplate("mail/ordersByProduct.mtt", {
contract: d.contract,
distribution: d,
orders: orders,
formatNum: App.current.view.formatNum,
currency: App.current.view.currency,
dDate: App.current.view.dDate,
hHour: App.current.view.hHour,
group: d.contract.amap
});
m.setHtmlBody(html);
App.sendMail(m);
App.sendMail(m);
}
/**
* Order summary for a member
* WARNING : its for one distrib, not for a whole basket !
*/
public static function sendOrderSummaryToMembers(d:db.Distribution){
public static function sendOrderSummaryToMembers(d:db.Distribution) {
var t = sugoi.i18n.Locale.texts;
var title = '[${d.contract.amap.name}] Votre commande pour le ${App.current.view.dDate(d.date)} (${d.contract.name})';
for( user in d.getUsers() ){
for (user in d.getUsers()) {
var m = new sugoi.mail.Mail();
m.addRecipient(user.email , user.getName(),user.id);
if(user.email2!=null) m.addRecipient(user.email2 , user.getName(),user.id);
m.setSender(App.config.get("default_email"),"Cagette.net");
m.addRecipient(user.email, user.getName(), user.id);
if (user.email2 != null)
m.addRecipient(user.email2, user.getName(), user.id);
m.setSender(App.config.get("default_email"), t._("Cagette.net"));
m.setSubject(title);
var orders = prepare(d.contract.getUserOrders(user,d));
var orders = prepare(d.contract.getUserOrders(user, d));
var html = App.current.processTemplate("mail/orderSummaryForMember.mtt", {
contract: d.contract,
distribution: d,
orders: orders,
formatNum: App.current.view.formatNum,
currency: App.current.view.currency,
dDate: App.current.view.dDate,
hHour: App.current.view.hHour,
group: d.contract.amap
});
var html = App.current.processTemplate("mail/orderSummaryForMember.mtt", {
contract:d.contract,
distribution:d,
orders:orders,
formatNum:App.current.view.formatNum,
currency:App.current.view.currency,
dDate:App.current.view.dDate,
hHour:App.current.view.hHour,
group:d.contract.amap
} );
m.setHtmlBody(html);
App.sendMail(m);
}
}
public static function sort(orders:Array<UserOrder>){
//order by lastname (+lastname2 if exists), then contract
public static function sort(orders:Array<UserOrder>) {
// order by lastname (+lastname2 if exists), then contract
orders.sort(function(a, b) {
if (a.userName + a.userId + a.userName2 + a.userId2 + a.contractId > b.userName + b.userId + b.userName2 + b.userId2 + b.contractId ) {
if (a.userName
+ a.userId
+ a.userName2
+ a.userId2
+ a.contractId > b.userName
+ b.userId
+ b.userName2
+ b.userId2
+ b.contractId) {
return 1;
}
if (a.userName + a.userId + a.userName2 + a.userId2 + a.contractId < b.userName + b.userId + b.userName2 + b.userId2 + b.contractId ) {
if (a.userName
+ a.userId
+ a.userName2
+ a.userId2
+ a.contractId < b.userName
+ b.userId
+ b.userName2
+ b.userId2
+ b.contractId) {
return -1;
}
return 0;
});
return orders;
}
}
}
+7 -5
View File
@@ -13,6 +13,8 @@ class WaitingListService{
var w = new db.WaitingList();
w.user = user;
w.group = group;
if (message == null ) message = "";
//message = "souscription = " + message;
w.message = message;
w.insert();
@@ -39,7 +41,7 @@ class WaitingListService{
public static function canRegister(user:db.User,group:db.Amap){
var t = sugoi.i18n.Locale.texts;
if ( db.WaitingList.manager.select($amapId == group.id && $user == user) != null) {
throw new Error(t._("You are already in the waiting list of this group"));
}
@@ -91,7 +93,7 @@ class WaitingListService{
group
);
}
wl.delete();
}
@@ -108,9 +110,9 @@ class WaitingListService{
var ua = new db.UserAmap();
ua.amap = wl.group;
ua.user = wl.user;
ua.insert();
ua.insert();
}
wl.delete();
//email the requester
@@ -131,7 +133,7 @@ class WaitingListService{
group
);
}
wl.delete();
}
+434
View File
@@ -0,0 +1,434 @@
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();
var PATH = Web.getCwd() + "../";
var json = haxe.Json.parse(sys.io.File.getContent(PATH + "package.json"));
App.VERSION = json.version;
App.log('configuration has been reloaded, VERSION=${App.VERSION}');
}
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");
}
}
+165
View File
@@ -0,0 +1,165 @@
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.BANDEAU_REGION = App.config.BANDEAU_REGION;
this.NAME = App.config.NAME;
this.isAdmin = app.user != null && app.user.isAdmin();
//Access basic functions in views
this.Std = Std;
this.Math = Math;
this.Config = App.config;
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;
}
}
+64
View File
@@ -0,0 +1,64 @@
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 BANDEAU_REGION: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() + "../";
xml = Xml.parse(sys.io.File.getContent(PATH + "config.xml")).firstElement();
NAME = get("name", "cagettepei");
LANG = get("lang", "fr");
LANGS = get("langs", "fr").split(";");
TPL = PATH + "lang/" + LANG + "/tpl/";
TPL_TMP = PATH + "lang/" + LANG + "/tmp/";
DEBUG = get("debug", "0") == "1";
BANDEAU_REGION = get("bandeau_region", "0") == "1";
HOST = get("host");
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();
}
}

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