Merge branch 'fix'

This commit is contained in:
Yuri Kuznetsov
2025-07-10 16:41:19 +03:00
8 changed files with 154 additions and 91 deletions
+7 -1
View File
@@ -54,7 +54,8 @@ class Admin
private Manager $adminNotificationManager,
private SystemRequirements $systemRequirements,
private ScheduledJob $scheduledJob,
private DataManager $dataManager
private DataManager $dataManager,
private Config\SystemConfig $systemConfig,
) {
if (!$this->user->isAdmin()) {
throw new Forbidden();
@@ -179,9 +180,14 @@ class Admin
* database: array<string, array<string, mixed>>,
* permission: array<string, array<string, mixed>>,
* }
* @throws Forbidden
*/
public function getActionSystemRequirementList(): object
{
if (!$this->user->isSuperAdmin() && $this->systemConfig->isRestrictedMode()) {
throw new Forbidden();
}
return (object) $this->systemRequirements->getAllRequiredList();
}
}
@@ -50,4 +50,14 @@ class SystemConfig
{
return (string) $this->config->get('version');
}
/**
* Is restricted mode.
*
* @since 9.1.8
*/
public function isRestrictedMode(): bool
{
return (bool) $this->config->get('restrictedMode');
}
}
+4
View File
@@ -232,6 +232,10 @@ class Email extends Entity
return $this->getFromContainer(self::ATTR_BODY_PLAIN);
}
if (!$this->isHtml()) {
return $this->getBody();
}
$body = $this->getBody() ?: '';
return EmailUtil::stripHtml($body) ?: null;
@@ -2,16 +2,23 @@
<span class="breadcrumb-separator"><span></span></span>
{{translate 'System Requirements' scope='Admin'}}</h3></div>
<div class="panel panel-default">
<table class="table table-striped table-no-overflow table-fixed">
<thead>
{{#if noAccess}}
<div class="panel panel-default">
<div class="panel-body">
{{translate 'error403' category='messages'}}
</div>
</div>
{{else}}
<div class="panel panel-default">
<table class="table table-striped table-no-overflow table-fixed">
<thead>
<tr>
<th><h5>{{translate 'PHP Settings' scope='Admin'}}</h5></th>
<th style="width: 24%"></th>
<th style="width: 24%"></th>
</tr>
</thead>
<tbody>
</thead>
<tbody>
{{#each phpRequirementList}}
<tr class="list-row">
<td class="cell">
@@ -21,27 +28,27 @@
<td class="cell">
{{#if acceptable}} <span class="text-success">{{translate 'Success' scope='Admin'}}</span>
{{else}}
<span class="text-danger">{{translate 'Fail' scope='Admin'}}
{{#ifEqual type 'lib'}} ({{translate 'extension is missing' scope='Admin'}}) {{/ifEqual}}
{{#ifEqual type 'param'}} ({{required}} {{translate 'is recommended' scope='Admin'}}) {{/ifEqual}}
<span class="text-danger">{{translate 'Fail' scope='Admin'}}
{{#ifEqual type 'lib'}} ({{translate 'extension is missing' scope='Admin'}}) {{/ifEqual}}
{{#ifEqual type 'param'}} ({{required}} {{translate 'is recommended' scope='Admin'}}) {{/ifEqual}}
</span> {{/if}}
</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
</tbody>
</table>
</div>
<div class="panel panel-default">
<table class="table table-striped table-no-overflow table-fixed">
<thead>
<div class="panel panel-default">
<table class="table table-striped table-no-overflow table-fixed">
<thead>
<tr>
<th><h5>{{translate 'Database Settings' scope='Admin'}}</h5></th>
<th style="width: 24%"></th>
<th style="width: 24%"></th>
</tr>
</thead>
<tbody>
</thead>
<tbody>
{{#each databaseRequirementList}}
<tr class="">
<td class="cell">
@@ -50,29 +57,29 @@
<td class="cell" style="width: 24%">{{actual}}</td>
<td class="cell" style="width: 24%">
{{#if acceptable}}
<span class="text-success">{{translate 'Success' scope='Admin'}}</span>
<span class="text-success">{{translate 'Success' scope='Admin'}}</span>
{{else}}
<span class="text-danger">{{translate 'Fail' scope='Admin'}}
{{#ifEqual type 'param'}} ({{required}} {{translate 'is recommended' scope='Admin'}}) {{/ifEqual}}
<span class="text-danger">{{translate 'Fail' scope='Admin'}}
{{#ifEqual type 'param'}} ({{required}} {{translate 'is recommended' scope='Admin'}}) {{/ifEqual}}
</span>
{{/if}}
</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
</tbody>
</table>
</div>
<div class="panel panel-default">
<table class="table table-striped table-no-overflow table-fixed">
<thead>
<div class="panel panel-default">
<table class="table table-striped table-no-overflow table-fixed">
<thead>
<tr>
<th><h5>{{translate 'Permissions' scope='Admin'}}</h5></th>
<th style="width: 24%"></th>
<th style="width: 24%"></th>
</tr>
</thead>
<tbody>
</thead>
<tbody>
{{#each permissionRequirementList}}
<tr>
<td class="cell">
@@ -81,24 +88,25 @@
<td class="cell" style="width: 24%">{{translate type scope='Admin' category='systemRequirements'}}</td>
<td class="cell" style="width: 24%">
{{#if acceptable}}
<span class="text-success">{{translate 'Success' scope='Admin'}}</span>
<span class="text-success">{{translate 'Success' scope='Admin'}}</span>
{{else}}
<span class="text-danger">{{translate 'Fail' scope='Admin'}}</span>
<span class="text-danger">{{translate 'Fail' scope='Admin'}}</span>
{{/if}}
</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
</tbody>
</table>
</div>
<div class="row">
<div class="col-md-12">
<div class="pull-right">
<a
target="_blank"
href="https://docs.espocrm.com/administration/server-configuration/"
><strong>{{translate 'Configuration Instructions' scope='Admin'}}</strong></a>
<div class="row">
<div class="col-md-12">
<div class="pull-right">
<a
target="_blank"
href="https://docs.espocrm.com/administration/server-configuration/"
><strong>{{translate 'Configuration Instructions' scope='Admin'}}</strong></a>
</div>
</div>
</div>
</div>
{{/if}}
+62 -44
View File
@@ -36,7 +36,6 @@ export default class extends View {
targetEntityType = null
storageKey = 'formulaSandbox'
setup() {
const entityTypeList = [''].concat(
this.getMetadata()
@@ -132,7 +131,7 @@ export default class extends View {
return;
}
let dataToStore = {
const dataToStore = {
script: this.model.get('script'),
targetType: this.model.get('targetType'),
targetId: this.model.get('targetId'),
@@ -143,6 +142,13 @@ export default class extends View {
});
}
/**
* @return {import('views/record/detail').default}
*/
getRecordView() {
return this.getView('record');
}
createRecordView() {
return this.createView('record', 'views/admin/formula-sandbox/record/edit', {
selector: '.record',
@@ -157,7 +163,7 @@ export default class extends View {
this.setPageTitle(this.getLanguage().translate('Formula Sandbox', 'labels', 'Admin'));
}
run() {
async run() {
const script = this.model.get('script');
this.model.set({
@@ -175,50 +181,62 @@ export default class extends View {
return;
}
Espo.Ajax
.postRequest('Formula/action/run', {
this.getRecordView().disableActionItems();
Espo.Ui.notifyWait();
/** @var {Record} */
let response;
try {
response = await Espo.Ajax.postRequest('Formula/action/run', {
expression: script,
targetId: this.model.get('targetId'),
targetType: this.model.get('targetType'),
})
.then(response => {
this.model.set('output', response.output || null);
let errorMessage = null;
if (!response.isSuccess) {
errorMessage = response.message || null;
}
this.model.set('errorMessage', errorMessage);
if (response.isSuccess) {
Espo.Ui.success(
this.translate('runSuccess', 'messages', 'Formula')
);
return;
}
if (response.isSyntaxError) {
let msg = this.translate('checkSyntaxError', 'messages', 'Formula');
if (response.message) {
msg += ' ' + response.message;
}
Espo.Ui.error(msg);
return;
}
let msg = this.translate('runError', 'messages', 'Formula');
if (response.message) {
msg += ' ' + response.message;
}
Espo.Ui.error(msg);
});
} catch (e) {
this.getRecordView().enableActionItems();
return;
}
this.getRecordView().enableActionItems();
this.model.set('output', response.output || null);
let errorMessage = null;
if (!response.isSuccess) {
errorMessage = response.message || null;
}
this.model.set('errorMessage', errorMessage);
if (response.isSuccess) {
Espo.Ui.success(
this.translate('runSuccess', 'messages', 'Formula')
);
return;
}
if (response.isSyntaxError) {
let msg = this.translate('checkSyntaxError', 'messages', 'Formula');
if (response.message) {
msg += ' ' + response.message;
}
Espo.Ui.error(msg);
return;
}
let msg = this.translate('runError', 'messages', 'Formula');
if (response.message) {
msg += ' ' + response.message;
}
Espo.Ui.error(msg);
}
}
@@ -27,6 +27,7 @@
************************************************************************/
import View from 'view';
import Ajax from 'ajax';
export default class extends View {
@@ -35,25 +36,40 @@ export default class extends View {
/** @type {Record} */
requirements
/**
* @private
* @type {boolean}
*/
noAccess = false
data() {
return {
phpRequirementList: this.requirements.php,
databaseRequirementList: this.requirements.database,
permissionRequirementList: this.requirements.permission,
noAccess: this.noAccess,
};
}
setup() {
this.requirements = {};
this.wait(this.loadData());
}
async loadData() {
Espo.Ui.notifyWait();
const promise = Espo.Ajax.getRequest('Admin/action/systemRequirementList').then(requirements => {
this.requirements = requirements;
try {
this.requirements = await Espo.Ajax.getRequest('Admin/action/systemRequirementList');
} catch(e) {
this.noAccess = true;
Espo.Ui.notify();
});
if ('errorIsHandled' in e) {
e.errorIsHandled = true;
}
}
this.wait(promise);
Espo.Ui.notify();
}
}
@@ -1,5 +1,5 @@
.filter > .form-group .field .selectize-control,
.selectize-control.input-sm {
.selectize-control.input-sm.form-control {
&.multi {
.selectize-input {
font-size: var(--font-size-small);
+1
View File
@@ -495,6 +495,7 @@ class EmailTest extends TestCase
public function testBodyPlain(): void
{
$this->email->setBody('<br />&nbsp;&amp;');
$this->email->setIsHtml();
$bodyPlain = $this->email->getBodyPlain();
$this->assertEquals(" \r\n &", $bodyPlain);