0
1
Fork 0

Merge branch 'dev' into 5122-multipleBasket

This commit is contained in:
Juan Ferrer 2023-02-14 17:10:34 +01:00
commit e8eab29887
80 changed files with 613 additions and 1115 deletions

View File

@ -33,9 +33,9 @@ RUN curl -sL https://apt.verdnatura.es/conf/verdnatura.gpg | apt-key add - \
> /etc/apt/sources.list.d/vn.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends \
php-apcu \
php-image-text \
php-text-captcha \
php-apcu \
php-zip \
hedera-web \
cron

2
debian/changelog vendored
View File

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

2
debian/control vendored
View File

@ -10,7 +10,7 @@ Vcs-Git: https://gitea.verdnatura.es/verdnatura/hedera-web
Package: hedera-web
Architecture: all
Depends: apache2 | httpd, nodejs, php-cli, php-vn-lib, php-apcu, php-imap, php-soap, libphp-phpmailer, php-gd, php-pear
Suggests: php-text-captcha, php-zip, cron
Suggests: php-image-text, php-text-captcha, php-zip, cron
Section: misc
Priority: optional
Description: Verdnatura's web page

1
debian/cron.d vendored
View File

@ -1,6 +1,5 @@
MAILTO=webmaster
*/1 * * * * root hedera-web.php -m misc/mail
*/4 * * * * root hedera-web.php -m tpv/confirm-mail
*/2 * * * * root hedera-web.php -m edi/load
0 23 * * * root hedera-web.php -m edi/clean
0 5 * * * root hedera-web.php -m edi/update

View File

@ -28,45 +28,54 @@ export default new Class({
}
,async onPassModifyClick() {
var oldPassword = this.$.oldPassword.value;
var newPassword = this.$.newPassword.value;
var repeatedPassword = this.$.repeatPassword.value;
if (newPassword == '' && repeatedPassword == '')
throw new Error(_('Passwords empty'));
if (newPassword !== repeatedPassword)
throw new Error(_('Passwords doesn\'t match'));
var verificationToken = this.hash.$.verificationToken;
var params = {newPassword};
let err;
const form = this.$.changePassword.node;
Vn.Node.disableInputs(form);
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);
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);
}
} catch(e) {
err = e;
Htk.Toast.showError(err.message);
const verificationToken = this.hash.$.verificationToken;
const params = {newPassword};
if (this.hash.$.verificationToken)
this.$.newPassword.select();
else
this.$.oldPassword.select();
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);
return;
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);
}
this.$.changePassword.hide();
this.hash.unset('verificationToken');
Htk.Toast.showMessage(_('Password changed!'));
this.$.userForm.refresh();
}
,onPassInfoClick() {

View File

@ -89,11 +89,13 @@
<input
id="new-password"
type="password"
placeholder="_New password"/>
placeholder="_New password"
autocomplete="new-password"/>
<input
id="repeat-password"
type="password"
placeholder="_Repeat password"/>
placeholder="_Repeat password"
autocomplete="new-password"/>
</div>
<div class="button-bar">
<button class="thin" on-click="this.onPassModifyClick()">

View File

@ -91,4 +91,3 @@
border-radius: 50%;
}
}

View File

@ -160,13 +160,9 @@ export default new Class({
this.$.assistant.moveNext();
},
goNextStep() {
this.$.assistant.moveNext();
},
onAddressClick(addressId) {
this.$.lot.set('address', addressId);
this.goNextStep();
this.$.assistant.moveNext();
},
onAddressChange() {

View File

@ -1,54 +1,55 @@
.checkout .bar {
margin-bottom: 16px;
}
.hedera-checkout {
.bar {
margin-bottom: 16px;
}
/* Step */
/* Step */
.answers button,
.answers p,
.radio > div {
font-size: 1.2rem;
.answers button,
.answers p,
.radio > div {
font-size: 1.2rem;
}
.answers .htk-select {
max-width: 15em;
margin: 0 auto;
font-size: 1.4rem;
}
.answers p {
margin: 0.3em 0;
}
.target {
max-width: 28em;
margin: 0 auto;
}
.address {
border-radius: 0.1em;
padding: 0.6em 1.4em;
}
.address.selected {
background-color: rgba(1, 1, 1, .1);
}
.address:hover {
cursor: pointer;
background-color: rgba(1, 1, 1, .05);
}
.address p.consignee {
font-weight: bold;
}
.radio {
max-width: 20em;
margin: 0 auto;
}
.radio > div {
padding: 0.5em;
}
.thin-calendar {
max-width: 24em;
margin: 0 auto;
box-shadow: none;
}
.htk-assistant .thin {
float: right;
}
}
.answers .htk-select {
max-width: 15em;
margin: 0 auto;
font-size: 1.4rem;
}
.answers p {
margin: 0.3em 0;
}
.target {
max-width: 28em;
margin: 0 auto;
}
.address {
border-radius: 0.1em;
padding: 0.6em 1.4em;
}
.address.selected {
background-color: rgba(1, 1, 1, .1);
}
.address:hover {
cursor: pointer;
background-color: rgba(1, 1, 1, .05);
}
.address p.consignee {
font-weight: bold;
}
.radio {
max-width: 20em;
margin: 0 auto;
}
.radio > div {
padding: 0.5em;
}
.thin-calendar {
max-width: 24em;
margin: 0 auto;
box-shadow: none;
}
.htk-assistant .thin {
float: right;
}

View File

@ -58,7 +58,7 @@
tip="_Cancel"
on-click="onCancelClick"/>
</div>
<div id="form" class="checkout">
<div id="form" class="hedera-checkout">
<div class="vn-w-sm">
<div class="box bar">
<htk-assistant-bar

View File

@ -106,11 +106,6 @@ export default new Class({
Vn.Node.addClass(this.$[id], 'selected');
},
disableButtons(disable) {
this.$.modify.disabled = disable;
this.$.confirm.disabled = disable;
},
onModifyClick() {
window.history.back();
},

View File

@ -1,93 +1,95 @@
.confirm .summary {
margin-bottom: 16px;
}
.confirm p {
margin: .2em 0;
}
.hedera-confirm {
.summary {
margin-bottom: 16px;
}
p {
margin: .2em 0;
}
/* Table */
/* Table */
.confirm .debt-info {
padding: 0;
}
.confirm .debt-info > table {
border-collapse: collapse;
}
.confirm td {
padding: .15em 0;
}
.confirm .sum-total > td {
border-top: solid 1px #DDD;
font-weight: bold;
}
.confirm .currency {
text-align: right;
}
.confirm .credit-info {
display: none;
}
.confirm .exceeded-info {
display: none;
color: #E53935;
}
.debt-info {
padding: 0;
}
.debt-info > table {
border-collapse: collapse;
}
td {
padding: .15em 0;
}
.sum-total > td {
border-top: solid 1px #DDD;
font-weight: bold;
}
.currency {
text-align: right;
}
.credit-info {
display: none;
}
.exceeded-info {
display: none;
color: #E53935;
}
/* Pay */
/* Pay */
.confirm .amount-selector,
.confirm .pay-methods > div {
display: none;
}
.confirm .pay-methods > div {
margin: .3em 0;
}
.confirm .pay-methods > div > label > input[type=radio] {
margin: 0;
margin-right: .5em;
vertical-align: middle;
}
.confirm .pay-methods > div > div {
padding: .5em 1.5em;
display: none;
}
.confirm .pay-methods > div.selected > div {
display: block;
}
.confirm .transfer-account {
margin-top: .5em;
}
.confirm .transfer-account > p {
margin: .1em 0;
}
.amount-selector,
.pay-methods > div {
display: none;
}
.pay-methods > div {
margin: .3em 0;
}
.pay-methods > div > label > input[type=radio] {
margin: 0;
margin-right: .5em;
vertical-align: middle;
}
.pay-methods > div > div {
padding: .5em 1.5em;
display: none;
}
.pay-methods > div.selected > div {
display: block;
}
.transfer-account {
margin-top: .5em;
}
.transfer-account > p {
margin: .1em 0;
}
.confirm .payment > div {
margin-bottom: 1.4em;
.payment > div {
margin-bottom: 1.4em;
}
.payment > .button-bar {
display: flex;
justify-content: space-between;
margin-bottom: 0;
margin-top: 32px;
}
.payment > .button-bar button{
font-size: 1.2rem;
border-radius: 2rem;
padding: .5rem 1rem;
margin: 0;
}
.modify-order {
border: 1px solid #1a1a1a;
}
.modify-order:hover {
color: white;
background-color: #1a1a1a;
}
.confirm-order {
border: 1px solid #8cc63f;
background-color: #8cc63f;
color: white;
}
.confirm-order:hover {
background-color: transparent;
color: #6b5;
}
}
.confirm .payment > .button-bar {
display: flex;
justify-content: space-between;
margin-bottom: 0;
margin-top: 32px;
}
.confirm .payment > .button-bar button{
font-size: 1.2rem;
border-radius: 2rem;
padding: .5rem 1rem;
margin: 0;
}
.confirm .modify-order {
border: 1px solid #1a1a1a;
}
.confirm .modify-order:hover {
color: white;
background-color: #1a1a1a;
}
.confirm .confirm-order {
border: 1px solid #8cc63f;
background-color: #8cc63f;
color: white;
}
.confirm .confirm-order:hover {
background-color: transparent;
color: #6b5;
}

View File

@ -27,14 +27,11 @@
tmp.orderTax;
</db-model>
</db-form>
<db-query id="confirm-query" on-ready="onConfirm">
CALL myBasket_confirm
</db-query>
</vn-group>
<div id="title">
<h1><t>Order summary</t></h1>
</div>
<div id="form" class="confirm">
<div id="form" class="hedera-confirm">
<div class="vn-w-sm">
<div class="box vn-pa-lg summary">
<div>

View File

@ -2,7 +2,7 @@
<div id="title">
<h1><t>Invoices</t></h1>
</div>
<div id="form" class="invoices">
<div id="form" class="hedera-invoices">
<htk-grid
class="box vn-w-sm"
show-header="false">

View File

@ -34,7 +34,7 @@
tip="_ShoppingBasket"
on-click="onBasketClick"/>
</div>
<div id="form" class="orders">
<div id="form" class="hedera-orders">
<htk-repeater
class="htk-list box confirmed vn-w-sm"
form-id="iter"

View File

@ -1,78 +1,79 @@
/* Header */
.hedera-ticket {
/* Header */
.ticket .head {
padding: 0;
padding-bottom: 3px;
border-bottom: 1px solid #DDD;
margin-bottom: 1px;
}
.ticket .head > div > div {
margin: 15px 0;
}
.ticket .head > div > div:first-child {
margin: 0;
}
.ticket .head p {
margin: 3px 0;
}
.ticket .head p.important {
font-size: 1.2rem;
font-weight: bold;
}
.ticket .total {
text-align: right;
}
.ticket .packages {
margin-top: 14px;
padding-top: 14px;
border-top: 1px solid #DDD;
display: block;
}
.head {
padding: 0;
padding-bottom: 3px;
border-bottom: 1px solid #DDD;
margin-bottom: 1px;
}
.head > div > div {
margin: 15px 0;
}
.head > div > div:first-child {
margin: 0;
}
.head p {
margin: 3px 0;
}
.head p.important {
font-size: 1.2rem;
font-weight: bold;
}
.total {
text-align: right;
}
.packages {
margin-top: 14px;
padding-top: 14px;
border-top: 1px solid #DDD;
display: block;
}
/* Lines */
/* Lines */
.ticket .line {
display: flex;
align-items: center;
gap: 12px;
margin: 10px 0;
height: 80px;
.line {
display: flex;
align-items: center;
gap: 12px;
margin: 10px 0;
height: 80px;
}
.line:last-child {
margin-bottom: 0;
}
.line > .photo {
flex: none;
border-radius: 50%;
width: 68px;
height: 68px;
gap: 0;
}
.line > .info {
flex: 1;
overflow: hidden;
}
.line > .info > * {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.line > .info > h2 {
font-size: 1rem;
font-weight: normal;
margin-bottom: 2px;
}
.line > .info > p {
margin: 0;
}
.line > .info > .tags {
color: #777;
}
.line > .info .discount {
color: green;
}
.line > .info > .subtotal {
float: right;
}
}
.ticket .line:last-child {
margin-bottom: 0;
}
.ticket .line > .photo {
flex: none;
border-radius: 50%;
width: 68px;
height: 68px;
gap: 0;
}
.ticket .line > .info {
flex: 1;
overflow: hidden;
}
.ticket .line > .info > * {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.ticket .line > .info > h2 {
font-size: 1rem;
font-weight: normal;
margin-bottom: 2px;
}
.ticket .line > .info > p {
margin: 0;
}
.ticket .line > .info > .tags {
color: #777;
}
.ticket .line > .info .discount {
color: green;
}
.ticket .line > .info > .subtotal {
float: right;
}

View File

@ -14,7 +14,7 @@
tip="_Print delivery note"
on-click="this.onPrintClick()"/>
</div>
<div id="form" class="ticket">
<div id="form" class="hedera-ticket">
<div class="box vn-w-sm vn-pa-lg">
<htk-loader class="head" form="ticket-form">
<h5>#{{ticket.id}}</h5>

View File

@ -218,11 +218,11 @@ module.exports = new Class({
,async _onConnError(conn, err) {
if (!(err instanceof Vn.JsonException)) return;
switch (err.exception) {
case 'UserDisabled':
case 'UserDisabledError':
Htk.Toast.showError(_('User disabled'));
await this._logout();
return;
case 'OutdatedVersion':
case 'OutdatedVersionError':
this._newVersion();
return;
}
@ -242,8 +242,8 @@ module.exports = new Class({
Htk.Toast.showError(_('You don\'t have enough privileges'));
else {
switch (err.exception) {
case 'UserDisabled':
case 'OutdatedVersion':
case 'UserDisabledError':
case 'OutdatedVersionError':
return;
}
if (err.statusCode == 401)

View File

@ -50,19 +50,16 @@ module.exports = new Class({
this.doc.body.appendChild(this.node);
Htk.Toast.pushTop(this.$.formHolder);
await this.refreshUserData();
Vn.Node.setText(this.$.userName, this.user.nickname);
const resultSet = await this._conn.execQuery(
'SELECT id, name, nickname FROM account.myUser;'
+'SELECT defaultForm FROM config;'
'SELECT defaultForm FROM config;'
+'SELECT url FROM imageConfig;'
+'SELECT dbproduccion FROM vn.config;'
+'SELECT productionDomain, testDomain FROM config;'
);
// Retrieving the user name
this.user = resultSet.fetchObject();
Vn.Node.setText(this.$.userName, this.user.nickname);
// Retrieving configuration parameters
Vn.Config.defaultForm = resultSet.fetchValue();
@ -119,6 +116,12 @@ module.exports = new Class({
Htk.Toast.showWarning(_('By using this site you accept cookies'));
}
}
,async refreshUserData() {
const resultSet = await this._conn.execQuery(
'SELECT id, name, nickname FROM account.myUser');
this.user = resultSet.fetchObject();
}
,async hide() {
if (!this._shown)
@ -474,11 +477,8 @@ module.exports = new Class({
this._conn.token = json;
sessionStorage.setItem('supplantUser', supplantUser);
const resultSet = await this._conn.execQuery(
'SELECT nickname FROM account.myUser');
const userName = resultSet.fetchValue();
Vn.Node.setText(this.$.supplanted, userName);
await this.refreshUserData();
Vn.Node.setText(this.$.supplanted, this.user.nickname);
this.$.supplant.classList.toggle('show', true);
await this.loadMenu();
}
@ -492,6 +492,7 @@ module.exports = new Class({
sessionStorage.removeItem('supplantUser');
this.$.supplant.classList.toggle('show', false);
await this.refreshUserData();
await this.loadMenu();
this._onFormChange();
}

View File

@ -9,11 +9,10 @@ module.exports = new Class({
this.tpvStatus = this.hash.$.tpvStatus;
if (this.tpvStatus) {
const query = 'CALL myTpvTransaction_end(#transaction, #status)';
this.conn.execQuery(query, {
transaction: this.tpvOrder,
this.conn.post('TpvTransactions/end', {
orderId: this.tpvOrder,
status: this.tpvStatus
});
})
}
return this.tpvStatus;
@ -24,22 +23,18 @@ module.exports = new Class({
}
,async _realPay(amount, company) {
if (!isNumeric(amount) || amount <= 0)
throw new UserError(_('AmountError'));
let json;
try {
json = await this.conn.send('tpv/transaction', {
amount: parseInt(amount)
,urlOk: this._makeUrl('ok')
,urlKo: this._makeUrl('ko')
,company: company
});
} catch(err) {
throw new UserError(_('PayError'));
if (!isNumeric(amount) || amount <= 0) {
Htk.Toast.showError(_('AmountError'));
return;
}
const json = await this.conn.post('TpvTransactions/start', {
amount: parseInt(amount),
urlOk: this._makeUrl('ok'),
urlKo: this._makeUrl('ko'),
company
});
const postValues = json.postValues;
const form = document.createElement('form');

View File

@ -10,15 +10,7 @@ module.exports = new Class({
value: {
type: String
,set(x) {
if (Vn.Value.compare(x, this._value))
return;
if (x instanceof Date)
x = x.clone();
this.valueChanged(x);
this.putValue(x);
this._notifyChanges();
this._setValue(x);
}
,get() {
return this._value;
@ -109,11 +101,11 @@ module.exports = new Class({
,_lockField: false
,_setValue(newValue) {
if (!this._putValue(newValue))
return;
if (!this._putValue(newValue)) return;
newValue = this._value;
if (!this._lockField)
this.putValue(newValue);
this.putValue(this._value);
if (this.conditionalFunc)
this.conditionalFunc(this, newValue);

View File

@ -54,7 +54,7 @@ module.exports = new Class({
,render() {
var radio = Vn.Browser.createRadio('', this.doc);
radio.checked = false;
radio.addEventListener('change', this._onChange.bind(this));
radio.addEventListener('change', () => this._onChange());
this._node = radio;
}

View File

@ -4,8 +4,6 @@ var htkRadioGroupUid = 0;
module.exports = new Class({
Extends: Htk.Field
,Tag: 'htk-radio-group'
,radioLock: false
,initialize(props) {
this.clear();

View File

@ -50,9 +50,8 @@ module.exports = new Class({
if (user !== null && user !== undefined) {
params = {
user: user,
password: pass,
remember: remember
user,
password: pass
};
} else
params = null;
@ -261,7 +260,7 @@ module.exports = new Class({
if (exception) {
exception = exception
.replace(/\\/g, '.')
.replace(/Exception$/, '')
.replace(/Exception$/, 'Error')
.replace(/^Vn\.Web\./, '');
err.exception = exception;

View File

@ -1,4 +1,3 @@
/**
* Holds a plain key-value javascript object and monitorizes changes over it.
*/
@ -10,14 +9,14 @@ module.exports = new Class({
*/
params: {
type: Object
}
},
/**
* Shortcut for params property.
*/
,$: {
$: {
type: Object
}
}
}
},
/**
* Gets a value from the lot.
@ -25,9 +24,9 @@ module.exports = new Class({
* @param {string} field The field name
* @return {*} The field value
*/
,get(field) {
return this.params[field];
}
get(field) {
return this.params[field];
},
/**
* Sets a value on the lot.
@ -35,49 +34,51 @@ module.exports = new Class({
* @param {string} field The field name
* @param {*} value The new field value
*/
,set(field, value) {
var params = {};
params[field] = value;
this.assign(params);
}
set(field, value) {
this.assign({[field]: value});
},
unset(field) {
this.assign({[field]: undefined});
},
/**
* Returns an array with the lot keys.
*
* @return {Array} The lot keys
*/
,keys() {}
keys() {},
/**
* Emits the 'change' signal on the lot.
*
* @param {Object} changes The changed params and its values
*/
,changed(changes) {
this.emit('change', changes);
}
changed(changes) {
this.emit('change', changes);
},
/**
* Copies all values from another lot.
*
* @param {Object} object The source object
*/
,assign() {}
assign() {},
/**
* Copies all values from another lot.
*
* @param {LotIface} lot The source lot
*/
,assignLot(lot) {
this.assign(lot.$);
}
assignLot(lot) {
this.assign(lot.$);
},
/**
* Resets all values.
*/
,reset() {
this.params = {};
}
reset() {
this.params = {};
}
});

View File

@ -1,66 +1,63 @@
module.exports =
{
removeChilds: function (node)
{
module.exports = {
removeChilds(node) {
var childs = node.childNodes;
if (childs)
while (childs.length > 0)
node.removeChild (childs[0]);
}
node.removeChild(childs[0]);
},
,remove: function (node)
{
remove(node) {
if (node.parentNode)
node.parentNode.removeChild (node);
}
node.parentNode.removeChild(node);
},
,setText: function (node, text)
{
Vn.Node.removeChilds (node);
setText(node, text) {
Vn.Node.removeChilds(node);
if (text)
node.appendChild (
node.ownerDocument.createTextNode (text));
}
node.appendChild(
node.ownerDocument.createTextNode(text));
},
,addClass: function (node, className)
{
/* var classes = node.className.split (' ');
addClass(node, className) {
/* var classes = node.className.split(' ');
if (classes.split (' ').indexOf (className) == -1)
if (classes.split(' ').indexOf(className) == -1)
*/ node.className = className +' '+ node.className;
}
},
,removeClass: function (node, className)
{
removeClass(node, className) {
var index = 0;
var found = false;
var classes = node.className.split (' ');
var classes = node.className.split(' ');
while ((index = classes.indexOf (className, index)) != -1)
{
classes.splice (index, 1);
while ((index = classes.indexOf(className, index)) != -1) {
classes.splice(index, 1);
found = true;
}
if (found)
node.className = classes.join (' ');
}
node.className = classes.join(' ');
},
,hide: function (node)
{
hide: function(node) {
node.style.display = 'none';
}
},
,show: function (node)
{
show: function(node) {
node.style.display = 'block';
},
disableInputs(formNode, disable = true) {
const inputs = formNode
.querySelectorAll('input, textarea, button, select');
for (const input of inputs)
input.disabled = disable;
}
};
$ = function (id)
{
return document.getElementById (id);
$ = function(id) {
return document.getElementById(id);
}

View File

@ -140,52 +140,33 @@ function sprintf(formatString) {
});
}
module.exports = {
regexpNumber: /%\.([0-9]+)d/g
,regexpString: /%s/g
function format(value, format) {
if (value === null || value === undefined)
return '';
,equals
,diff
,partialDiff
,kvClone
,simpleClone
,simpleEquals
,sprintf
,compare(a, b) {
if (a === b)
return true;
if (a instanceof Date && b instanceof Date)
return a.getTime() === b.getTime();
return false;
if (format)
switch (typeof value) {
case 'number':
return format.replace(/%\.([0-9]+)d/g,
(_, digits) => new Number(value).toFixed(parseInt(digits)));
case 'string':
return format.replace(/%s/g,
() => value);
case 'object':
if (value instanceof Date)
return VnDate.strftime(value, format);
}
,format(value, format) {
if (value === null || value === undefined)
return '';
if (format)
switch (typeof value) {
case 'number':
return format.replace(this.regexpNumber,
this.replaceNumber.bind(null, value));
case 'string':
return format.replace(this.regexpString,
this.replaceString.bind(null, value));
case 'object':
if (value instanceof Date)
return VnDate.strftime(value, format);
}
return value;
}
return value;
}
,replaceNumber(value, token, digits) {
return new Number(value).toFixed(parseInt(digits));
}
,replaceString(value) {
return value;
}
module.exports = {
equals,
diff,
partialDiff,
kvClone,
simpleClone,
simpleEquals,
sprintf,
format,
};

View File

@ -1,6 +1,6 @@
{
"name": "hedera-web",
"version": "22.48.4",
"version": "22.48.10",
"description": "Verdnatura web page",
"license": "GPL-3.0",
"repository": {

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

View File

@ -1,2 +1,4 @@
UpdateYourBrowser: Actualitza el teu navegador
ContinueAnyway: Continuar igualment
BrowserVersionNotCompatible: El vostre navegador no és compatible amb aquesta versió de la pàgina web
PushHereToInstallFirefox: Clica aquí per instal·lar Firefox

View File

@ -1,2 +1,4 @@
UpdateYourBrowser: Upgrade your browser
ContinueAnyway: Continue anyway
BrowserVersionNotCompatible: Your browser is not compatible with this version of the website
PushHereToInstallFirefox: Click here to install Firefox

View File

@ -1,2 +1,4 @@
UpdateYourBrowser: Actualiza tu navegador
ContinueAnyway: Continuar de todos modos
BrowserVersionNotCompatible: Tu navegador no es compatible con esta versión de la página web
PushHereToInstallFirefox: Pulsa aquí para instalar Firefox

View File

@ -1,2 +1,4 @@
UpdateYourBrowser: Mettez à jour votre navigateur
ContinueAnyway: Continuer
BrowserVersionNotCompatible: Votre navigateur n'est pas compatible avec cette version du site
PushHereToInstallFirefox: Cliquez ici pour installer Firefox

View File

@ -1,2 +0,0 @@
UpdateYourBrowser: Upgrade your browser
ContinueAnyway: Continue anyway

View File

@ -1,2 +1,4 @@
UpdateYourBrowser: Atualize seu navegador
ContinueAnyway: Continuar de todas maneiras
BrowserVersionNotCompatible: Seu navegador não é compatível com esta versão do site
PushHereToInstallFirefox: Clique aqui para instalar o Firefox

View File

@ -1,26 +1,53 @@
*
{
* {
font-family: 'Roboto';
}
img
{
position: absolute;
margin-top: -200px;
margin-left: -200px;
top: 50%;
left: 50%;
.box {
text-align: center;
margin: 0 auto;
max-width: 380px;
padding: 40px;
padding-bottom: 60px;
}
#continue
{
position: absolute;
.logo {
padding: 5px;
max-width: 100%;
}
h2 {
font-weight: normal;
font-size: 22px;
}
.browser-logo {
display: block;
margin: 30px auto;
height: 120px;
}
.download {
display: block;
}
.bottom {
position: fixed;
bottom: 0;
left: 0;
right: 0;
margin: 1em;
text-align: center;
color: white;
}
a
{
color: #444;
.continue {
color: white;
background-color: #8cc63f;
padding: 5px;
border-radius: 3px;
}
.continue:hover {
text-decoration: none;
background-color: #7eb239;
}
a {
color: #6a1;
border-width: 0;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}

View File

@ -2,20 +2,35 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="viewport" content="user-scalable=no"/>
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=no"/>
<meta name="content-language" content="<?=$lang?>"/>
<link href="//fonts.googleapis.com/css?family=Roboto" rel="stylesheet" type="text/css"/>
<?=css("$dir/style")?>
<title>Verdnatura</title>
</head>
<body>
<div>
<a href="http://www.mozilla.org/es-ES/firefox/new/" target="_blank">
<img src="<?=$dir?>/update-browser.png" alt="<?=s('UpdateYourBrowser')?>"></img>
<div class="box">
<img
class="logo"
src="image/logo.png"
alt="VerdNatura">
</img>
<h2><?=s('BrowserVersionNotCompatible')?></h2>
<a
class="download"
href="http://www.mozilla.org/firefox/new/"
target="_blank">
<img
class="browser-logo"
src="<?=$dir?>/firefox.png"
alt="Firefox">
</img>
<?=s('PushHereToInstallFirefox')?>
</a>
</div>
<div id="continue">
<a href="?skipBrowser=true">
<div class="bottom">
<a class="continue"
href="?skipBrowser=true">
<?=s('ContinueAnyway')?>
</a>
</div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

View File

@ -1,4 +0,0 @@
{
"Recover password": "Recover password",
"Press on the following link to change your password.": "Press on the following link to change your password."
}

View File

@ -0,0 +1,2 @@
recoverPassword: Recover password
pressLinkToRecoverPassword: Press on the following link to change your password.

View File

@ -1,4 +0,0 @@
{
"Recover password": "Restaurar contraseña",
"Press on the following link to change your password.": "Pulsa en el siguiente link para cambiar tu contraseña."
}

View File

@ -0,0 +1,2 @@
recoverPassword: Restaurar contraseña
pressLinkToRecoverPassword: Pulsa en el siguiente link para cambiar tu contraseña.

View File

@ -1,4 +0,0 @@
{
"Recover password": "Réinitialisation du mot de passe",
"Press on the following link to change your password.": "Appuyez sur le lien suivant pour changer votre mot de passe."
}

View File

@ -0,0 +1,2 @@
recoverPassword: Réinitialisation du mot de passe
pressLinkToRecoverPassword: Appuyez sur le lien suivant pour changer votre mot de passe.

View File

@ -1,4 +0,0 @@
{
"Recover password": "Recuperar palavra-passe",
"Press on the following link to change your password.": "Pressione o botão para modificar sua palavra-passe."
}

View File

@ -0,0 +1,2 @@
recoverPassword: Recuperar palavra-passe
pressLinkToRecoverPassword: Pressione o botão para modificar sua palavra-passe.

View File

@ -1,7 +1,7 @@
<?php $title = s('Recover password') ?>
<?php $title = s('recoverPassword') ?>
<p>
<?=s('Press on the following link to change your password.')?>
<?=s('pressLinkToRecoverPassword')?>
</p>
<a href="<?=$url?>">
<?=s('Recover password')?>
<?=s('recoverPassword')?>
</a>

View File

@ -10,7 +10,7 @@ class Supplant extends Vn\Web\JsonRequest {
'SELECT id FROM account.user WHERE `name` = #',
[$_REQUEST['supplantUser']]
);
/*
$isClient = $db->getValue(
'SELECT COUNT(*) > 0 FROM vn.client WHERE id = #',
[$userId]
@ -24,7 +24,7 @@ class Supplant extends Vn\Web\JsonRequest {
);
if ($hasAccount)
throw new Web\ForbiddenException(s('The user is not impersonable'));
*/
return $this->service->createToken($_REQUEST['supplantUser']);
}
}

View File

@ -1,5 +0,0 @@
{
"InvalidAction": "Acció invàlida"
,"EmptyQuery": "Consulta buida"
}

2
rest/core/locale/ca.yml Normal file
View File

@ -0,0 +1,2 @@
InvalidAction: Acció invàlida
EmptyQuery: Consulta buida

View File

@ -1,5 +0,0 @@
{
"InvalidAction": "Invalid action"
,"EmptyQuery": "Empty query"
}

2
rest/core/locale/en.yml Normal file
View File

@ -0,0 +1,2 @@
InvalidAction: Invalid action
EmptyQuery: Empty query

View File

@ -1,7 +0,0 @@
{
"InvalidAction": "Acción inválida"
,"EmptyQuery": "Consulta vacía"
,"Invalid password": "Contraseña inválida"
,"Password does not meet requirements":
"La nueva contraseña no reune los requisitos de seguridad necesarios"
}

2
rest/core/locale/es.yml Normal file
View File

@ -0,0 +1,2 @@
InvalidAction: Acción inválida
EmptyQuery: Consulta vacía

View File

@ -1,5 +0,0 @@
{
"InvalidAction": "Action non valide"
,"EmptyQuery": "Requête vide"
}

2
rest/core/locale/fr.yml Normal file
View File

@ -0,0 +1,2 @@
InvalidAction: Action non valide
EmptyQuery: Requête vide

View File

@ -1,5 +0,0 @@
{
"InvalidAction": "Ação Inválida"
,"EmptyQuery": "Consulta vazía"
}

2
rest/core/locale/pt.yml Normal file
View File

@ -0,0 +1,2 @@
InvalidAction: Ação Inválida
EmptyQuery: Consulta vazía

View File

@ -22,53 +22,34 @@ class Query extends Vn\Web\JsonRequest {
function run($db) {
$results = [];
$db->multiQuery($_REQUEST['sql']);
try {
$db->multiQuery($_REQUEST['sql']);
do {
$result = $db->storeResult();
do {
$result = $db->storeResult();
if ($result !== FALSE) {
$results[] = $this->transformResult($result);
$result->free();
} else
$results[] = TRUE;
}
while ($db->moreResults() && $db->nextResult());
if ($result !== FALSE) {
$results[] = $this->transformResult($result);
$result->free();
} else
$results[] = TRUE;
if ($db->checkWarnings()
&&($result = $db->query('SHOW WARNINGS'))) {
$sql = 'SELECT `description`, @warn `code`
FROM `message` WHERE `code` = @warn';
while ($row = $result->fetch_object()) {
if ($row->Code == 1265
&&($warning = $db->getObject($sql)))
trigger_error("{$warning->code}: {$warning->description}", E_USER_WARNING);
else
trigger_error("{$row->Code}: {$row->Message}", E_USER_WARNING);
}
while ($db->moreResults() && $db->nextResult());
// Checks for warnings
if ($db->checkWarnings()
&&($result = $db->query('SHOW WARNINGS'))) {
$sql = 'SELECT `description`, @warn `code`
FROM `message` WHERE `code` = @warn';
while ($row = $result->fetch_object()) {
if ($row->Code == 1265
&&($warning = $db->getObject($sql)))
trigger_error("{$warning->code}: {$warning->description}", E_USER_WARNING);
else
trigger_error("{$row->Code}: {$row->Message}", E_USER_WARNING);
}
}
// Checks for errors
$db->checkError();
} catch (Vn\Db\Exception $e) {
if ($e->getCode() == 1644) {
$dbMessage = $e->getMessage();
$sql = 'SELECT `description` FROM `message` WHERE `code` = #';
$message = $db->getValue($sql, [$dbMessage]);
if ($message)
throw new Lib\UserException($message, $dbMessage);
}
throw $e;
}
$db->checkError();
return $results;
}

View File

@ -1,29 +0,0 @@
{
"Cant lock cache": "The cache could not be blocked"
,"Bad file format": "Unrecognized file format"
,"File not choosed": "You have not selected any file"
,"Permission denied": "You are not allowed to upload the file"
,"File upload error": "Failed to upload the file, check that size is not too large"
,"File save error": "Failed to save the file: %s"
,"File size error": "The file must be no longer than %.2f MB"
,"Bad file name": "The file name must contain only lowercase letters, digits or the '_' character"
,"Bad collection name": "Invalid collection name"
,"Collection not exists": "Collection does not exist"
,"Unreferenced file": "The file is not referenced by the database"
,"Cannot update matching id": "Cannot update matching id"
,"Com error": "Error communicating with the server"
,"Image open error": "Error opening the image file"
,"Operation disabled": "Operation disabled for security"
,"Image added": "Image added correctly"
,"ErrIniSize": "File exceeds the upload_max_filesize directive in php.ini"
,"ErrFormSize": "File exceeds the MAX_FILE_SIZE specified in the HTML form"
,"ErrPartial": "File was partially uploaded"
,"ErrNoFile": "No file was uploaded"
,"ErrNoTmpDir": "Missing a temporary folder"
,"ErrCantWrite": "Failed to write file to disk"
,"ErrExtension": "File upload stopped by extension"
,"ErrDefault": "Unknown upload error"
,"Sync complete": "Synchronization complete"
}

27
rest/image/locale/en.yml Normal file
View File

@ -0,0 +1,27 @@
Cant lock cache: The cache could not be blocked
Bad file format: Unrecognized file format
File not choosed: You have not selected any file
Permission denied: You are not allowed to upload the file
File upload error: Failed to upload the file, check that size is not too large
File save error: 'Failed to save the file: %s'
File size error: The file must be no longer than %.2f MB
Bad file name: 'The file name must contain only lowercase letters, digits or the ''_'' character'
Bad collection name: Invalid collection name
Collection not exists: Collection does not exist
Unreferenced file: The file is not referenced by the database
Cannot update matching id: Cannot update matching id
Com error: Error communicating with the server
Image open error: Error opening the image file
Operation disabled: Operation disabled for security
Image added: Image added correctly
ErrIniSize: File exceeds the upload_max_filesize directive in php.ini
ErrFormSize: File exceeds the MAX_FILE_SIZE specified in the HTML form
ErrPartial: File was partially uploaded
ErrNoFile: No file was uploaded
ErrNoTmpDir: Missing a temporary folder
ErrCantWrite: Failed to write file to disk
ErrExtension: File upload stopped by extension
ErrDefault: Unknown upload error
Sync complete: Synchronization complete

View File

@ -1,29 +0,0 @@
{
"Cant lock cache": "La caché no pudo ser bloqueada"
,"Bad file format": "Formato de archivo no reconocido"
,"File not choosed": "No has seleccionado ningún archivo"
,"Permission denied": "No tienes permiso para subir el fichero"
,"File upload error": "Error al subir el fichero, comprueba que su tamaño no sea demasiado grande"
,"File save error": "Error al guardar el fichero: %s"
,"File size error": "El fichero no debe ocupar más de %.2f MB"
,"Bad file name": "El nombre del archivo solo debe contener letras minúsculas, dígitos o el carácter '_'"
,"Bad collection name": "Nombre de colección no válido"
,"Collection not exists": "La colección no existe"
,"Unreferenced file": "El archivo no está referenciado por la base de datos"
,"Cannot update matching id": "No es posible actualizar los ítems con id coincidente"
,"Com error": "Error en la comunicación con el servidor"
,"Image open error": "Error al abrir el archivo de imagen"
,"Operation disabled": "Operación deshabilitada por seguridad"
,"Image added": "Imagen añadida correctamente"
,"ErrIniSize": "File exceeds the upload_max_filesize directive in php.ini"
,"ErrFormSize": "File exceeds the MAX_FILE_SIZE specified in the HTML form"
,"ErrPartial": "File was partially uploaded"
,"ErrNoFile": "No file was uploaded"
,"ErrNoTmpDir": "Missing a temporary folder"
,"ErrCantWrite": "Failed to write file to disk"
,"ErrExtension": "File upload stopped by extension"
,"ErrDefault": "Unknown upload error"
,"Sync complete": "Sincronización completada"
}

27
rest/image/locale/es.yml Normal file
View File

@ -0,0 +1,27 @@
Cant lock cache: La caché no pudo ser bloqueada
Bad file format: Formato de archivo no reconocido
File not choosed: No has seleccionado ningún archivo
Permission denied: No tienes permiso para subir el fichero
File upload error: Error al subir el fichero, comprueba que su tamaño no sea demasiado grande
File save error: 'Error al guardar el fichero: %s'
File size error: El fichero no debe ocupar más de %.2f MB
Bad file name: 'El nombre del archivo solo debe contener letras minúsculas, dígitos o el carácter ''_'''
Bad collection name: Nombre de colección no válido
Collection not exists: La colección no existe
Unreferenced file: El archivo no está referenciado por la base de datos
Cannot update matching id: No es posible actualizar los ítems con id coincidente
Com error: Error en la comunicación con el servidor
Image open error: Error al abrir el archivo de imagen
Operation disabled: Operación deshabilitada por seguridad
Image added: Imagen añadida correctamente
ErrIniSize: File exceeds the upload_max_filesize directive in php.ini
ErrFormSize: File exceeds the MAX_FILE_SIZE specified in the HTML form
ErrPartial: File was partially uploaded
ErrNoFile: No file was uploaded
ErrNoTmpDir: Missing a temporary folder
ErrCantWrite: Failed to write file to disk
ErrExtension: File upload stopped by extension
ErrDefault: Unknown upload error
Sync complete: Sincronización completada

View File

@ -1,29 +0,0 @@
{
"Cant lock cache": "O cache não pôde ser bloqueado"
,"Bad file format": "Formato de arquivo inválido"
,"File not choosed": "Não selecionastes nenhum arquivo"
,"Permission denied": "Não estas autorizado a subir o arquivo"
,"File upload error": "Erro ao subir o arquivo, verifique o tamanho"
,"File save error": "Erro ao salvar o arquivo: %s"
,"File size error": "O arquivo não deve ser maior que: %.2f MB"
,"Bad file name": "O nome do arquivo deve conter somente letras minusculas, numeros ou '_' "
,"Bad collection name": "Nome de coleção inválido"
,"Collection not exists": "Coleção não existe"
,"Unreferenced file": "O arquivo não é referenciado pelo banco de dados"
,"Cannot update matching id": "Não é possível atualizar os itens com id coincidente"
,"Com error": "Erro de comunicação com o servidor"
,"Image open error": "Erro ao abrir a imagem"
,"Operation disabled": "Operação desativada por segurança"
,"Image added": "Imagem adicionada corretamente"
,"ErrIniSize": "Arquivo supera o tamanho maximo de protocolo em php.ini"
,"ErrFormSize": "Arquivo supera o tamanho maximo de protocolo em HTML form"
,"ErrPartial": "Arquivo subido parcialmente"
,"ErrNoFile": "Nenhum arquivo subido"
,"ErrNoTmpDir": "Falta a pasta de arquivo temporal"
,"ErrCantWrite": "Erro ao gravar arquivo no disco"
,"ErrExtension": "Erro de extensão do arquivo"
,"ErrDefault": "Erro desconhecido ao subir arquivo"
,"Sync complete": "Sincronização completa"
}

27
rest/image/locale/pt.yml Normal file
View File

@ -0,0 +1,27 @@
Cant lock cache: O cache não pôde ser bloqueado
Bad file format: Formato de arquivo inválido
File not choosed: Não selecionastes nenhum arquivo
Permission denied: Não estas autorizado a subir o arquivo
File upload error: Erro ao subir o arquivo, verifique o tamanho
File save error: 'Erro ao salvar o arquivo: %s'
File size error: O arquivo não deve ser maior que %.2f MB
Bad file name: 'O nome do arquivo deve conter somente letras minusculas, numeros ou ''_'''
Bad collection name: Nome de coleção inválido
Collection not exists: Coleção não existe
Unreferenced file: O arquivo não é referenciado pelo banco de dados
Cannot update matching id: Não é possível atualizar os itens com id coincidente
Com error: Erro de comunicação com o servidor
Image open error: Erro ao abrir a imagem
Operation disabled: Operação desativada por segurança
Image added: Imagem adicionada corretamente
ErrIniSize: Arquivo supera o tamanho maximo de protocolo em php.ini
ErrFormSize: Arquivo supera o tamanho maximo de protocolo em HTML form
ErrPartial: Arquivo subido parcialmente
ErrNoFile: Nenhum arquivo subido
ErrNoTmpDir: Falta a pasta de arquivo temporal
ErrCantWrite: Erro ao gravar arquivo no disco
ErrExtension: Erro de extensão do arquivo
ErrDefault: Erro desconhecido ao subir arquivo
Sync complete: Sincronização completa

View File

@ -1,6 +0,0 @@
{
"An automated message could not be delivered": "Un mensaje automatizado no se ha podido entregar"
,"Notification from IT department about problem.": "Desde el departamento de informática te enviamos este correo porque ha habido un problema al intentar entregar un correo automatizado."
,"If you have questions, resend this email to cau@verdnatura.es.": "Si tienes dudas, reenvia este correo a cau@verdnatura.es."
,"The response from the remote server was:": "La respuesta del servidor remoto ha sido:"
}

4
rest/misc/locale/es.yml Normal file
View File

@ -0,0 +1,4 @@
automatedMessageNotDelivered: Un mensaje automatizado no se ha podido entregar
ITProblemNotification: Desde el departamento de informática te enviamos este correo porque ha habido un problema al intentar entregar un correo automatizado.
ifQuestionsResendEmailToIT: Si tienes dudas, reenvia este correo a cau@verdnatura.es.
'The response from the remote server was:': 'La respuesta del servidor remoto ha sido:'

View File

@ -48,13 +48,13 @@ class Mail extends Vn\Lib\Method {
if ($row->replyTo) {
Vn\Lib\Locale::set('es');
$errorMsg =
'<p>'. s('Notification from IT department about problem.') .'</p>'
.'<p>'. s('If you have questions, resend this email to cau@verdnatura.es.') .'</p>'
'<p>'. s('ITProblemNotification') .'</p>'
.'<p>'. s('ifQuestionsResendEmailToIT') .'</p>'
.'<p style="color: gray">'. $status .'</p>';
$errorMail = $mailer->createObject($row->replyTo,
$errorMsg,
s('An automated message could not be delivered')
s('automatedMessageNotDelivered')
);
$errorMail->AddStringAttachment(
$mail->getSentMIMEMessage(),

View File

@ -1,98 +0,0 @@
<?php
require_once(__DIR__.'/tpv.php');
/**
* Gets transaction confirmations from the IMAP mailbox.
*/
class ConfirmMail extends Vn\Lib\Method {
function run($db) {
$imap = NULL;
$imapConf = $db->getObject(
'SELECT host, user, pass, cleanPeriod, successFolder, errorFolder
FROM tpvImapConfig'
);
$mailbox = sprintf('{%s/imap/ssl/novalidate-cert}',
$imapConf->host);
$imap = imap_open($mailbox
,$imapConf->user
,base64_decode($imapConf->pass)
);
if (!$imap)
throw new Exception(imap_last_error());
// Fetchs and confirms new transaction mails
$count = 0;
$inbox = imap_search($imap, 'ALL');
if ($inbox)
foreach ($inbox as $msg) {
// Decodes the mail body
$params = [];
$body = imap_fetchbody($imap, $msg, '1');
$strings = explode(';', $body);
foreach ($strings as $string) {
$x = explode(':', $string);
$params[trim($x[0])] = trim($x[1]);
}
// Confirms the transaction
$success = FALSE;
try {
$success = Tpv::confirm($db, $params);
} catch (\Exception $e) {
trigger_error($e->getMessage(), E_USER_WARNING);
}
// Moves the processed mail to another folder
if ($success)
$folder = $imapConf->successFolder;
else
$folder = $imapConf->errorFolder;
if (!imap_mail_move($imap, $msg, "$folder"))
trigger_error(imap_last_error(), E_USER_WARNING);
$count++;
}
imap_expunge($imap);
// Cleans the old mails
$deleted = 0;
if (rand(1, 20) == 1) {
$folders = array(
$imapConf->successFolder
,$imapConf->errorFolder
);
$date = new \DateTime(NULL);
$date->sub(new \DateInterval($imapConf->cleanPeriod));
$filter = sprintf('BEFORE "%s"', $date->format('D, j M Y'));
foreach ($folders as $folder)
if (imap_reopen($imap, $mailbox.$folder))
if ($messages = imap_search($imap, $filter)) {
foreach ($messages as $message)
imap_delete($imap, $message);
imap_expunge($imap);
$deleted += count($messages);
}
}
echo "$count mails processed, $deleted mails deleted.\n";
}
}

View File

@ -1,13 +0,0 @@
<?php
require_once(__DIR__.'/tpv.php');
/**
* Gets transaction confirmation from HTTP POST.
*/
class ConfirmPost extends Vn\Web\RestRequest {
function run($db) {
Tpv::confirm($db, $_POST);
}
}

View File

@ -1,91 +0,0 @@
<?php
require_once('vn/web/util.php');
require_once(__DIR__.'/tpv.php');
/**
* Gets transaction confirmation from SOAP service.
*/
class ConfirmSoap extends Vn\Web\RestRequest {
function run($db) {
global $tpvConfirmSoap;
$tpvConfirmSoap = $this;
ini_set('soap.wsdl_cache_enabled', FALSE);
$server = new SoapServer(__DIR__ .'/soap.wsdl');
$server->addFunction('procesaNotificacionSIS');
$server->handle();
}
}
function procesaNotificacionSIS($XML) {
global $tpvConfirmSoap;
$db = $tpvConfirmSoap->app->getSysConn();
$status = 'OK';
$requestString = $XML;
// Processes the request
try {
$xml = new SimpleXMLElement($requestString);
$params =(array) $xml->{'Request'};
if (!(isset($params['Ds_Amount'])
&& isset($params['Ds_Order'])
&& isset($params['Ds_MerchantCode'])
&& isset($params['Ds_Currency'])
&& isset($params['Ds_Response'])))
throw new Exception('Missing required parameters');
// Checks the signature
$start = strpos($requestString, '<Request');
$end = strrpos($requestString, '</Request>');
$shaString = substr($requestString, $start, $end - $start + 10);
$key = $db->getValue(
'SELECT secretKey FROM tpvMerchant WHERE id = #'
,[$params['Ds_MerchantCode']]
);
if (sha1($shaString.$key) != $xml->{'Signature'})
throw new Exception('Invalid signature');
// Confirms the transaction
Tpv::confirm($db, $params);
} catch (Exception $e) {
$status = 'KO';
}
// Generates the response
$responseString = file_get_contents(__DIR__ .'/soap-reply.xml');
$xml = new SimpleXMLElement($responseString);
$response = $xml->{'Response'};
$response->{'Ds_Response_Merchant'} = $status;
$xml->{'Signature'} = sha1($response->asXML().$key);
return $xml->asXML();
/*
// Another way to generate the response
$xmlResponse =
'<Response Ds_Version="0.0">
<Ds_Response_Merchant>'. $status .'</Ds_Response_Merchant>
</Response>';
$xmlMessage =
'<Message>
'. $xmlResponse .'
<Signature>'. sha1($xmlResponse.$key) .'</Signature>
</Message>';
return $xmlMessage;
*/}

View File

@ -1,6 +0,0 @@
<Message>
<Response Ds_Version="0.0">
<Ds_Response_Merchant></Ds_Response_Merchant>
</Response>
<Signature></Signature>
</Message>

View File

@ -1,40 +0,0 @@
<!ELEMENT Message (Request, Signature)>
<!ELEMENT Request (
Fecha,
Hora,
Ds_SecurePayment,
Ds_Amount,
Ds_Currency,
Ds_Order,
Ds_MerchantCode,
Ds_Terminal,
Ds_Response,
Ds_MerchantData?,
Ds_Card_Type?,
Ds_TransactionType,
Ds_ConsumerLanguage,
Ds_ErrorCode?,
Ds_CardCountry?,
Ds_AuthorisationCode?
)>
<!ATTLIST Request Ds_Version CDATA #REQUIRED>
<!ELEMENT Fecha (#PCDATA)>
<!ELEMENT Hora (#PCDATA)>
<!ELEMENT Ds_SecurePayment (#PCDATA)>
<!ELEMENT Ds_Amount (#PCDATA)>
<!ELEMENT Ds_Currency (#PCDATA)>
<!ELEMENT Ds_Order (#PCDATA)>
<!ELEMENT Ds_MerchantCode (#PCDATA)>
<!ELEMENT Ds_Terminal (#PCDATA)>
<!ELEMENT Ds_Response (#PCDATA)>
<!ELEMENT Ds_MerchantData (#PCDATA)>
<!ELEMENT Ds_Card_Type (#PCDATA)>
<!ELEMENT Ds_TransactionType (#PCDATA)>
<!ELEMENT Ds_ConsumerLanguage (#PCDATA)>
<!ELEMENT Ds_ErrorCode (#PCDATA)>
<!ELEMENT Ds_CardCountry (#PCDATA)>
<!ELEMENT Ds_AuthorisationCode (#PCDATA)>
<!ELEMENT Signature (#PCDATA)>

View File

@ -1,20 +0,0 @@
<Message>
<Request Ds_Version='0.0'>
<Fecha>21/10/2014</Fecha>
<Hora>17:56</Hora>
<Ds_SecurePayment>1</Ds_SecurePayment>
<DS_Card_Type>D</DS_Card_Type>
<Ds_Card_Country>724</Ds_Card_Country>
<Ds_Amount>1</Ds_Amount>
<Ds_Currency>978</Ds_Currency>
<Ds_Order>000000007216</Ds_Order>
<Ds_MerchantCode>329744999</Ds_MerchantCode>
<Ds_Terminal>001</Ds_Terminal>
<Ds_Response>0000</Ds_Response>
<Ds_MerchantData></Ds_MerchantData>
<Ds_TransactionType>0</Ds_TransactionType>
<Ds_ConsumerLanguage>1</Ds_ConsumerLanguage>
<Ds_AuthorisationCode>563451</Ds_AuthorisationCode>
</Request>
<Signature>b97d1aba50aac5efc0915f59a70e24fc94cb3ffe</Signature>
</Message>

View File

@ -1,56 +0,0 @@
<?php
if (isset($_POST['key'])) {
ini_set('soap.wsdl_cache_enabled', FALSE);
$requestString = file_get_contents(__DIR__.'/soap-request.xml');
$client = new SoapClient(__DIR__.'/soap.wsdl');
$result = $client->__soapCall('procesaNotificacionSIS', [
'XML' => $requestString
]);
$xml = new SimpleXMLElement($result);
$key = $_POST['key'];
$start = strpos($result, '<Response');
$end = strrpos($result, '</Response>');
$shaString = substr($result, $start, $end - $start + 11);
$shaHash = sha1($shaString.$key);
$isValid = $xml->{'Signature'} == $shaHash;
} else {
$key = '';
$result = '';
$shaHash = '';
$isValid = FALSE;
}
?>
<html>
<head>
<title>
TPV SOAP Client
</title>
</head>
<body>
<form action="?" method="post">
<label>Key:</label>
<input type="password" value="<?=$key?>" name="key"/>
<input type="submit"/>
</form>
<h2>Response</h2>
<p>
<pre><?=htmlentities($result)?></pre>
</p>
<h2>Signature</h2>
<p>
Calculated: <?=$shaHash?>
</p>
<p>
Valid: <input type="checkbox" <?=($isValid ? 'checked' : '')?>/>
</p>
</body>
</html>

View File

@ -1,51 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<definitions name="InotificacionSIS"
targetNamespace="https://sis.sermepa.es/sis/InotificacionSIS.wsdl"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tns="https://sis.sermepa.es/sis/InotificacionSIS.wsdl"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns="http://schemas.xmlsoap.org/wsdl/">
<message name="procesaNotificacionSISRequest">
<part name="XML" type="xs:string"/>
</message>
<message name="procesaNotificacionSISResponse">
<part name="return" type="xs:string"/>
</message>
<portType name="InotificacionSISPortType">
<operation name="procesaNotificacionSIS">
<input message="tns:procesaNotificacionSISRequest"/>
<output message="tns:procesaNotificacionSISResponse"/>
</operation>
</portType>
<binding name="InotificacionSISBinding" type="tns:InotificacionSISPortType">
<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
<operation name="procesaNotificacionSIS">
<soap:operation
soapAction="urn:InotificacionSIS#procesaNotificacionSIS" style="rpc"/>
<input>
<soap:body use="encoded"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="InotificacionSIS"/>
</input>
<output>
<soap:body use="encoded"
encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="InotificacionSIS"/>
</output>
</operation>
</binding>
<service name="InotificacionSISService">
<port name="InotificacionSIS" binding="tns:InotificacionSISBinding">
<soap:address
location="http://localhost/~juan/hedera-web/tpv/soap.php"/>
</port>
</service>
</definitions>

View File

@ -1,33 +0,0 @@
<?php
class Tpv {
/**
* Tryes to confirm a transaction with the given params.
*/
static function confirm($db, $params) {
if (!(isset($params['Ds_Amount'])
&& isset($params['Ds_Order'])
&& isset($params['Ds_MerchantCode'])
&& isset($params['Ds_Currency'])
&& isset($params['Ds_Response'])))
return FALSE;
if (isset($params['Ds_ErrorCode']))
$error = $params['Ds_ErrorCode'];
else
$error = NULL;
return $db->query(
'CALL tpvTransaction_confirm(#, #, #, #, #, #)',
[
$params['Ds_Amount']
,$params['Ds_Order']
,$params['Ds_MerchantCode']
,$params['Ds_Currency']
,$params['Ds_Response']
,$error
]
);
}
}

View File

@ -1,67 +0,0 @@
<?php
/**
* Starts a new TPV transaction and returns the params.
*/
class Transaction extends Vn\Web\JsonRequest {
const PARAMS = ['amount'];
function run($db) {
$amount = (int) $_REQUEST['amount'];
$companyId = empty($_REQUEST['company']) ? NULL : $_REQUEST['company'];
$row = $db->getObject('CALL myTpvTransaction_start(#, #)',
[$amount, $companyId]);
if (!isset($row))
throw new Exception('Transaction error');
$transactionId = str_pad($row->transactionId, 12, '0', STR_PAD_LEFT);
$merchantUrl = $row->merchantUrl ? $row->merchantUrl : '';
$urlOk = empty($_REQUEST['urlOk']) ? '' :
str_replace('_transactionId_', $transactionId, $_REQUEST['urlOk']);
$urlKo = empty($_REQUEST['urlKo']) ? '' :
str_replace('_transactionId_', $transactionId, $_REQUEST['urlKo']);
$params = [
'Ds_Merchant_Amount' => $amount
,'Ds_Merchant_Order' => $transactionId
,'Ds_Merchant_MerchantCode' => $row->merchant
,'Ds_Merchant_Currency' => $row->currency
,'Ds_Merchant_TransactionType' => $row->transactionType
,'Ds_Merchant_Terminal' => $row->terminal
,'Ds_Merchant_MerchantURL' => $merchantUrl
,'Ds_Merchant_UrlOK' => $urlOk
,'Ds_Merchant_UrlKO' => $urlKo
];
$encodedParams = base64_encode(json_encode($params));
$key = base64_decode($row->secretKey);
$bytes = [0, 0, 0, 0, 0, 0, 0, 0];
$iv = implode(array_map('chr', $bytes));
$paddedData = $transactionId;
if (strlen($paddedData) % 8) {
$paddedData = str_pad($paddedData,
strlen($paddedData) + 8 - strlen($paddedData) % 8, "\0");
}
$encryptedData = openssl_encrypt($paddedData,
'des-ede3-cbc', $key, OPENSSL_RAW_DATA | OPENSSL_NO_PADDING , $iv);
$signature = base64_encode(hash_hmac('sha256', $encodedParams, $encryptedData, TRUE));
$url = $row->url;
$postValues = [
'Ds_SignatureVersion' => 'HMAC_SHA256_V1'
,'Ds_MerchantParameters' => $encodedParams
,'Ds_Signature' => $signature
];
return [
'url' => $url
,'postValues' => $postValues
];
}
}

View File

@ -50,9 +50,18 @@ class RestService extends Service {
try {
$res = $method->run($methodDb);
} catch (Db\Exception $e) {
if ($e->getCode() == 1644)
throw new UserException(s($e->getMessage()));
} catch (\Vn\Db\Exception $e) {
if ($e->getCode() == 1644) {
$eMessage = $e->getMessage();
$tMessage = $db->getValue(
'SELECT `description` FROM `message` WHERE `code` = #',
[$eMessage]
);
if (!$tMessage) $tMessage = $eMessage;
throw new Lib\UserException($tMessage, $eMessage);
}
throw $e;
}
if ($method::SECURITY == Security::DEFINER)

View File

@ -3,6 +3,7 @@
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=no"/>
<title>Not available - Verdnatura</title>
<style type="text/css">
body {
@ -22,7 +23,7 @@
font-weight: normal;
}
div a {
color: #2962FF;
color: #6a1;
text-decoration: none;
}
</style>