formula check syntax

This commit is contained in:
Yuri Kuznetsov
2022-01-13 14:33:08 +02:00
parent 4c11bf663b
commit 4deeac9513
9 changed files with 305 additions and 7 deletions

View File

@@ -0,0 +1,68 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Controllers;
use Espo\Core\Api\Request;
use Espo\Tools\Formula\Service;
use Espo\Core\Exceptions\BadRequest;
use Espo\Core\Exceptions\ForbiddenSilent;
use Espo\Entities\User;
use stdClass;
class Formula
{
private Service $service;
public function __construct(Service $service, User $user)
{
$this->service = $service;
if (!$user->isAdmin()) {
throw new ForbiddenSilent();
}
}
public function postActionCheckSyntax(Request $request): stdClass
{
$expression = $request->getParsedBody()->expression ?? null;
if (!$expression || !is_string($expression)) {
throw new BadRequest("No or non-string expression.");
}
return $this->service
->checkSyntax($expression)
->toStdClass();
}
}

View File

@@ -31,4 +31,18 @@ namespace Espo\Core\Formula\Exceptions;
class SyntaxError extends Error
{
private $shortMessage = null;
public static function create(string $message, ?string $shortMessage = null): self
{
$obj = new static($message);
$obj->shortMessage = $shortMessage;
return $obj;
}
public function getShortMessage(): ?string
{
return $this->shortMessage ?? $this->getMessage();
}
}

View File

@@ -260,7 +260,10 @@ class Parser
}
if ($braceCounter !== 0) {
throw new SyntaxError('Incorrect round brackets in expression ' . $expression . '.');
throw SyntaxError::create(
'Incorrect round brackets in expression ' . $expression . '.',
'Incorrect round brackets.'
);
}
if (

View File

@@ -0,0 +1,9 @@
{
"labels": {
"Check Syntax": "Check Syntax"
},
"messages": {
"checkSyntaxSuccess": "Syntax is correct.",
"checkSyntaxError": "Syntax error."
}
}

View File

@@ -0,0 +1,3 @@
{
"languageIsGlobal": true
}

View File

@@ -0,0 +1,57 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Tools\Formula;
use Espo\Core\Formula\Parser;
use Espo\Core\Formula\Exceptions\SyntaxError;
class Service
{
private Parser $parser;
public function __construct(Parser $parser)
{
$this->parser = $parser;
}
public function checkSyntax(string $expression): SyntaxCheckResult
{
try {
$this->parser->parse($expression);
$result = SyntaxCheckResult::createSuccess();
}
catch (SyntaxError $e) {
return SyntaxCheckResult::createError($e);
}
return $result;
}
}

View File

@@ -0,0 +1,83 @@
<?php
/************************************************************************
* This file is part of EspoCRM.
*
* EspoCRM - Open Source CRM application.
* Copyright (C) 2014-2021 Yurii Kuznietsov, Taras Machyshyn, Oleksii Avramenko
* Website: https://www.espocrm.com
*
* EspoCRM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* EspoCRM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with EspoCRM. If not, see http://www.gnu.org/licenses/.
*
* The interactive user interfaces in modified source and object code versions
* of this program must display Appropriate Legal Notices, as required under
* Section 5 of the GNU General Public License version 3.
*
* In accordance with Section 7(b) of the GNU General Public License version 3,
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
************************************************************************/
namespace Espo\Tools\Formula;
use Espo\Core\Formula\Exceptions\SyntaxError;
use stdClass;
class SyntaxCheckResult
{
private bool $isSuccess = false;
private ?string $message = null;
private function __construct(bool $isSuccess)
{
$this->isSuccess = $isSuccess;
}
public static function createSuccess(): self
{
return new self(true);
}
public static function createError(SyntaxError $exception): self
{
$obj = new self(false);
$obj->message = $exception->getShortMessage();
return $obj;
}
public function isSuccess(): bool
{
return $this->isSuccess;
}
public function getMessage(): ?string
{
return $this->message;
}
public function toStdClass(): stdClass
{
$data = (object) [];
$data->isSuccess = $this->isSuccess();
if (!$this->isSuccess) {
$data->message = $this->message;
}
return $data;
}
}

View File

@@ -1,22 +1,35 @@
<div class="row">
<div class="{{#if hasInsert}}col-md-10 col-sm-10 col-xs-12{{else}}col-md-12{{/if}}">
<div class="{{#if hasSide}}col-md-10 col-sm-10 col-xs-12{{else}}col-md-12{{/if}}">
<div id="{{containerId}}">{{value}}</div>
</div>
{{#if hasInsert}}
{{#if hasSide}}
<div class="col-md-2 col-sm-2 col-xs-12">
<div class="button-container">
<div class="btn-group pull-right">
<button type="button" class="btn btn-default btn-sm dropdown-toggle btn-icon" data-toggle="dropdown"><span class="fas fa-plus"></span></button>
{{#if hasCheckSyntax}}
<button
type="button"
class="btn btn-default btn-sm dropdown-toggle btn-icon"
data-action="checkSyntax"
title="{{translate 'Check Syntax' scope='Formula'}}"
><span class="fas fa-play"></span></button>
{{/if}}
{{#if hasInsert}}
<button
type="button"
class="btn btn-default btn-sm dropdown-toggle btn-icon"
data-toggle="dropdown"
><span class="fas fa-plus"></span></button>
<ul class="dropdown-menu pull-right">
{{#if targetEntityType}}
<li><a href="javascript:" data-action="addAttribute">{{translate 'Attribute'}}</a></li>
{{/if}}
<li><a href="javascript:" data-action="addFunction">{{translate 'Function'}}</a></li>
</ul>
{{/if}}
</div>
</div>
</div>
{{/if}}
</div>
</div>

View File

@@ -40,6 +40,10 @@ define('views/fields/formula', 'views/fields/text', function (Dep) {
maxLineEditCount: 200,
insertDisabled: false,
checkSyntaxDisabled: false,
events: {
'click [data-action="addAttribute"]': function () {
this.addAttribute();
@@ -47,6 +51,9 @@ define('views/fields/formula', 'views/fields/text', function (Dep) {
'click [data-action="addFunction"]': function () {
this.addFunction();
},
'click [data-action="checkSyntax"]': function () {
this.checkSyntax();
},
},
setup: function () {
@@ -69,7 +76,8 @@ define('views/fields/formula', 'views/fields/text', function (Dep) {
this.params.targetEntityType ||
this.targetEntityType;
this.insertDisabled = this.options.insertDisabled;
this.insertDisabled = this.insertDisabled || this.options.insertDisabled;
this.checkSyntaxDisabled = this.checkSyntaxDisabled || this.options.checkSyntaxDisabled;
this.containerId = 'editor-' + Math.floor((Math.random() * 10000) + 1).toString();
@@ -107,9 +115,13 @@ define('views/fields/formula', 'views/fields/text', function (Dep) {
data: function () {
var data = Dep.prototype.data.call(this);
data.containerId = this.containerId;
data.targetEntityType = this.targetEntityType;
data.hasSide = !this.insertDisabled && !this.checkSyntaxDisabled;
data.hasInsert = !this.insertDisabled;
data.hasCheckSyntax = !this.checkSyntaxDisabled;
return data;
},
@@ -346,5 +358,41 @@ define('views/fields/formula', 'views/fields/text', function (Dep) {
return attributeList;
},
checkSyntax: function () {
let expression = this.editor.getValue();
if (!expression) {
Espo.Ui.success(
this.translate('checkSyntaxSuccess', 'messages', 'Formula')
);
return;
}
Espo.Ajax
.postRequest('Formula/action/checkSyntax', {
expression: expression,
})
.then(response => {
if (response.isSuccess) {
Espo.Ui.success(
this.translate('checkSyntaxSuccess', 'messages', 'Formula')
);
return;
}
let message = this.translate('checkSyntaxError', 'messages', 'Formula');
if (response.message) {
message += ' ' + response.message;
}
Espo.Ui.error(message);
return;
});
},
});
});