1
0
mirror of https://github.com/typemill/typemill.git synced 2025-08-05 13:47:37 +02:00

Version 2.1.0 add testmail feature

This commit is contained in:
trendschau
2024-01-11 21:40:26 +01:00
parent 1593bed754
commit c5437b9bfe
6 changed files with 149 additions and 41 deletions

View File

@@ -0,0 +1,61 @@
<?php
namespace Typemill\Controllers;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Slim\Routing\RouteContext;
use Typemill\Models\User;
use Typemill\Models\SimpleMail;
use Typemill\Static\Translations;
class ControllerApiTestmail extends Controller
{
public function send(Request $request, Response $response)
{
if(!isset($this->settings['mailfrom']) or !filter_var($this->settings['mailfrom'], FILTER_VALIDATE_EMAIL))
{
$response->getBody()->write(json_encode([
'message' => Translations::translate('The from mail is missing or it is not a valid e-mail address.')
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
}
$user = new User();
$username = $request->getAttribute('c_username');
if(!$user->setUser($username))
{
$response->getBody()->write(json_encode([
'message' => Translations::translate('We did not find the a user or usermail.')
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
}
$userdata = $user->getUserData();
$mail = new SimpleMail($this->settings);
$subject = Translations::translate('Testmail from Typemill');
$message = Translations::translate('This is a testmail from Typemill and if you read this e-mail, then everything works fine.');
$send = $mail->send($userdata['email'], $subject, $message);
if(!$send)
{
$response->getBody()->write(json_encode([
'message' => Translations::translate('We could not send the testmail to your e-mail address. Reason: ') . $mail->error
]));
return $response->withHeader('Content-Type', 'application/json')->withStatus(500);
}
$response->getBody()->write(json_encode([
'message' => Translations::translate('The testmail has been send, please check your inbox and your spam-folder to varify that you received the mail.')
]));
return $response->withHeader('Content-Type', 'application/json');
}
}

View File

@@ -25,7 +25,7 @@ class ControllerWebAuth extends Controller
$input = $request->getParsedBody();
$validation = new Validation();
$securitylog = $this->settings['securitylog'] ?? false;
$authcodeactive = $this->settings['authcode'] ?? false;
$authcodeactive = $this->isAuthcodeActive($this->settings);
$authtitle = Translations::translate('Verification code missing?');
$authtext = Translations::translate('If you did not receive an email with the verification code, then the username or password you entered was wrong. Please try again.');
@@ -108,8 +108,6 @@ class ControllerWebAuth extends Controller
$send = $mail->send($userdata['email'], $subject, $message);
$send = true;
if(!$send)
{
$authtitle = Translations::translate('Error sending email');
@@ -158,6 +156,21 @@ class ControllerWebAuth extends Controller
}
private function isAuthcodeActive($settings)
{
if(
isset($settings['authcode']) &&
$settings['authcode'] &&
isset($settings['mailfrom']) &&
filter_var($settings['mailfrom'], FILTER_VALIDATE_EMAIL)
)
{
return true;
}
return false;
}
# login a user with valid authcode
public function loginWithAuthcode(Request $request, Response $response)
{

View File

@@ -32,14 +32,13 @@ app.component('component-textarea', {
<textarea rows="8" class="w-full border border-stone-300 text-stone-900 bg-stone-200 px-2 py-3"
:id="id"
:class="errors[name] ? ' border-red-500 bg-red-100' : ' border-stone-300 bg-stone-200'"
:class="css"
:readonly="readonly"
:required="required"
:disabled="disabled"
:name="name"
:placeholder="placeholder"
:value="value"
@input="update($event, name)"></textarea>
@input="update($event, name)"></textarea><slot></slot>
<p v-if="errors[name]" class="text-xs text-red-500">{{ errors[name] }}</p>
<p v-else class="text-xs">{{ $filters.translate(description) }}</p>
</div>`,
@@ -81,8 +80,7 @@ app.component('component-codearea', {
:name="name"
:placeholder="placeholder"
:value="value"
@input="update($event, name)">
</textarea>
@input="update($event, name)"></textarea><slot></slot>
<pre aria-hidden="true" class="highlight hljs"><code data-el="highlight" v-html="highlighted"></code></pre>
</div>
<p v-if="errors[name]" class="text-xs text-red-500">{{ errors[name] }}</p>
@@ -141,7 +139,7 @@ app.component('component-select', {
@change="update($event,name)">
<option disabled value="">Please select</option>
<option v-for="option,optionkey in options" v-bind:value="optionkey">{{option}}</option>
</select>
</select><slot></slot>
<p v-if="errors[name]" class="text-xs text-red-500">{{ errors[name] }}</p>
<p v-else class="text-xs">{{ $filters.translate(description) }}</p>
</div>`,
@@ -173,7 +171,7 @@ app.component('component-checkbox', {
v-model="checked"
@change="update(checked, name)">
<span class="ml-2 text-sm">{{ $filters.translate(checkboxlabel) }}</span>
</label>
</label><slot></slot>
<p v-if="errors[name]" class="text-xs text-red-500">{{ errors[name] }}</p>
<p v-else class="text-xs">{{ $filters.translate(description) }}</p>
</div>`,
@@ -209,7 +207,7 @@ app.component('component-checkboxlist', {
v-model="checkedoptions"
@change="update(checkedoptions, name)">
<span class="ml-2 text-sm">{{ $filters.translate(option) }}</span>
</label>
</label><slot></slot>
<p v-if="errors[name]" class="text-xs text-red-500">{{ errors[name] }}</p>
<p v-else class="text-xs">{{ $filters.translate(description) }}</p>
</div>`,
@@ -249,7 +247,7 @@ app.component('component-radio', {
v-model="picked"
@change="update(picked, name)">
<span class="ml-2 text-sm">{{ $filters.translate(option) }}</span>
</label>
</label><slot></slot>
<p v-if="errors[name]" class="text-xs text-red-500">{{ errors[name] }}</p>
<p v-else class="text-xs">{{ $filters.translate(description) }}</p>
</div>`,
@@ -277,7 +275,7 @@ app.component('component-number', {
:name="name"
:placeholder="placeholder"
:value="value"
@input="update($event, name)">
@input="update($event, name)"><slot></slot>
<p v-if="errors[name]" class="text-xs text-red-500">{{ errors[name] }}</p>
<p v-else class="text-xs">{{ $filters.translate(description) }}</p>
</div>`,
@@ -308,7 +306,7 @@ app.component('component-date', {
:name="name"
:placeholder="placeholder"
:value="value"
@input="update($event, name)">
@input="update($event, name)"><slot></slot>
</div>
<p v-if="errors[name]" class="text-xs text-red-500">{{ errors[name] }}</p>
<p v-else class="text-xs">{{ $filters.translate(description) }}</p>
@@ -342,7 +340,7 @@ app.component('component-email', {
:name="name"
:placeholder="placeholder"
:value="value"
@input="update($event, name)">
@input="update($event, name)"><slot></slot>
</div>
<p v-if="errors[name]" class="text-xs text-red-500">{{ errors[name] }}</p>
<p v-else class="text-xs">{{ $filters.translate(description) }}</p>
@@ -375,7 +373,7 @@ app.component('component-tel', {
:name="name"
:placeholder="placeholder"
:value="value"
@input="update($event, name)">
@input="update($event, name)"><slot></slot>
</div>
<p v-if="errors[name]" class="text-xs text-red-500">{{ errors[name] }}</p>
<p v-else class="text-xs">{{ $filters.translate(description) }}</p>
@@ -409,7 +407,7 @@ app.component('component-url', {
:name="name"
:placeholder="placeholder"
:value="value"
@input="update($event, name)">
@input="update($event, name)"><slot></slot>
</div>
<p v-if="errors[name]" class="text-xs text-red-500">{{ errors[name] }}</p>
<p v-else class="text-xs">{{ $filters.translate(description) }}</p>
@@ -442,7 +440,7 @@ app.component('component-color', {
:name="name"
:placeholder="placeholder"
:value="value"
@input="update($event, name)">
@input="update($event, name)"><slot></slot>
</div>
<p v-if="errors[name]" class="text-xs text-red-500">{{ errors[name] }}</p>
<p v-else class="text-xs">{{ $filters.translate(description) }}</p>

View File

@@ -17,6 +17,13 @@ const app = Vue.createApp({
:userroles="userroles"
:value="formData[fieldname]"
v-bind="subfieldDefinition">
<slot v-if="fieldname == 'mailfrom'">
<button
class = "absolute px-2 py-3 ml-2 text-stone-50 bg-stone-700 hover:bg-stone-900 hover:text-white transition duration-100 cursor-pointer"
style = "right:0px; width:200px;"
@click.prevent = "testmail()"
>send testmail</button>
</slot>
</component>
</fieldset>
</div>
@@ -85,6 +92,32 @@ const app = Vue.createApp({
this.currentTab = tab;
this.reset();
},
testmail: function()
{
this.reset();
var self = this;
tmaxios.post('/api/v1/testmail',{
'url': data.urlinfo.route,
})
.then(function (response)
{
self.messageClass = 'bg-teal-500';
self.message = response.data.message;
})
.catch(function (error)
{
if(error.response)
{
self.message = handleErrorMessage(error);
self.messageClass = 'bg-rose-500';
if(error.response.data.errors !== undefined)
{
self.errors = error.response.data.errors;
}
}
});
},
save: function()
{
this.reset();
@@ -109,7 +142,7 @@ const app = Vue.createApp({
self.errors = error.response.data.errors;
}
}
});
});
},
reset: function()
{

View File

@@ -19,6 +19,7 @@ use Typemill\Controllers\ControllerApiAuthorArticle;
use Typemill\Controllers\ControllerApiAuthorBlock;
use Typemill\Controllers\ControllerApiAuthorMeta;
use Typemill\Controllers\ControllerApiAuthorShortcode;
use Typemill\Controllers\ControllerApiTestmail;
$app->group('/api/v1', function (RouteCollectorProxy $group) use ($acl) {
@@ -35,6 +36,7 @@ $app->group('/api/v1', function (RouteCollectorProxy $group) use ($acl) {
$group->post('/plugin', ControllerApiSystemPlugins::class . ':updatePlugin')->setName('api.plugin.set')->add(new ApiAuthorization($acl, 'system', 'update')); # admin
$group->post('/extensions', ControllerApiSystemExtensions::class . ':activateExtension')->setName('api.extension.activate')->add(new ApiAuthorization($acl, 'system', 'update')); # admin
$group->post('/versioncheck', ControllerApiSystemVersions::class . ':checkVersions')->setName('api.versioncheck')->add(new ApiAuthorization($acl, 'system', 'update')); # admin
$group->post('/testmail', ControllerApiTestmail::class . ':send')->setName('api.testmail')->add(new ApiAuthorization($acl, 'system', 'update')); # admin
$group->get('/users/getbynames', ControllerApiSystemUsers::class . ':getUsersByNames')->setName('api.usersbynames')->add(new ApiAuthorization($acl, 'user', 'update')); # admin
$group->get('/users/getbyemail', ControllerApiSystemUsers::class . ':getUsersByEmail')->setName('api.usersbyemail')->add(new ApiAuthorization($acl, 'user', 'update')); # admin
$group->get('/users/getbyrole', ControllerApiSystemUsers::class . ':getUsersByRole')->setName('api.usersbyrole')->add(new ApiAuthorization($acl, 'user', 'update')); # admin

View File

@@ -164,22 +164,22 @@ fieldsetmail:
legend: Email
fields:
mailfrom:
type: text
type: email
label: 'Mail From (required)'
placeholder: sender@yourmail.org
maxlength: 60
description: 'Enter an email address that should send emails (sender).'
maxlength: 100
description: 'Enter an email address that sends the e-mails (sender). The e-mail-feature will be used for recovery and verification e-mails. Send a testmail to your user-account to verify that you receive the e-mails.'
mailfromname:
type: text
label: 'Mail From Name (optional)'
placeholder: sender name
maxlength: 60
maxlength: 100
description: 'Optionally enter a name for the sender address. If not set, the from-address will be visible.'
replyto:
type: text
label: 'Reply To (optional)'
placeholder: noreply@yourmail.org
maxlength: 60
maxlength: 100
description: 'Optionally enter a "reply to" address for answers from the receiver. If not set, answers will go to the from-address.'
fieldsetrecover:
type: fieldset
@@ -189,7 +189,7 @@ fieldsetrecover:
type: checkbox
label: 'Recover password'
checkboxlabel: 'Activate a password recovery in the login form.'
description: "Be aware that some providers might reject emails send with this feature (php-mail)."
description: "From mail is required for this feature. Send a testmail before you use this feature."
recoversubject:
type: text
label: 'Email subject'
@@ -204,15 +204,11 @@ fieldsetsecurity:
type: fieldset
legend: Security
fields:
securitylog:
type: checkbox
label: 'Security log'
checkboxlabel: 'Track spam and suspicious actions in a logfile'
authcode:
type: checkbox
label: 'Login Verification (recommended)'
checkboxlabel: 'Verify your login with a 5-digit code send by email.'
description: 'Please test this with your account. If you do not get an email, make sure you have ftp-access to disable the feature in the settings.yaml manually. The verification code will be valid for 5 minutes. Be aware that device fingerprints will be stored in the user accounts. Make sure this complies with privacy legislation in your country.'
description: 'From mail is required for this feature. Send a testmail before you use this feature. Make sure you have ftp-access to disable the feature in settings.yaml on failure. The verification code will be valid for 5 minutes. Be aware that device fingerprints will be stored in the user accounts.'
authcaptcha:
type: radio
label: 'Use captcha in authentication forms'
@@ -220,6 +216,10 @@ fieldsetsecurity:
disabled: 'Disable'
standard: 'Always show'
aftererror: 'Show after first wrong input'
securitylog:
type: checkbox
label: 'Security log'
checkboxlabel: 'Track spam and suspicious actions in a logfile'
fieldsetdeveloper:
type: fieldset
legend: "Developer"
@@ -242,18 +242,19 @@ fieldsetdeveloper:
headersoff:
type: checkbox
label: "Disable Custom Headers"
checkboxlabel: "Disable all custom headers of Typemill and send your own headers instead."
corsdomains:
type: textarea
label: "Allowed Domains for API-Access (CORS)"
placeholder: 'https://my-website-that-uses-the-api.org,https://another-website-using-the-api.org'
description: "Add all domains separated with comma, that should have access to the Typemill API. Domains will be added to the cors-header."
cspdomains:
type: textarea
label: "Allowed Domains for Content on Typemill (CSP)"
placeholder: 'https://www.google.com,*google.com'
description: "Add all domains separated with comma, that you want to integrate on your Typemill website. Domains will be added to the csp-header. Usually done with plugins and themes, but add manually if something is blocked."
checkboxlabel: "Disable all custom headers of Typemill (except cors) and send your own headers instead."
cspdisabled:
type: checkbox
label: "Disable CSP Headers"
checkboxlabel: "Disable all csp (content security policy) headers for this website."
checkboxlabel: "Disable all csp headers (content security policy) for this website."
cspdomains:
type: textarea
label: "Allowed Domains for Content on Typemill (CSP-Headers)"
placeholder: 'https://www.google.com,*google.com'
description: "List all domains, separated by commas, to allow content integration, such as iframes, on your Typemill website. Domains will be added to the csp-header. Usually done with plugins and themes, but add manually if something is blocked."
corsdomains:
type: textarea
label: "Allowed Domains for API-Access (CORS-Headers)"
placeholder: 'https://my-website-that-uses-the-api.org,https://another-website-using-the-api.org'
description: "List all domains, separated by comma, that should have access to the Typemill API. Domains will be added to the cors-header."