Compare commits

...
55 Commits
Author SHA1 Message Date
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
132 changed files with 10978 additions and 2412 deletions
+3 -2
View File
@@ -25,13 +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
config-dev.xml
config-prod.xml
+1 -1
View File
@@ -1 +1 @@
1.8.1
1.13.6
+7 -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,15 +64,16 @@ 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
+66 -50
View File
@@ -1,69 +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 -L https://deb.nodesource.com/setup_8.x | bash -
* apt install -y nodejs=8.17.0-1nodesource1
* apt install -y sudo haxe apache2 make git imagemagick gettext libapache2-mod-neko mariadb-server
* mysql -u root <<EOF
## 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"
/>
```
@@ -72,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
--------------
@@ -98,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"/>
+53 -138
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,44 +98,47 @@
::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 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="http://www.cagette.net/wiki" target="_blank">::_("Documentation")::</a>
<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>
@@ -148,62 +147,23 @@
<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>
<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='250'></img>
::end::
</div>
<div class="row">
<div class="col-md-12">
<div class="col-md-12">
<!-- SQL LOG -->
<table class="sqlLog table table-bordered" ::cond sqlLog::>
::foreach r sqlLog::
@@ -218,50 +178,5 @@
</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::
+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::-->
+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>
+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.8.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.13.6"}
+36 -30
View File
@@ -1,5 +1,4 @@
import db.User;
import thx.semver.Version;
import Common;
class App extends sugoi.BaseApp {
@@ -10,13 +9,7 @@ class App extends sugoi.BaseApp {
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 static var VERSION = "cagette-pei"; // empty yet, cf.BaseApp.loadConfig
public function new() {
super();
@@ -77,10 +70,14 @@ class App extends sugoi.BaseApp {
}
public static function log(t:Dynamic, ?infos:haxe.PosInfos) {
// if (App.config.DEBUG) {
// neko.Web.logMessage(Std.string(t)); // write in Apache error log
// }
neko.Web.logMessage('[${infos.fileName}:${infos.lineNumber} ${infos.className}#${infos.methodName}] ' + Std.string(t));
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) {
@@ -206,7 +203,7 @@ class App extends sugoi.BaseApp {
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");
@@ -230,24 +227,34 @@ class App extends sugoi.BaseApp {
}
public static function getMailer():sugoi.mail.IMailer {
var mailer:sugoi.mail.IMailer = new sugoi.mail.BufferedMailer();
var typeOfMailer = sugoi.db.Variable.get("mailer");
App.log('type of mailer is <$typeOfMailer>');
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 (App.config.DEBUG) {
// Dev env : emails are written to tmp folder
mailer = new sugoi.mail.DebugMailer();
} else {
if (sugoi.db.Variable.get("mailer") == null) {
var msg = sugoi.i18n.Locale.texts._("Please configure the email settings in a <href='/admin/emails'>this section</a>");
throw sugoi.ControllerAction.ErrorAction("/", msg);
}
App.log('mailer in DEBUG mode, no deferred mail from CRON');
if (sugoi.db.Variable.get("mailer") == "mandrill") {
// Buffered emails with Mandrill
untyped mailer.defineFinalMailer("mandrill");
} else {
// Buffered emails with SMTP
untyped mailer.defineFinalMailer("smtp");
}
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;
}
@@ -265,7 +272,6 @@ class App extends sugoi.BaseApp {
App.log('about to send email with subject=<${m.getSubject()}> ...');
getMailer().send(m, params, function(o) {});
App.log('email from=<${m.getSender().email}>, subject=<${m.getSubject()}> successfully sent');
}
public static function quickMail(to:String, subject:String, html:String, ?group:db.Amap) {
@@ -274,7 +280,7 @@ class App extends sugoi.BaseApp {
var e = new sugoi.mail.Mail();
e.setSubject(subject);
e.setRecipient(to);
e.setSender(App.config.get("default_email"), "Cagette.net");
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);
+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"));
}
}
-2
View File
@@ -19,8 +19,6 @@ class ContractAdmin extends Controller {
if (!app.user.isContractManager())
throw Error("/", t._("You don't have the authorization to manage contracts"));
view.nav = ["contractadmin"];
App.log("first message");
}
public function sendNav(c) {
+3 -5
View File
@@ -77,7 +77,7 @@ class Cron extends Controller {
}
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()}));
@@ -306,7 +306,7 @@ class Cron extends Controller {
try {
var m = new Mail();
m.setSender(App.config.get("default_email"), "Cagette.net");
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());
@@ -444,7 +444,6 @@ class Cron extends Controller {
*/
function sendEmailsfromBuffer() {
App.log("Send Emails from Buffer");
print("<h3>Send Emails from Buffer</h3>");
// send
for (e in sugoi.db.BufferedMail.manager.search($sdate == null, {limit: 50, orderBy: -cdate}, false)) {
@@ -452,8 +451,7 @@ class Cron extends Controller {
if (e.isSent())
continue;
App.log('Send Email id=${e.id} - title=${e.title}');
print('#${e.id} - ${e.title}');
App.log('Send Email id=#${e.id} - title=${e.title}');
e.finallySend();
Sys.sleep(0.1);
}
+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;
}
}
+51 -48
View File
@@ -9,10 +9,10 @@ import Common;
import tools.ArrayTool;
class Main extends Controller {
/**
* public pages
* public pages
*/
function doGroup(d:haxe.web.Dispatch){
d.dispatch(new controller.Group());
@@ -24,23 +24,26 @@ class Main extends Controller {
@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");
if (db.Amap.manager.get(1, false) != null) throw Redirect("/group/1");
else throw Redirect("/user/login");
}
view.amap = group;
//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
view.newGroup = app.session.data.newGroup == true;
@@ -50,12 +53,12 @@ class Main extends Controller {
var distribs = MultiDistrib.getFromTimeRange(group,now,in3Month);
view.distribs = distribs;
//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
var e = Blocks([], "home");
app.event(e);
@@ -67,21 +70,21 @@ class Main extends Controller {
}
}
//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());
@@ -91,38 +94,38 @@ class Main extends Controller {
//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);
//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 }} ));
}
}
@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)
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();
@@ -132,26 +135,26 @@ Called from controller/Main.hx line 117
return;
}
}
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) {
view.category = 'stats';
d.dispatch(new Stats());
}
@logged
function doAccount(d:Dispatch) {
view.category = 'account';
@@ -163,31 +166,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 +202,63 @@ 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){
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
sys.db.Admin.handler();
}
}
+397 -393
View File
File diff suppressed because it is too large Load Diff
+18 -14
View File
@@ -31,16 +31,16 @@ class Messages extends Controller {
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 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 emails to listId=<$listId> to destination=${dest.length}');
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)
@@ -51,28 +51,32 @@ class Messages extends Controller {
// send mail confirmation link
var e = new sugoi.mail.Mail();
e.setSubject(form.getValueOf("subject"));
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);
e.setSender(App.config.get("default_email"), form.getValueOf("senderName"));
e.setReplyTo(form.getValueOf("senderMail"), form.getValueOf("senderName"));
App.log('add CC header to message: ${e.getSubject()}');
e.setHeader("CC", "pvincent@comptoirduvrac.re");
// 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");
if (text == null)
text = "";
var html = app.processTemplate("mail/message.mtt", {text: text, group: app.user.amap, list: getListName(listId)});
e.setHtmlBody(html);
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.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();
+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();
}
}
+102
View File
@@ -0,0 +1,102 @@
package mt.net.helper;
import mt.Compat;
/**
* BreadCrumb (fil d'ariane)
*
* For breadcrumb translation,
* create "breadcrumb_*" entries in the translation file for each section.
*
* @author fbarbut
**/
class BreadCrumb{
public var lang : String;
public var sections : Array<String>; /* raw sections like "game/tetris/highscores" */
public var translation : Hash<String>; /* translated sections */
public function new(uri:String,?translator:String->String) {
sections = [];
translation = new Hash<String>();
//remove GET params
uri = uri.split('?')[0];
if( uri.substr(0,1) == "/" )
uri = uri.substr(1);
var uri = uri.split('/');
/*modify*/
for (i in 0...uri.length) {
var u = uri[i];
if(u == null)
continue;
if(Std.parseInt(u) != null) {
uri[i] = null;
continue;
}
if(u == 'index.n' || u == '') {
u = uri[i] = "home";
continue;
}
}
/*remove nulls*/
uri = Lambda.array(Lambda.filter(uri,function(e) return e!=null));
sections = uri;
/*translation*/
if(translator != null) {
for(s in sections) {
var name = translator("breadcrumb_" + s);
if(name != "#breadcrumb_" + s + "#") {
translation.set(s, name);
}else {
//no translation
translation.set(s, s);
}
}
}
}
public function toString(?mode=1, ?format:String):Dynamic {
switch(mode) {
case 1 :
/* raw sections */
return sections;
case 2 :
/* translated sections */
return getTranslatedSections();
}
return null;
//if(format != null) {
//format = StringTools.replace(format, "::rank::", Std.string(rank));
//format = StringTools.replace(format, "::suffix::", Std.string(suffix));
//return format;
//}else {
//return rank + suffix;
//}
}
public function getTranslatedSections() {
var out = [];
for(s in sections) {
out.push(translation.get(s));
}
return out;
}
}
+21
View File
@@ -0,0 +1,21 @@
package mt.net.helper;
class Helper {
/**
* Init all helpers in applicatio view.
*/
public static function init(context:Dynamic, viewHelpers:Array<Dynamic>) {
/* gros paté (c) ncannasse */
for(vh in viewHelpers) {
var methodName = Type.getClassName(Type.getClass(vh)).split(".").pop().toLowerCase();
var toString = Reflect.field(vh, "toString");
var nargs : Int = untyped $nargs(toString);
Reflect.setField(context, methodName , Reflect.makeVarArgs(function(p:Array<Dynamic>) {
while( p.length < nargs ) p.push(null);
return Reflect.callMethod(vh, toString, p);
}));
}
}
}
+113
View File
@@ -0,0 +1,113 @@
package mt.net.helper;
/**
* Ordinal number helper
*
* @author fbarbut
**/
class Ordinal /*implements IHelper*/{
public var lang : String;
public static var AVAILABLE_LANGS = ['fr','en','es'];
public function new(_lang:String) {
lang = _lang.toLowerCase();
if(!Lambda.has(AVAILABLE_LANGS, lang)) lang = 'en';
}
/**
* Get output, like "1st" or "2nd"
* @param rank number/rank starting from 1
* @param ?sex 1:male,2:female, 3: neutral (german)
* @param ?format text format like "::rank::<span>::suffix::</span>"
*/
public function toString(rank:Int, ?sex:Int, ?format:String):String {
var suffix = "";
if(sex == null) sex = 1;
switch(lang) {
case "fr":
if(rank == 1) {
suffix = (sex==1)?"er":"ère";
}else {
suffix = "ème";
}
case "es":
//http://reglasdeortografia.com/numerales.html
suffix = (sex == 1)?"o":"a";
case "de":
switch(sex) {
case 1 :
suffix = "ter";
case 2 :
suffix = "te";
default :
suffix = "tes";
}
default:
//"en":
if(rank == 1) {
suffix = "st";
}else if(rank == 2) {
suffix = "nd";
}else if(rank == 3) {
suffix = "rd";
}else {
suffix = "th";
}
/*DE
*
* Karsten :
Hihi. Un peu compliqué pour l'Allemand comme les suffix/mots dépendend de nombre exacte. ...en plus il y un troisième sexe.
Je propose tu utilise ce modèle qui fonctionne toujours: > X : X.
Voici le modèle complète :
> masculin:
> X = 1 : Erster
> X = 2 : Zweiter
> X = 3 : Dritter
> X = 4-19 : Xter
> X = 20-1000 : Xster
... + : Les derniers 3 chiffres décident de nouveau (comme indiqué anvant)
p.ex. 232456 = Y[232]X[456]
> neutre :
> X = 1 : Erstes
> X = 2 : Zweites
> X = 3 : Drittes
> X = 4-19 : Xtes
> X = 20-1000 : Xstes
... + : Les derniers 3 chiffres décident de nouveau (comme indiqué anvant)
p.ex. 232456 = Y[232]X[456]
> feminin :
> X = 1 : Erste
> X = 2 : Zweite
> X = 3 : Dritte
> X = 4-19 : Xte
> X = 20-1000 : Xste
... + : Les derniers 3 chiffres décident de nouveau (comme indiqué anvant)
p.ex. 232456 = Y[232]X[456]>
* */
}
if(format != null) {
format = StringTools.replace(format, "::rank::", Std.string(rank));
format = StringTools.replace(format, "::suffix::", Std.string(suffix));
return format;
}else {
return rank + suffix;
}
}
}
+8
View File
@@ -0,0 +1,8 @@
C'est quoi un helper ( ou view helper ? )
C'est un petit composant qui permet de rendre en html des éléments complexes, difficiles à rendre en macro templo.
Utiliser Helper.init() avec le Context d'une application et une liste de helpers pour les rendres disponible dans le Context de l'application web.
Inspiré de http://framework.zend.com/manual/1.12/en/zend.view.helpers.html
+166
View File
@@ -0,0 +1,166 @@
package sugoi.helper;
/**
* Simple class to print a HTML table
*
* @author fbarbut
*
* It should be able to print a lot of structures :
* - a spod request result
* - arrays, lists
* - anonymous objects
*
* usage :
* var t = new Table();
* t.title = "the table";
* t.setContent( [{toto:1,tata:2},{toto:4,tata:7}] );
* neko.Lib.print( t.toString(); );
**/
class Table{
//table content
public var title : String;
public var head : Array<String>;
public var content : Array<Array<Dynamic>>;
//table CSS class
public var tableCSSClass : String;
public function new(?defaultCss:String) {
if(defaultCss != null) tableCSSClass = defaultCss;
}
public function toString(?param:Dynamic):String {
if(param != null) {
setContent(param);
return go();
}else {
return "null";
}
}
public function go():String {
var output = "";
output += "<table " + (tableCSSClass != null?"class=\"" + tableCSSClass + "\"":"") +">";
if (title != null) {
var length = 0;
for(row in content) {
for(cell in row) {
length++;
}
break;
}
//alors là je comprends pas du tout du tout du tout pourquoi content[0].length ne marche pas...
//output += content[0]+ " "+Type.getClass(content[0])+" "+content[0].length ;
output += "<tr><th colspan='"+length+"'>" + title + "</th></tr>";
}
if (head != null) {
output += "<tr>";
for ( cell in head ) {
output += "<th>" + cell + "</th>";
}
output += "</tr>";
}
for (row in content) {
output += "<tr>";
for (cell in row) {
output += "<td>"+cell+"</td>";
}
output += "</tr>";
}
output += "</table>";
return output;
}
/**
* Iterable -> Array
*/
function fromIterableToArray<T>(iterable) {
var out = [];
var iterable : Iterable<T> = cast iterable;
for (el in iterable) {
out.push(el);
}
return out;
}
/**
* Reflectable -> Array
*/
function fromReflectableToArray<T>(reflectable) {
var out = [];
//get fields
for (field in Reflect.fields(reflectable) ) {
if(!Reflect.isFunction(Reflect.field(reflectable,field)) && field!="__cache__" && field!="__lock")
out.push(Reflect.field(reflectable,field));
}
return out;
}
public function toArray<T>(c:Dynamic) {
if (c.iterator!=null) { //Reflect.hasField(c, "iterator")
//trace("fromIterableToArray "+c);
return fromIterableToArray(c);
}else {
//trace("fromReflectableToArray "+c);
return fromReflectableToArray(c);
}
}
public function setContent<T>(c:Dynamic) {
//header
head = [];
//content
content = [];
var row = [];
//on essaye de voir si il y a une deuxieme dimension au tableau (ex : liste d'objets )
try{
for (obj in toArray(c) ) {
row = [];
for (prop in toArray(obj)) {
//get header
if(head.length==0){
for (prop in Reflect.fields(obj)) {
//if the field is not a function, lets add it :
if(!Reflect.isFunction(Reflect.field(obj,prop)) && prop!="__cache__")
head.push(prop);
}
}
row.push(prop);
}
content.push(row);
}
}catch(e :Dynamic) {
content = [];
head = ["field","value"];
//a priori c'est un objet simple pas une liste , donc on liste ses propriétés
for (field in Reflect.fields(c) ) {
var row = [];
if(!Reflect.isFunction(Reflect.field(c, field)) && field != "__cache__" && field != "__lock") {
row.push(field);
row.push(Reflect.field(c, field));
}
content.push(row);
}
}
return content;
}
}

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