test #48

Merged
juan merged 32 commits from test into dev 2024-02-15 12:32:01 +00:00
39 changed files with 436 additions and 230 deletions

2
Jenkinsfile vendored
View File

@ -38,7 +38,7 @@ pipeline {
} }
agent { agent {
docker { docker {
image 'registry.verdnatura.es/debuild:2.21.3-vn2' image 'registry.verdnatura.es/debuild:2.23.4-vn1'
registryUrl 'https://registry.verdnatura.es/' registryUrl 'https://registry.verdnatura.es/'
registryCredentialsId 'docker-registry' registryCredentialsId 'docker-registry'
args '-v /mnt/appdata/reprepro:/reprepro' args '-v /mnt/appdata/reprepro:/reprepro'

2
debian/changelog vendored
View File

@ -1,4 +1,4 @@
hedera-web (23.6.14) stable; urgency=low hedera-web (23.40.6) stable; urgency=low
* Initial Release. * Initial Release.

2
debian/links vendored
View File

@ -1,3 +1,3 @@
usr/share/hedera-web/hedera-web.php usr/bin/hedera-web.php usr/share/hedera-web/hedera-web.php usr/bin/hedera-web.php
etc/hedera-web/apache.conf etc/apache2/conf-available/hedera-web.conf etc/hedera-web/apache.conf etc/apache2/conf-available/hedera-web.conf
etc/hedera-web/php.ini etc/php/7.0/apache2/conf.d/99-hedera-web.ini etc/hedera-web/php.ini etc/php/8.2/apache2/conf.d/99-hedera-web.ini

View File

@ -7,79 +7,7 @@ export default new Class({
activate() { activate() {
this.$.userModel.setInfo('c', 'myClient', 'hedera'); this.$.userModel.setInfo('c', 'myClient', 'hedera');
this.$.userModel.setInfo('u', 'myUser', 'account'); this.$.userModel.setInfo('u', 'myUser', 'account');
this.$.changePassword.conn = this.conn
if (this.hash.$.verificationToken) this.$.changePassword.user = this.gui.user
this.onPassChangeClick();
}
,onPassChangeClick() {
this.$.oldPassword.value = '';
this.$.newPassword.value = '';
this.$.repeatPassword.value = '';
var verificationToken = this.hash.$.verificationToken;
this.$.oldPassword.style.display = verificationToken ? 'none' : 'block';
this.$.changePassword.show();
if (verificationToken)
this.$.newPassword.focus();
else
this.$.oldPassword.focus();
}
,async onPassModifyClick() {
const form = this.$.changePassword.node;
Vn.Node.disableInputs(form);
try {
const oldPassword = this.$.oldPassword.value;
const newPassword = this.$.newPassword.value;
const repeatedPassword = this.$.repeatPassword.value;
try {
if (newPassword == '' && repeatedPassword == '')
throw new Error(_('Passwords empty'));
if (newPassword !== repeatedPassword)
throw new Error(_('Passwords doesn\'t match'));
} catch (err) {
return Htk.Toast.showError(err.message);
}
const verificationToken = this.hash.$.verificationToken;
const params = {newPassword};
try {
if (verificationToken) {
params.verificationToken = verificationToken;
await this.conn.send('user/restore-password', params);
} else {
let userId = this.gui.user.id;
params.oldPassword = oldPassword;
await this.conn.patch(
`Accounts/${userId}/changePassword`, params);
}
} catch(err) {
Htk.Toast.showError(err.message);
if (verificationToken)
this.$.newPassword.select();
else
this.$.oldPassword.select();
return;
}
this.hash.unset('verificationToken');
await this.conn.open(this.gui.user.name, newPassword);
this.$.changePassword.hide();
} finally {
Vn.Node.disableInputs(form, false);
}
Htk.Toast.showMessage(_('Password changed!'));
}
,onPassInfoClick() {
this.$.passwordInfo.show();
} }
}); });

View File

@ -1,11 +1,5 @@
<vn> <vn>
<vn-group> <vn-group>
<db-form v-model="passwordForm">
<db-model property="model">
SELECT length, nAlpha, nUpper, nDigits, nPunct
FROM account.userPassword
</db-model>
</db-form>
<db-form id="user-form"> <db-form id="user-form">
<db-model property="model" id="user-model" updatable="true"> <db-model property="model" id="user-model" updatable="true">
SELECT u.id, u.name, u.email, u.nickname, SELECT u.id, u.name, u.email, u.nickname,
@ -27,7 +21,7 @@
<htk-bar-button <htk-bar-button
icon="lock_reset" icon="lock_reset"
tip="_Change password" tip="_Change password"
on-click="this.onPassChangeClick()"/> on-click="this.$.changePassword.open()"/>
</div> </div>
<div id="form" class="conf"> <div id="form" class="conf">
<div class="form box vn-w-sm vn-pa-lg"> <div class="form box vn-w-sm vn-pa-lg">
@ -74,64 +68,5 @@
</div> </div>
</div> </div>
</div> </div>
<htk-popup <htk-change-password id="change-password"/>
id="change-password"
modal="true">
<div property="child-node" class="htk-dialog vn-w-xs vn-pa-lg">
<div class="form">
<h5 class="vn-mb-md">
<t>Change password</t>
</h5>
<input
id="old-password"
type="password"
placeholder="_Old password"/>
<input
id="new-password"
type="password"
placeholder="_New password"
autocomplete="new-password"/>
<input
id="repeat-password"
type="password"
placeholder="_Repeat password"
autocomplete="new-password"/>
</div>
<div class="button-bar">
<button class="thin" on-click="this.onPassModifyClick()">
<t>Modify</t>
</button>
<button class="thin" on-click="this.onPassInfoClick()">
<t>Requirements</t>
</button>
<div class="clear"/>
</div>
</div>
</htk-popup>
<htk-popup
id="password-info"
modal="true">
<div property="child-node" class="htk-dialog pass-info vn-w-xs vn-pa-lg">
<h5 class="vn-mb-md">
<t>Password requirements</t>
</h5>
<ul>
<li>
{{passwordForm.length}} <t>characters long</t>
</li>
<li>
{{passwordForm.nAlpha}} <t>alphabetic characters</t>
</li>
<li>
{{passwordForm.nUpper}} <t>capital letters</t>
</li>
<li>
{{passwordForm.nDigits}} <t>digits</t>
</li>
<li>
{{passwordForm.nPunct}} <t>symbols</t>
</li>
</ul>
</div>
</htk-popup>
</vn> </vn>

View File

@ -7,8 +7,8 @@ export default new Class({
,activate() { ,activate() {
if (!this.hash.$.to) if (!this.hash.$.to)
this.hash.assign({ this.hash.assign({
from: new Date(), from: Date.vnNew(),
to: new Date() to: Date.vnNew()
}); });
} }
}); });

View File

@ -14,7 +14,7 @@ export default new Class({
,refreshCaptcha() { ,refreshCaptcha() {
params = { params = {
srv: 'rest:misc/captcha', srv: 'rest:misc/captcha',
stamp: new Date().getTime() stamp: Date.vnNew().getTime()
}; };
this.$.captchaImg.src = '?'+ Vn.Url.makeUri(params); this.$.captchaImg.src = '?'+ Vn.Url.makeUri(params);
} }

View File

@ -13,7 +13,8 @@ BecauseOurSalesDep: >-
Pour nos professionnels de service commercial qui sera toujours de trouver une Pour nos professionnels de service commercial qui sera toujours de trouver une
solution à vos besoins. solution à vos besoins.
BecauseOurWorkShop: Parce que nous avons un atelier de couture pour aider. BecauseOurWorkShop: Parce que nous avons un atelier de couture pour aider.
BecauseWeHaveWhatYouNeed: Parce que nous avons ce que vous avez besoin quand vous en avez besoin ... BecauseWeHaveWhatYouNeed: >-
Parce que nous avons ce que vous avez besoin quand vous en avez besoin ...
AboutDesc: >- AboutDesc: >-
Nous sommes une société spécialisée dans le commerce de gros et de la Nous sommes une société spécialisée dans le commerce de gros et de la
distribution d'une large gamme d'accessoires, des verts et des fleurs à des distribution d'une large gamme d'accessoires, des verts et des fleurs à des
@ -31,6 +32,6 @@ AboutDisp: >-
Mercaflor - Mercavalencia (Valencia) qui effectuent des ventes directes Mercaflor - Mercavalencia (Valencia) qui effectuent des ventes directes
seulement. seulement.
AboutOrder: >- AboutOrder: >-
Vous pouvez faire vos commandes et réservations par téléphone au +33 781 533 Vous pouvez faire vos commandes et réservations par téléphone au +33 783 285
900, en ligne grâce à notre site Internet ou directement dans nos 437, en ligne grâce à notre site Internet ou directement dans nos
installations. installations.

View File

@ -203,6 +203,12 @@ const Catalog = new Class({
this.hideMenu(); this.hideMenu();
} }
,itemRenderer(builder, form) {
var minQuantity = builder.$.minQuantity;
minQuantity.style.display = form.$.minQuantity
? 'block' : 'hidden';
}
,realmRenderer(builder, form) { ,realmRenderer(builder, form) {
var link = builder.$.link; var link = builder.$.link;
link.href = this.hash.make({ link.href = this.hash.make({
@ -273,6 +279,8 @@ const Catalog = new Class({
orderId: this.orderId orderId: this.orderId
}); });
this.$.cardPopup.show(event.currentTarget); this.$.cardPopup.show(event.currentTarget);
this.$.cardMinQuantity.style.display = form.$.minQuantity
? 'block' : 'none';
} }
,onAddLotClick(column, value, row) { ,onAddLotClick(column, value, row) {
@ -317,7 +325,7 @@ const Catalog = new Class({
} }
if (amountSum > 0) { if (amountSum > 0) {
this.conn.execQuery(sql); await this.conn.execQuery(sql);
var itemName = this.$.$card.get('item'); var itemName = this.$.$card.get('item');
Htk.Toast.showMessage( Htk.Toast.showMessage(

View File

@ -46,3 +46,4 @@ NoMoreAmountAvailable: No hi ha més quantitat disponible
MinimalGrouping: Empaquetat mínim MinimalGrouping: Empaquetat mínim
Available: Disponible Available: Disponible
GroupingPrice: Preu per grup GroupingPrice: Preu per grup
MinimalQuantity: Quantitat mínima

View File

@ -46,3 +46,4 @@ NoMoreAmountAvailable: No more amount available
MinimalGrouping: Minimal packing MinimalGrouping: Minimal packing
Available: Available Available: Available
GroupingPrice: Price per group GroupingPrice: Price per group
MinimalQuantity: Minimal quantity

View File

@ -46,3 +46,4 @@ NoMoreAmountAvailable: No hay más cantidad disponible
MinimalGrouping: Empaquetado mínimo MinimalGrouping: Empaquetado mínimo
Available: Disponible Available: Disponible
GroupingPrice: Precio per grupo GroupingPrice: Precio per grupo
MinimalQuantity: Cantidad mínima

View File

@ -46,3 +46,4 @@ NoMoreAmountAvailable: Pas plus disponible
MinimalGrouping: Emballage minimal MinimalGrouping: Emballage minimal
Available: Disponible Available: Disponible
GroupingPrice: Prix par groupe GroupingPrice: Prix par groupe
MinimalQuantity: Quantité minimum

View File

@ -46,3 +46,4 @@ NoMoreAmountAvailable: Não há mais quantidade disponível
MinimalGrouping: Embalagem mínima MinimalGrouping: Embalagem mínima
Available: Disponível Available: Disponível
GroupingPrice: Preço por grupo GroupingPrice: Preço por grupo
MinimalQuantity: Quantidade mínima

View File

@ -192,6 +192,25 @@
margin: 0; margin: 0;
margin-bottom: 1px; margin-bottom: 1px;
} }
& > .min-quantity {
bottom: 32px;
right: 0;
}
}
.min-quantity {
position: absolute;
display: none;
bottom: 30px;
right: 0;
font-size: .8rem;
color: #a44;
cursor: pointer;
& > span {
font-size: 16px;
vertical-align: middle;
}
} }
/* Tags */ /* Tags */
@ -266,7 +285,7 @@
flex: auto; flex: auto;
overflow: hidden; overflow: hidden;
margin: 10px; margin: 10px;
height: 170px; height: 185px;
& > h2 { & > h2 {
max-height: 3rem; max-height: 3rem;
@ -351,6 +370,7 @@
& > .top { & > .top {
padding: 14px; padding: 14px;
position: relative;
& > .item-info { & > .item-info {
margin-left: 126px; margin-left: 126px;
@ -371,6 +391,11 @@
margin-top: 15px 0; margin-top: 15px 0;
font-size: .9rem; font-size: .9rem;
} }
& > .min-quantity {
bottom: 0;
right: 0;
padding: 14px;
}
} }
& > .lots-grid { & > .lots-grid {
border-top: 1px solid #DDD; border-top: 1px solid #DDD;

View File

@ -115,8 +115,9 @@
WHERE #filter; WHERE #filter;
CALL myOrder_calcCatalogFull(#orderId); CALL myOrder_calcCatalogFull(#orderId);
SELECT i.id, i.longName item, i.subName, SELECT i.id, i.longName item, i.subName,
i.tag5, i.value5, i.tag6, i.value6, i.tag7, i.value7, i.tag5, i.value5, i.tag6, i.value6,
i.relevancy, i.size, i.category, i.tag7, i.value7, i.tag8, i.value8,
i.relevancy, i.size, i.category, i.minQuantity,
k.name ink, p.name producer, o.name origin, k.name ink, p.name producer, o.name origin,
b.available, b.price, b.`grouping`, b.available, b.price, b.`grouping`,
i.image, im.updated i.image, im.updated
@ -148,7 +149,8 @@
id="grid-view" id="grid-view"
empty-message="_Choose filter from right menu" empty-message="_Choose filter from right menu"
form-id="item" form-id="item"
model="items"> model="items"
renderer="itemRenderer">
<custom> <custom>
<div <div
id="item-box" id="item-box"
@ -186,6 +188,10 @@
<td>{{item.tag7}}</td> <td>{{item.tag7}}</td>
<td>{{item.value7}}</td> <td>{{item.value7}}</td>
</tr> </tr>
<tr>
<td>{{item.tag8}}</td>
<td>{{item.value8}}</td>
</tr>
</table> </table>
<div class="available-price"> <div class="available-price">
<span class="grouping" title="_MinimalGrouping"> <span class="grouping" title="_MinimalGrouping">
@ -198,6 +204,12 @@
{{Vn.Value.format(item.price, '%.02d€')}} {{Vn.Value.format(item.price, '%.02d€')}}
</span> </span>
</div> </div>
<div id="min-quantity" class="min-quantity" title="_MinimalQuantity">
<span class="htk-icon material-symbols-rounded">
production_quantity_limits
</span>
{{item.minQuantity}}
</div>
</div> </div>
</div> </div>
</custom> </custom>
@ -456,6 +468,12 @@
</tr> </tr>
</custom> </custom>
</htk-repeater> </htk-repeater>
<div id="card-min-quantity" class="min-quantity" title="_MinimalQuantity">
<span class="htk-icon material-symbols-rounded">
production_quantity_limits
</span>
{{card.minQuantity}}
</div>
</div> </div>
<htk-grid class="lots-grid" show-header="false"> <htk-grid class="lots-grid" show-header="false">
<db-model <db-model

View File

@ -8,7 +8,7 @@ export default new Class({
this.autoStepLocked = true; this.autoStepLocked = true;
this.$.assistant.stepsIndex = this.agencySteps; this.$.assistant.stepsIndex = this.agencySteps;
this.today = new Date(); this.today = Date.vnNew();
this.today.setHours(0, 0, 0, 0); this.today.setHours(0, 0, 0, 0);
}, },
@ -22,8 +22,8 @@ export default new Class({
let date; let date;
const row = orderForm.$ || defaultsForm.$ || {}; const row = orderForm.$ || defaultsForm.$ || {};
if (!date || date.getTime() < (new Date()).getTime()) { if (!date || date.getTime() < (Date.vnNew()).getTime()) {
date = new Date(); date = Date.vnNew();
date.setHours(0, 0, 0, 0); date.setHours(0, 0, 0, 0);
let addDays = 0; let addDays = 0;

View File

@ -6,7 +6,7 @@ export default new Class({
activate() { activate() {
this.$.lot.assign({ this.$.lot.assign({
date: new Date(), date: Date.vnNew(),
useIds: false useIds: false
}); });
}, },

View File

@ -65,3 +65,20 @@ Account: Compte
Addresses: Adreces Addresses: Adreces
Load an order: Si us plau carrega una comanda pendent a la cistella o en comença una de nova Load an order: Si us plau carrega una comanda pendent a la cistella o en comença una de nova
Old password: Contrasenya antiga
New password: Nova contrasenya
Repeat password: Repetir contrasenya
Requirements: Requisits
Modify: Modificar
Password requirements: Requisits de contrasenya
characters long: caràcters de longitud
alphabetic characters: caràcters alfabètics
capital letters: majúscules
digits: dígits
symbols: símbols
Password changed!: Contrasenya modificada!
Password doesn't meet the requirements: ''
Passwords doesn't match: Les contrasenyes no coincideixen!
Passwords empty: Les contrasenyes en blanc
Change password: Canvia la contrasenya

View File

@ -65,3 +65,22 @@ Account: Cuenta
Addresses: Direcciones Addresses: Direcciones
Load an order: Por favor carga un pedido pendiente en la cesta o empieza uno nuevo Load an order: Por favor carga un pedido pendiente en la cesta o empieza uno nuevo
Old password: Contaseña antigua
New password: Nueva contraseña
Repeat password: Repetir contraseña
Requirements: Requisitos
Modify: Modificar
Password requirements: Requisitos de constraseña
characters long: carácteres de longitud
alphabetic characters: carácteres alfabéticos
capital letters: letras mayúsculas
digits: dígitos
symbols: 'símbolos. Ej: $%&.'
Password changed!: ¡Contraseña modificada!
Password doesn't meet the requirements: >-
La nueva contraseña no reune los requisitos de seguridad necesarios, pulsa en
info para más detalle
Passwords doesn't match: ¡Las contraseñas no coinciden!
Passwords empty: Contraseña vacía
Change password: Cambiar contraseña

View File

@ -5,7 +5,7 @@ Remind me: Retenir mon mot de passe
Log in as guest: Entrez en tant qu'invité Log in as guest: Entrez en tant qu'invité
Login: Se connecter Login: Se connecter
Login mail: info@verdnatura.es Login mail: info@verdnatura.es
Login phone: +33 781 533 900 Login phone: +33 783 285 437
Password forgotten? Push here: Vous avez oublié votre mot de passe? Password forgotten? Push here: Vous avez oublié votre mot de passe?
Yet you are not a customer?: Vous n'êtes pas encore client? Yet you are not a customer?: Vous n'êtes pas encore client?
Sign up: S'inscrire Sign up: S'inscrire
@ -65,3 +65,20 @@ Account: Compte
Addresses: Adresses Addresses: Adresses
Load an order: Veuillez télécharger une commande en attente dans le panier ou en démarrer une nouvelle Load an order: Veuillez télécharger une commande en attente dans le panier ou en démarrer une nouvelle
Old password: Ancien mot de passe
New password: Nouveau mot de passe
Repeat password: Répéter le mot de passe
Requirements: Exigences
Modify: Modifier
Password requirements: Mot de passe exigences
characters long: Longs caractères
alphabetic characters: les caractères alphabétiques
capital letters: lettres majuscules
digits: chiffres
symbols: 'symboles. Ej: $%&.'
Password changed!: Mot de passe modifié!
Password doesn't meet the requirements: ''
Passwords doesn't match: Les mots de passe ne correspondent pas!
Passwords empty: ''
Change password: Changer le mot de passe

View File

@ -63,3 +63,20 @@ Account: Conta
Addresses: Moradas Addresses: Moradas
Load an order: Carregue um pedido pendente no carrinho ou inicie um novo Load an order: Carregue um pedido pendente no carrinho ou inicie um novo
Old password: Palavra-passe antiga
New password: Nova Palavra-passe
Repeat password: Repetir Palavra-passe
Requirements: Requisitos
Modify: Modificar
Password requirements: Requisitos de Palavra-passe
characters long: caracteres
alphabetic characters: caracteres alfabéticos
capital letters: letras maiúsculas
digits: dígitos
symbols: 'símbolos. Ej: $%&.'
Password changed!: Palavra-passe Modificada!
Password doesn't meet the requirements: Palavra-passe não atende aos requisitos
Passwords doesn't match: As Palavras-Passe não coincidem!
Passwords empty: Palavra-passe vazia
Change password: Mudar Palavra-passe

View File

@ -26,6 +26,12 @@ module.exports = new Class({
self._onSubmit(); self._onSubmit();
return false; return false;
}; };
if(this.hash.$.verificationToken){
this.$.changePassword.conn = this.conn;
this.$.changePassword.token = this.hash.$.verificationToken;
this.$.changePassword.open();
}
} }
,_onConnLoadChange(conn, isLoading) { ,_onConnLoadChange(conn, isLoading) {
@ -94,7 +100,11 @@ module.exports = new Class({
return; return;
} }
await this._conn.send('user/recover-password', {recoverUser}); await this.conn.post('VnUsers/recoverPassword', {
user: recoverUser,
app: 'hedera'
});
Htk.Toast.showMessage(_('A mail has been sent wich you can recover your password')); Htk.Toast.showMessage(_('A mail has been sent wich you can recover your password'));
} }
}); });

View File

@ -54,4 +54,5 @@
</div> </div>
</div> </div>
</div> </div>
<htk-change-password id="change-password"/>
</vn> </vn>

View File

@ -141,7 +141,7 @@ module.exports = new Class({
} }
,goToCurrentMonth() { ,goToCurrentMonth() {
var date = new Date(); var date = Date.vnNew();
this.goToMonth(date.getFullYear(), date.getMonth()); this.goToMonth(date.getFullYear(), date.getMonth());
} }
@ -179,7 +179,7 @@ module.exports = new Class({
// Marks the current day // Marks the current day
var today = new Date(); var today = Date.vnNew();
if (this.year == today.getFullYear() if (this.year == today.getFullYear()
&& this.month == today.getMonth()) { && this.month == today.getMonth()) {

View File

@ -0,0 +1,111 @@
var Component = require('vn/component');
var Toast = require('../toast');
var Tpl = require('./ui.xml').default;
/**
* A change password popup.
*/
module.exports = new Class({
Extends: Component,
Tag: 'htk-change-password',
Properties: {
/**
* The token url.
*/
token: {
type: String
,set(value) {
this._token = value;
}
,get() {
return this._token;
}
},
conn: {
type: Db.Connection
,set(x) {
this._conn = x
}
,get() {
return this._conn;
}
},
user: {
type: Object
}
},
initialize(props) {
Component.prototype.initialize.call(this, props);
},
async open() {
this.passwordForm = await this.conn.get('UserPasswords/findOne')
this.loadTemplateFromString(Tpl);
this.$.oldPassword.value = '';
this.$.newPassword.value = '';
this.$.repeatPassword.value = '';
this.$.oldPassword.style.display = this.token ? 'none' : 'block';
this.$.changePassword.open();
if (this.token)
this.$.newPassword.focus();
else
this.$.oldPassword.focus();
},
async onPassModifyClick() {
const form = this.$.changePassword.node;
Vn.Node.disableInputs(form);
try {
const oldPassword = this.$.oldPassword.value;
const newPassword = this.$.newPassword.value;
const repeatedPassword = this.$.repeatPassword.value;
try {
if (newPassword == '' && repeatedPassword == '')
throw new Error(_('Passwords empty'));
if (newPassword !== repeatedPassword)
throw new Error(_('Passwords doesn\'t match'));
} catch (err) {
return Toast.showError(err.message);
}
const params = {newPassword};
try {
if (this.token) {
const headers = {
Authorization: this.token
};
await this.conn.post('VnUsers/reset-password', params, {headers});
} else {
params.userId = this.user.id;
params.oldPassword = oldPassword;
await this.conn.patch(`Accounts/change-password`, params);
}
} catch(err) {
Toast.showError(err.message);
if (this.token)
this.$.newPassword.select();
else
this.$.oldPassword.select();
return;
}
if(this.user)
await this.conn.open(this.user.name, newPassword);
this.$.changePassword.hide();
} finally {
Vn.Node.disableInputs(form, false);
}
Toast.showMessage(_('Password changed!'));
}
});

View File

@ -0,0 +1,62 @@
<vn>
<htk-popup
id="change-password"
modal="true">
<div property="child-node" class="htk-dialog vn-w-xs vn-pa-lg">
<div class="form">
<h5 class="vn-mb-md">
<t>Change password</t>
</h5>
<input
id="old-password"
type="password"
placeholder="_Old password"/>
<input
id="new-password"
type="password"
placeholder="_New password"
autocomplete="new-password"/>
<input
id="repeat-password"
type="password"
placeholder="_Repeat password"
autocomplete="new-password"/>
</div>
<div class="button-bar">
<button class="thin" on-click="this.onPassModifyClick()">
<t>Modify</t>
</button>
<button class="thin" on-click="this.$.passwordInfo.show()">
<t>Requirements</t>
</button>
<div class="clear"/>
</div>
</div>
</htk-popup>
<htk-popup
id="password-info"
modal="true">
<div property="child-node" class="htk-dialog pass-info vn-w-xs vn-pa-lg">
<h5 class="vn-mb-md">
<t>Password requirements</t>
</h5>
<ul>
<li>
{{this.passwordForm.length}} <t>characters long</t>
</li>
<li>
{{this.passwordForm.nAlpha}} <t>alphabetic characters</t>
</li>
<li>
{{this.passwordForm.nUpper}} <t>capital letters</t>
</li>
<li>
{{this.passwordForm.nDigits}} <t>digits</t>
</li>
<li>
{{this.passwordForm.nPunct}} <t>symbols</t>
</li>
</ul>
</div>
</htk-popup>
</vn>

View File

@ -19,6 +19,7 @@ Htk = module.exports = {
,List : require('./list') ,List : require('./list')
,Field : require('./field') ,Field : require('./field')
,Column : require('./column') ,Column : require('./column')
,ChangePassword : require('./change-password')
}; };
var Fields = { var Fields = {

View File

@ -192,7 +192,7 @@ module.exports = new Class({
} }
,_onFileUpload(cell) { ,_onFileUpload(cell) {
this._stamp = new Date().getTime(); this._stamp = Date.vnNew().getTime();
this._refreshSrc(cell); this._refreshSrc(cell);
this.popup.hide(); this.popup.hide();
} }

View File

@ -3,13 +3,11 @@
*/ */
module.exports = module.exports =
{ {
set: function (key, value, days) set: function(key, value, days) {
{
var strCookie = key + '=' + value + ';'; var strCookie = key + '=' + value + ';';
if (days != undefined) if (days != undefined) {
{ var date = Date.vnNew();
var date = new Date ();
date.setTime(date.getTime() + days * 86400000); date.setTime(date.getTime() + days * 86400000);
strCookie += 'expires=' + date.toGMTString(); strCookie += 'expires=' + date.toGMTString();
} }
@ -17,18 +15,15 @@ module.exports =
document.cookie = strCookie; document.cookie = strCookie;
} }
,unset: function (key) ,unset: function(key) {
{
this.set(key, '', -1); this.set(key, '', -1);
} }
,get: function (key) ,get: function(key) {
{
var cookie = new String(document.cookie); var cookie = new String(document.cookie);
var start = cookie.indexOf(key + '='); var start = cookie.indexOf(key + '=');
if (start != -1) if (start != -1) {
{
var end; var end;
start += key.length + 1; start += key.length + 1;
@ -43,8 +38,7 @@ module.exports =
return null; return null;
} }
,getInt: function (key) ,getInt: function(key) {
{
var value = this.get(key); var value = this.get(key);
if (value != null) if (value != null)
@ -53,8 +47,7 @@ module.exports =
return null; return null;
} }
,getFloat: function (key) ,getFloat: function(key) {
{
var value = this.get(key); var value = this.get(key);
if (value != null) if (value != null)
@ -63,8 +56,7 @@ module.exports =
return null; return null;
} }
,check: function (key) ,check: function(key) {
{
return this.get(key) != null; return this.get(key) != null;
} }
}; };

View File

@ -6,6 +6,22 @@ Date.prototype.clone = function() {
return new Date(this.getTime()); return new Date(this.getTime());
} }
Date.vnUTC = () => {
const env = process.env.NODE_ENV;
if (!env || env === 'development')
return new Date(Date.UTC(2001, 0, 1, 11));
return new Date();
};
Date.vnNew = () => {
return new Date(Date.vnUTC());
};
Date.vnNow = () => {
return new Date(Date.vnUTC()).getTime();
};
module.exports = module.exports =
{ {
WDays: [ WDays: [

View File

@ -85,6 +85,7 @@ module.exports = new Class({
const config = { const config = {
headers: {'Authorization': token} headers: {'Authorization': token}
}; };
await this.send('user/logout', null, config);
await this.post('Accounts/logout', null, config); await this.post('Accounts/logout', null, config);
} }
}, },
@ -139,15 +140,16 @@ module.exports = new Class({
/* /*
* Called when REST response is received. * Called when REST response is received.
*/ */
async sendWithUrl(method, url, params) { async sendWithUrl(method, url, params, config) {
return this.request({ config = Object.assign({}, config, {
method, method,
url, url,
data: Vn.Url.makeUri(params), data: Vn.Url.makeUri(params)
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
}); });
config.headers = Object.assign({}, config.headers, {
'Content-Type': 'application/x-www-form-urlencoded'
});
return this.request(config);
}, },
async get(url, config) { async get(url, config) {

View File

@ -1,6 +1,6 @@
{ {
"name": "hedera-web", "name": "hedera-web",
"version": "23.6.14", "version": "23.40.6",
"description": "Verdnatura web page", "description": "Verdnatura web page",
"license": "GPL-3.0", "license": "GPL-3.0",
"repository": { "repository": {
@ -42,7 +42,8 @@
"scripts": { "scripts": {
"front": "webpack serve --open", "front": "webpack serve --open",
"back": "cd ../salix && gulp backOnly", "back": "cd ../salix && gulp backOnly",
"db": "cd ../vn-database && myvc run", "php": "php -S 127.0.0.1:3001 -t . index.php",
"db": "cd ../vn-database && myt run",
"build": "rm -rf build/ ; webpack", "build": "rm -rf build/ ; webpack",
"clean": "rm -rf build/" "clean": "rm -rf build/"
} }

View File

@ -1,6 +1,7 @@
<?php <?php
use Vn\Lib; use Vn\Lib;
use Vn\Lib\Locale;
use Vn\Web\Security; use Vn\Web\Security;
use Vn\Lib\Type; use Vn\Lib\Type;
@ -37,12 +38,16 @@ class Query extends Vn\Web\JsonRequest {
if ($db->checkWarnings() if ($db->checkWarnings()
&& ($result = $db->query('SHOW WARNINGS'))) { && ($result = $db->query('SHOW WARNINGS'))) {
$sql = 'SELECT `description`, @warn `code` $sql =
FROM `message` WHERE `code` = @warn'; 'SELECT IFNULL(i.`description`, m.`description`) `description`, @warn `code`
FROM `message` m
LEFT JOIN `messageI18n` i
ON i.`code` = m.`code` AND i.lang = #
WHERE m.`code` = @warn';
while ($row = $result->fetch_object()) { while ($row = $result->fetch_object()) {
if ($row->Code == 1265 if ($row->Code == 1265
&&($warning = $db->getObject($sql))) && ($warning = $db->getObject($sql, [Locale::get()])))
trigger_error("{$warning->code}: {$warning->description}", E_USER_WARNING); trigger_error("{$warning->code}: {$warning->description}", E_USER_WARNING);
else else
trigger_error("{$row->Code}: {$row->Message}", E_USER_WARNING); trigger_error("{$row->Code}: {$row->Message}", E_USER_WARNING);

View File

@ -18,7 +18,7 @@ class Segment {
switch ($type) { switch ($type) {
case Type::DATE: case Type::DATE:
$tmp = new Date(); $tmp = Date.vnNew();
$tmp->setDate(substr($v, 0, 4), substr($v, 4, 2), substr($v, 6, 2)); $tmp->setDate(substr($v, 0, 4), substr($v, 4, 2), substr($v, 6, 2));
return $tmp; return $tmp;
case Type::TIME: case Type::TIME:

7
rest/user/logout.php Normal file
View File

@ -0,0 +1,7 @@
<?php
class Logout extends Vn\Web\JsonRequest {
function run($db) {
$_SESSION['user'] = null;
}
}

View File

@ -5,6 +5,7 @@ namespace Vn\Web;
require_once 'libphp-phpmailer/autoload.php'; require_once 'libphp-phpmailer/autoload.php';
use Vn\Lib\UserException; use Vn\Lib\UserException;
use PHPMailer\PHPMailer\PHPMailer;
class Mailer { class Mailer {
private $conf; private $conf;
@ -19,7 +20,7 @@ class Mailer {
function createObject($mailTo, $body, $subject) { function createObject($mailTo, $body, $subject) {
$conf = $this->conf; $conf = $this->conf;
$mail = new \PHPMailer(); $mail = new PHPMailer();
$mail->isSMTP(); $mail->isSMTP();
$mail->Host = $conf->host; $mail->Host = $conf->host;

View File

@ -54,8 +54,12 @@ class RestService extends Service {
if ($e->getCode() == 1644) { if ($e->getCode() == 1644) {
$eMessage = $e->getMessage(); $eMessage = $e->getMessage();
$tMessage = $db->getValue( $tMessage = $db->getValue(
'SELECT `description` FROM `messageL10n` WHERE `code` = #', 'SELECT IFNULL(i.`description`, m.`description`) `description`
[$eMessage] FROM `message` m
LEFT JOIN `messageI18n` i
ON i.`code` = m.`code` AND i.lang = #
WHERE m.`code` = #',
[Locale::get(), $eMessage]
); );
if (!$tMessage) $tMessage = $eMessage; if (!$tMessage) $tMessage = $eMessage;
throw new Lib\UserException($tMessage, $eMessage); throw new Lib\UserException($tMessage, $eMessage);

View File

@ -158,8 +158,10 @@ abstract class Service {
[$token] [$token]
); );
if (!$userId) if (!$userId) {
$_SESSION['user'] = null;
throw new SessionExpiredException(); throw new SessionExpiredException();
}
$anonymousUser = FALSE; $anonymousUser = FALSE;
$user = $db->getValue( $user = $db->getValue(
@ -175,9 +177,11 @@ abstract class Service {
[$user] [$user]
); );
if (!$isActive) if (!$isActive) {
$_SESSION['user'] = null;
throw new UserDisabledException(); throw new UserDisabledException();
} }
}
$db->query('CALL account.myUser_loginWithName(#)', [$user]); $db->query('CALL account.myUser_loginWithName(#)', [$user]);
@ -187,7 +191,6 @@ abstract class Service {
$_SESSION['user'] = $user; $_SESSION['user'] = $user;
// Registering the user access // Registering the user access
if (isset($_SESSION['access']) && $userChanged) if (isset($_SESSION['access']) && $userChanged)
$db->query( $db->query(
'CALL visitUser_new(#, #)', 'CALL visitUser_new(#, #)',