From 4deeac9513bc1fe6b5aec3512fe61310ee240f8f Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Thu, 13 Jan 2022 14:33:08 +0200 Subject: [PATCH] formula check syntax --- application/Espo/Controllers/Formula.php | 68 +++++++++++++++ .../Core/Formula/Exceptions/SyntaxError.php | 14 ++++ application/Espo/Core/Formula/Parser.php | 5 +- .../Espo/Resources/i18n/en_US/Formula.json | 9 ++ .../Resources/metadata/scopes/Formula.json | 3 + application/Espo/Tools/Formula/Service.php | 57 +++++++++++++ .../Espo/Tools/Formula/SyntaxCheckResult.php | 83 +++++++++++++++++++ client/res/templates/fields/formula/edit.tpl | 23 +++-- client/src/views/fields/formula.js | 50 ++++++++++- 9 files changed, 305 insertions(+), 7 deletions(-) create mode 100644 application/Espo/Controllers/Formula.php create mode 100644 application/Espo/Resources/i18n/en_US/Formula.json create mode 100644 application/Espo/Resources/metadata/scopes/Formula.json create mode 100644 application/Espo/Tools/Formula/Service.php create mode 100644 application/Espo/Tools/Formula/SyntaxCheckResult.php diff --git a/application/Espo/Controllers/Formula.php b/application/Espo/Controllers/Formula.php new file mode 100644 index 0000000000..fa564f1ba4 --- /dev/null +++ b/application/Espo/Controllers/Formula.php @@ -0,0 +1,68 @@ +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(); + } +} diff --git a/application/Espo/Core/Formula/Exceptions/SyntaxError.php b/application/Espo/Core/Formula/Exceptions/SyntaxError.php index ca667435bd..22108f208d 100644 --- a/application/Espo/Core/Formula/Exceptions/SyntaxError.php +++ b/application/Espo/Core/Formula/Exceptions/SyntaxError.php @@ -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(); + } } diff --git a/application/Espo/Core/Formula/Parser.php b/application/Espo/Core/Formula/Parser.php index 2c993cfd5d..c4a45d7e6c 100644 --- a/application/Espo/Core/Formula/Parser.php +++ b/application/Espo/Core/Formula/Parser.php @@ -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 ( diff --git a/application/Espo/Resources/i18n/en_US/Formula.json b/application/Espo/Resources/i18n/en_US/Formula.json new file mode 100644 index 0000000000..292613cdb5 --- /dev/null +++ b/application/Espo/Resources/i18n/en_US/Formula.json @@ -0,0 +1,9 @@ +{ + "labels": { + "Check Syntax": "Check Syntax" + }, + "messages": { + "checkSyntaxSuccess": "Syntax is correct.", + "checkSyntaxError": "Syntax error." + } +} diff --git a/application/Espo/Resources/metadata/scopes/Formula.json b/application/Espo/Resources/metadata/scopes/Formula.json new file mode 100644 index 0000000000..89c3030cc6 --- /dev/null +++ b/application/Espo/Resources/metadata/scopes/Formula.json @@ -0,0 +1,3 @@ +{ + "languageIsGlobal": true +} diff --git a/application/Espo/Tools/Formula/Service.php b/application/Espo/Tools/Formula/Service.php new file mode 100644 index 0000000000..58b6e1647a --- /dev/null +++ b/application/Espo/Tools/Formula/Service.php @@ -0,0 +1,57 @@ +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; + } +} diff --git a/application/Espo/Tools/Formula/SyntaxCheckResult.php b/application/Espo/Tools/Formula/SyntaxCheckResult.php new file mode 100644 index 0000000000..538a4c5bd6 --- /dev/null +++ b/application/Espo/Tools/Formula/SyntaxCheckResult.php @@ -0,0 +1,83 @@ +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; + } +} diff --git a/client/res/templates/fields/formula/edit.tpl b/client/res/templates/fields/formula/edit.tpl index 56a645fb6c..beac485641 100644 --- a/client/res/templates/fields/formula/edit.tpl +++ b/client/res/templates/fields/formula/edit.tpl @@ -1,22 +1,35 @@ -
-
+
{{value}}
- {{#if hasInsert}} + {{#if hasSide}}
- + {{#if hasCheckSyntax}} + + {{/if}} + {{#if hasInsert}} + + {{/if}}
{{/if}} -
\ No newline at end of file +
diff --git a/client/src/views/fields/formula.js b/client/src/views/fields/formula.js index 297e84e30c..9e9efde715 100644 --- a/client/src/views/fields/formula.js +++ b/client/src/views/fields/formula.js @@ -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; + }); + }, + }); });