Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions config/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,13 @@
['_name' => 'lock:remove'],
);

// SendMail
$routes->connect(
'/sendmail',
['controller' => 'SendMail', 'action' => 'index'],
['_name' => 'sendmail:index'],
);

// Session
$routes->get(
'/session/{name}',
Expand Down
5 changes: 4 additions & 1 deletion locales/default.pot
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: BEdita 4 \n"
"POT-Creation-Date: 2025-08-26 08:17:45 \n"
"POT-Creation-Date: 2025-10-29 11:08:16 \n"
"MIME-Version: 1.0 \n"
"Content-Transfer-Encoding: 8bit \n"
"Language-Team: BEdita I18N & I10N Team \n"
Expand Down Expand Up @@ -1765,6 +1765,9 @@ msgstr ""
msgid "View original"
msgstr ""

msgid "Send"
msgstr ""

msgid "Address"
msgstr ""

Expand Down
5 changes: 4 additions & 1 deletion locales/en_US/default.po
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: BEdita Manager \n"
"POT-Creation-Date: 2025-08-26 08:17:45 \n"
"POT-Creation-Date: 2025-10-29 11:08:16 \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: BEdita I18N & I10N Team \n"
Expand Down Expand Up @@ -1768,6 +1768,9 @@ msgstr ""
msgid "View original"
msgstr ""

msgid "Send"
msgstr ""

msgid "Address"
msgstr ""

Expand Down
5 changes: 4 additions & 1 deletion locales/it_IT/default.po
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: BEdita Manager \n"
"POT-Creation-Date: 2025-08-26 08:17:45 \n"
"POT-Creation-Date: 2025-10-29 11:08:16 \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: BEdita I18N & I10N Team \n"
Expand Down Expand Up @@ -1791,6 +1791,9 @@ msgstr ""
msgid "View original"
msgstr "Vedi originale"

msgid "Send"
msgstr "Invia"

msgid "Address"
msgstr "Indirizzo"

Expand Down
1 change: 1 addition & 0 deletions resources/js/app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
AddRelatedById: () => import(/* webpackChunkName: "add-related-by-id" */'app/components/add-related-by-id/add-related-by-id'),
UploadedObject: () => import(/* webpackChunkName: "uploaded-object" */'app/components/uploaded-object/uploaded-object.vue'),
RibbonItem: () => import(/* webpackChunkName: "ribbon-item" */'./components/ribbon-item/ribbon-item.vue'),
MailPreview: () => import(/* webpackChunkName: "mail-preview" */'./components/mail-preview/mail-preview.vue'),
AppIcon,
},

Expand Down Expand Up @@ -646,6 +647,6 @@
Vue.component('ObjectCaptions', _vueInstance.$options.components.ObjectCaptions);
Vue.component('ObjectInfo', _vueInstance.$options.components.ObjectInfo);
Vue.component('RelatedObjectsFilter', _vueInstance.$options.components.RelatedObjectsFilter);
Vue.component('Thumbnail', _vueInstance.$options.components.Thumbnail);

Check warning on line 650 in resources/js/app/app.js

View workflow job for this annotation

GitHub Actions / Check javascript build (22.x)

Component name "Thumbnail" should always be multi-word

Check warning on line 650 in resources/js/app/app.js

View workflow job for this annotation

GitHub Actions / Check javascript build (20.x)

Component name "Thumbnail" should always be multi-word

Check warning on line 650 in resources/js/app/app.js

View workflow job for this annotation

GitHub Actions / Check javascript build (24.x)

Component name "Thumbnail" should always be multi-word
Vue.component('RibbonItem', _vueInstance.$options.components.RibbonItem);
Vue.component('UploadedObject', _vueInstance.$options.components.UploadedObject);
122 changes: 122 additions & 0 deletions resources/js/app/components/mail-preview/mail-preview.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
<template>
<div class="mail-preview">
<div class="text-preview mt-05">
<div :value="text"
v-html="text"
/>
</div>

<div class="input text mt-05"
v-for="placeholder in placeholders"
:key="placeholder"
>
<input type="text"
:placeholder="placeholder"
v-model="variables[placeholder]"
>
</div>

<div class="mt-05 send">
<input type="text"
placeholder="[email protected]"
v-model="destination"
>
<button
class="button button-outlined"
:class="{ 'is-loading-spinner': loading }"
:disabled="!destination"
@click.prevent.stop="send"
>
<app-icon icon="carbon:email" />
<span class="ml-05">{{ msgSend }}</span>
</button>
</div>
</div>
</template>
<script>
import { t } from 'ttag';
export default {
name: 'MailPreview',
props: {
text: {
type: String,
required: true
},
uname: {
type: String,
required: true
}
},
data() {
return {
destination: '',
loading: false,
msgSend: t`Send`,
placeholders: [],
variables: {},
}
},
mounted() {
this.$nextTick(() => {
this.placeholders = this.text.match(/{{(.*?)}}/g).map(placeholder => placeholder.replace(/{{|}}/g, '').trim().toLowerCase());
this.placeholders.sort();
this.placeholders = [...new Set(this.placeholders)];
this.placeholders.forEach((placeholder) => {
this.$set(this.variables, placeholder, '');
});
});
},
methods: {
async send() {
try {
this.loading = true;
const response = await fetch(`${BEDITA.base}/sendmail`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': BEDITA.csrfToken
},
body: JSON.stringify({
name: this.uname,
data: this.variables,
config: {
to: this.destination,
}
})
});
const json = await response.json();
if (json?.error) {
throw new Error(json.error);
}
} catch (error) {
BEDITA.error(error);
} finally {
this.loading = false;
}
}
},
}
</script>
<style>
div.mail-preview > .send {
display: grid;
grid-template-columns: 1fr 100px;
}
div.mail-preview > .text-preview {
border: 1px dotted #ccc;
color: #000;
background-color: #FFF;
border-radius: 5px;
padding: 2rem 2rem;
font-size: medium;
}
div.mail-preview > .text-preview > div {
white-space: pre-line;
}
div.mail-preview > .text-preview > div > p {
margin-top: 0.5rem;
}
div.mail-preview > .text-preview > div > p > a {
color: #007bff;
}
</style>
1 change: 1 addition & 0 deletions resources/js/app/components/property-view/property-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export default {
ObjectCategories: () => import(/* webpackChunkName: "object-categories" */'app/components/object-categories/object-categories'),
PlaceholderList: () => import(/* webpackChunkName: "placeholder-list" */'app/components/placeholder-list/placeholder-list'),
MediaItem: () => import(/* webpackChunkName: "media-item" */'app/components/media-item/media-item'),
MailPreview: () => import(/* webpackChunkName: "mail-preview" */'app/components/mail-preview/mail-preview.vue'),
},

props: {
Expand Down
1 change: 1 addition & 0 deletions resources/js/app/pages/modules/view.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export default {
KeyValueList: () => import(/* webpackChunkName: "key-value-list" */'app/components/json-fields/key-value-list'),
StringList: () => import(/* webpackChunkName: "string-list" */'app/components/json-fields/string-list'),
LanguageSelector:() => import(/* webpackChunkName: "language-selector" */'app/components/language-selector/language-selector'),
MailPreview: () => import(/* webpackChunkName: "mail-preview" */'app/components/mail-preview/mail-preview.vue'),
},

props: {
Expand Down
48 changes: 48 additions & 0 deletions src/Controller/SendMailController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);

namespace App\Controller;

use BEdita\WebTools\ApiClientProvider;
use Cake\Utility\Hash;
use Exception;

/**
* SendMail Controller
*/
class SendMailController extends AppController
{
/**
* @inheritDoc
*/
public function initialize(): void
{
parent::initialize();
$this->FormProtection->setConfig('unlockedActions', ['index']);
}

/**
* @inheritDoc
*/
public function index(): void
{
$this->getRequest()->allowMethod(['post']);
$this->viewBuilder()->setClassName('Json');
try {
$payload = $this->getRequest()->getData();
foreach ($payload['data'] as $key => $value) {
if (strpos($key, '.') !== false) {
unset($payload['data'][$key]);
$payload['data'] = Hash::insert($payload['data'], $key, $value);
}
}
ApiClientProvider::getApiClient()->post('/placeholders/send', (string)json_encode($payload));
$response = ['message' => 'Email sent successfully'];
$this->set('response', $response);
$this->setSerialize(['response']);
} catch (Exception $e) {
$this->set('error', $e->getMessage());
$this->setSerialize(['error']);
}
}
}
2 changes: 2 additions & 0 deletions templates/Element/Form/mail_preview.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<mail-preview :text="{{ object.attributes.body|json_encode }}" uname="{{ object.attributes.uname }}">
</mail-preview>
60 changes: 60 additions & 0 deletions tests/TestCase/Controller/SendMailControllerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

namespace App\Test\TestCase\Controller;

use App\Controller\SendMailController;
use BEdita\SDK\BEditaClient;
use BEdita\WebTools\ApiClientProvider;
use Cake\Http\ServerRequest;
use Cake\TestSuite\TestCase;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\CoversMethod;

/**
* {@see \App\Controller\SendMailController} Test Case
*/
#[CoversClass(SendMailController::class)]
#[CoversMethod(SendMailController::class, 'index')]
#[CoversMethod(SendMailController::class, 'initialize')]
class SendMailControllerTest extends TestCase
{
/**
* Test `index` method
*
* @return void
*/
public function testIndex(): void
{
$config = [
'environment' => [
'REQUEST_METHOD' => 'POST',
],
'post' => [
'data' => [
'user.name' => 'John',
'user.surname' => 'Doe',
],
],
];
$request = new ServerRequest($config);
$controller = new SendMailController($request);
$controller->index();
$expected = '[404] Not Found';
$actual = $controller->viewBuilder()->getVar('error');
static::assertEquals($expected, $actual);

// mock /placeholders/send to simulate success response
$safeClient = ApiClientProvider::getApiClient();
$apiClient = $this->getMockBuilder(BEditaClient::class)
->setConstructorArgs(['https://api.example.org'])
->getMock();
$apiClient->method('post')
->willReturn(null);
ApiClientProvider::setApiClient($apiClient);
$controller->index();
$expected = 'Email sent successfully';
$actual = $controller->viewBuilder()->getVar('response')['message'];
static::assertEquals($expected, $actual);
ApiClientProvider::setApiClient($safeClient);
}
}
Loading