mirror of
https://github.com/typemill/typemill.git
synced 2025-07-29 02:10:20 +02:00
Merge branch 'v2.1.0' into v2.x
This commit is contained in:
7
.gitignore
vendored
7
.gitignore
vendored
@@ -1,10 +1,11 @@
|
||||
cache/sitemap.xml
|
||||
content/index.yaml
|
||||
content/00-welcome/index.yaml
|
||||
content/00-welcome/00-setup.yaml
|
||||
content/00-welcome/00-setup-your-website.yaml
|
||||
content/00-welcome/01-write-content.yaml
|
||||
content/00-welcome/02-get-help.yaml
|
||||
content/00-welcome/03-markdown-test.yaml
|
||||
content/00-welcome/02-manage-access.yaml
|
||||
content/00-welcome/03-get-help.yaml
|
||||
content/00-welcome/04-markdown-test.yaml
|
||||
content/01-cyanine-theme/index.yaml
|
||||
content/01-cyanine-theme/00-landingpage.yaml
|
||||
content/01-cyanine-theme/01-colors-and-fonts.yaml
|
||||
|
12
composer.lock
generated
12
composer.lock
generated
@@ -1582,16 +1582,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher",
|
||||
"version": "v5.4.26",
|
||||
"version": "v5.4.34",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/event-dispatcher.git",
|
||||
"reference": "5dcc00e03413f05c1e7900090927bb7247cb0aac"
|
||||
"reference": "e3bca343efeb613f843c254e7718ef17c9bdf7a3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/5dcc00e03413f05c1e7900090927bb7247cb0aac",
|
||||
"reference": "5dcc00e03413f05c1e7900090927bb7247cb0aac",
|
||||
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/e3bca343efeb613f843c254e7718ef17c9bdf7a3",
|
||||
"reference": "e3bca343efeb613f843c254e7718ef17c9bdf7a3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1647,7 +1647,7 @@
|
||||
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/event-dispatcher/tree/v5.4.26"
|
||||
"source": "https://github.com/symfony/event-dispatcher/tree/v5.4.34"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -1663,7 +1663,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2023-07-06T06:34:20+00:00"
|
||||
"time": "2023-12-27T21:12:56+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/event-dispatcher-contracts",
|
||||
|
@@ -221,17 +221,19 @@ class ControllerApiFile extends Controller
|
||||
# if the previous check of the mtype with the base64 string failed, then do it now again with the temporary file
|
||||
if(!$mtype)
|
||||
{
|
||||
$fullPath = $this->settings['rootPath'] . $filePath;
|
||||
$filePath = str_replace('media/files', 'media/tmp', $fileinfo['url']);
|
||||
$filePath = str_replace('/', DIRECTORY_SEPARATOR, $filePath);
|
||||
$fullPath = $this->settings['rootPath'] . DIRECTORY_SEPARATOR . $filePath;
|
||||
$finfo = finfo_open( FILEINFO_MIME_TYPE );
|
||||
$mtype = @finfo_file( $finfo, $fullPath );
|
||||
finfo_close($finfo);
|
||||
|
||||
if(!$mtype OR !$this->checkAllowedMimeTypes($mtype, $extension))
|
||||
{
|
||||
$media->clearTempFolder();
|
||||
$media->clearTempFolder($immediate = true);
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => Translations::translate('The mime-type is missing, not allowed or does not fit to the file extension.')
|
||||
'message' => Translations::translate('The mime-type is missing, not allowed or does not fit to the file extension.') . ' mtype: ' . $mtype . ', ext: ' . $extension
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
|
||||
|
@@ -12,10 +12,11 @@ class ControllerApiGlobals extends Controller
|
||||
{
|
||||
$navigation = new Navigation();
|
||||
$systemNavigation = $navigation->getSystemNavigation(
|
||||
$userrole = $request->getAttribute('c_userrole'),
|
||||
$acl = $this->c->get('acl'),
|
||||
$urlinfo = $this->c->get('urlinfo'),
|
||||
$dispatcher = $this->c->get('dispatcher')
|
||||
$userrole = $request->getAttribute('c_userrole'),
|
||||
$acl = $this->c->get('acl'),
|
||||
$urlinfo = $this->c->get('urlinfo'),
|
||||
$dispatcher = $this->c->get('dispatcher'),
|
||||
$parser = $this->routeParser
|
||||
);
|
||||
|
||||
# won't work because api has no session, instead you have to pass user
|
||||
|
61
system/typemill/Controllers/ControllerApiTestmail.php
Normal file
61
system/typemill/Controllers/ControllerApiTestmail.php
Normal 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');
|
||||
}
|
||||
}
|
@@ -7,6 +7,7 @@ use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Slim\Routing\RouteContext;
|
||||
use Typemill\Models\Validation;
|
||||
use Typemill\Models\User;
|
||||
use Typemill\Models\SimpleMail;
|
||||
use Typemill\Static\Translations;
|
||||
|
||||
class ControllerWebAuth extends Controller
|
||||
@@ -21,80 +22,363 @@ class ControllerWebAuth extends Controller
|
||||
|
||||
public function login(Request $request, Response $response)
|
||||
{
|
||||
/*
|
||||
if( ( null !== $request->getattribute('csrf_result') ) OR ( $request->getattribute('csrf_result') === false ) )
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'The form has a timeout, please try again.');
|
||||
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'));
|
||||
}
|
||||
*/
|
||||
|
||||
$input = $request->getParsedBody();
|
||||
$validation = new Validation();
|
||||
# $settings = $this->c->get('settings');
|
||||
|
||||
if($validation->signin($input) === true)
|
||||
{
|
||||
$user = new User();
|
||||
$securitylog = $this->settings['securitylog'] ?? 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.');
|
||||
|
||||
if(!$user->setUserWithPassword($input['username']))
|
||||
if($validation->signin($input) !== true)
|
||||
{
|
||||
if($securitylog)
|
||||
{
|
||||
\Typemill\Static\Helpers::addLogEntry('login: invalid data');
|
||||
}
|
||||
|
||||
if($this->c->get('flash'))
|
||||
{
|
||||
$this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.'));
|
||||
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
|
||||
}
|
||||
|
||||
$userdata = $user->getUserData();
|
||||
|
||||
if($userdata && password_verify($input['password'], $userdata['password']))
|
||||
{
|
||||
# check if user has confirmed the account
|
||||
if(isset($userdata['optintoken']) && $userdata['optintoken'])
|
||||
{
|
||||
$this->c->get('flash')->addMessage('error', Translations::translate('Your registration is not confirmed yet. Please check your e-mails and use the confirmation link.'));
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
|
||||
}
|
||||
|
||||
$user->login();
|
||||
|
||||
# return $response->withHeader('Location', $this->routeParser->urlFor('settings.show'))->withStatus(302);
|
||||
|
||||
# if user is allowed to view content-area
|
||||
$acl = $this->c->get('acl');
|
||||
if($acl->hasRole($userdata['userrole']) && $acl->isAllowed($userdata['userrole'], 'content', 'view'))
|
||||
{
|
||||
$editor = (isset($this->settings['editor']) && $this->settings['editor'] == 'visual') ? 'visual' : 'raw';
|
||||
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('content.' . $editor))->withStatus(302);
|
||||
}
|
||||
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('user.account'))->withStatus(302);
|
||||
}
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
|
||||
}
|
||||
|
||||
if(isset($this->settings['securitylog']) && $this->settings['securitylog'])
|
||||
$user = new User();
|
||||
|
||||
if(!$user->setUserWithPassword($input['username']))
|
||||
{
|
||||
\Typemill\Static\Helpers::addLogEntry('wrong login');
|
||||
if($securitylog)
|
||||
{
|
||||
\Typemill\Static\Helpers::addLogEntry('login: user not found');
|
||||
}
|
||||
|
||||
if($this->c->get('flash'))
|
||||
{
|
||||
$this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.'));
|
||||
}
|
||||
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
|
||||
}
|
||||
|
||||
$this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.'));
|
||||
$userdata = $user->getUserData();
|
||||
$authcodedata = $this->checkAuthcode($userdata);
|
||||
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
|
||||
if($userdata && !password_verify($input['password'], $userdata['password']))
|
||||
{
|
||||
if($securitylog)
|
||||
{
|
||||
\Typemill\Static\Helpers::addLogEntry('login: wrong password');
|
||||
}
|
||||
|
||||
# always show authcode page, so attacker does not know if email or password was wrong or mail was send.
|
||||
if($authcodeactive && !$authcodedata['valid'])
|
||||
{
|
||||
# a bit slower because send mail takes some time usually
|
||||
usleep(rand(100000, 200000));
|
||||
|
||||
# show authcode page
|
||||
return $this->c->get('view')->render($response, 'auth/authcode.twig', [
|
||||
'username' => $userdata['username'],
|
||||
'authtitle' => $authtitle,
|
||||
'authtext' => $authtext
|
||||
]);
|
||||
}
|
||||
|
||||
if($this->c->get('flash'))
|
||||
{
|
||||
$this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.'));
|
||||
}
|
||||
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
|
||||
}
|
||||
|
||||
# check device fingerprint
|
||||
if($authcodeactive)
|
||||
{
|
||||
$fingerprint = $this->generateDeviceFingerprint();
|
||||
if(!$this->findDeviceFingerprint($fingerprint, $userdata))
|
||||
{
|
||||
# invalidate authcodedata so user has to use a new authcode again
|
||||
$authcodedata['valid'] = false;
|
||||
$authcodedata['validated'] = 12345;
|
||||
}
|
||||
}
|
||||
|
||||
if($authcodeactive && !$authcodedata['valid'] )
|
||||
{
|
||||
# generate new authcode
|
||||
$authcodevalue = rand(10000, 99999);
|
||||
|
||||
$mail = new SimpleMail($this->settings);
|
||||
|
||||
$subject = Translations::translate('Your Typemill verification code');
|
||||
$message = Translations::translate('Dear user') . ',<br><br>';
|
||||
$message .= Translations::translate('Someone tried to log in to your Typemill website and we want to make sure it is you. Enter the following verification code to finish your login. The code will be valid for 5 minutes.');
|
||||
$message .= '<br><br>' . $authcodevalue . '<br><br>';
|
||||
$message .= Translations::translate('If you did not make this login attempt, please reset your password immediately.');
|
||||
|
||||
$send = $mail->send($userdata['email'], $subject, $message);
|
||||
|
||||
if(!$send)
|
||||
{
|
||||
$authtitle = Translations::translate('Error sending email');
|
||||
$authtext = Translations::translate('We could not send the email with the verification code to your address. Reason: ') . $mail->error;
|
||||
}
|
||||
else
|
||||
{
|
||||
# store authcode
|
||||
$user->setValue('authcodedata', $authcodevalue . ':' . time() . ':' . $authcodedata['validated']);
|
||||
$user->updateUser();
|
||||
}
|
||||
|
||||
# show authcode page
|
||||
return $this->c->get('view')->render($response, 'auth/authcode.twig', [
|
||||
'username' => $userdata['username'],
|
||||
'authtitle' => $authtitle,
|
||||
'authtext' => $authtext
|
||||
]);
|
||||
}
|
||||
|
||||
# check if user has confirmed the account
|
||||
if(isset($userdata['optintoken']) && $userdata['optintoken'])
|
||||
{
|
||||
if($securitylog)
|
||||
{
|
||||
\Typemill\Static\Helpers::addLogEntry('login: user not confirmed yet.');
|
||||
}
|
||||
|
||||
if($this->c->get('flash'))
|
||||
{
|
||||
$this->c->get('flash')->addMessage('error', Translations::translate('Your registration is not confirmed yet. Please check your e-mails and use the confirmation link.'));
|
||||
}
|
||||
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
|
||||
}
|
||||
|
||||
$user->login();
|
||||
|
||||
# if user is allowed to view content-area
|
||||
$acl = $this->c->get('acl');
|
||||
if($acl->hasRole($userdata['userrole']) && $acl->isAllowed($userdata['userrole'], 'content', 'view'))
|
||||
{
|
||||
$editor = (isset($this->settings['editor']) && $this->settings['editor'] == 'visual') ? 'visual' : 'raw';
|
||||
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('content.' . $editor))->withStatus(302);
|
||||
}
|
||||
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('user.account'))->withStatus(302);
|
||||
}
|
||||
|
||||
/**
|
||||
* log out a user
|
||||
*
|
||||
* @param obj $request the slim request object
|
||||
* @param obj $response the slim response object
|
||||
* @return obje $response with redirect to route
|
||||
*/
|
||||
|
||||
|
||||
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)
|
||||
{
|
||||
$input = $request->getParsedBody();
|
||||
$validation = new Validation();
|
||||
$securitylog = $this->settings['securitylog'] ?? false;
|
||||
|
||||
if($validation->authcode($input) !== true)
|
||||
{
|
||||
if($securitylog)
|
||||
{
|
||||
\Typemill\Static\Helpers::addLogEntry('login: invalid verification code format');
|
||||
}
|
||||
|
||||
if($this->c->get('flash'))
|
||||
{
|
||||
$this->c->get('flash')->addMessage('error', Translations::translate('Invalid verification code format, please try again.'));
|
||||
}
|
||||
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
|
||||
}
|
||||
|
||||
$user = new User();
|
||||
|
||||
if(!$user->setUserWithPassword($input['username']))
|
||||
{
|
||||
if($securitylog)
|
||||
{
|
||||
\Typemill\Static\Helpers::addLogEntry('login: user not found');
|
||||
}
|
||||
|
||||
if($this->c->get('flash'))
|
||||
{
|
||||
$this->c->get('flash')->addMessage('error', Translations::translate('Wrong password or username, please try again.'));
|
||||
}
|
||||
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
|
||||
}
|
||||
|
||||
$userdata = $user->getUserData();
|
||||
$authcodevalue = $input['code-1'] . $input['code-2'] . $input['code-3'] . $input['code-4'] . $input['code-5'];
|
||||
$authcodedata = $this->checkAuthcode($userdata);
|
||||
|
||||
if(!$this->validateAuthcode($authcodevalue, $authcodedata))
|
||||
{
|
||||
if($securitylog)
|
||||
{
|
||||
\Typemill\Static\Helpers::addLogEntry('login: verification code wrong or outdated.');
|
||||
}
|
||||
|
||||
if($this->c->get('flash'))
|
||||
{
|
||||
$this->c->get('flash')->addMessage('error', Translations::translate('The verification was wrong or outdated, please start again.'));
|
||||
}
|
||||
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
|
||||
}
|
||||
|
||||
# add the device fingerprint if not set yet
|
||||
$fingerprints = $userdata['fingerprints'] ?? [];
|
||||
$fingerprint = $this->generateDeviceFingerprint();
|
||||
if(!$this->findDeviceFingerprint($fingerprint, $userdata))
|
||||
{
|
||||
$fingerprints[] = $fingerprint;
|
||||
$user->setValue('fingerprints', $fingerprints);
|
||||
}
|
||||
|
||||
# update authcode lastValidation and store
|
||||
$user->setValue('authcodedata', $authcodevalue . ':' . $authcodedata['generated'] . ':' . time());
|
||||
$user->updateUser();
|
||||
|
||||
$user->login();
|
||||
|
||||
# if user is allowed to view content-area
|
||||
$acl = $this->c->get('acl');
|
||||
if($acl->hasRole($userdata['userrole']) && $acl->isAllowed($userdata['userrole'], 'content', 'view'))
|
||||
{
|
||||
$editor = (isset($this->settings['editor']) && $this->settings['editor'] == 'visual') ? 'visual' : 'raw';
|
||||
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('content.' . $editor))->withStatus(302);
|
||||
}
|
||||
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('user.account'))->withStatus(302);
|
||||
}
|
||||
|
||||
|
||||
# log out a user
|
||||
public function logout(Request $request, Response $response)
|
||||
{
|
||||
\Typemill\Static\Session::stopSession();
|
||||
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
|
||||
}
|
||||
|
||||
|
||||
# check if the stored authcode in userdata is valid and/or fresh
|
||||
private function checkAuthcode($userdata)
|
||||
{
|
||||
# format: 12345:time(generated):time(validated)
|
||||
|
||||
$authcodedata = $userdata['authcodedata'] ?? false;
|
||||
|
||||
if(!$authcodedata)
|
||||
{
|
||||
return $authcode = [
|
||||
'value' => 12345,
|
||||
'generated' => 12345,
|
||||
'validated' => 12345,
|
||||
'valid' => false,
|
||||
'fresh' => false
|
||||
];
|
||||
}
|
||||
|
||||
$validation = new Validation();
|
||||
$authcodedata = explode(":", $authcodedata);
|
||||
|
||||
# validate format here, do we need it?
|
||||
|
||||
$now = time();
|
||||
$lastValidation = 60 * 60 * 24;
|
||||
$lastGeneration = 60 * 5;
|
||||
$valid = false;
|
||||
$fresh = false;
|
||||
|
||||
# if last validation is less than 24 hours old
|
||||
if($now - $lastValidation < $authcodedata[2])
|
||||
{
|
||||
$valid = true;
|
||||
}
|
||||
|
||||
# if last generation is less than 5 minutes old
|
||||
if($now - $lastGeneration < $authcodedata[1])
|
||||
{
|
||||
$fresh = true;
|
||||
}
|
||||
|
||||
$authcode = [
|
||||
'value' => $authcodedata[0],
|
||||
'generated' => $authcodedata[1],
|
||||
'validated' => $authcodedata[2],
|
||||
'valid' => $valid,
|
||||
'fresh' => $fresh
|
||||
];
|
||||
|
||||
return $authcode;
|
||||
}
|
||||
|
||||
# check if the submitted authcode is the same as the stored authcode
|
||||
private function validateAuthcode($authcodevalue, $authcodedata)
|
||||
{
|
||||
if($authcodedata['valid'] === true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if($authcodedata['fresh'] === false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if($authcodevalue == $authcodedata['value'])
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
# create a simple device fingerprint
|
||||
private function generateDeviceFingerprint()
|
||||
{
|
||||
$userAgent = $_SERVER['HTTP_USER_AGENT'];
|
||||
$ipAddress = $_SERVER['REMOTE_ADDR'];
|
||||
$acceptLanguage = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? $_SERVER['HTTP_ACCEPT_LANGUAGE'] : '';
|
||||
|
||||
$fingerprint = md5($userAgent . $ipAddress . $acceptLanguage);
|
||||
|
||||
return $fingerprint;
|
||||
}
|
||||
|
||||
# create a simple device fingerprint
|
||||
private function findDeviceFingerprint($fingerprint, $userdata)
|
||||
{
|
||||
if(!isset($userdata['fingerprints']) or empty($userdata['fingerprints']))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!in_array($fingerprint, $userdata['fingerprints']))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -2,18 +2,20 @@
|
||||
|
||||
namespace Typemill\Controllers;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Typemill\Models\StorageWrapper;
|
||||
use Typemill\Static\Translations;
|
||||
|
||||
class ControllerWebDownload extends Controller
|
||||
{
|
||||
public function download($request, $response, $args)
|
||||
public function download(Request $request, Response $response, $args)
|
||||
{
|
||||
$filename = isset($args['params']) ? $args['params'] : false;
|
||||
if(!$filename)
|
||||
{
|
||||
$response->getBody()->write(Translations::translate('the requested file does not exist.'))->withStatus(404);
|
||||
return $response;
|
||||
$response->getBody()->write(Translations::translate('the requested file does not exist.'));
|
||||
return $response->withStatus(404);
|
||||
}
|
||||
|
||||
$storage = new StorageWrapper('\Typemill\Models\Storage');
|
||||
@@ -26,8 +28,8 @@ class ControllerWebDownload extends Controller
|
||||
$allowedFiletypes = [];
|
||||
if(!$this->validate($filepath, $filename, $allowedFiletypes))
|
||||
{
|
||||
$response->getBody()->write(Translations::translate('the requested filetype does not exist.'))->withStatus(404);
|
||||
return $response;
|
||||
$response->getBody()->write(Translations::translate('the requested filetype does not exist.'));
|
||||
return $response->withStatus(404);
|
||||
}
|
||||
|
||||
if($restrictions && isset($restrictions[$filefolder . $filename]))
|
||||
|
@@ -134,6 +134,44 @@ class ControllerWebFrontend extends Controller
|
||||
$metadata = $this->c->get('dispatcher')->dispatch(new OnMetaLoaded($metadata),'onMetaLoaded')->getData();
|
||||
|
||||
|
||||
# REFERENCE FEATURE
|
||||
if(isset($metadata['meta']['referencetype']) && $metadata['meta']['referencetype'] != 'disable')
|
||||
{
|
||||
$referenceurl = rtrim($urlinfo['baseurl'], '/') . '/' . trim($metadata['meta']['reference'], '/');
|
||||
|
||||
switch ($metadata['meta']['referencetype']) {
|
||||
case 'redirect301':
|
||||
return $response->withHeader('Location', $referenceurl)->withStatus(301);
|
||||
break;
|
||||
case 'redirect302':
|
||||
return $response->withHeader('Location', $referenceurl)->withStatus(302);
|
||||
break;
|
||||
case 'outlink':
|
||||
return $response->withHeader('Location', $metadata['meta']['reference'])->withStatus(301);
|
||||
break;
|
||||
case 'copy':
|
||||
$refpageinfo = $extendedNavigation[$metadata['meta']['reference']] ?? false;
|
||||
if(!$refpageinfo)
|
||||
{
|
||||
return $this->c->get('view')->render($response->withStatus(404), '404.twig', [
|
||||
'title' => 'Referenced page not found',
|
||||
'description' => 'We did not find the page that has been referenced. Please inform the website owner to fix it in meta reference.'
|
||||
]);
|
||||
}
|
||||
|
||||
$refKeyPathArray = explode(".", $refpageinfo['keyPath']);
|
||||
$refItem = $navigation->getItemWithKeyPath($draftNavigation, $refKeyPathArray);
|
||||
|
||||
# GET THE CONTENT FROM REFENCED PAGE
|
||||
$liveMarkdown = $content->getLiveMarkdown($refItem);
|
||||
$liveMarkdown = $this->c->get('dispatcher')->dispatch(new OnMarkdownLoaded($liveMarkdown), 'onMarkdownLoaded')->getData();
|
||||
$markdownArray = $content->markdownTextToArray($liveMarkdown);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# CHECK ACCESS RESTRICTIONS
|
||||
$restricted = $this->checkRestrictions($metadata['meta'], $username, $userrole);
|
||||
if($restricted)
|
||||
|
@@ -7,6 +7,7 @@ use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Slim\Routing\RouteContext;
|
||||
use Typemill\Models\User;
|
||||
use Typemill\Models\Validation;
|
||||
use Typemill\Models\SimpleMail;
|
||||
use Typemill\Static\Translations;
|
||||
use Typemill\Extensions\ParsedownExtension;
|
||||
|
||||
@@ -50,15 +51,9 @@ class ControllerWebRecover extends Controller
|
||||
$link = '<a href="'. $url . '">' . $url . '</a>';
|
||||
|
||||
# define the headers
|
||||
$headers = 'Content-Type: text/html; charset=utf-8' . "\r\n";
|
||||
$headers .= 'Content-Transfer-Encoding: base64' . "\r\n";
|
||||
if(isset($settings['recoverfrom']) && $settings['recoverfrom'] != '')
|
||||
{
|
||||
$headers .= 'From: ' . $settings['recoverfrom'];
|
||||
}
|
||||
$mail = new SimpleMail($settings);
|
||||
|
||||
$subjectline = (isset($settings['recoversubject']) && ($settings['recoversubject'] != '') ) ? $settings['recoversubject'] : 'Recover your password';
|
||||
$subject = '=?UTF-8?B?' . base64_encode($subjectline) . '?=';
|
||||
$subject = (isset($settings['recoversubject']) && ($settings['recoversubject'] != '') ) ? $settings['recoversubject'] : 'Recover your password';
|
||||
|
||||
$messagetext = Translations::translate('Dear user');
|
||||
$messagetext .= ",<br/><br/>";
|
||||
@@ -72,16 +67,14 @@ class ControllerWebRecover extends Controller
|
||||
$messagetext = $parsedown->markup($contentArray);
|
||||
}
|
||||
|
||||
$message = base64_encode($messagetext . "<br/><br/>" . $link);
|
||||
$message = $messagetext . "<br/><br/>" . $link;
|
||||
|
||||
# $send = mail($requiredUser['email'], $subject, $message, $headers);
|
||||
$send = $mail->send($requiredUser['email'], $subject, $message);
|
||||
|
||||
$send = false;
|
||||
|
||||
if($send == 'delete')
|
||||
if(!$send)
|
||||
{
|
||||
$title = Translations::translate('Error sending email');
|
||||
$message = Translations::translate('Dear ') . $requiredUser['username'] . ', ' . Translations::translate('we could not send the email with the password instructions to your address. Please contact the website owner and ask for help.');
|
||||
$message = Translations::translate('Dear ') . $requiredUser['username'] . ', ' . Translations::translate('we could not send the email with the password instructions to your address. Reason: ') . $mail->error;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@@ -95,7 +95,11 @@ class ControllerWebSetup extends Controller
|
||||
|
||||
# create initial settings file
|
||||
$settingsModel = new Settings();
|
||||
$settingsModel->createSettings();
|
||||
$settingsModel->createSettings([
|
||||
'author' => $params['username'],
|
||||
'mailfrom' => $params['email'],
|
||||
'mailfromname' => $params['username']
|
||||
]);
|
||||
|
||||
$urlinfo = $this->c->get('urlinfo');
|
||||
$route = $urlinfo['baseurl'] . '/tm/system';
|
||||
|
14
system/typemill/Events/OnCspLoaded.php
Normal file
14
system/typemill/Events/OnCspLoaded.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Events;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event for the page rendering data.
|
||||
*/
|
||||
|
||||
class OnCspLoaded extends BaseEvent
|
||||
{
|
||||
|
||||
}
|
@@ -63,6 +63,9 @@ class ApiAuthentication
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
########### WHY NOT USE BASIC AUTH PARAMS FROM URI ?
|
||||
|
||||
$params = [];
|
||||
|
||||
if (preg_match("/Basic\s+(.*)$/i", $request->getHeaderLine("Authorization"), $matches))
|
||||
|
62
system/typemill/Middleware/CorsHeadersMiddleware.php
Normal file
62
system/typemill/Middleware/CorsHeadersMiddleware.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Middleware;
|
||||
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
|
||||
use Slim\Psr7\Response;
|
||||
|
||||
class CorsHeadersMiddleware implements MiddlewareInterface
|
||||
{
|
||||
protected $settings;
|
||||
|
||||
protected $urlinfo;
|
||||
|
||||
public function __construct($settings, $urlinfo)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
|
||||
$this->urlinfo = $urlinfo;
|
||||
}
|
||||
|
||||
public function process(Request $request, RequestHandler $handler) :response
|
||||
{
|
||||
# add the custom headers to the response after everything is processed
|
||||
$response = $handler->handle($request);
|
||||
|
||||
###################
|
||||
# CORS HEADER #
|
||||
###################
|
||||
|
||||
$origin = $request->getHeaderLine('Origin');
|
||||
$corsdomains = isset($this->settings['corsdomains']) ? trim($this->settings['corsdomains']) : false;
|
||||
$whitelist = [];
|
||||
|
||||
if($corsdomains && $corsdomains != '')
|
||||
{
|
||||
$corsdomains = explode(",", $corsdomains);
|
||||
foreach($corsdomains as $domain)
|
||||
{
|
||||
$domain = trim($domain);
|
||||
if($domain != '')
|
||||
{
|
||||
$whitelist[] = $domain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!$origin OR $origin == '' OR !isset($whitelist[$origin]))
|
||||
{
|
||||
# set current website as default origin and block all cross origin calls
|
||||
$origin = $this->urlinfo['baseurl'];
|
||||
}
|
||||
|
||||
$response = $response->withHeader('Access-Control-Allow-Origin', $origin)
|
||||
->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization')
|
||||
->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS')
|
||||
->withHeader('Access-Control-Allow-Credentials', 'true');
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
84
system/typemill/Middleware/CspHeadersMiddleware.php
Normal file
84
system/typemill/Middleware/CspHeadersMiddleware.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Middleware;
|
||||
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
|
||||
use Slim\Psr7\Response;
|
||||
|
||||
class CspHeadersMiddleware implements MiddlewareInterface
|
||||
{
|
||||
protected $settings;
|
||||
|
||||
protected $cspFromPlugins;
|
||||
|
||||
protected $cspFromTheme;
|
||||
|
||||
public function __construct($settings, $cspFromPlugins, $cspFromTheme)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
|
||||
$this->cspFromPlugins = $cspFromPlugins;
|
||||
|
||||
$this->cspFromTheme = $cspFromTheme;
|
||||
}
|
||||
|
||||
public function process(Request $request, RequestHandler $handler) :response
|
||||
{
|
||||
# add the custom headers to the response after everything is processed
|
||||
$response = $handler->handle($request);
|
||||
|
||||
if(isset($this->settings['cspdisabled']) && $this->settings['cspdisabled'])
|
||||
{
|
||||
return $response;
|
||||
}
|
||||
|
||||
$whitelist = ["'unsafe-inline'", "'unsafe-eval'", "'self'", "data:", "*.youtube-nocookie.com", "*.youtube.com"];
|
||||
|
||||
$cspdomains = isset($this->settings['cspdomains']) ? trim($this->settings['cspdomains']) : false;
|
||||
|
||||
if($cspdomains && $cspdomains != '')
|
||||
{
|
||||
$cspdomains = explode(",", $cspdomains);
|
||||
foreach($cspdomains as $cspdomain)
|
||||
{
|
||||
$cspdomain = trim($cspdomain);
|
||||
if($cspdomain != '')
|
||||
{
|
||||
$whitelist[] = $cspdomain;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# add csp from plugins
|
||||
if($this->cspFromPlugins && is_array($this->cspFromPlugins) && !empty($this->cspFromPlugins))
|
||||
{
|
||||
$whitelist = array_merge($whitelist, $this->cspFromPlugins);
|
||||
}
|
||||
|
||||
# add csp from current theme
|
||||
if($this->cspFromTheme && is_array($this->cspFromTheme) && !empty($this->cspFromTheme))
|
||||
{
|
||||
$whitelist = array_merge($whitelist, $this->cspFromTheme);
|
||||
}
|
||||
|
||||
$whitelist = array_unique($whitelist);
|
||||
|
||||
# do not add csp header if disabled-flag is found
|
||||
if(in_array("disable", $whitelist))
|
||||
{
|
||||
return $response;
|
||||
}
|
||||
|
||||
$whitelist = implode(' ', $whitelist);
|
||||
|
||||
# Define the Content Security Policy header
|
||||
$cspHeader = "default-src " . $whitelist . ";";
|
||||
|
||||
# Add the Content Security Policy header to the response
|
||||
$response = $response->withHeader('Content-Security-Policy', $cspHeader);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
47
system/typemill/Middleware/CustomHeadersMiddleware.php
Normal file
47
system/typemill/Middleware/CustomHeadersMiddleware.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Middleware;
|
||||
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
|
||||
use Slim\Psr7\Response;
|
||||
|
||||
class CustomHeadersMiddleware implements MiddlewareInterface
|
||||
{
|
||||
protected $settings;
|
||||
|
||||
public function __construct($settings)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
public function process(Request $request, RequestHandler $handler) :response
|
||||
{
|
||||
$scheme = $request->getUri()->getScheme();
|
||||
|
||||
# add the custom headers to the response after everything is processed
|
||||
$response = $handler->handle($request);
|
||||
|
||||
$response = $response->withoutHeader('Server');
|
||||
$response = $response->withHeader('X-Powered-By', 'Typemill');
|
||||
|
||||
$headersOff = $this->settings['headersoff'] ?? false;
|
||||
|
||||
if(!$headersOff)
|
||||
{
|
||||
$response = $response
|
||||
->withHeader('X-Content-Type-Options', 'nosniff')
|
||||
->withHeader('X-Frame-Options', 'SAMEORIGIN')
|
||||
->withHeader('X-XSS-Protection', '1;mode=block')
|
||||
->withHeader('Referrer-Policy', 'no-referrer-when-downgrade');
|
||||
|
||||
if($scheme == 'https')
|
||||
{
|
||||
$response = $response->withHeader('Strict-Transport-Security', 'max-age=63072000');
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
@@ -8,7 +8,7 @@ use Slim\Flash\Messages;
|
||||
|
||||
class FlashMessages
|
||||
{
|
||||
private $container;
|
||||
public $container;
|
||||
|
||||
public function __construct($container)
|
||||
{
|
||||
|
28
system/typemill/Middleware/RemoveCredentialsMiddleware.php
Normal file
28
system/typemill/Middleware/RemoveCredentialsMiddleware.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Middleware;
|
||||
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
|
||||
use Slim\Psr7\Response;
|
||||
|
||||
class RemoveCredentialsMiddleware implements MiddlewareInterface
|
||||
{
|
||||
public function process(Request $request, RequestHandler $handler) :response
|
||||
{
|
||||
$uri = $request->getUri();
|
||||
|
||||
# Remove user information (username:password) from the URI
|
||||
$uri = $uri->withUserInfo('');
|
||||
|
||||
# Create a new request with the modified URI
|
||||
$request = $request->withUri($uri);
|
||||
|
||||
# we could add basic auth credentials to request for later usage
|
||||
|
||||
$response = $handler->handle($request);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
@@ -11,54 +11,52 @@ use Typemill\Models\User;
|
||||
|
||||
class SessionMiddleware implements MiddlewareInterface
|
||||
{
|
||||
protected $segments;
|
||||
protected $segments;
|
||||
|
||||
protected $route;
|
||||
protected $route;
|
||||
|
||||
public function __construct($segments, $route)
|
||||
{
|
||||
$this->segments = $segments;
|
||||
|
||||
protected $uri;
|
||||
|
||||
public function __construct($segments, $route, $uri)
|
||||
{
|
||||
$this->segments = $segments;
|
||||
|
||||
$this->route = $route;
|
||||
|
||||
$this->uri = $uri;
|
||||
}
|
||||
|
||||
$this->route = $route;
|
||||
}
|
||||
|
||||
public function process(Request $request, RequestHandler $handler) :response
|
||||
{
|
||||
$scheme = $request->getUri()->getScheme();
|
||||
|
||||
# start session
|
||||
Session::startSessionForSegments($this->segments, $this->route, $scheme);
|
||||
{
|
||||
$uri = $request->getUri();
|
||||
|
||||
$authenticated = (
|
||||
(isset($_SESSION['username'])) &&
|
||||
(isset($_SESSION['login']))
|
||||
)
|
||||
? true : false;
|
||||
$scheme = $request->getUri()->getScheme();
|
||||
|
||||
# start session
|
||||
Session::startSessionForSegments($this->segments, $this->route, $scheme);
|
||||
|
||||
if($authenticated)
|
||||
{
|
||||
# add userdata to the request for later use
|
||||
$user = new User();
|
||||
$authenticated = (
|
||||
(isset($_SESSION['username'])) &&
|
||||
(isset($_SESSION['login']))
|
||||
)
|
||||
? true : false;
|
||||
|
||||
if($user->setUser($_SESSION['username']))
|
||||
{
|
||||
$userdata = $user->getUserData();
|
||||
if($authenticated)
|
||||
{
|
||||
# add userdata to the request for later use
|
||||
$user = new User();
|
||||
|
||||
$request = $request->withAttribute('c_username', $userdata['username']);
|
||||
$request = $request->withAttribute('c_userrole', $userdata['userrole']);
|
||||
if(isset($userdata['darkmode']))
|
||||
{
|
||||
$request = $request->withAttribute('c_darkmode', $userdata['darkmode']);
|
||||
}
|
||||
}
|
||||
}
|
||||
if($user->setUser($_SESSION['username']))
|
||||
{
|
||||
$userdata = $user->getUserData();
|
||||
|
||||
$request = $request->withAttribute('c_username', $userdata['username']);
|
||||
$request = $request->withAttribute('c_userrole', $userdata['userrole']);
|
||||
if(isset($userdata['darkmode']))
|
||||
{
|
||||
$request = $request->withAttribute('c_darkmode', $userdata['darkmode']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$response = $handler->handle($request);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
}
|
@@ -41,7 +41,7 @@ class Media
|
||||
$this->tmpFolder = $this->basepath . 'media' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
public function clearTempFolder()
|
||||
public function clearTempFolder($immediate = NULL)
|
||||
{
|
||||
$files = scandir($this->tmpFolder);
|
||||
$now = time();
|
||||
@@ -54,13 +54,15 @@ class Media
|
||||
$filelink = $this->tmpFolder . $file;
|
||||
if(file_exists($filelink))
|
||||
{
|
||||
$filetime = filemtime($filelink);
|
||||
if($now - $filetime > 1800)
|
||||
$filetime = filemtime($filelink);
|
||||
$delete = $immediate ? $immediate : ($now - $filetime > 1800);
|
||||
|
||||
if($delete)
|
||||
{
|
||||
if(!unlink($filelink))
|
||||
{
|
||||
$result = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -224,18 +224,26 @@ class Settings
|
||||
return $settingsDefinitions;
|
||||
}
|
||||
|
||||
public function createSettings()
|
||||
public function createSettings(array $defaultSettings = NULL)
|
||||
{
|
||||
$language = Translations::whichLanguage();
|
||||
$defaults = [
|
||||
'language' => Translations::whichLanguage()
|
||||
];
|
||||
|
||||
if($defaultSettings)
|
||||
{
|
||||
$defaults = array_merge($defaults, $defaultSettings);
|
||||
}
|
||||
|
||||
$initialSettings = $this->storage->updateYaml('settingsFolder', '', 'settings.yaml', [
|
||||
'language' => $language
|
||||
$defaults
|
||||
]);
|
||||
|
||||
if($initialSettings)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
65
system/typemill/Models/SimpleMail.php
Normal file
65
system/typemill/Models/SimpleMail.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Models;
|
||||
|
||||
use Typemill\Static\Translations;
|
||||
|
||||
class SimpleMail
|
||||
{
|
||||
private $from = false;
|
||||
|
||||
private $reply = false;
|
||||
|
||||
public $error;
|
||||
|
||||
public function __construct($settings)
|
||||
{
|
||||
if(isset($settings['mailfrom']) && $settings['mailfrom'] != '')
|
||||
{
|
||||
$this->from = trim($settings['mailfrom']);
|
||||
|
||||
if(isset($settings['mailfromname']) && $settings['mailfromname'] != '')
|
||||
{
|
||||
$this->from = '=?UTF-8?B?' . base64_encode($settings['mailfromname']) . '?= <' . trim($settings['mailfrom']) . '>';
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($settings['mailreply']) && $settings['mailreply'] != '')
|
||||
{
|
||||
$this->reply = trim($settings['mailreply']);
|
||||
}
|
||||
}
|
||||
|
||||
public function send(string $to, string $subject, string $message)
|
||||
{
|
||||
if(!$this->from)
|
||||
{
|
||||
$this->error = Translations::translate('Email address in system settings is missing.');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
# 'Reply-To: webmaster@example.com' . "\r\n" .
|
||||
|
||||
$headers = 'Content-Type: text/html; charset=utf-8' . "\r\n";
|
||||
$headers .= 'Content-Transfer-Encoding: base64' . "\r\n";
|
||||
$headers .= 'From: ' . $this->from . "\r\n";
|
||||
if($this->reply)
|
||||
{
|
||||
$headers .= 'Reply-To: base64' . $this->reply . "\r\n";
|
||||
}
|
||||
$headers .= 'X-Mailer: PHP/' . phpversion();
|
||||
|
||||
$subject = '=?UTF-8?B?' . base64_encode($subject) . '?=';
|
||||
$message = base64_encode($message);
|
||||
|
||||
$send = mail($to, $subject, $message, $headers);
|
||||
|
||||
if($send !== true)
|
||||
{
|
||||
$this->error = error_get_last()['message'];
|
||||
}
|
||||
|
||||
return $send;
|
||||
}
|
||||
}
|
@@ -274,6 +274,32 @@ class Validation
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* validation for authcode confirmation
|
||||
*
|
||||
* @param array $params with form data.
|
||||
* @return obj $v the validation object passed to a result method.
|
||||
*/
|
||||
|
||||
public function authcode(array $params)
|
||||
{
|
||||
$v = new Validator($params);
|
||||
$v->rule('required', ['username', 'code-1', 'code-2', 'code-3', 'code-4', 'code-5'])->message("Required");
|
||||
$v->rule('alphaNum', 'username')->message("Invalid characters");
|
||||
$v->rule('regex', 'code-1', '/^[0-9]{1}$/')->message("Must be 1-9");
|
||||
$v->rule('regex', 'code-2', '/^[0-9]{1}$/')->message("Must be 1-9");
|
||||
$v->rule('regex', 'code-3', '/^[0-9]{1}$/')->message("Must be 1-9");
|
||||
$v->rule('regex', 'code-4', '/^[0-9]{1}$/')->message("Must be 1-9");
|
||||
$v->rule('regex', 'code-5', '/^[0-9]{1}$/')->message("Must be 1-9");
|
||||
|
||||
if($v->validate())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* validation for setup user (in backoffice)
|
||||
@@ -786,7 +812,7 @@ class Validation
|
||||
break;
|
||||
case "text":
|
||||
$v->rule('noHTML', $fieldName);
|
||||
$v->rule('lengthMax', $fieldName, 500);
|
||||
$v->rule('lengthMax', $fieldName, 1000);
|
||||
# $v->rule('regex', $fieldName, '/^[\pL0-9_ \-\.\?\!\/\:]*$/u');
|
||||
break;
|
||||
case "textarea":
|
||||
|
@@ -194,23 +194,6 @@ abstract class Plugin implements EventSubscriberInterface
|
||||
$this->container->get('assets')->addJS($JS);
|
||||
}
|
||||
|
||||
/*
|
||||
protected function addEditorJS($JS)
|
||||
{
|
||||
$this->container->get('assets')->addEditorJS($JS);
|
||||
}
|
||||
|
||||
protected function addEditorInlineJS($JS)
|
||||
{
|
||||
$this->container->get('assets')->addEditorInlineJS($JS);
|
||||
}
|
||||
|
||||
protected function addEditorCSS($CSS)
|
||||
{
|
||||
$this->container->get('assets')->addEditorCSS($CSS);
|
||||
}
|
||||
*/
|
||||
|
||||
protected function addInlineJS($JS)
|
||||
{
|
||||
$this->container->get('assets')->addInlineJS($JS);
|
||||
|
@@ -6,29 +6,6 @@ use Typemill\Models\StorageWrapper;
|
||||
|
||||
class Helpers{
|
||||
|
||||
public static function urlInfo($uri)
|
||||
{
|
||||
$basepath = preg_replace('/(.*)\/.*/', '$1', $_SERVER['SCRIPT_NAME']);
|
||||
$currentpath = $uri->getPath();
|
||||
$route = str_replace($basepath, '', $currentpath);
|
||||
$scheme = $uri->getScheme();
|
||||
$authority = $uri->getAuthority();
|
||||
$protocol = ($scheme ? $scheme . ':' : '') . ($authority ? '//' . $authority : '');
|
||||
$baseurl = $protocol . $basepath;
|
||||
$currenturl = $protocol . $currentpath;
|
||||
|
||||
return [
|
||||
'basepath' => $basepath,
|
||||
'currentpath' => $currentpath,
|
||||
'route' => $route,
|
||||
'scheme' => $scheme,
|
||||
'authority' => $authority,
|
||||
'protocol' => $protocol,
|
||||
'baseurl' => $baseurl,
|
||||
'currenturl' => $currenturl
|
||||
];
|
||||
}
|
||||
|
||||
public static function getUserIP()
|
||||
{
|
||||
$client = @$_SERVER['HTTP_CLIENT_IP'];
|
||||
@@ -51,7 +28,6 @@ class Helpers{
|
||||
return $ip;
|
||||
}
|
||||
|
||||
|
||||
public static function addLogEntry($action)
|
||||
{
|
||||
$line = self::getUserIP();
|
||||
|
@@ -43,9 +43,7 @@ class License
|
||||
}
|
||||
elseif($licenseStatus === true)
|
||||
{
|
||||
echo '<pre>';
|
||||
print_r($licensedata);
|
||||
die();
|
||||
die('Static License licenceStatus is true');
|
||||
}
|
||||
else
|
||||
{
|
||||
|
106
system/typemill/Static/Urlinfo.php
Normal file
106
system/typemill/Static/Urlinfo.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Static;
|
||||
|
||||
## MOVE TO MIDDLEWARE IN 2.2.0
|
||||
|
||||
class Urlinfo
|
||||
{
|
||||
# we need to get urlinfos to use in frontend and to inject into assets and container before middleware starts and request-object is available.
|
||||
public static function getUrlInfo($basepath, $uri, $settings)
|
||||
{
|
||||
$uri = $uri->withUserInfo('');
|
||||
$uri = $uri->withPort(null);
|
||||
|
||||
$currentpath = $uri->getPath();
|
||||
$route = str_replace($basepath, '', $currentpath);
|
||||
|
||||
$query = $uri->getQuery();
|
||||
parse_str($query, $params);
|
||||
|
||||
# proxy detection
|
||||
if(isset($settings['proxy']) && $settings['proxy'] && isset($_SERVER['HTTP_X_FORWARDED_HOST']))
|
||||
{
|
||||
$trustedProxies = ( isset($settings['trustedproxies']) && !empty($settings['trustedproxies']) ) ? explode(",", $settings['trustedproxies']) : [];
|
||||
|
||||
$proxyuri = self::updateUri($uri, $trustedProxies);
|
||||
|
||||
if($proxyuri)
|
||||
{
|
||||
# use uri from proxy
|
||||
$uri = $proxyuri;
|
||||
|
||||
# standard basepath is empty
|
||||
$basepath = "";
|
||||
|
||||
# if proxy has basepath, then
|
||||
if (isset($_SERVER['HTTP_X_FORWARDED_PREFIX']))
|
||||
{
|
||||
# Use X-Forwarded-Prefix if available
|
||||
$basepath = rtrim($_SERVER['HTTP_X_FORWARDED_PREFIX'], '/') . '/';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$scheme = $uri->getScheme();
|
||||
$authority = $uri->getAuthority();
|
||||
$protocol = ($scheme ? $scheme . ':' : '') . ($authority ? '//' . $authority : '');
|
||||
$baseurl = $protocol . $basepath;
|
||||
$currenturl = $baseurl . $route;
|
||||
|
||||
return [
|
||||
'basepath' => $basepath,
|
||||
'currentpath' => $currentpath,
|
||||
'route' => $route,
|
||||
'scheme' => $scheme,
|
||||
'authority' => $authority,
|
||||
'protocol' => $protocol,
|
||||
'baseurl' => $baseurl,
|
||||
'baseurlWithoutProxy' => false, # add the base url without proxy maybe needed for license?
|
||||
'currenturl' => $currenturl,
|
||||
'params' => $params
|
||||
];
|
||||
}
|
||||
|
||||
private static function updateUri($uri, $trustedProxies)
|
||||
{
|
||||
# optionally check trusted proxies
|
||||
$ipAddress = $_SERVER['REMOTE_ADDR'] ?? null;
|
||||
if (
|
||||
$ipAddress
|
||||
&& !empty($trustedProxies)
|
||||
&& !in_array($ipAddress, $trustedProxies)
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
# get scheme from proxy
|
||||
$scheme = $_SERVER['HTTP_X_FORWARDED_PROTO'] ?? null;
|
||||
if (
|
||||
$scheme
|
||||
&& in_array($scheme, ['http', 'https'])
|
||||
)
|
||||
{
|
||||
$uri = $uri->withScheme($scheme);
|
||||
}
|
||||
|
||||
# get host from proxy
|
||||
$host = $_SERVER['HTTP_X_FORWARDED_HOST'] ?? null;
|
||||
if (
|
||||
$host
|
||||
)
|
||||
{
|
||||
$host = trim(current(explode(',', $host)));
|
||||
|
||||
$pos = strpos($host, ':');
|
||||
if ($pos !== false)
|
||||
{
|
||||
$host = strstr($host, ':', true);
|
||||
}
|
||||
$uri = $uri->withHost($host);
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
}
|
104
system/typemill/author/auth/authcode.twig
Normal file
104
system/typemill/author/auth/authcode.twig
Normal file
@@ -0,0 +1,104 @@
|
||||
{% extends 'layouts/layoutAuth.twig' %}
|
||||
|
||||
{% block title %}Login Verification Code{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="lg:flex-row flex flex-col-reverse justify-end">
|
||||
|
||||
<div class="lg:w-1/2 bg-teal-600 text-white min-h-screen flex justify-center items-center">
|
||||
<div class="max-w-md content-center">
|
||||
|
||||
<h1 class="text-4xl py-5">Login Verification Code</h1>
|
||||
|
||||
<p>{{ translate('Enter the verification code from your email:') }}</a>
|
||||
|
||||
<form method="POST" action="{{ url_for("auth.authcode") }}" autocomplete="off">
|
||||
|
||||
<fieldset>
|
||||
|
||||
<div class="flex justify-between my-2">
|
||||
<input
|
||||
type="text"
|
||||
name="code-1"
|
||||
pattern="[0-9]"
|
||||
maxlength="1"
|
||||
oninput="moveToNextField(this)"
|
||||
class="mr-2 form-control block w-full px-3 py-3 text-xl text-center font-bold text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-non"
|
||||
required>
|
||||
<input
|
||||
type="text"
|
||||
name="code-2"
|
||||
pattern="[0-9]"
|
||||
maxlength="1"
|
||||
oninput="moveToNextField(this)"
|
||||
class="mr-2 form-control block w-full px-3 py-3 text-xl text-center font-bold text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-non"
|
||||
required>
|
||||
<input
|
||||
type="text"
|
||||
name="code-3"
|
||||
pattern="[0-9]"
|
||||
maxlength="1"
|
||||
oninput="moveToNextField(this)"
|
||||
class="mr-2 form-control block w-full px-3 py-3 text-xl text-center font-bold text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-non"
|
||||
required>
|
||||
<input
|
||||
type="text"
|
||||
name="code-4"
|
||||
pattern="[0-9]"
|
||||
maxlength="1"
|
||||
oninput="moveToNextField(this)"
|
||||
class="mr-2 form-control block w-full px-3 py-3 text-xl text-center font-bold text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-non"
|
||||
required>
|
||||
<input
|
||||
type="text"
|
||||
name="code-5"
|
||||
pattern="[0-9]"
|
||||
maxlength="1"
|
||||
oninput="moveToNextField(this)"
|
||||
class="form-control block w-full px-3 py-3 text-xl text-center font-bold text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-non"
|
||||
required>
|
||||
</div>
|
||||
|
||||
<input type="hidden" value="{{username}}" name="username">
|
||||
|
||||
<div class="personal-mail hidden">
|
||||
<label>Personal Mail</label>
|
||||
<input type="text" name="personal-honey-mail">
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="submit"
|
||||
value="{{ translate('Confirm') }}"
|
||||
class="block w-full mt-6 px-3 py-3 border-0 font-medium text-xs leading-tight uppercase border-2 border-stone-50 text-stone-50 pointer hover:bg-stone-50 hover:text-stone-800 cursor-pointer focus:outline-none focus:ring-0 transition duration-100 ease-in-out"
|
||||
/>
|
||||
|
||||
</fieldset>
|
||||
<script>
|
||||
function moveToNextField(currentField) {
|
||||
var inputValue = currentField.value;
|
||||
var inputLength = inputValue.length;
|
||||
if (inputLength === 1)
|
||||
{
|
||||
var nextField = currentField.nextElementSibling;
|
||||
if (nextField)
|
||||
{
|
||||
nextField.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lg:w-1/2 lg:bg-white lg:text-black p-5 bg-teal-600 text-white border-b border-white content-center flex justify-center items-center">
|
||||
<div class="max-w-md content-center">
|
||||
<h2 class="text-4xl py-5">{{ authtitle }}</h2>
|
||||
<p class="py-2">{{ authtext }}</p>
|
||||
<a class="text-teal-600 py-2" href="{{ url_for('auth.show') }}">→ {{ translate('Back to login') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
@@ -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>
|
||||
|
@@ -43,35 +43,6 @@ const app = Vue.createApp({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
<div v-for="(fieldDefinition, fieldname) in formDefinitions">
|
||||
<fieldset class="flex flex-wrap justify-between border-2 border-stone-200 p-4 my-8" v-if="fieldDefinition.type == 'fieldset'">
|
||||
<legend class="text-lg font-medium">{{ fieldDefinition.legend }}</legend>
|
||||
<component v-for="(subfieldDefinition, subfieldname) in fieldDefinition.fields"
|
||||
:key="subfieldname"
|
||||
:is="selectComponent(subfieldDefinition.type)"
|
||||
:errors="errors"
|
||||
:name="subfieldname"
|
||||
:userroles="userroles"
|
||||
:value="formData[subfieldname]"
|
||||
v-bind="subfieldDefinition">
|
||||
</component>
|
||||
</fieldset>
|
||||
<component v-else
|
||||
:key="fieldname"
|
||||
:is="selectComponent(fieldDefinition.type)"
|
||||
:errors="errors"
|
||||
:name="fieldname"
|
||||
:userroles="userroles"
|
||||
:value="formData[fieldname]"
|
||||
v-bind="fieldDefinition">
|
||||
</component>
|
||||
</div>
|
||||
<div class="my-5">
|
||||
<div :class="messageClass" class="block w-full h-8 px-3 py-1 my-1 text-white transition duration-100">{{ message }}</div>
|
||||
<input type="submit" @click.prevent="save()" value="save" class="w-full p-3 my-1 bg-stone-700 hover:bg-stone-900 text-white cursor-pointer transition duration-100">
|
||||
</div>
|
||||
-->
|
||||
</form>
|
||||
</Transition>`,
|
||||
data() {
|
||||
|
@@ -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()
|
||||
{
|
||||
|
@@ -17,11 +17,4 @@
|
||||
app.config.globalProperties.$filters = translatefilter;
|
||||
app.mount('#license');
|
||||
</script>
|
||||
<script src="https://cdn.paddle.com/paddle/paddle.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
Paddle.Environment.set('sandbox');
|
||||
Paddle.Setup({ vendor: 10629 });
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
@@ -1,3 +1,5 @@
|
||||
# German (Deutsche)
|
||||
# Author: Sebastian Schürmanns
|
||||
'-LICENSE_AND_YOUR_WEBSITE_MUST_RUN_UNDER_THE_DOMAIN_OF_YOUR_LICENSE.': '-Lizenz und die Webseite muss unter der Domain von der Lizenz laufen.'
|
||||
'ACCESS_&_RIGHTS': 'Zugang & Rechte'
|
||||
ACCESS_FOR: 'Zugang für'
|
||||
|
@@ -1,3 +1,5 @@
|
||||
# English
|
||||
# Author: Sebastian Schuermanns
|
||||
'-LICENSE_AND_YOUR_WEBSITE_MUST_RUN_UNDER_THE_DOMAIN_OF_YOUR_LICENSE.': ''
|
||||
'ACCESS_&_RIGHTS': ''
|
||||
ACCESS_FOR: ''
|
||||
|
@@ -1,3 +1,5 @@
|
||||
# French (Français)
|
||||
# Author:
|
||||
'-LICENSE_AND_YOUR_WEBSITE_MUST_RUN_UNDER_THE_DOMAIN_OF_YOUR_LICENSE.': ''
|
||||
'ACCESS_&_RIGHTS': ''
|
||||
ACCESS_FOR: ''
|
||||
|
@@ -1,3 +1,5 @@
|
||||
# Italian (Italiano) pls ignore autoupdates
|
||||
# Author: Severo Iuliano (https://github.com/iusvar)
|
||||
'-LICENSE_AND_YOUR_WEBSITE_MUST_RUN_UNDER_THE_DOMAIN_OF_YOUR_LICENSE.': ''
|
||||
'ACCESS_&_RIGHTS': ''
|
||||
ACCESS_FOR: ''
|
||||
|
@@ -3,6 +3,7 @@
|
||||
use Slim\Routing\RouteCollectorProxy;
|
||||
use Typemill\Middleware\ApiAuthentication;
|
||||
use Typemill\Middleware\ApiAuthorization;
|
||||
use Typemill\Middleware\CorsHeadersMiddleware;
|
||||
use Typemill\Controllers\ControllerApiGlobals;
|
||||
use Typemill\Controllers\ControllerApiMedia;
|
||||
use Typemill\Controllers\ControllerApiSystemSettings;
|
||||
@@ -18,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) {
|
||||
|
||||
@@ -34,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
|
||||
@@ -85,13 +88,13 @@ $app->group('/api/v1', function (RouteCollectorProxy $group) use ($acl) {
|
||||
$group->get('/meta', ControllerApiAuthorMeta::class . ':getMeta')->setName('api.meta.get')->add(new ApiAuthorization($acl, 'mycontent', 'view'));
|
||||
$group->post('/meta', ControllerApiAuthorMeta::class . ':updateMeta')->setName('api.metadata.update')->add(new ApiAuthorization($acl, 'mycontent', 'update'));
|
||||
|
||||
})->add(new ApiAuthentication());
|
||||
})->add(new CorsHeadersMiddleware($settings, $urlinfo))->add(new ApiAuthentication());
|
||||
|
||||
# api-routes from plugins
|
||||
if(isset($routes['api']) && !empty($routes['api']))
|
||||
{
|
||||
foreach($routes['api'] as $pluginRoute)
|
||||
{
|
||||
{
|
||||
$method = $pluginRoute['httpMethod'] ?? false;
|
||||
$route = $pluginRoute['route'] ?? false;
|
||||
$class = $pluginRoute['class'] ?? false;
|
||||
@@ -102,12 +105,12 @@ if(isset($routes['api']) && !empty($routes['api']))
|
||||
if($resources && $privilege)
|
||||
{
|
||||
# protected api requires authentication and authorization
|
||||
$app->{$method}($route, $class)->setName($name)->add(new ApiAuthorization($acl, $resource, $privilege))->add(new ApiAuthentication());
|
||||
$app->{$method}($route, $class)->setName($name)->add(new ApiAuthorization($acl, $resource, $privilege))->add(new CorsHeadersMiddleware($settings, $urlinfo))->add(new ApiAuthentication());
|
||||
}
|
||||
else
|
||||
{
|
||||
# public api routes
|
||||
$app->{$method}($route, $class)->setName($name);
|
||||
$app->{$method}($route, $class)->setName($name)->add(new CorsHeadersMiddleware($settings, $urlinfo));
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,6 +4,7 @@ use Slim\Routing\RouteCollectorProxy;
|
||||
use Typemill\Middleware\WebRedirectIfAuthenticated;
|
||||
use Typemill\Middleware\WebRedirectIfUnauthenticated;
|
||||
use Typemill\Middleware\WebAuthorization;
|
||||
use Typemill\Middleware\CspHeadersMiddleware;
|
||||
use Typemill\Controllers\ControllerWebSetup;
|
||||
use Typemill\Controllers\ControllerWebAuth;
|
||||
use Typemill\Controllers\ControllerWebRecover;
|
||||
@@ -18,6 +19,11 @@ $app->group('/tm', function (RouteCollectorProxy $group) use ($settings) {
|
||||
$group->get('/login', ControllerWebAuth::class . ':show')->setName('auth.show');
|
||||
$group->post('/login', ControllerWebAuth::class . ':login')->setName('auth.login');
|
||||
|
||||
if(isset($settings['authcode']) && $settings['authcode'])
|
||||
{
|
||||
$group->post('/authcode', ControllerWebAuth::class . ':loginWithAuthcode')->setName('auth.authcode');
|
||||
}
|
||||
|
||||
if(isset($settings['recoverpw']) && $settings['recoverpw'])
|
||||
{
|
||||
$group->get('/recover', ControllerWebRecover::class . ':showRecoverForm')->setName('auth.recoverform');
|
||||
@@ -26,7 +32,7 @@ $app->group('/tm', function (RouteCollectorProxy $group) use ($settings) {
|
||||
$group->post('/reset', ControllerWebRecover::class . ':resetPassword')->setName('auth.reset');
|
||||
}
|
||||
|
||||
})->add(new WebRedirectIfAuthenticated($routeParser, $settings));
|
||||
})->add(new CspHeadersMiddleware($settings, $cspFromPlugins, $cspFromTheme))->add(new WebRedirectIfAuthenticated($routeParser, $settings));
|
||||
|
||||
# AUTHOR AREA (requires authentication)
|
||||
$app->group('/tm', function (RouteCollectorProxy $group) use ($routeParser,$acl) {
|
||||
@@ -46,10 +52,11 @@ $app->group('/tm', function (RouteCollectorProxy $group) use ($routeParser,$acl)
|
||||
$group->get('/content/visual[/{route:.*}]', ControllerWebAuthor::class . ':showBlox')->setName('content.visual')->add(new WebAuthorization($routeParser, $acl, 'mycontent', 'view'));
|
||||
$group->get('/content/raw[/{route:.*}]', ControllerWebAuthor::class . ':showRaw')->setName('content.raw')->add(new WebAuthorization($routeParser, $acl, 'mycontent', 'view'));
|
||||
|
||||
})->add(new WebRedirectIfUnauthenticated($routeParser));
|
||||
})->add(new CspHeadersMiddleware($settings, $cspFromPlugins, $cspFromTheme))->add(new WebRedirectIfUnauthenticated($routeParser));
|
||||
|
||||
$app->redirect('/tm', $routeParser->urlFor('auth.show'), 302);
|
||||
$app->redirect('/tm/', $routeParser->urlFor('auth.show'), 302);
|
||||
$app->redirect('/tm/authcode', $routeParser->urlFor('auth.show'), 302);
|
||||
|
||||
# downloads
|
||||
$app->get('/media/files[/{params:.*}]', ControllerWebDownload::class . ':download')->setName('download.file');
|
||||
@@ -68,11 +75,11 @@ if(isset($routes['web']) && !empty($routes['web']))
|
||||
|
||||
if($resources && $privilege)
|
||||
{
|
||||
$app->{$method}($route, $class)->setName($name)->add(new WebAuthorization($routeParser, $acl, $resource, $privilege))->add(new WebRedirectIfUnauthenticated($routeParser));
|
||||
$app->{$method}($route, $class)->setName($name)->add(new CspHeadersMiddleware($settings, $cspFromPlugins, $cspFromTheme))->add(new WebAuthorization($routeParser, $acl, $resource, $privilege))->add(new WebRedirectIfUnauthenticated($routeParser));
|
||||
}
|
||||
else
|
||||
{
|
||||
$app->{$method}($route, $class)->setName($name);
|
||||
$app->{$method}($route, $class)->setName($name)->add(new CspHeadersMiddleware($settings, $cspFromPlugins, $cspFromTheme));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,10 +87,10 @@ if(isset($routes['web']) && !empty($routes['web']))
|
||||
if(isset($settings['access']) && $settings['access'] != '')
|
||||
{
|
||||
# if access for website is restricted
|
||||
$app->get('/[{route:.*}]', ControllerWebFrontend::class . ':index')->setName('home')->add(new WebAuthorization($routeParser, $acl, 'account', 'view'));
|
||||
$app->get('/[{route:.*}]', ControllerWebFrontend::class . ':index')->setName('home')->add(new CspHeadersMiddleware($settings, $cspFromPlugins, $cspFromTheme))->add(new WebAuthorization($routeParser, $acl, 'account', 'view'));
|
||||
}
|
||||
else
|
||||
{
|
||||
# if access is not restricted
|
||||
$app->get('/[{route:.*}]', ControllerWebFrontend::class . ':index')->setName('home');
|
||||
$app->get('/[{route:.*}]', ControllerWebFrontend::class . ':index')->setName('home')->add(new CspHeadersMiddleware($settings, $cspFromPlugins, $cspFromTheme));
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
version: '2.0.3'
|
||||
version: '2.1.0'
|
||||
title: 'Typemill'
|
||||
author: 'Unknown'
|
||||
copyright: false
|
||||
|
@@ -78,20 +78,24 @@ meta:
|
||||
hidden: true
|
||||
css: hidden
|
||||
pattern: '[0-9][0-9]-[0-9][0-9]-[0-9][0-9]'
|
||||
# fieldsetreference:
|
||||
# type: fieldset
|
||||
# legend: Reference
|
||||
# fields:
|
||||
# reference:
|
||||
# type: text
|
||||
# label: Reference to page
|
||||
# maxlength: 60
|
||||
# referencetype:
|
||||
# type: radio
|
||||
# label: Type of reference
|
||||
# options:
|
||||
# copy: Copy (copy the content of the referenced page)
|
||||
# redirect: Redirect (redirect the user to the referenced page)
|
||||
fieldsetreference:
|
||||
type: fieldset
|
||||
legend: Reference
|
||||
fields:
|
||||
reference:
|
||||
type: text
|
||||
label: Reference to page
|
||||
placeholder: '/path/to/internal/page or https://exgernal-page.org'
|
||||
maxlength: 200
|
||||
referencetype:
|
||||
type: radio
|
||||
label: Type of reference
|
||||
options:
|
||||
disable: Disable
|
||||
redirect301: PERMANENT REDIRECT (301) the user to the referenced internal page
|
||||
redirect302: TEMPORARY REDIRECT (302) the user to the referenced internal page
|
||||
copy: COPY the content of the referenced internal page
|
||||
outlink: LINK to an external page
|
||||
fieldsetvisibility:
|
||||
type: fieldset
|
||||
legend: Visibility
|
||||
|
@@ -4,19 +4,20 @@ fieldsetsystem:
|
||||
fields:
|
||||
title:
|
||||
type: text
|
||||
label: Website title
|
||||
label: 'Website title'
|
||||
maxlength: 60
|
||||
css: lg:w-half
|
||||
author:
|
||||
type: text
|
||||
label: Website owner
|
||||
label: 'Website owner'
|
||||
css: lg:w-half
|
||||
maxlength: 60
|
||||
copyright:
|
||||
type: select
|
||||
label: Copyright
|
||||
label: 'Copyright'
|
||||
css: lg:w-half
|
||||
maxlength: 60
|
||||
description: 'Used for copyright and year in footer.'
|
||||
options:
|
||||
'©': '©'
|
||||
'CC-BY': 'CC-BY'
|
||||
@@ -30,11 +31,13 @@ fieldsetsystem:
|
||||
label: Year
|
||||
css: lg:w-half
|
||||
maxlength: 4
|
||||
description: 'Used for copyright and year in footer.'
|
||||
language:
|
||||
type: select
|
||||
label: Language (author area)
|
||||
label: 'Language (author area)'
|
||||
css: lg:w-half
|
||||
maxlength: 60
|
||||
description: 'Used for translations in author area, themes, and plugins.'
|
||||
options:
|
||||
'en': 'English'
|
||||
'ru': 'Russian'
|
||||
@@ -44,15 +47,16 @@ fieldsetsystem:
|
||||
'fr': 'French'
|
||||
langattr:
|
||||
type: text
|
||||
label: Language attribute (website)
|
||||
label: 'Language attribute (website)'
|
||||
css: lg:w-half
|
||||
maxlength: 5
|
||||
description: Please use ISO 639-1 codes like "en"
|
||||
description: 'Used for frontend language attribute. Please use ISO 639-1 codes like "en".'
|
||||
sitemap:
|
||||
type: text
|
||||
label: Google sitemap (readonly)
|
||||
label: 'Google sitemap (readonly)'
|
||||
css: lg:w-half
|
||||
disabled: true
|
||||
description: 'Submit the url above in google search console to support indexing.'
|
||||
fieldsetmedia:
|
||||
type: fieldset
|
||||
legend: Media
|
||||
@@ -63,48 +67,48 @@ fieldsetmedia:
|
||||
favicon:
|
||||
type: image
|
||||
label: Favicon
|
||||
description: Only PNG format will work.
|
||||
description: 'Only PNG format will work.'
|
||||
liveimagewidth:
|
||||
type: number
|
||||
label: Standard width for live pictures
|
||||
label: 'Standard width for live pictures'
|
||||
placeholder: 820
|
||||
description: Default width of live images is 820px. Changes will apply to future uploads.
|
||||
description: 'Default width of live images is 820px. Changes will apply to future uploads.'
|
||||
css: lg:w-half
|
||||
liveimageheight:
|
||||
type: number
|
||||
label: Standard height for live pictures
|
||||
description: If you add a value for the height, then the image will be cropped.
|
||||
label: 'Standard height for live pictures'
|
||||
description: 'If you add a value for the height, then the image will be cropped.'
|
||||
css: lg:w-half
|
||||
maximageuploads:
|
||||
type: number
|
||||
label: Maximum size for image uploads in MB
|
||||
description: The maximum image size might be limited by your server settings.
|
||||
label: 'Maximum size for image uploads in MB'
|
||||
description: 'The maximum image size might be limited by your server settings.'
|
||||
allowsvg:
|
||||
type: checkbox
|
||||
label: Allow svg
|
||||
checkboxlabel: Allow the upload of svg images
|
||||
checkboxlabel: 'Allow the upload of svg images'
|
||||
convertwebp:
|
||||
type: checkbox
|
||||
label: Convert to webp
|
||||
checkboxlabel: Try to convert uploaded images into the webp-format
|
||||
label: 'Convert to webp'
|
||||
checkboxlabel: 'Try to convert uploaded images into the webp-format for better performance.'
|
||||
maxfileuploads:
|
||||
type: number
|
||||
label: Maximum size for file uploads in MB
|
||||
description: The maximum file size might be limited by your server settings.
|
||||
label: 'Maximum size for file uploads in MB'
|
||||
description: 'The maximum file size might be limited by your server settings.'
|
||||
fieldsetwriting:
|
||||
type: fieldset
|
||||
legend: Writing
|
||||
fields:
|
||||
editor:
|
||||
type: radio
|
||||
label: Standard editor mode
|
||||
label: 'Standard editor mode'
|
||||
css: lg:w-half
|
||||
options:
|
||||
'visual': 'visual editor'
|
||||
'raw': 'raw editor'
|
||||
formats:
|
||||
type: checkboxlist
|
||||
label: Format options for visual editor
|
||||
label: 'Format options for visual editor'
|
||||
css: lg:w-half
|
||||
options:
|
||||
'markdown': 'markdown'
|
||||
@@ -124,12 +128,12 @@ fieldsetwriting:
|
||||
'shortcode': 'shortcode'
|
||||
headlineanchors:
|
||||
type: checkbox
|
||||
label: Headline anchors
|
||||
checkboxlabel: Show anchors next to headline in frontend
|
||||
label: 'Headline anchors'
|
||||
checkboxlabel: 'Show anchors next to headline in frontend'
|
||||
urlschemes:
|
||||
type: text
|
||||
label: Url schemes
|
||||
description: Add more url schemes for external links e.g. like dict:// (comma separated list)
|
||||
label: 'Url schemes'
|
||||
description: 'Add more url schemes for external links e.g. like dict:// (comma separated list)'
|
||||
maxlength: 60
|
||||
fieldsetaccess:
|
||||
type: fieldset
|
||||
@@ -137,73 +141,120 @@ fieldsetaccess:
|
||||
fields:
|
||||
access:
|
||||
type: checkbox
|
||||
label: Website restriction
|
||||
checkboxlabel: Show the website only to authenticated users and redirect all other users to the login page.
|
||||
label: 'Website restriction'
|
||||
checkboxlabel: 'Show the website only to authenticated users and redirect all other users to the login page.'
|
||||
pageaccess:
|
||||
type: checkbox
|
||||
label: Page restriction
|
||||
checkboxlabel: Activate individual restrictions for pages in the meta-tab of each page.
|
||||
label: 'Page restriction'
|
||||
checkboxlabel: 'Activate individual restrictions for pages in the meta-tab of each page.'
|
||||
hrdelimiter:
|
||||
type: checkbox
|
||||
label: Content break
|
||||
checkboxlabel: Cut restricted content after the first hr-element on a page (per default content will be cut after title).
|
||||
label: 'Content break'
|
||||
checkboxlabel: 'Cut restricted content after the first hr-element on a page (per default content will be cut after title).'
|
||||
restrictionnotice:
|
||||
type: textarea
|
||||
label: Restriction notice (use markdown)
|
||||
label: 'Restriction notice (use markdown)'
|
||||
maxlength: 2000
|
||||
wraprestrictionnotice:
|
||||
type: checkbox
|
||||
label: Wrap restriction notice
|
||||
checkboxlabel: Wrap the restriction notice above into a notice-4 element (which can be designed as special box)
|
||||
label: 'Wrap restriction notice'
|
||||
checkboxlabel: 'Wrap the restriction notice above into a notice-4 element (which can be designed as special box)'
|
||||
fieldsetmail:
|
||||
type: fieldset
|
||||
legend: Email
|
||||
fields:
|
||||
mailfrom:
|
||||
type: email
|
||||
label: 'Mail From (required)'
|
||||
placeholder: sender@yourmail.org
|
||||
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: 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: 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
|
||||
legend: Recover
|
||||
fields:
|
||||
recoverpw:
|
||||
type: checkbox
|
||||
label: 'Recover password'
|
||||
checkboxlabel: 'Activate a password recovery in the login form.'
|
||||
description: "From mail is required for this feature. Send a testmail before you use this feature."
|
||||
recoversubject:
|
||||
type: text
|
||||
label: 'Email subject'
|
||||
placeholder: 'Recover your password'
|
||||
maxlength: 60
|
||||
recovermessage:
|
||||
type: textarea
|
||||
label: 'Text before recover link in email message'
|
||||
descriptiion: 'The recover-link will be active for 24 hours.'
|
||||
maxlength: 2000
|
||||
fieldsetsecurity:
|
||||
type: fieldset
|
||||
legend: Security
|
||||
fields:
|
||||
securitylog:
|
||||
authcode:
|
||||
type: checkbox
|
||||
label: Security log
|
||||
checkboxlabel: Track spam and suspicious actions in a logfile
|
||||
label: 'Login Verification (recommended)'
|
||||
checkboxlabel: 'Verify your login with a 5-digit code send by email.'
|
||||
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
|
||||
label: 'Use captcha in authentication forms'
|
||||
options:
|
||||
disabled: Disable
|
||||
standard: Always show
|
||||
aftererror: Show after first wrong input
|
||||
recoverpw:
|
||||
disabled: 'Disable'
|
||||
standard: 'Always show'
|
||||
aftererror: 'Show after first wrong input'
|
||||
securitylog:
|
||||
type: checkbox
|
||||
label: Recover password
|
||||
checkboxlabel: Activate the password recovery.
|
||||
recoverfrom:
|
||||
type: text
|
||||
label: Sender email
|
||||
placeholder: your@email.org
|
||||
maxlength: 60
|
||||
recoversubject:
|
||||
type: text
|
||||
label: Email subject
|
||||
placeholder: Recover your password
|
||||
maxlength: 60
|
||||
recovermessage:
|
||||
type: textarea
|
||||
label: Text before recover link in email message
|
||||
maxlength: 2000
|
||||
label: 'Security log'
|
||||
checkboxlabel: 'Track spam and suspicious actions in a logfile'
|
||||
fieldsetdeveloper:
|
||||
type: fieldset
|
||||
legend: Developer
|
||||
legend: "Developer"
|
||||
fields:
|
||||
displayErrorDetails:
|
||||
type: checkbox
|
||||
label: Error reporting
|
||||
checkboxlabel: Display application errors
|
||||
label: "Error reporting"
|
||||
checkboxlabel: "Display application errors"
|
||||
twigcache:
|
||||
type: checkbox
|
||||
label: Twig cache
|
||||
checkboxlabel: Activate the cache for twig templates
|
||||
label: "Twig cache"
|
||||
checkboxlabel: "Activate the cache for twig templates"
|
||||
proxy:
|
||||
type: checkbox
|
||||
label: Proxy
|
||||
checkboxlabel: Use x-forwarded-header.
|
||||
label: "Proxy"
|
||||
checkboxlabel: "Use x-forwarded-header"
|
||||
trustedproxies:
|
||||
type: text
|
||||
label: Trusted IPs for proxies (comma separated)
|
||||
label: "Trusted IPs for proxies (comma separated)"
|
||||
headersoff:
|
||||
type: checkbox
|
||||
label: "Disable Custom Headers"
|
||||
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 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."
|
||||
|
@@ -16,11 +16,14 @@ use Typemill\Static\Plugins;
|
||||
use Typemill\Static\Translations;
|
||||
use Typemill\Static\Permissions;
|
||||
use Typemill\Static\Helpers;
|
||||
use Typemill\Static\Urlinfo;
|
||||
use Typemill\Events\OnSettingsLoaded;
|
||||
use Typemill\Events\OnPluginsLoaded;
|
||||
use Typemill\Events\OnSessionSegmentsLoaded;
|
||||
use Typemill\Events\OnRolesPermissionsLoaded;
|
||||
use Typemill\Events\OnResourcesLoaded;
|
||||
use Typemill\Events\OnCspLoaded;
|
||||
use Typemill\Middleware\RemoveCredentialsMiddleware;
|
||||
use Typemill\Middleware\SessionMiddleware;
|
||||
use Typemill\Middleware\OldInputMiddleware;
|
||||
use Typemill\Middleware\ValidationErrorsMiddleware;
|
||||
@@ -28,6 +31,7 @@ use Typemill\Middleware\JsonBodyParser;
|
||||
use Typemill\Middleware\FlashMessages;
|
||||
use Typemill\Middleware\AssetMiddleware;
|
||||
use Typemill\Middleware\SecurityMiddleware;
|
||||
use Typemill\Middleware\CustomHeadersMiddleware;
|
||||
use Typemill\Extensions\TwigCsrfExtension;
|
||||
use Typemill\Extensions\TwigUrlExtension;
|
||||
use Typemill\Extensions\TwigUserExtension;
|
||||
@@ -48,14 +52,16 @@ ini_set('display_errors', 0);
|
||||
ini_set('display_startup_errors', 0);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
|
||||
/****************************
|
||||
* LOAD SETTINGS *
|
||||
* LOAD SETTINGS *
|
||||
****************************/
|
||||
|
||||
$settingsModel = new Settings();
|
||||
|
||||
$settings = $settingsModel->loadSettings();
|
||||
|
||||
|
||||
/****************************
|
||||
* HANDLE DISPLAY ERRORS *
|
||||
****************************/
|
||||
@@ -65,19 +71,9 @@ if(isset($settings['displayErrorDetails']) && $settings['displayErrorDetails'])
|
||||
# ini_set('display_startup_errors', 1);
|
||||
}
|
||||
|
||||
/****************************
|
||||
* ADD PATH-INFOS FOR LATER *
|
||||
****************************/
|
||||
|
||||
# ADD THEM TO THE SETTINGS AND YOU HAVE THEM EVERYWHERE??
|
||||
$uriFactory = new UriFactory();
|
||||
$uri = $uriFactory->createFromGlobals($_SERVER);
|
||||
$urlinfo = Helpers::urlInfo($uri);
|
||||
|
||||
$timer['settings'] = microtime(true);
|
||||
|
||||
/****************************
|
||||
* CREATE CONTAINER *
|
||||
* CREATE CONTAINER + APP *
|
||||
****************************/
|
||||
|
||||
# https://www.slimframework.com/docs/v4/start/upgrade.html#changes-to-container
|
||||
@@ -93,11 +89,40 @@ $routeParser = $app->getRouteCollector()->getRouteParser();
|
||||
# add route parser to container to use named routes in controller
|
||||
$container->set('routeParser', $routeParser);
|
||||
|
||||
# set urlinfo
|
||||
$container->set('urlinfo', $urlinfo);
|
||||
|
||||
/*******************************
|
||||
* Basepath *
|
||||
******************************/
|
||||
|
||||
# in slim 4 you alsways have to set application basepath
|
||||
$app->setBasePath($urlinfo['basepath']);
|
||||
$basepath = preg_replace('/(.*)\/.*/', '$1', $_SERVER['SCRIPT_NAME']);
|
||||
$app->setBasePath($basepath);
|
||||
|
||||
|
||||
/****************
|
||||
* URLINFO *
|
||||
****************/
|
||||
|
||||
# WE DO NOT NEED IT HERE?
|
||||
# WE CAN ADD IT TO CONTAINER IN MIDDLEWARE AFTER PROXY DETECTION
|
||||
|
||||
# WE NEED FOR
|
||||
# - LICENSE (base url)
|
||||
# - Each plugin to add container
|
||||
# - SESSION SEGMEŃTS (route)
|
||||
# - TRANSLATIONS (route)
|
||||
# - ASSETS (route)
|
||||
# - TWIG URL EXTENSION
|
||||
# - SESSION MIDDLEWARE
|
||||
|
||||
$uriFactory = new UriFactory();
|
||||
$uri = $uriFactory->createFromGlobals($_SERVER);
|
||||
$urlinfo = Urlinfo::getUrlInfo($basepath, $uri, $settings);
|
||||
|
||||
$timer['settings'] = microtime(true);
|
||||
|
||||
# set urlinfo
|
||||
$container->set('urlinfo', $urlinfo);
|
||||
|
||||
$timer['container'] = microtime(true);
|
||||
|
||||
@@ -304,6 +329,8 @@ foreach($middleware as $pluginMiddleware)
|
||||
}
|
||||
}
|
||||
|
||||
$app->add(new CustomHeadersMiddleware($settings));
|
||||
|
||||
$app->add(new AssetMiddleware($assets, $container->get('view')));
|
||||
|
||||
$app->add(new ValidationErrorsMiddleware($container->get('view')));
|
||||
@@ -312,11 +339,11 @@ $app->add(new SecurityMiddleware($routeParser, $container->get('settings')));
|
||||
|
||||
$app->add(new OldInputMiddleware($container->get('view')));
|
||||
|
||||
$app->add(new FlashMessages($container));
|
||||
|
||||
# Add Twig-View Middleware
|
||||
$app->add(TwigMiddleware::createFromContainer($app));
|
||||
|
||||
$app->add(new FlashMessages($container));
|
||||
|
||||
# add JsonBodyParser Middleware
|
||||
$app->add(new JsonBodyParser());
|
||||
|
||||
@@ -343,7 +370,9 @@ $errorMiddleware->setErrorHandler(HttpNotFoundException::class, function ($reque
|
||||
|
||||
$app->add($errorMiddleware);
|
||||
|
||||
$app->add(new SessionMiddleware($session_segments, $urlinfo['route'], $uri));
|
||||
$app->add(new SessionMiddleware($session_segments, $urlinfo['route']));
|
||||
|
||||
$app->add(new RemoveCredentialsMiddleware());
|
||||
|
||||
if(isset($settings['proxy']) && $settings['proxy'])
|
||||
{
|
||||
@@ -355,9 +384,25 @@ if(isset($settings['proxy']) && $settings['proxy'])
|
||||
$timer['middleware'] = microtime(true);
|
||||
|
||||
|
||||
/******************************
|
||||
* GET CSP FROM PLUGINS/THEMES *
|
||||
******************************/
|
||||
|
||||
# get additional csp headers from plugins
|
||||
$cspFromPlugins = $dispatcher->dispatch(new OnCspLoaded([]), 'onCspLoaded')->getData();
|
||||
|
||||
# get additional csp headers from theme
|
||||
$cspFromTheme = [];
|
||||
$themeSettings = $settingsModel->getObjectSettings('themes', $settings['theme']);
|
||||
if(isset($themeSettings['csp']) && is_array($themeSettings['csp']) && !empty($themeSettings['csp']) )
|
||||
{
|
||||
$cspFromTheme = $themeSettings['csp'];
|
||||
}
|
||||
|
||||
/************************
|
||||
* ADD ROUTES *
|
||||
************************/
|
||||
|
||||
if(isset($settings['setup']) && $settings['setup'] == true)
|
||||
{
|
||||
require __DIR__ . '/routes/setup.php';
|
||||
@@ -370,6 +415,7 @@ else
|
||||
|
||||
$timer['routes'] = microtime(true);
|
||||
|
||||
|
||||
/************************
|
||||
* RUN APP *
|
||||
************************/
|
||||
|
Reference in New Issue
Block a user