mirror of
https://github.com/typemill/typemill.git
synced 2025-08-05 05:37:45 +02:00
Settings area V2
This commit is contained in:
@@ -5,17 +5,8 @@ namespace Typemill\Controllers;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
|
||||
class ControllerSystemApi extends ControllerData
|
||||
class ControllerApiGlobal extends ControllerData
|
||||
{
|
||||
public function getSettings(Request $request, Response $response)
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'settings' => $this->settings
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
|
||||
}
|
||||
|
||||
public function getSystemNavi(Request $request, Response $response)
|
||||
{
|
||||
# won't work because api has no session, instead you have to pass user
|
||||
@@ -34,4 +25,13 @@ class ControllerSystemApi extends ControllerData
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
|
||||
}
|
||||
|
||||
public function getTranslations(Request $request, Response $response)
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'translations' => $this->c->get('translations'),
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
|
||||
}
|
||||
}
|
54
system/typemill/Controllers/ControllerApiSystemPlugins.php
Normal file
54
system/typemill/Controllers/ControllerApiSystemPlugins.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Controllers;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Typemill\Models\Validation;
|
||||
use Typemill\Models\Yaml;
|
||||
use Typemill\Models\User;
|
||||
|
||||
class ControllerApiSystemPlugins extends ControllerData
|
||||
{
|
||||
public function updatePlugin(Request $request, Response $response)
|
||||
{
|
||||
# minimum permission are admin rights
|
||||
if(!$this->c->get('acl')->isAllowed($request->getAttribute('userrole'), 'system', 'update'))
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'You are not allowed to update settings.'
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(403);
|
||||
}
|
||||
|
||||
$params = $request->getParsedBody();
|
||||
$pluginname = $params['plugin'];
|
||||
$plugininput = $params['settings'];
|
||||
$yaml = new Yaml('\Typemill\Models\Storage');
|
||||
$formdefinitions = $yaml->getYaml('plugins' . DIRECTORY_SEPARATOR . $pluginname, $pluginname . '.yaml');
|
||||
|
||||
# validate input
|
||||
$validator = new Validation();
|
||||
$this->recursiveValidation($formdefinitions['forms']['fields'], $plugininput, $validator, $themeOrPlugin = 'plugins', $name = $pluginname);
|
||||
|
||||
if(!empty($this->errors))
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'Please correct tbe errors in form.',
|
||||
'errors' => $this->errors
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
|
||||
}
|
||||
|
||||
# store updated settings here
|
||||
$yaml->updateYaml('settings', 'settings.yaml', $this->settings);
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'settings have been saved',
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
|
||||
}
|
||||
}
|
76
system/typemill/Controllers/ControllerApiSystemSettings.php
Normal file
76
system/typemill/Controllers/ControllerApiSystemSettings.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Controllers;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Typemill\Models\Validation;
|
||||
use Typemill\Models\Yaml;
|
||||
use Typemill\Models\User;
|
||||
|
||||
|
||||
# how to translate results in API call ???
|
||||
# we should translate in backend instead of twig or vue
|
||||
|
||||
class ControllerApiSystemSettings extends ControllerData
|
||||
{
|
||||
public function getSettings(Request $request, Response $response)
|
||||
{
|
||||
# minimum permission are admin rights
|
||||
if(!$this->c->get('acl')->isAllowed($request->getAttribute('userrole'), 'system', 'update'))
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'You are not allowed to update settings.'
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(403);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'settings' => $this->settings
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
|
||||
}
|
||||
|
||||
public function updateSettings(Request $request, Response $response)
|
||||
{
|
||||
# minimum permission are admin rights
|
||||
if(!$this->c->get('acl')->isAllowed($request->getAttribute('userrole'), 'system', 'update'))
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'You are not allowed to update settings.'
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(403);
|
||||
}
|
||||
|
||||
$params = $request->getParsedBody();
|
||||
$settingsinput = $params['settings'];
|
||||
$yaml = new Yaml('\Typemill\Models\Storage');
|
||||
$formdefinitions = $yaml->getYaml('system/typemill/settings', 'system.yaml');
|
||||
|
||||
# validate input
|
||||
$validator = new Validation();
|
||||
$this->recursiveValidation($formdefinitions, $settingsinput, $validator);
|
||||
|
||||
if(!empty($this->errors))
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'Please correct errors in form.',
|
||||
'errors' => $this->errors
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
|
||||
}
|
||||
|
||||
# store updated settings here
|
||||
$yaml->updateYaml('settings', 'settings.yaml', $this->settings);
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'settings have been saved',
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
|
||||
}
|
||||
}
|
54
system/typemill/Controllers/ControllerApiSystemThemes.php
Normal file
54
system/typemill/Controllers/ControllerApiSystemThemes.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Controllers;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Typemill\Models\Validation;
|
||||
use Typemill\Models\Yaml;
|
||||
use Typemill\Models\User;
|
||||
|
||||
class ControllerApiSystemThemes extends ControllerData
|
||||
{
|
||||
public function updateTheme(Request $request, Response $response)
|
||||
{
|
||||
# minimum permission are admin rights
|
||||
if(!$this->c->get('acl')->isAllowed($request->getAttribute('userrole'), 'system', 'update'))
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'You are not allowed to update settings.'
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(403);
|
||||
}
|
||||
|
||||
$params = $request->getParsedBody();
|
||||
$themename = $params['theme'];
|
||||
$themeinput = $params['settings'];
|
||||
$yaml = new Yaml('\Typemill\Models\Storage');
|
||||
$formdefinitions = $yaml->getYaml('themes' . DIRECTORY_SEPARATOR . $themename, $themename . '.yaml');
|
||||
|
||||
# validate input
|
||||
$validator = new Validation();
|
||||
$this->recursiveValidation($formdefinitions['forms']['fields'], $themeinput, $validator, $themeOrPlugin = 'themes', $name = $themename);
|
||||
|
||||
if(!empty($this->errors))
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'Please correct tbe errors in form.',
|
||||
'errors' => $this->errors
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
|
||||
}
|
||||
|
||||
# store updated settings here
|
||||
$yaml->updateYaml('settings', 'settings.yaml', $this->settings);
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'settings have been saved',
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
|
||||
}
|
||||
}
|
337
system/typemill/Controllers/ControllerApiSystemUsers.php
Normal file
337
system/typemill/Controllers/ControllerApiSystemUsers.php
Normal file
@@ -0,0 +1,337 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Controllers;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Typemill\Models\Validation;
|
||||
use Typemill\Models\Yaml;
|
||||
use Typemill\Models\User;
|
||||
|
||||
class ControllerApiSystemUsers extends ControllerData
|
||||
{
|
||||
|
||||
# getCurrentUser
|
||||
# getUserByName
|
||||
|
||||
#returns userdata
|
||||
public function getUsersByNames($request, $response, $args)
|
||||
{
|
||||
# minimum permission are admin rights
|
||||
if(!$this->c->get('acl')->isAllowed($request->getAttribute('userrole'), 'system', 'update'))
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'You are not allowed to update settings.'
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(403);
|
||||
}
|
||||
|
||||
$usernames = $request->getQueryParams()['usernames'] ?? false;
|
||||
$user = new User();
|
||||
$userdata = [];
|
||||
|
||||
if($usernames)
|
||||
{
|
||||
foreach($usernames as $username)
|
||||
{
|
||||
$existinguser = $user->setUser($username);
|
||||
if($existinguser)
|
||||
{
|
||||
$userdata[] = $user->getUserData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'userdata' => $userdata
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
|
||||
}
|
||||
|
||||
# returns userdata
|
||||
public function getUsersByEmail($request, $response, $args)
|
||||
{
|
||||
# minimum permission are admin rights
|
||||
if(!$this->c->get('acl')->isAllowed($request->getAttribute('userrole'), 'system', 'update'))
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'You are not allowed to update settings.'
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(403);
|
||||
}
|
||||
|
||||
$email = $request->getQueryParams()['email'] ?? false;
|
||||
$user = new User();
|
||||
$userdata = [];
|
||||
|
||||
$usernames = $user->findUsersByEmail($email);
|
||||
|
||||
if($usernames)
|
||||
{
|
||||
foreach($usernames as $username)
|
||||
{
|
||||
$user->setUser($username);
|
||||
$userdata[] = $user->getUserData();
|
||||
}
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'userdata' => $userdata
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
|
||||
}
|
||||
|
||||
#returns userdata
|
||||
public function getUsersByRole($request, $response, $args)
|
||||
{
|
||||
# minimum permission are admin rights
|
||||
if(!$this->c->get('acl')->isAllowed($request->getAttribute('userrole'), 'system', 'update'))
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'You are not allowed to update settings.'
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(403);
|
||||
}
|
||||
|
||||
$role = $request->getQueryParams()['role'] ?? false;
|
||||
$user = new User();
|
||||
$userdata = [];
|
||||
|
||||
$usernames = $user->findUsersByRole($role);
|
||||
|
||||
if($usernames)
|
||||
{
|
||||
foreach($usernames as $username)
|
||||
{
|
||||
$user->setUser($username);
|
||||
$userdata[] = $user->getUserData();
|
||||
}
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'userdata' => $userdata
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
|
||||
}
|
||||
|
||||
public function updateUser($request, $response, $args)
|
||||
{
|
||||
$params = $request->getParsedBody();
|
||||
$userdata = $params['userdata'] ?? false;
|
||||
$username = $params['userdata']['username'] ?? false;
|
||||
$isAdmin = $this->c->get('acl')->isAllowed($request->getAttribute('userrole'), 'userlist', 'write');
|
||||
|
||||
if(!$userdata OR !$username)
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'Userdata and username is required.'
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
|
||||
}
|
||||
|
||||
# if a non-admin-user tries to update another account
|
||||
if(!$isAdmin AND ($username !== $request->getAttribute('username')) )
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'You are not allowed to update another user.'
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(403);
|
||||
}
|
||||
|
||||
# make sure that invalid password input is stripped out
|
||||
if(isset($userdata['password']) && $userdata['password'] == '' )
|
||||
{
|
||||
unset($userdata['password']);
|
||||
unset($userdata['newpassword']);
|
||||
}
|
||||
|
||||
$user = new User();
|
||||
|
||||
# make sure you set a user with password when you update, otherwise it will delete the password completely
|
||||
$user->setUserWithPassword($username);
|
||||
|
||||
$userfields = $this->getUserFields($request->getAttribute('userrole'));
|
||||
|
||||
# validate input
|
||||
$validator = new Validation();
|
||||
|
||||
# loop through form-definitions, ignores everything that is not defined in yaml
|
||||
foreach($userfields as $fieldname => $fielddefinitions)
|
||||
{
|
||||
# if there is no value for a field
|
||||
if(!isset($userdata[$fieldname]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
# ignore readonly-fields
|
||||
if(isset($fielddefinitions['readonly']) && ($fielddefinitions['readonly'] !== false) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
# new password needs special validation
|
||||
if($fieldname == 'password')
|
||||
{
|
||||
$validationresult = $validator->newPassword($userdata);
|
||||
|
||||
if($validationresult === true)
|
||||
{
|
||||
# encrypt new password
|
||||
$newpassword = $user->generatePassword($userdata['newpassword']);
|
||||
|
||||
# if input is valid, overwrite value in original user
|
||||
$user->setValue('password', $newpassword);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->errors[$fieldname] = $validationresult[$fieldname][0];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
# standard validation
|
||||
$validationresult = $validator->field($fieldname, $userdata[$fieldname], $fielddefinitions);
|
||||
|
||||
if($validationresult === true)
|
||||
{
|
||||
# if input is valid, overwrite value in original user
|
||||
$user->setValue($fieldname, $userdata[$fieldname]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->errors[$fieldname] = $validationresult[$fieldname][0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!empty($this->errors))
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'Please correct tbe errors in form.',
|
||||
'errors' => $this->errors
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
|
||||
}
|
||||
|
||||
if(!$user->updateUser())
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => $user->getError()
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'User has been updated.'
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
/*
|
||||
public function updateUser($request, $response, $args)
|
||||
{
|
||||
# check if user is allowed to view (edit) userlist and other users
|
||||
if(!$this->c->acl->isAllowed($_SESSION['role'], 'userlist', 'write'))
|
||||
{
|
||||
# if an editor tries to update other userdata than its own
|
||||
if($_SESSION['user'] !== $userdata['username'])
|
||||
{
|
||||
return $response->withRedirect($this->c->router->pathFor('user.account'));
|
||||
}
|
||||
|
||||
# non admins cannot change their userrole, so set it to session-value
|
||||
$userdata['userrole'] = $_SESSION['role'];
|
||||
}
|
||||
|
||||
|
||||
|
||||
$params = $request->getParams();
|
||||
$userdata = $params['user'];
|
||||
$user = new User();
|
||||
$validate = new Validation();
|
||||
$userroles = $this->c->acl->getRoles();
|
||||
|
||||
$redirectRoute = ($userdata['username'] == $_SESSION['user']) ? $this->c->router->pathFor('user.account') : $this->c->router->pathFor('user.show', ['username' => $userdata['username']]);
|
||||
|
||||
# validate standard fields for users
|
||||
if($validate->existingUser($userdata, $userroles))
|
||||
{
|
||||
# validate custom input fields and return images
|
||||
$userfields = $this->getUserFields($userdata['userrole']);
|
||||
$imageFields = $this->validateInput('users', 'user', $userdata, $validate, $userfields);
|
||||
|
||||
if(!empty($imageFields))
|
||||
{
|
||||
$images = $request->getUploadedFiles();
|
||||
|
||||
if(isset($images['user']))
|
||||
{
|
||||
# set image size
|
||||
$settings = $this->c->get('settings');
|
||||
$imageSizes = $settings['images'];
|
||||
$imageSizes['live'] = ['width' => 500, 'height' => 500];
|
||||
$settings->replace(['images' => $imageSizes]);
|
||||
$imageresult = $this->saveImages($imageFields, $userdata, $settings, $images['user']);
|
||||
|
||||
if(isset($_SESSION['slimFlash']['error']))
|
||||
{
|
||||
return $response->withRedirect($redirectRoute);
|
||||
}
|
||||
elseif(isset($imageresult['username']))
|
||||
{
|
||||
$userdata = $imageresult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# check for errors and redirect to path, if errors found
|
||||
if(isset($_SESSION['errors']))
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'Please correct the errors');
|
||||
return $response->withRedirect($redirectRoute);
|
||||
}
|
||||
|
||||
if(empty($userdata['password']) AND empty($userdata['newpassword']))
|
||||
{
|
||||
# make sure no invalid passwords go into model
|
||||
unset($userdata['password']);
|
||||
unset($userdata['newpassword']);
|
||||
|
||||
$user->updateUser($userdata);
|
||||
$this->c->flash->addMessage('info', 'Saved all changes');
|
||||
return $response->withRedirect($redirectRoute);
|
||||
}
|
||||
elseif($validate->newPassword($userdata))
|
||||
{
|
||||
$userdata['password'] = $userdata['newpassword'];
|
||||
unset($userdata['newpassword']);
|
||||
|
||||
$user->updateUser($userdata);
|
||||
$this->c->flash->addMessage('info', 'Saved all changes');
|
||||
return $response->withRedirect($redirectRoute);
|
||||
}
|
||||
}
|
||||
|
||||
# change error-array for formbuilder
|
||||
$errors = $_SESSION['errors'];
|
||||
unset($_SESSION['errors']);
|
||||
$_SESSION['errors']['user'] = $errors;#
|
||||
|
||||
$this->c->flash->addMessage('error', 'Please correct your input');
|
||||
return $response->withRedirect($redirectRoute);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
@@ -12,6 +12,8 @@ use Typemill\Events\OnSystemnaviLoaded;
|
||||
|
||||
class ControllerData extends Controller
|
||||
{
|
||||
protected $errors = [];
|
||||
|
||||
protected function getMainNavigation($userrole)
|
||||
{
|
||||
$yaml = new Yaml('\Typemill\Models\Storage');
|
||||
@@ -27,11 +29,11 @@ class ControllerData extends Controller
|
||||
if($acl->isAllowed($userrole, $naviitem['aclresource'], $naviitem['aclprivilege']))
|
||||
{
|
||||
# not nice: check if the navi-item is active (e.g if segments like "content" or "system" is in current url)
|
||||
if($name == 'content' && strpos($this->settings['routepath'], 'tm/content'))
|
||||
if($name == 'content' && strpos($this->c->get('urlinfo')['route'], 'tm/content'))
|
||||
{
|
||||
$naviitem['active'] = true;
|
||||
}
|
||||
elseif($name == 'account' && strpos($this->settings['routepath'], 'tm/account'))
|
||||
elseif($name == 'account' && strpos($this->c->get('urlinfo')['route'], 'tm/account'))
|
||||
{
|
||||
$naviitem['active'] = true;
|
||||
}
|
||||
@@ -74,7 +76,7 @@ class ControllerData extends Controller
|
||||
{
|
||||
# check if the navi-item is active (e.g if segments like "content" or "system" is in current url)
|
||||
# a bit fragile because url-segment and name/key in systemnavi.yaml and plugins have to be the same
|
||||
if(strpos($this->settings['routepath'], 'tm/' . $name))
|
||||
if(strpos($this->c->get('urlinfo')['route'], 'tm/' . $name))
|
||||
{
|
||||
$naviitem['active'] = true;
|
||||
}
|
||||
@@ -87,4 +89,185 @@ class ControllerData extends Controller
|
||||
|
||||
return $allowedsystemnavi;
|
||||
}
|
||||
|
||||
protected function getThemeDetails()
|
||||
{
|
||||
$themes = $this->getThemes();
|
||||
|
||||
$themeDetails = [];
|
||||
foreach($themes as $themeName)
|
||||
{
|
||||
$themeDetails[$themeName] = $this->getThemeDefinition($themeName);
|
||||
}
|
||||
|
||||
return $themeDetails;
|
||||
}
|
||||
|
||||
protected function getThemes()
|
||||
{
|
||||
$themeFolder = $this->c->get('settings')['rootPath'] . DIRECTORY_SEPARATOR . $this->c->get('settings')['themeFolder'];
|
||||
$themeFolderC = scandir($themeFolder);
|
||||
$themes = [];
|
||||
foreach ($themeFolderC as $key => $theme)
|
||||
{
|
||||
if (!in_array($theme, [".",".."]))
|
||||
{
|
||||
if (is_dir($themeFolder . DIRECTORY_SEPARATOR . $theme))
|
||||
{
|
||||
$themes[] = $theme;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $themes;
|
||||
}
|
||||
|
||||
protected function getThemeDefinition($themeName)
|
||||
{
|
||||
$yaml = new Yaml('\Typemill\Models\Storage');
|
||||
|
||||
$themeSettings = $yaml->getYaml('themes' . DIRECTORY_SEPARATOR . $themeName, $themeName . '.yaml');
|
||||
|
||||
# add standard-textarea for custom css
|
||||
$themeSettings['forms']['fields']['customcss'] = [
|
||||
'type' => 'textarea',
|
||||
'label' => 'Custom CSS',
|
||||
'rows' => 10,
|
||||
'class' => 'codearea',
|
||||
'description' => 'You can overwrite the theme-css with your own css here.'
|
||||
];
|
||||
|
||||
# add image preview file
|
||||
$themeSettings['preview'] = 'http://localhost/typemill/themes/' . $themeName . '/' . $themeName . '.png';
|
||||
|
||||
return $themeSettings;
|
||||
}
|
||||
|
||||
protected function getPluginDetails()
|
||||
{
|
||||
$plugins = $this->getPlugins();
|
||||
|
||||
$pluginDetails = [];
|
||||
foreach($plugins as $pluginName)
|
||||
{
|
||||
$pluginDetails[$pluginName] = $this->getPluginDefinition($pluginName);
|
||||
}
|
||||
|
||||
return $pluginDetails;
|
||||
}
|
||||
|
||||
protected function getPlugins()
|
||||
{
|
||||
$pluginFolder = $this->c->get('settings')['rootPath'] . DIRECTORY_SEPARATOR . $this->c->get('settings')['pluginFolder'];
|
||||
$pluginFolderC = scandir($pluginFolder);
|
||||
$plugins = [];
|
||||
foreach ($pluginFolderC as $key => $plugin)
|
||||
{
|
||||
if (!in_array($plugin, [".",".."]))
|
||||
{
|
||||
if (is_dir($pluginFolder . DIRECTORY_SEPARATOR . $plugin))
|
||||
{
|
||||
$plugins[] = $plugin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $plugins;
|
||||
}
|
||||
|
||||
protected function getPluginDefinition($pluginName)
|
||||
{
|
||||
$yaml = new Yaml('\Typemill\Models\Storage');
|
||||
|
||||
$pluginSettings = $yaml->getYaml('plugins' . DIRECTORY_SEPARATOR . $pluginName, $pluginName . '.yaml');
|
||||
|
||||
return $pluginSettings;
|
||||
}
|
||||
|
||||
protected function getUserFields($userrole,$inspectorrole = NULL)
|
||||
{
|
||||
if(!$inspectorrole)
|
||||
{
|
||||
# if there is no inspector-role we assume that it is the same role like the userrole
|
||||
# for example account is always visible by the same user
|
||||
# edit user can be done by another user like admin.
|
||||
$inspectorrole = $userrole;
|
||||
}
|
||||
|
||||
$yaml = new Yaml('\Typemill\Models\Storage');
|
||||
|
||||
$userfields = $yaml->getYaml('system/typemill/settings', 'user.yaml');
|
||||
|
||||
# if a plugin with a role has been deactivated, then users with the role throw an error, so set them back to member...
|
||||
if(!$this->c->get('acl')->hasRole($userrole))
|
||||
{
|
||||
$userrole = 'member';
|
||||
}
|
||||
|
||||
# dispatch fields;
|
||||
#$fields = $this->c->dispatcher->dispatch('onUserfieldsLoaded', new OnUserfieldsLoaded($fields))->getData();
|
||||
|
||||
# only roles who can edit content need profile image and description
|
||||
if($this->c->get('acl')->isAllowed($userrole, 'mycontent', 'create'))
|
||||
{
|
||||
$newfield['image'] = ['label' => 'Profile-Image', 'type' => 'image'];
|
||||
$newfield['description'] = ['label' => 'Author-Description (Markdown)', 'type' => 'textarea'];
|
||||
|
||||
$userfields = array_slice($userfields, 0, 1, true) + $newfield + array_slice($userfields, 1, NULL, true);
|
||||
# array_splice($fields,1,0,$newfield);
|
||||
}
|
||||
|
||||
# Only admin can change userroles
|
||||
if($this->c->get('acl')->isAllowed($inspectorrole, 'userlist', 'write'))
|
||||
{
|
||||
$definedroles = $this->c->get('acl')->getRoles();
|
||||
$options = [];
|
||||
|
||||
# we need associative array to make select-field with key/value work
|
||||
foreach($definedroles as $role)
|
||||
{
|
||||
$options[$role] = $role;
|
||||
}
|
||||
|
||||
$userfields['userrole'] = ['label' => 'Role', 'type' => 'select', 'options' => $options];
|
||||
}
|
||||
|
||||
return $userfields;
|
||||
}
|
||||
|
||||
protected function recursiveValidation($formdefinitions, $input, $validator, $themeOrPlugin = false, $name = false)
|
||||
{
|
||||
# loop through form-definitions, ignores everything that is not defined in yaml
|
||||
foreach($formdefinitions as $fieldname => $fielddefinitions)
|
||||
{
|
||||
if(is_array($fielddefinitions) && $fielddefinitions['type'] == 'fieldset')
|
||||
{
|
||||
$this->recursiveValidation($fielddefinitions['fields'], $input, $validator, $themeOrPlugin, $name);
|
||||
}
|
||||
|
||||
$fieldvalue = isset($input[$fieldname]) ? $input[$fieldname] : false;
|
||||
|
||||
if($fieldvalue)
|
||||
{
|
||||
$validationresult = $validator->field($fieldname, $fieldvalue, $fielddefinitions);
|
||||
|
||||
if($validationresult === true)
|
||||
{
|
||||
# if input is valid, overwrite value in original settings
|
||||
if($themeOrPlugin)
|
||||
{
|
||||
$this->settings[$themeOrPlugin][$name][$fieldname] = $fieldvalue;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->settings[$fieldname] = $fieldvalue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->errors[$fieldname] = $validationresult[$fieldname][0];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -1,73 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Controllers;
|
||||
|
||||
use DI\Container;
|
||||
use Slim\Views\Twig;
|
||||
use Typemill\Events\OnTwigLoaded;
|
||||
|
||||
class ControllerWeb extends Controller
|
||||
{
|
||||
public function __construct(Container $container)
|
||||
{
|
||||
/*
|
||||
parent::__construct($container);
|
||||
|
||||
echo '<br>add twig';
|
||||
|
||||
$settings = $this->settings;
|
||||
|
||||
$csrf = isset($_SESSION) ? $this->c->get('csrf') : false;
|
||||
|
||||
$this->c->set('view', function() use ($settings, $csrf)
|
||||
{
|
||||
$twig = Twig::create(
|
||||
[
|
||||
# path to templates
|
||||
$settings['rootPath'] . $settings['authorFolder'],
|
||||
$settings['rootPath'] . DIRECTORY_SEPARATOR . 'themes' . DIRECTORY_SEPARATOR . $settings['theme'],
|
||||
],
|
||||
[
|
||||
# settings
|
||||
'cache' => ( isset($settings['twigcache']) && $settings['twigcache'] ) ? $settings['rootPath'] . '/cache/twig' : false,
|
||||
'debug' => isset($settings['displayErrorDetails'])
|
||||
]
|
||||
);
|
||||
|
||||
# placeholder for flash and errors, will be filled later with middleware
|
||||
$twig->getEnvironment()->addGlobal('errors', NULL);
|
||||
$twig->getEnvironment()->addGlobal('flash', NULL);
|
||||
|
||||
# add extensions
|
||||
$twig->addExtension(new \Twig\Extension\DebugExtension());
|
||||
# $twig->addExtension(new \Nquire\Extensions\TwigUserExtension());
|
||||
if($csrf)
|
||||
{
|
||||
$twig->addExtension(new \Typemill\Extensions\TwigCsrfExtension($csrf));
|
||||
}
|
||||
|
||||
return $twig;
|
||||
});
|
||||
|
||||
protected function setUrlCollection($uri)
|
||||
{
|
||||
$scheme = $uri->getScheme();
|
||||
$authority = $uri->getAuthority();
|
||||
$protocol = ($scheme ? $scheme . ':' : '') . ($authority ? '//' . $authority : '');
|
||||
|
||||
$this->currentPath = $uri->getPath();
|
||||
$this->fullBaseUrl = $protocol . $this->basePath;
|
||||
$this->fullCurrentUrl = $protocol . $this->currentPath;
|
||||
|
||||
$this->urlCollection = [
|
||||
'basePath' => $this->basePath,
|
||||
'currentPath' => $this->currentPath,
|
||||
'fullBaseUrl' => $this->fullBaseUrl,
|
||||
'fullCurrentUrl' => $this->fullCurrentUrl
|
||||
];
|
||||
}
|
||||
|
||||
$this->c->get('dispatcher')->dispatch(new OnTwigLoaded(false), 'onTwigLoaded');
|
||||
*/
|
||||
}
|
||||
}
|
497
system/typemill/Controllers/ControllerWebSystem.php
Normal file
497
system/typemill/Controllers/ControllerWebSystem.php
Normal file
@@ -0,0 +1,497 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Controllers;
|
||||
|
||||
use Typemill\Models\Yaml;
|
||||
use Typemill\Models\User;
|
||||
|
||||
class ControllerWebSystem extends ControllerData
|
||||
{
|
||||
public function showSettings($request, $response, $args)
|
||||
{
|
||||
$yaml = new Yaml('\Typemill\Models\Storage');
|
||||
$systemfields = $yaml->getYaml('system/typemill/settings', 'system.yaml');
|
||||
$translations = $this->c->get('translations');
|
||||
|
||||
# add full url for sitemap to settings
|
||||
$this->settings['sitemap'] = $this->c->get('urlinfo')['baseurl'] . '/cache/sitemap.xml';
|
||||
|
||||
return $this->c->get('view')->render($response, 'system/system.twig', [
|
||||
# 'basicauth' => $user->getBasicAuth(),
|
||||
'settings' => $this->settings,
|
||||
'mainnavi' => $this->getMainNavigation($request->getAttribute('userrole')),
|
||||
'systemnavi' => $this->getSystemNavigation($request->getAttribute('userrole')),
|
||||
'jsdata' => [
|
||||
'settings' => $this->settings,
|
||||
'system' => $systemfields,
|
||||
'labels' => $translations,
|
||||
'urlinfo' => $this->c->get('urlinfo')
|
||||
]
|
||||
#'captcha' => $this->checkIfAddCaptcha(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function showThemes($request, $response, $args)
|
||||
{
|
||||
$yaml = new Yaml('\Typemill\Models\Storage');
|
||||
$translations = $this->c->get('translations');
|
||||
$themeSettings = $this->getThemeDetails();
|
||||
|
||||
$themedata = [];
|
||||
|
||||
foreach($this->settings['themes'] as $themename => $themeinputs)
|
||||
{
|
||||
$themedata[$themename] = $themeinputs;
|
||||
$themedata[$themename]['customcss'] = $yaml->getFile('cache', $themename . '-custom.css');
|
||||
}
|
||||
|
||||
return $this->c->get('view')->render($response, 'system/themes.twig', [
|
||||
'settings' => $this->settings,
|
||||
'mainnavi' => $this->getMainNavigation($request->getAttribute('userrole')),
|
||||
'systemnavi' => $this->getSystemNavigation($request->getAttribute('userrole')),
|
||||
'jsdata' => [
|
||||
'settings' => $themedata,
|
||||
'themes' => $themeSettings,
|
||||
'labels' => $translations,
|
||||
'urlinfo' => $this->c->get('urlinfo')
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function showPlugins($request, $response, $args)
|
||||
{
|
||||
# $yaml = new Yaml('\Typemill\Models\Storage');
|
||||
$translations = $this->c->get('translations');
|
||||
$pluginSettings = $this->getPluginDetails();
|
||||
|
||||
$plugindata = [];
|
||||
|
||||
foreach($this->settings['plugins'] as $pluginname => $plugininputs)
|
||||
{
|
||||
$plugindata[$pluginname] = $plugininputs;
|
||||
}
|
||||
|
||||
return $this->c->get('view')->render($response, 'system/plugins.twig', [
|
||||
'settings' => $this->settings,
|
||||
'mainnavi' => $this->getMainNavigation($request->getAttribute('userrole')),
|
||||
'systemnavi' => $this->getSystemNavigation($request->getAttribute('userrole')),
|
||||
'jsdata' => [
|
||||
'settings' => $plugindata,
|
||||
'plugins' => $pluginSettings,
|
||||
'labels' => $translations,
|
||||
'urlinfo' => $this->c->get('urlinfo')
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function showUsers($request, $response, $args)
|
||||
{
|
||||
$translations = $this->c->get('translations');
|
||||
$user = new User();
|
||||
$usernames = $user->getAllUsers();
|
||||
$userdata = [];
|
||||
|
||||
$count = 0;
|
||||
foreach($usernames as $username)
|
||||
{
|
||||
if($count == 10) break;
|
||||
$user->setUser($username);
|
||||
$userdata[] = $user->getUserData();
|
||||
$count++;
|
||||
}
|
||||
|
||||
return $this->c->get('view')->render($response, 'system/users.twig', [
|
||||
'settings' => $this->settings,
|
||||
'mainnavi' => $this->getMainNavigation($request->getAttribute('userrole')),
|
||||
'systemnavi' => $this->getSystemNavigation($request->getAttribute('userrole')),
|
||||
'jsdata' => [
|
||||
'totalusers' => count($usernames),
|
||||
'usernames' => $usernames,
|
||||
'userdata' => $userdata,
|
||||
'userroles' => $this->c->get('acl')->getRoles(),
|
||||
'labels' => $translations,
|
||||
'urlinfo' => $this->c->get('urlinfo')
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function showAccount($request, $response, $args)
|
||||
{
|
||||
|
||||
$translations = $this->c->get('translations');
|
||||
$username = $request->getAttribute('username');
|
||||
$user = new User();
|
||||
|
||||
$user->setUser($username);
|
||||
$userdata = $user->getUserData();
|
||||
$userfields = $this->getUserFields($userdata['userrole']);
|
||||
|
||||
return $this->c->get('view')->render($response, 'system/account.twig', [
|
||||
'settings' => $this->settings,
|
||||
'mainnavi' => $this->getMainNavigation($request->getAttribute('userrole')),
|
||||
'systemnavi' => $this->getSystemNavigation($request->getAttribute('userrole')),
|
||||
'jsdata' => [
|
||||
'userdata' => $userdata,
|
||||
'userfields' => $userfields,
|
||||
'userroles' => $this->c->get('acl')->getRoles(),
|
||||
'labels' => $translations,
|
||||
'urlinfo' => $this->c->get('urlinfo')
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
public function showBlank($request, $response, $args)
|
||||
{
|
||||
$user = new User();
|
||||
$settings = $this->c->get('settings');
|
||||
$route = $request->getAttribute('route');
|
||||
$navigation = $this->getMainNavigation();
|
||||
|
||||
$content = '<h1>Hello</h1><p>I am the showBlank method from the settings controller</p><p>In most cases I have been called from a plugin. But if you see this content, then the plugin does not work or has forgotten to inject its own content.</p>';
|
||||
|
||||
return $this->render($response, 'settings/blank.twig', array(
|
||||
'settings' => $settings,
|
||||
'acl' => $this->c->acl,
|
||||
'navigation' => $navigation,
|
||||
'content' => $content,
|
||||
'route' => $route->getName()
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public function showUser($request, $response, $args)
|
||||
{
|
||||
# if user has no rights to watch userlist, then redirect to
|
||||
if(!$this->c->acl->isAllowed($_SESSION['role'], 'userlist', 'view') && $_SESSION['user'] !== $args['username'] )
|
||||
{
|
||||
return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $_SESSION['user']] ));
|
||||
}
|
||||
|
||||
# get settings
|
||||
$settings = $this->c->get('settings');
|
||||
|
||||
# get user with userdata
|
||||
$user = new User();
|
||||
$userdata = $user->getSecureUser($args['username']);
|
||||
|
||||
if(!$userdata)
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'User does not exists');
|
||||
return $response->withRedirect($this->c->router->pathFor('user.account'));
|
||||
}
|
||||
|
||||
# instantiate field-builder
|
||||
$fieldsModel = new Fields($this->c);
|
||||
|
||||
# get the field-definitions
|
||||
$fieldDefinitions = $this->getUserFields($userdata['userrole']);
|
||||
|
||||
# prepare userdata for field-builder
|
||||
$userSettings['users']['user'] = $userdata;
|
||||
|
||||
# generate the input form
|
||||
$userform = $fieldsModel->getFields($userSettings, 'users', 'user', $fieldDefinitions);
|
||||
|
||||
$route = $request->getAttribute('route');
|
||||
$navigation = $this->getMainNavigation();
|
||||
|
||||
# set navigation active
|
||||
$navigation['Users']['active'] = true;
|
||||
|
||||
if(isset($userdata['lastlogin']))
|
||||
{
|
||||
$userdata['lastlogin'] = date("d.m.Y H:i:s", $userdata['lastlogin']);
|
||||
}
|
||||
|
||||
return $this->render($response, 'settings/user.twig', array(
|
||||
'settings' => $settings,
|
||||
'acl' => $this->c->acl,
|
||||
'navigation' => $navigation,
|
||||
'usersettings' => $userSettings, // needed for image url in form, will overwrite settings for field-template
|
||||
'userform' => $userform, // field model, needed to generate frontend-field
|
||||
'userdata' => $userdata, // needed to fill form with data
|
||||
'route' => $route->getName() // needed to set link active
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
public function newUser($request, $response, $args)
|
||||
{
|
||||
$user = new User();
|
||||
$users = $user->getUsers();
|
||||
$userroles = $this->c->acl->getRoles();
|
||||
$route = $request->getAttribute('route');
|
||||
$settings = $this->c->get('settings');
|
||||
$navigation = $this->getMainNavigation();
|
||||
|
||||
# set navigation active
|
||||
$navigation['Users']['active'] = true;
|
||||
|
||||
return $this->render($response, 'settings/usernew.twig', array(
|
||||
'settings' => $settings,
|
||||
'acl' => $this->c->acl,
|
||||
'navigation' => $navigation,
|
||||
'users' => $users,
|
||||
'userrole' => $userroles,
|
||||
'route' => $route->getName()
|
||||
));
|
||||
}
|
||||
|
||||
public function createUser($request, $response, $args)
|
||||
{
|
||||
if($request->isPost())
|
||||
{
|
||||
if( $request->getattribute('csrf_result') === false )
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'The form has a timeout, please try again.');
|
||||
return $response->withRedirect($this->c->router->pathFor('user.new'));
|
||||
}
|
||||
|
||||
$params = $request->getParams();
|
||||
$user = new User();
|
||||
$validate = new Validation();
|
||||
$userroles = $this->c->acl->getRoles();
|
||||
|
||||
if($validate->newUser($params, $userroles))
|
||||
{
|
||||
$userdata = array(
|
||||
'username' => $params['username'],
|
||||
'email' => $params['email'],
|
||||
'userrole' => $params['userrole'],
|
||||
'password' => $params['password']);
|
||||
|
||||
$user->createUser($userdata);
|
||||
|
||||
$this->c->flash->addMessage('info', 'Welcome, there is a new user!');
|
||||
return $response->withRedirect($this->c->router->pathFor('user.list'));
|
||||
}
|
||||
|
||||
$this->c->flash->addMessage('error', 'Please correct your input');
|
||||
return $response->withRedirect($this->c->router->pathFor('user.new'));
|
||||
}
|
||||
}
|
||||
|
||||
public function updateUser($request, $response, $args)
|
||||
{
|
||||
|
||||
if($request->isPost())
|
||||
{
|
||||
if( $request->getattribute('csrf_result') === false )
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'The form has a timeout, please try again.');
|
||||
return $response->withRedirect($this->c->router->pathFor('user.account'));
|
||||
}
|
||||
|
||||
$params = $request->getParams();
|
||||
$userdata = $params['user'];
|
||||
$user = new User();
|
||||
$validate = new Validation();
|
||||
$userroles = $this->c->acl->getRoles();
|
||||
|
||||
$redirectRoute = ($userdata['username'] == $_SESSION['user']) ? $this->c->router->pathFor('user.account') : $this->c->router->pathFor('user.show', ['username' => $userdata['username']]);
|
||||
|
||||
# check if user is allowed to view (edit) userlist and other users
|
||||
if(!$this->c->acl->isAllowed($_SESSION['role'], 'userlist', 'write'))
|
||||
{
|
||||
# if an editor tries to update other userdata than its own
|
||||
if($_SESSION['user'] !== $userdata['username'])
|
||||
{
|
||||
return $response->withRedirect($this->c->router->pathFor('user.account'));
|
||||
}
|
||||
|
||||
# non admins cannot change their userrole, so set it to session-value
|
||||
$userdata['userrole'] = $_SESSION['role'];
|
||||
}
|
||||
|
||||
# validate standard fields for users
|
||||
if($validate->existingUser($userdata, $userroles))
|
||||
{
|
||||
# validate custom input fields and return images
|
||||
$userfields = $this->getUserFields($userdata['userrole']);
|
||||
$imageFields = $this->validateInput('users', 'user', $userdata, $validate, $userfields);
|
||||
|
||||
if(!empty($imageFields))
|
||||
{
|
||||
$images = $request->getUploadedFiles();
|
||||
|
||||
if(isset($images['user']))
|
||||
{
|
||||
# set image size
|
||||
$settings = $this->c->get('settings');
|
||||
$imageSizes = $settings['images'];
|
||||
$imageSizes['live'] = ['width' => 500, 'height' => 500];
|
||||
$settings->replace(['images' => $imageSizes]);
|
||||
$imageresult = $this->saveImages($imageFields, $userdata, $settings, $images['user']);
|
||||
|
||||
if(isset($_SESSION['slimFlash']['error']))
|
||||
{
|
||||
return $response->withRedirect($redirectRoute);
|
||||
}
|
||||
elseif(isset($imageresult['username']))
|
||||
{
|
||||
$userdata = $imageresult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# check for errors and redirect to path, if errors found
|
||||
if(isset($_SESSION['errors']))
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'Please correct the errors');
|
||||
return $response->withRedirect($redirectRoute);
|
||||
}
|
||||
|
||||
if(empty($userdata['password']) AND empty($userdata['newpassword']))
|
||||
{
|
||||
# make sure no invalid passwords go into model
|
||||
unset($userdata['password']);
|
||||
unset($userdata['newpassword']);
|
||||
|
||||
$user->updateUser($userdata);
|
||||
$this->c->flash->addMessage('info', 'Saved all changes');
|
||||
return $response->withRedirect($redirectRoute);
|
||||
}
|
||||
elseif($validate->newPassword($userdata))
|
||||
{
|
||||
$userdata['password'] = $userdata['newpassword'];
|
||||
unset($userdata['newpassword']);
|
||||
|
||||
$user->updateUser($userdata);
|
||||
$this->c->flash->addMessage('info', 'Saved all changes');
|
||||
return $response->withRedirect($redirectRoute);
|
||||
}
|
||||
}
|
||||
|
||||
# change error-array for formbuilder
|
||||
$errors = $_SESSION['errors'];
|
||||
unset($_SESSION['errors']);
|
||||
$_SESSION['errors']['user'] = $errors;#
|
||||
|
||||
$this->c->flash->addMessage('error', 'Please correct your input');
|
||||
return $response->withRedirect($redirectRoute);
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteUser($request, $response, $args)
|
||||
{
|
||||
if($request->isPost())
|
||||
{
|
||||
if( $request->getattribute('csrf_result') === false )
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'The form has a timeout, please try again.');
|
||||
return $response->withRedirect($this->c->router->pathFor('user.account'));
|
||||
}
|
||||
|
||||
$params = $request->getParams();
|
||||
$validate = new Validation();
|
||||
$user = new User();
|
||||
|
||||
# check if user is allowed to view (edit) userlist and other users
|
||||
if(!$this->c->acl->isAllowed($_SESSION['role'], 'userlist', 'write'))
|
||||
{
|
||||
# if an editor tries to delete other user than its own
|
||||
if($_SESSION['user'] !== $params['username'])
|
||||
{
|
||||
return $response->withRedirect($this->c->router->pathFor('user.account'));
|
||||
}
|
||||
}
|
||||
|
||||
if($validate->username($params['username']))
|
||||
{
|
||||
$userdata = $user->getSecureUser($params['username']);
|
||||
if(!$userdata)
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'Ups, we did not find that user');
|
||||
return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $params['username']]));
|
||||
}
|
||||
|
||||
$user->deleteUser($params['username']);
|
||||
|
||||
$this->c->dispatcher->dispatch('onUserDeleted', new OnUserDeleted($userdata));
|
||||
|
||||
# if user deleted his own account
|
||||
if($_SESSION['user'] == $params['username'])
|
||||
{
|
||||
session_destroy();
|
||||
return $response->withRedirect($this->c->router->pathFor('auth.show'));
|
||||
}
|
||||
|
||||
$this->c->flash->addMessage('info', 'Say goodbye, the user is gone!');
|
||||
return $response->withRedirect($this->c->router->pathFor('user.list'));
|
||||
}
|
||||
|
||||
$this->c->flash->addMessage('error', 'Ups, it is not a valid username');
|
||||
return $response->withRedirect($this->c->router->pathFor('user.show', ['username' => $params['username']]));
|
||||
}
|
||||
}
|
||||
|
||||
public function clearCache($request, $response, $args)
|
||||
{
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
$dir = $this->settings['basePath'] . 'cache';
|
||||
|
||||
$error = $this->writeCache->deleteCacheFiles($dir);
|
||||
if($error)
|
||||
{
|
||||
return $response->withJson(['errors' => $error], 500);
|
||||
}
|
||||
|
||||
# create a new draft structure
|
||||
$this->setFreshStructureDraft();
|
||||
|
||||
# create a new draft structure
|
||||
$this->setFreshStructureLive();
|
||||
|
||||
# create a new draft structure
|
||||
$this->setFreshNavigation();
|
||||
|
||||
# update the sitemap
|
||||
$this->updateSitemap();
|
||||
|
||||
return $response->withJson(array('errors' => false));
|
||||
}
|
||||
|
||||
|
||||
protected function saveImages($imageFields, $userInput, $userSettings, $files)
|
||||
{
|
||||
# initiate image processor with standard image sizes
|
||||
$processImages = new ProcessImage($userSettings['images']);
|
||||
|
||||
if(!$processImages->checkFolders('images'))
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'Please make sure that your media folder exists and is writable.');
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach($imageFields as $fieldName => $imageField)
|
||||
{
|
||||
if(isset($userInput[$fieldName]))
|
||||
{
|
||||
# handle single input with single file upload
|
||||
$image = $files[$fieldName];
|
||||
|
||||
if($image->getError() === UPLOAD_ERR_OK)
|
||||
{
|
||||
# not the most elegant, but createImage expects a base64-encoded string.
|
||||
$imageContent = $image->getStream()->getContents();
|
||||
$imageData = base64_encode($imageContent);
|
||||
$imageSrc = 'data: ' . $image->getClientMediaType() . ';base64,' . $imageData;
|
||||
|
||||
if($processImages->createImage($imageSrc, $image->getClientFilename(), $userSettings['images'], $overwrite = NULL))
|
||||
{
|
||||
# returns image path to media library
|
||||
$userInput[$fieldName] = $processImages->publishImage();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $userInput;
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
@@ -6,19 +6,11 @@ use Twig\Extension\AbstractExtension;
|
||||
|
||||
class TwigUrlExtension extends AbstractExtension
|
||||
{
|
||||
protected $uri;
|
||||
protected $basepath;
|
||||
protected $scheme;
|
||||
protected $authority;
|
||||
protected $protocol;
|
||||
protected $urlinfo;
|
||||
|
||||
public function __construct($uri, $basepath)
|
||||
public function __construct($urlinfo)
|
||||
{
|
||||
$this->uri = $uri;
|
||||
$this->basepath = $basepath;
|
||||
$this->scheme = $uri->getScheme();
|
||||
$this->authority = $uri->getAuthority();
|
||||
$this->protocol = ($this->scheme ? $this->scheme . ':' : '') . ($this->authority ? '//' . $this->authority : '');
|
||||
$this->urlinfo = $urlinfo;
|
||||
}
|
||||
|
||||
public function getFunctions()
|
||||
@@ -32,16 +24,16 @@ class TwigUrlExtension extends AbstractExtension
|
||||
|
||||
public function baseUrl()
|
||||
{
|
||||
return $this->protocol . $this->basepath;
|
||||
return $this->urlinfo['baseurl'];
|
||||
}
|
||||
|
||||
public function currentUrl()
|
||||
{
|
||||
return $this->protocol . $this->uri->getPath();
|
||||
return $this->urlinfo['currenturl'];
|
||||
}
|
||||
|
||||
public function currentPath()
|
||||
{
|
||||
return $this->uri->getPath();
|
||||
return $this->urlinfo['route'];
|
||||
}
|
||||
}
|
@@ -11,8 +11,6 @@ class JsonBodyParser implements MiddlewareInterface
|
||||
{
|
||||
public function process(Request $request, RequestHandler $handler) :response
|
||||
{
|
||||
#echo '<br> JSON Body parser';
|
||||
|
||||
$contentType = $request->getHeaderLine('Content-Type');
|
||||
|
||||
if (strstr($contentType, 'application/json'))
|
||||
|
@@ -7,6 +7,7 @@ use Slim\Routing\RouteParser;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
|
||||
use Slim\Psr7\Response;
|
||||
use Typemill\Models\User;
|
||||
|
||||
class RedirectIfUnauthenticated implements MiddlewareInterface
|
||||
{
|
||||
@@ -23,17 +24,28 @@ class RedirectIfUnauthenticated implements MiddlewareInterface
|
||||
)
|
||||
? true : false;
|
||||
|
||||
if(!$authenticated)
|
||||
if($authenticated)
|
||||
{
|
||||
# this executes only middleware code and not code from route
|
||||
$response = new Response();
|
||||
|
||||
return $response->withHeader('Location', $this->router->urlFor('auth.show'))->withStatus(302);
|
||||
# here we have to load userdata and pass them through request or response
|
||||
$user = new User();
|
||||
|
||||
if($user->setUser($_SESSION['username']))
|
||||
{
|
||||
$userdata = $user->getUserData();
|
||||
|
||||
$request = $request->withAttribute('username', $userdata['username']);
|
||||
$request = $request->withAttribute('userrole', $userdata['userrole']);
|
||||
|
||||
# this executes code from routes first and then executes middleware
|
||||
$response = $handler->handle($request);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
# this executes code from routes first and then executes middleware
|
||||
$response = $handler->handle($request);
|
||||
|
||||
return $response;
|
||||
# this executes only middleware code and not code from route
|
||||
$response = new Response();
|
||||
|
||||
return $response->withHeader('Location', $this->router->urlFor('auth.show'))->withStatus(302);
|
||||
}
|
||||
}
|
@@ -6,6 +6,7 @@ use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
|
||||
use Slim\Routing\RouteContext;
|
||||
use Slim\Psr7\Response;
|
||||
use Typemill\Models\User;
|
||||
|
||||
class RestrictApiAccess
|
||||
{
|
||||
@@ -14,38 +15,52 @@ class RestrictApiAccess
|
||||
$routeContext = RouteContext::fromRequest($request);
|
||||
$baseURL = $routeContext->getBasePath();
|
||||
|
||||
if ($request->hasHeader('X-Session-Auth')) {
|
||||
|
||||
# check if it a session based authentication
|
||||
if ($request->hasHeader('X-Session-Auth'))
|
||||
{
|
||||
session_start();
|
||||
|
||||
$authenticated = (
|
||||
(isset($_SESSION['username'])) &&
|
||||
(isset($_SESSION['userrole'])) &&
|
||||
(isset($_SESSION['login']))
|
||||
)
|
||||
? true : false;
|
||||
|
||||
if($authenticated)
|
||||
{
|
||||
$response = $handler->handle($request);
|
||||
# here we have to load userdata and pass them through request or response
|
||||
$user = new User();
|
||||
|
||||
return $response;
|
||||
if($user->setUser($_SESSION['username']))
|
||||
{
|
||||
$userdata = $user->getUserData();
|
||||
|
||||
$request = $request->withAttribute('username', $userdata['username']);
|
||||
$request = $request->withAttribute('userrole', $userdata['userrole']);
|
||||
|
||||
$response = $handler->handle($request);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# elseif ($request->getHeaderLine('X-Requested-With') === 'XMLHttpRequest') {
|
||||
# advantage: all xhr-calls to the api will be session based
|
||||
# no direct calls from javascript possible
|
||||
# only from server
|
||||
# if you use this, then all xhr-calls need a session.
|
||||
# no direct xhr calls without session are possible
|
||||
# might increase security, but can have unwanted cases e.g. when you
|
||||
# want to provide public api accessible for all by javascript (do you ever want??)
|
||||
# }
|
||||
|
||||
|
||||
# this is for api-key authentication
|
||||
$user = isset($_SERVER['PHP_AUTH_USER']) ? $_SERVER['PHP_AUTH_USER'] : false;
|
||||
$apikey = isset($_SERVER['PHP_AUTH_PW']) ? $_SERVER['PHP_AUTH_PW'] : false;
|
||||
|
||||
if($user && $apikey)
|
||||
{
|
||||
# get user
|
||||
# get user with username
|
||||
# or get user with apikey
|
||||
|
||||
# check if user has tmpApiKey
|
||||
# check if user has permanentApiKey
|
||||
# check if user has tmpApiKey
|
||||
|
@@ -75,10 +75,17 @@ class Storage
|
||||
{
|
||||
$this->error = "Could not open and read the file $filename in folder $folder.";
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
$writefile = fwrite($openfile, $data);
|
||||
if(!$writefile)
|
||||
{
|
||||
$this->error = "Could not write to the file $filename in folder $folder.";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
fwrite($openfile, $data);
|
||||
fclose($openfile);
|
||||
|
||||
return true;
|
||||
@@ -90,6 +97,7 @@ class Storage
|
||||
{
|
||||
# ??? should be with basepath???
|
||||
$fileContent = file_get_contents($folder . DIRECTORY_SEPARATOR . $filename);
|
||||
|
||||
return $fileContent;
|
||||
}
|
||||
|
||||
|
@@ -18,14 +18,15 @@ class User
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->userDir = getcwd() . '/system/settings/users';
|
||||
$this->userDir = getcwd() . '/settings/users';
|
||||
$this->yaml = new Yaml('\Typemill\Models\Storage');
|
||||
}
|
||||
|
||||
public function setUser(string $username)
|
||||
{
|
||||
if(!$this->user)
|
||||
{
|
||||
# if no user is set or requested user has a different username
|
||||
# if(!$this->user OR ($this->user['username'] != $username))
|
||||
# {
|
||||
$this->user = $this->yaml->getYaml('settings/users', $username . '.yaml');
|
||||
|
||||
if(!$this->user)
|
||||
@@ -40,7 +41,7 @@ class User
|
||||
|
||||
# delete password from public userdata
|
||||
unset($this->user['password']);
|
||||
}
|
||||
# }
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -62,22 +63,22 @@ class User
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getError()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
public function getUserData()
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function getError()
|
||||
{
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
public function getAllUsers()
|
||||
{
|
||||
# check if users directory exists
|
||||
if(!is_dir($this->userDir))
|
||||
{
|
||||
$this->error = 'Directory $this->userDir does not exist.';
|
||||
$this->error = "Directory $this->userDir does not exist.";
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -116,6 +117,40 @@ class User
|
||||
return false;
|
||||
}
|
||||
|
||||
public function setValue($key, $value)
|
||||
{
|
||||
$this->user[$key] = $value;
|
||||
}
|
||||
|
||||
public function unsetValue($key)
|
||||
{
|
||||
unset($this->user[$key]);
|
||||
}
|
||||
|
||||
public function updateUser()
|
||||
{
|
||||
if($this->yaml->updateYaml('settings/users', $this->user['username'] . '.yaml', $this->user))
|
||||
{
|
||||
$this->deleteUserIndex();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->error = $this->yaml->getError();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public function unsetFromUser(array $keys)
|
||||
{
|
||||
if(empty($keys) OR !$this->user)
|
||||
@@ -138,7 +173,7 @@ class User
|
||||
return true;
|
||||
}
|
||||
|
||||
public function updateUser()
|
||||
public function updateUserOld()
|
||||
{
|
||||
# add password back to userdata before you store/update user
|
||||
if($this->password)
|
||||
@@ -156,6 +191,7 @@ class User
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function updateUserWithInput(array $input)
|
||||
{
|
||||
if(!isset($input['username']) OR !$this->user)
|
||||
@@ -273,15 +309,15 @@ class User
|
||||
# accepts email with or without asterix and returns userdata
|
||||
public function findUsersByEmail($email)
|
||||
{
|
||||
$usernames = [];
|
||||
|
||||
$usernames = [];
|
||||
|
||||
# Make sure that we scan only the first 11 files even if there are some thousand users.
|
||||
if ($dh = opendir($this->userDir))
|
||||
{
|
||||
$count = 1;
|
||||
$count = 0;
|
||||
$exclude = array('..', '.', '.logins', 'tmuserindex-mail.txt', 'tmuserindex-role.txt');
|
||||
|
||||
while ( ($userfile = readdir($dh)) !== false && $count <= 11 )
|
||||
while ( ($userfile = readdir($dh)) !== false && $count <= 10 )
|
||||
{
|
||||
if(in_array($userfile, $exclude)){ continue; }
|
||||
|
||||
@@ -292,35 +328,46 @@ class User
|
||||
closedir($dh);
|
||||
}
|
||||
|
||||
$countusers = count($usernames);
|
||||
|
||||
if($countusers == 0)
|
||||
if(count($usernames) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
# use a simple dirty search if there are less than 10 users (only in use for new user registrations)
|
||||
if($countusers <= 10)
|
||||
elseif(count($usernames) <= 9)
|
||||
{
|
||||
foreach($usernames as $username)
|
||||
# perform a simple search because we have less than 10 registered users
|
||||
return $this->searchEmailSimple($usernames,$email);
|
||||
}
|
||||
else
|
||||
{
|
||||
# perform search in an index for many users
|
||||
return $this->searchEmailByIndex($email);
|
||||
}
|
||||
}
|
||||
|
||||
private function searchEmailSimple($usernames, $email)
|
||||
{
|
||||
foreach($usernames as $username)
|
||||
{
|
||||
$this->setUser($username);
|
||||
$user = $this->getUserData();
|
||||
|
||||
if($user['email'] == $email)
|
||||
{
|
||||
$userdata = $this->getSecureUser($username);
|
||||
|
||||
if($userdata['email'] == $email)
|
||||
{
|
||||
return $userdata;
|
||||
}
|
||||
return [$username];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function searchEmailByIndex($email)
|
||||
{
|
||||
# if there are more than 10 users, search with an index
|
||||
$usermails = $this->getUserMailIndex();
|
||||
$usermails = $this->getUserMailIndex();
|
||||
$usernames = [];
|
||||
|
||||
# search with starting asterix, ending asterix or without asterix
|
||||
if($email[0] == '*')
|
||||
{
|
||||
$userdata = [];
|
||||
$search = substr($email, 1);
|
||||
$length = strlen($search);
|
||||
|
||||
@@ -328,17 +375,12 @@ class User
|
||||
{
|
||||
if(substr($usermail, -$length) == $search)
|
||||
{
|
||||
$userdata[] = $username;
|
||||
$usernames[] = $username;
|
||||
}
|
||||
}
|
||||
|
||||
$userdata = empty($userdata) ? false : $userdata;
|
||||
|
||||
return $userdata;
|
||||
}
|
||||
elseif(substr($email, -1) == '*')
|
||||
{
|
||||
$userdata = [];
|
||||
$search = substr($email, 0, -1);
|
||||
$length = strlen($search);
|
||||
|
||||
@@ -346,21 +388,21 @@ class User
|
||||
{
|
||||
if(substr($usermail, 0, $length) == $search)
|
||||
{
|
||||
$userdata[] = $username;
|
||||
$usernames[] = $username;
|
||||
}
|
||||
}
|
||||
|
||||
$userdata = empty($userdata) ? false : $userdata;
|
||||
|
||||
return $userdata;
|
||||
}
|
||||
elseif(isset($usermails[$email]))
|
||||
{
|
||||
$userdata[] = $usermails[$email];
|
||||
return $userdata;
|
||||
$usernames[] = $usermails[$email];
|
||||
}
|
||||
|
||||
return false;
|
||||
if(empty($usernames))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return $usernames;
|
||||
}
|
||||
|
||||
public function getUserMailIndex()
|
||||
@@ -376,12 +418,13 @@ class User
|
||||
}
|
||||
}
|
||||
|
||||
$usernames = $this->getUsers();
|
||||
$usernames = $this->getAllUsers();
|
||||
$usermailindex = [];
|
||||
|
||||
foreach($usernames as $username)
|
||||
{
|
||||
$userdata = $this->getSecureUser($username);
|
||||
$this->setUser($username);
|
||||
$userdata = $this->getUserData();
|
||||
|
||||
$usermailindex[$userdata['email']] = $username;
|
||||
}
|
||||
@@ -391,42 +434,8 @@ class User
|
||||
return $usermailindex;
|
||||
}
|
||||
|
||||
# accepts email with or without asterix and returns usernames
|
||||
public function findUsersByRole($role)
|
||||
{
|
||||
|
||||
/*
|
||||
# get all user files
|
||||
$usernames = $this->getUsers();
|
||||
|
||||
$countusers = count($usernames);
|
||||
|
||||
if($countusers == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
# use a simple dirty search if there are less than 10 users (not in use right now)
|
||||
if($countusers <= 4)
|
||||
{
|
||||
$userdata = [];
|
||||
foreach($usernames as $key => $username)
|
||||
{
|
||||
$userdetails = $this->getSecureUser($username);
|
||||
|
||||
if($userdetails['userrole'] == $role)
|
||||
{
|
||||
$userdata[] = $userdetails;
|
||||
}
|
||||
}
|
||||
if(empty($userdata))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return $userdata;
|
||||
}
|
||||
*/
|
||||
$userroles = $this->getUserRoleIndex();
|
||||
|
||||
if(isset($userroles[$role]))
|
||||
@@ -449,7 +458,7 @@ class User
|
||||
}
|
||||
}
|
||||
|
||||
$usernames = $this->getUsers();
|
||||
$usernames = $this->getAllUsers();
|
||||
$userroleindex = [];
|
||||
|
||||
foreach($usernames as $username)
|
||||
|
@@ -47,7 +47,8 @@ class Validation
|
||||
# checks if email is available if userdata is updated
|
||||
Validator::addRule('emailChanged', function($field, $value, array $params, array $fields) use ($user)
|
||||
{
|
||||
$userdata = $user->getSecureUser($fields['username']);
|
||||
$user->setUserWithPassword($fields['username']);
|
||||
$userdata = $user->getUserData();
|
||||
if($userdata['email'] == $value){ return true; } # user has not updated his email
|
||||
|
||||
$email = trim($value);
|
||||
@@ -58,8 +59,8 @@ class Validation
|
||||
# checks if username is free when create new user
|
||||
Validator::addRule('userAvailable', function($field, $value, array $params, array $fields) use ($user)
|
||||
{
|
||||
$activeUser = $user->getUser($value);
|
||||
$inactiveUser = $user->getUser("_" . $value);
|
||||
$activeUser = $user->setUser($value);
|
||||
$inactiveUser = $user->setUser("_" . $value);
|
||||
if($activeUser OR $inactiveUser){ return false; }
|
||||
return true;
|
||||
}, 'taken');
|
||||
@@ -67,8 +68,7 @@ class Validation
|
||||
# checks if user exists when userdata is updated
|
||||
Validator::addRule('userExists', function($field, $value, array $params, array $fields) use ($user)
|
||||
{
|
||||
$userdata = $user->getUser($value);
|
||||
if($userdata){ return true; }
|
||||
if($user->setUser($value)){ return true; }
|
||||
return false;
|
||||
}, 'does not exist');
|
||||
|
||||
@@ -108,8 +108,14 @@ class Validation
|
||||
|
||||
Validator::addRule('checkPassword', function($field, $value, array $params, array $fields) use ($user)
|
||||
{
|
||||
$userdata = $user->getUser($fields['username']);
|
||||
if($userdata && password_verify($value, $userdata['password'])){ return true; }
|
||||
if($user->setUserWithPassword($fields['username']))
|
||||
{
|
||||
$userdata = $user->getUserData();
|
||||
if(password_verify($value, $userdata['password']))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}, 'wrong password');
|
||||
|
||||
@@ -249,7 +255,7 @@ class Validation
|
||||
*
|
||||
* @param array $params with form data.
|
||||
* @return obj $v the validation object passed to a result method.
|
||||
*/
|
||||
*
|
||||
|
||||
public function newPassword(array $params)
|
||||
{
|
||||
@@ -260,6 +266,29 @@ class Validation
|
||||
|
||||
return $this->validationResult($v);
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* validation for changing the password api case
|
||||
*
|
||||
* @param array $params with form data.
|
||||
* @return obj $v the validation object passed to a result method.
|
||||
*/
|
||||
|
||||
public function newPassword(array $params)
|
||||
{
|
||||
$v = new Validator($params);
|
||||
$v->rule('required', ['password', 'newpassword']);
|
||||
$v->rule('lengthBetween', 'newpassword', 5, 20);
|
||||
$v->rule('checkPassword', 'password')->message("Password is wrong");
|
||||
|
||||
if($v->validate())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return $v->errors();
|
||||
}
|
||||
|
||||
/**
|
||||
* validation for password recovery
|
||||
@@ -283,7 +312,7 @@ class Validation
|
||||
*
|
||||
* @param array $params with form data.
|
||||
* @return obj $v the validation object passed to a result method.
|
||||
*/
|
||||
*
|
||||
|
||||
public function settings(array $params, array $copyright, array $formats, $name = false)
|
||||
{
|
||||
@@ -312,6 +341,7 @@ class Validation
|
||||
|
||||
return $this->validationResult($v, $name);
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* validation for content editor
|
||||
@@ -442,11 +472,11 @@ class Validation
|
||||
* @return obj $v the validation object passed to a result method.
|
||||
*/
|
||||
|
||||
public function objectField($fieldName, $fieldValue, $objectName, $fieldDefinitions, $skiprequired = NULL)
|
||||
public function field($fieldName, $fieldValue, $fieldDefinitions)
|
||||
{
|
||||
$v = new Validator(array($fieldName => $fieldValue));
|
||||
|
||||
if(isset($fieldDefinitions['required']) && !$skiprequired)
|
||||
if(isset($fieldDefinitions['required']))
|
||||
{
|
||||
$v->rule('required', $fieldName);
|
||||
}
|
||||
@@ -548,7 +578,15 @@ class Validation
|
||||
$v->rule('lengthMax', $fieldName, 1000);
|
||||
$v->rule('regex', $fieldName, '/^[\pL0-9_ \-]*$/u');
|
||||
}
|
||||
return $this->validationResult($v, $objectName);
|
||||
|
||||
if(!$v->validate())
|
||||
{
|
||||
return $v->errors();
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
return $this->validationResult($v);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -6,6 +6,29 @@ 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'];
|
||||
|
@@ -6,11 +6,11 @@ use Typemill\Models\Yaml;
|
||||
|
||||
class Translations
|
||||
{
|
||||
public static function loadTranslations($settings)
|
||||
public static function loadTranslations($settings, $route)
|
||||
{
|
||||
$yaml = new Yaml($settings['storage']);
|
||||
|
||||
$urlsegments = explode('/',trim($settings['routepath'],'/'));
|
||||
$urlsegments = explode('/',trim($route,'/'));
|
||||
|
||||
$environment = 'frontend';
|
||||
if( ($urlsegments[0] === 'tm' OR $urlsegments[0] === 'setup') )
|
||||
|
@@ -6,7 +6,7 @@
|
||||
|
||||
<div class="flex justify-end">
|
||||
|
||||
<div class="bg-rose-600 text-white min-h-screen w-1/2 flex justify-center items-center">
|
||||
<div class="bg-teal-600 text-white min-h-screen w-1/2 flex justify-center items-center">
|
||||
<div class="max-w-md content-center">
|
||||
|
||||
<h1 class="text-6xl py-5">Login</h1>
|
||||
|
@@ -15,3 +15,15 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
}
|
||||
.initial-enter-active,
|
||||
.initial-leave-active {
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
.initial-enter-from,
|
||||
.initial-leave-to {
|
||||
opacity: 0;
|
||||
}
|
@@ -616,6 +616,10 @@ video {
|
||||
}
|
||||
}
|
||||
|
||||
.pointer-events-none {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.static {
|
||||
position: static;
|
||||
}
|
||||
@@ -628,6 +632,39 @@ video {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.inset-y-0 {
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
.left-0 {
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.right-0 {
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.top-0 {
|
||||
top: 0px;
|
||||
}
|
||||
|
||||
.top-1 {
|
||||
top: 0.25rem;
|
||||
}
|
||||
|
||||
.right-1 {
|
||||
right: 0.25rem;
|
||||
}
|
||||
|
||||
.bottom-0 {
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
.top-3 {
|
||||
top: 0.75rem;
|
||||
}
|
||||
|
||||
.m-0 {
|
||||
margin: 0px;
|
||||
}
|
||||
@@ -641,10 +678,59 @@ video {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.my-8 {
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.my-5 {
|
||||
margin-top: 1.25rem;
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.my-1 {
|
||||
margin-top: 0.25rem;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.my-4 {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.my-3 {
|
||||
margin-top: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.mt-6 {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.mb-3 {
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.mt-5 {
|
||||
margin-top: 1.25rem;
|
||||
}
|
||||
|
||||
.mb-5 {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.mb-1 {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.ml-2 {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.mb-2 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.mt-2 {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
@@ -653,8 +739,16 @@ video {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.mt-5 {
|
||||
margin-top: 1.25rem;
|
||||
.mb-4 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.mt-auto {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.mt-8 {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.mt-7 {
|
||||
@@ -669,10 +763,6 @@ video {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.mb-1 {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
@@ -689,6 +779,10 @@ video {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.inline-flex {
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.table {
|
||||
display: table;
|
||||
}
|
||||
@@ -697,6 +791,26 @@ video {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.h-8 {
|
||||
height: 2rem;
|
||||
}
|
||||
|
||||
.h-12 {
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
.h-6 {
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.h-5 {
|
||||
height: 1.25rem;
|
||||
}
|
||||
|
||||
.h-64 {
|
||||
height: 16rem;
|
||||
}
|
||||
|
||||
.min-h-screen {
|
||||
min-height: 100vh;
|
||||
}
|
||||
@@ -709,22 +823,38 @@ video {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.w-1\/5 {
|
||||
width: 20%;
|
||||
.w-half {
|
||||
width: 48%;
|
||||
}
|
||||
|
||||
.w-4\/5 {
|
||||
width: 80%;
|
||||
.w-6 {
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
.w-1\/4 {
|
||||
width: 25%;
|
||||
.w-5 {
|
||||
width: 1.25rem;
|
||||
}
|
||||
|
||||
.w-2\/3 {
|
||||
width: 66.666667%;
|
||||
}
|
||||
|
||||
.w-1\/3 {
|
||||
width: 33.333333%;
|
||||
}
|
||||
|
||||
.w-10 {
|
||||
width: 2.5rem;
|
||||
}
|
||||
|
||||
.w-3\/4 {
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
.w-1\/4 {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.max-w-md {
|
||||
max-width: 28rem;
|
||||
}
|
||||
@@ -733,14 +863,6 @@ video {
|
||||
max-width: 72rem;
|
||||
}
|
||||
|
||||
.max-w-xl {
|
||||
max-width: 36rem;
|
||||
}
|
||||
|
||||
.max-w-sm {
|
||||
max-width: 24rem;
|
||||
}
|
||||
|
||||
.transform {
|
||||
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
|
||||
}
|
||||
@@ -749,10 +871,22 @@ video {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.flex-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.flex-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.content-center {
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.items-start {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.items-center {
|
||||
align-items: center;
|
||||
}
|
||||
@@ -769,6 +903,24 @@ video {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.justify-around {
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.overflow-auto {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.overflow-hidden {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.rounded {
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
@@ -781,10 +933,18 @@ video {
|
||||
border-width: 0px;
|
||||
}
|
||||
|
||||
.border-2 {
|
||||
border-width: 2px;
|
||||
}
|
||||
|
||||
.border-b-2 {
|
||||
border-bottom-width: 2px;
|
||||
}
|
||||
|
||||
.border-b-4 {
|
||||
border-bottom-width: 4px;
|
||||
}
|
||||
|
||||
.border-l-2 {
|
||||
border-left-width: 2px;
|
||||
}
|
||||
@@ -793,10 +953,6 @@ video {
|
||||
border-right-width: 2px;
|
||||
}
|
||||
|
||||
.border-b-4 {
|
||||
border-bottom-width: 4px;
|
||||
}
|
||||
|
||||
.border-l-4 {
|
||||
border-left-width: 4px;
|
||||
}
|
||||
@@ -815,6 +971,16 @@ video {
|
||||
border-color: rgb(231 229 228 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.border-red-500 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(239 68 68 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.border-stone-300 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(214 211 209 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.border-stone-700 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(68 64 60 / var(--tw-border-opacity));
|
||||
@@ -825,9 +991,19 @@ video {
|
||||
border-color: rgb(245 245 244 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.border-slate-100 {
|
||||
.border-stone-50 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(241 245 249 / var(--tw-border-opacity));
|
||||
border-color: rgb(250 250 249 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.border-white {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(255 255 255 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.border-cyan-500 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(6 182 212 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.border-slate-200 {
|
||||
@@ -835,9 +1011,9 @@ video {
|
||||
border-color: rgb(226 232 240 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.bg-rose-600 {
|
||||
.bg-teal-600 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(225 29 72 / var(--tw-bg-opacity));
|
||||
background-color: rgb(13 148 136 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-white {
|
||||
@@ -845,19 +1021,29 @@ video {
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-stone-100 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(245 245 244 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-stone-700 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(68 64 60 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-stone-50 {
|
||||
.bg-teal-500 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(250 250 249 / var(--tw-bg-opacity));
|
||||
background-color: rgb(20 184 166 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-rose-500 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(244 63 94 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-stone-100 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(245 245 244 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-red-100 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(254 226 226 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-stone-200 {
|
||||
@@ -865,28 +1051,35 @@ video {
|
||||
background-color: rgb(231 229 228 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-stone-400 {
|
||||
.bg-stone-50 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(168 162 158 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-stone-300 {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(214 211 209 / var(--tw-bg-opacity));
|
||||
background-color: rgb(250 250 249 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.bg-clip-padding {
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
.p-3 {
|
||||
padding: 0.75rem;
|
||||
.bg-center {
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.p-4 {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.p-3 {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.p-8 {
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.p-1 {
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.p-2 {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
@@ -916,11 +1109,49 @@ video {
|
||||
padding-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.px-2 {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.py-2 {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.px-4 {
|
||||
padding-left: 1rem;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.pl-3 {
|
||||
padding-left: 0.75rem;
|
||||
}
|
||||
|
||||
.pl-10 {
|
||||
padding-left: 2.5rem;
|
||||
}
|
||||
|
||||
.pr-2 {
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.pr-1 {
|
||||
padding-right: 0.25rem;
|
||||
}
|
||||
|
||||
.pr-10 {
|
||||
padding-right: 2.5rem;
|
||||
}
|
||||
|
||||
.pr-3 {
|
||||
padding-right: 0.75rem;
|
||||
}
|
||||
|
||||
.pl-8 {
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
.pt-4 {
|
||||
padding-top: 1rem;
|
||||
}
|
||||
@@ -929,12 +1160,8 @@ video {
|
||||
padding-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.pb-2 {
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.pb-1 {
|
||||
padding-bottom: 0.25rem;
|
||||
.text-right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.text-6xl {
|
||||
@@ -952,11 +1179,31 @@ video {
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
.text-lg {
|
||||
font-size: 1.125rem;
|
||||
line-height: 1.75rem;
|
||||
}
|
||||
|
||||
.text-xl {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.75rem;
|
||||
}
|
||||
|
||||
.text-sm {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
}
|
||||
|
||||
.text-2xl {
|
||||
font-size: 1.5rem;
|
||||
line-height: 2rem;
|
||||
}
|
||||
|
||||
.text-3xl {
|
||||
font-size: 1.875rem;
|
||||
line-height: 2.25rem;
|
||||
}
|
||||
|
||||
.font-normal {
|
||||
font-weight: 400;
|
||||
}
|
||||
@@ -1000,17 +1247,51 @@ video {
|
||||
color: rgb(225 29 72 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-teal-500 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(20 184 166 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-red-500 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(239 68 68 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-gray-500 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(107 114 128 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-teal-600 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(13 148 136 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.underline {
|
||||
-webkit-text-decoration-line: underline;
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.no-underline {
|
||||
-webkit-text-decoration-line: none;
|
||||
text-decoration-line: none;
|
||||
}
|
||||
|
||||
.shadow {
|
||||
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
|
||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||
}
|
||||
|
||||
.outline {
|
||||
outline-style: solid;
|
||||
}
|
||||
|
||||
.blur {
|
||||
--tw-blur: blur(8px);
|
||||
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
||||
}
|
||||
|
||||
.drop-shadow-md {
|
||||
--tw-drop-shadow: drop-shadow(0 4px 3px rgb(0 0 0 / 0.07)) drop-shadow(0 2px 2px rgb(0 0 0 / 0.06));
|
||||
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
||||
@@ -1044,23 +1325,26 @@ video {
|
||||
transition-duration: 150ms;
|
||||
}
|
||||
|
||||
.duration-100 {
|
||||
transition-duration: 100ms;
|
||||
}
|
||||
|
||||
.ease-in-out {
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.hover\:border-b-4:hover {
|
||||
border-bottom-width: 4px;
|
||||
}
|
||||
|
||||
.hover\:border-stone-700:hover {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(68 64 60 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.hover\:border-cyan-300:hover {
|
||||
.hover\:border-teal-500:hover {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(103 232 249 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.hover\:border-cyan-500:hover {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(6 182 212 / var(--tw-border-opacity));
|
||||
border-color: rgb(20 184 166 / var(--tw-border-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-gray-200:hover {
|
||||
@@ -1068,9 +1352,24 @@ video {
|
||||
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-stone-700:hover {
|
||||
.hover\:bg-stone-900:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(68 64 60 / var(--tw-bg-opacity));
|
||||
background-color: rgb(28 25 23 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-teal-600:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(13 148 136 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-stone-200:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(231 229 228 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-stone-100:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(245 245 244 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:bg-stone-50:hover {
|
||||
@@ -1078,9 +1377,9 @@ video {
|
||||
background-color: rgb(250 250 249 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.hover\:text-white:hover {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
.hover\:underline:hover {
|
||||
-webkit-text-decoration-line: underline;
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.focus\:border-blue-600:focus {
|
||||
@@ -1093,11 +1392,6 @@ video {
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.focus\:bg-stone-700:focus {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(68 64 60 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.focus\:bg-stone-50:focus {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(250 250 249 / var(--tw-bg-opacity));
|
||||
@@ -1108,11 +1402,6 @@ video {
|
||||
color: rgb(55 65 81 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.focus\:text-white:focus {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.focus\:outline-none:focus {
|
||||
outline: 2px solid transparent;
|
||||
outline-offset: 2px;
|
||||
@@ -1124,17 +1413,14 @@ video {
|
||||
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
|
||||
}
|
||||
|
||||
.active\:bg-stone-700:active {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(68 64 60 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.active\:bg-stone-50:active {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(250 250 249 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
.active\:text-white:active {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.dark\:text-gray-400 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(156 163 175 / var(--tw-text-opacity));
|
||||
}
|
||||
}
|
449
system/typemill/author/js/codejar.js
Normal file
449
system/typemill/author/js/codejar.js
Normal file
@@ -0,0 +1,449 @@
|
||||
const globalWindow = window;
|
||||
export function CodeJar(editor, highlight, opt = {}) {
|
||||
const options = Object.assign({ tab: '\t', indentOn: /[({\[]$/, moveToNewLine: /^[)}\]]/, spellcheck: false, catchTab: true, preserveIdent: true, addClosing: true, history: true, window: globalWindow }, opt);
|
||||
const window = options.window;
|
||||
const document = window.document;
|
||||
let listeners = [];
|
||||
let history = [];
|
||||
let at = -1;
|
||||
let focus = false;
|
||||
let callback;
|
||||
let prev; // code content prior keydown event
|
||||
editor.setAttribute('contenteditable', 'plaintext-only');
|
||||
editor.setAttribute('spellcheck', options.spellcheck ? 'true' : 'false');
|
||||
editor.style.outline = 'none';
|
||||
editor.style.overflowWrap = 'break-word';
|
||||
editor.style.overflowY = 'auto';
|
||||
editor.style.whiteSpace = 'pre-wrap';
|
||||
let isLegacy = false; // true if plaintext-only is not supported
|
||||
highlight(editor);
|
||||
if (editor.contentEditable !== 'plaintext-only')
|
||||
isLegacy = true;
|
||||
if (isLegacy)
|
||||
editor.setAttribute('contenteditable', 'true');
|
||||
const debounceHighlight = debounce(() => {
|
||||
const pos = save();
|
||||
highlight(editor, pos);
|
||||
restore(pos);
|
||||
}, 30);
|
||||
let recording = false;
|
||||
const shouldRecord = (event) => {
|
||||
return !isUndo(event) && !isRedo(event)
|
||||
&& event.key !== 'Meta'
|
||||
&& event.key !== 'Control'
|
||||
&& event.key !== 'Alt'
|
||||
&& !event.key.startsWith('Arrow');
|
||||
};
|
||||
const debounceRecordHistory = debounce((event) => {
|
||||
if (shouldRecord(event)) {
|
||||
recordHistory();
|
||||
recording = false;
|
||||
}
|
||||
}, 300);
|
||||
const on = (type, fn) => {
|
||||
listeners.push([type, fn]);
|
||||
editor.addEventListener(type, fn);
|
||||
};
|
||||
on('keydown', event => {
|
||||
if (event.defaultPrevented)
|
||||
return;
|
||||
prev = toString();
|
||||
if (options.preserveIdent)
|
||||
handleNewLine(event);
|
||||
else
|
||||
legacyNewLineFix(event);
|
||||
if (options.catchTab)
|
||||
handleTabCharacters(event);
|
||||
if (options.addClosing)
|
||||
handleSelfClosingCharacters(event);
|
||||
if (options.history) {
|
||||
handleUndoRedo(event);
|
||||
if (shouldRecord(event) && !recording) {
|
||||
recordHistory();
|
||||
recording = true;
|
||||
}
|
||||
}
|
||||
if (isLegacy)
|
||||
restore(save());
|
||||
});
|
||||
on('keyup', event => {
|
||||
if (event.defaultPrevented)
|
||||
return;
|
||||
if (event.isComposing)
|
||||
return;
|
||||
if (prev !== toString())
|
||||
debounceHighlight();
|
||||
debounceRecordHistory(event);
|
||||
if (callback)
|
||||
callback(toString());
|
||||
});
|
||||
on('focus', _event => {
|
||||
focus = true;
|
||||
});
|
||||
on('blur', _event => {
|
||||
focus = false;
|
||||
});
|
||||
on('paste', event => {
|
||||
recordHistory();
|
||||
handlePaste(event);
|
||||
recordHistory();
|
||||
if (callback)
|
||||
callback(toString());
|
||||
});
|
||||
function save() {
|
||||
const s = getSelection();
|
||||
const pos = { start: 0, end: 0, dir: undefined };
|
||||
let { anchorNode, anchorOffset, focusNode, focusOffset } = s;
|
||||
if (!anchorNode || !focusNode)
|
||||
throw 'error1';
|
||||
// Selection anchor and focus are expected to be text nodes,
|
||||
// so normalize them.
|
||||
if (anchorNode.nodeType === Node.ELEMENT_NODE) {
|
||||
const node = document.createTextNode('');
|
||||
anchorNode.insertBefore(node, anchorNode.childNodes[anchorOffset]);
|
||||
anchorNode = node;
|
||||
anchorOffset = 0;
|
||||
}
|
||||
if (focusNode.nodeType === Node.ELEMENT_NODE) {
|
||||
const node = document.createTextNode('');
|
||||
focusNode.insertBefore(node, focusNode.childNodes[focusOffset]);
|
||||
focusNode = node;
|
||||
focusOffset = 0;
|
||||
}
|
||||
visit(editor, el => {
|
||||
if (el === anchorNode && el === focusNode) {
|
||||
pos.start += anchorOffset;
|
||||
pos.end += focusOffset;
|
||||
pos.dir = anchorOffset <= focusOffset ? '->' : '<-';
|
||||
return 'stop';
|
||||
}
|
||||
if (el === anchorNode) {
|
||||
pos.start += anchorOffset;
|
||||
if (!pos.dir) {
|
||||
pos.dir = '->';
|
||||
}
|
||||
else {
|
||||
return 'stop';
|
||||
}
|
||||
}
|
||||
else if (el === focusNode) {
|
||||
pos.end += focusOffset;
|
||||
if (!pos.dir) {
|
||||
pos.dir = '<-';
|
||||
}
|
||||
else {
|
||||
return 'stop';
|
||||
}
|
||||
}
|
||||
if (el.nodeType === Node.TEXT_NODE) {
|
||||
if (pos.dir != '->')
|
||||
pos.start += el.nodeValue.length;
|
||||
if (pos.dir != '<-')
|
||||
pos.end += el.nodeValue.length;
|
||||
}
|
||||
});
|
||||
// collapse empty text nodes
|
||||
editor.normalize();
|
||||
return pos;
|
||||
}
|
||||
function restore(pos) {
|
||||
const s = getSelection();
|
||||
let startNode, startOffset = 0;
|
||||
let endNode, endOffset = 0;
|
||||
if (!pos.dir)
|
||||
pos.dir = '->';
|
||||
if (pos.start < 0)
|
||||
pos.start = 0;
|
||||
if (pos.end < 0)
|
||||
pos.end = 0;
|
||||
// Flip start and end if the direction reversed
|
||||
if (pos.dir == '<-') {
|
||||
const { start, end } = pos;
|
||||
pos.start = end;
|
||||
pos.end = start;
|
||||
}
|
||||
let current = 0;
|
||||
visit(editor, el => {
|
||||
if (el.nodeType !== Node.TEXT_NODE)
|
||||
return;
|
||||
const len = (el.nodeValue || '').length;
|
||||
if (current + len > pos.start) {
|
||||
if (!startNode) {
|
||||
startNode = el;
|
||||
startOffset = pos.start - current;
|
||||
}
|
||||
if (current + len > pos.end) {
|
||||
endNode = el;
|
||||
endOffset = pos.end - current;
|
||||
return 'stop';
|
||||
}
|
||||
}
|
||||
current += len;
|
||||
});
|
||||
if (!startNode)
|
||||
startNode = editor, startOffset = editor.childNodes.length;
|
||||
if (!endNode)
|
||||
endNode = editor, endOffset = editor.childNodes.length;
|
||||
// Flip back the selection
|
||||
if (pos.dir == '<-') {
|
||||
[startNode, startOffset, endNode, endOffset] = [endNode, endOffset, startNode, startOffset];
|
||||
}
|
||||
s.setBaseAndExtent(startNode, startOffset, endNode, endOffset);
|
||||
}
|
||||
function beforeCursor() {
|
||||
const s = getSelection();
|
||||
const r0 = s.getRangeAt(0);
|
||||
const r = document.createRange();
|
||||
r.selectNodeContents(editor);
|
||||
r.setEnd(r0.startContainer, r0.startOffset);
|
||||
return r.toString();
|
||||
}
|
||||
function afterCursor() {
|
||||
const s = getSelection();
|
||||
const r0 = s.getRangeAt(0);
|
||||
const r = document.createRange();
|
||||
r.selectNodeContents(editor);
|
||||
r.setStart(r0.endContainer, r0.endOffset);
|
||||
return r.toString();
|
||||
}
|
||||
function handleNewLine(event) {
|
||||
if (event.key === 'Enter') {
|
||||
const before = beforeCursor();
|
||||
const after = afterCursor();
|
||||
let [padding] = findPadding(before);
|
||||
let newLinePadding = padding;
|
||||
// If last symbol is "{" ident new line
|
||||
if (options.indentOn.test(before)) {
|
||||
newLinePadding += options.tab;
|
||||
}
|
||||
// Preserve padding
|
||||
if (newLinePadding.length > 0) {
|
||||
preventDefault(event);
|
||||
event.stopPropagation();
|
||||
insert('\n' + newLinePadding);
|
||||
}
|
||||
else {
|
||||
legacyNewLineFix(event);
|
||||
}
|
||||
// Place adjacent "}" on next line
|
||||
if (newLinePadding !== padding && options.moveToNewLine.test(after)) {
|
||||
const pos = save();
|
||||
insert('\n' + padding);
|
||||
restore(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
function legacyNewLineFix(event) {
|
||||
// Firefox does not support plaintext-only mode
|
||||
// and puts <div><br></div> on Enter. Let's help.
|
||||
if (isLegacy && event.key === 'Enter') {
|
||||
preventDefault(event);
|
||||
event.stopPropagation();
|
||||
if (afterCursor() == '') {
|
||||
insert('\n ');
|
||||
const pos = save();
|
||||
pos.start = --pos.end;
|
||||
restore(pos);
|
||||
}
|
||||
else {
|
||||
insert('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
function handleSelfClosingCharacters(event) {
|
||||
const open = `([{'"`;
|
||||
const close = `)]}'"`;
|
||||
const codeAfter = afterCursor();
|
||||
const codeBefore = beforeCursor();
|
||||
const escapeCharacter = codeBefore.substr(codeBefore.length - 1) === '\\';
|
||||
const charAfter = codeAfter.substr(0, 1);
|
||||
if (close.includes(event.key) && !escapeCharacter && charAfter === event.key) {
|
||||
// We already have closing char next to cursor.
|
||||
// Move one char to right.
|
||||
const pos = save();
|
||||
preventDefault(event);
|
||||
pos.start = ++pos.end;
|
||||
restore(pos);
|
||||
}
|
||||
else if (open.includes(event.key)
|
||||
&& !escapeCharacter
|
||||
&& (`"'`.includes(event.key) || ['', ' ', '\n'].includes(charAfter))) {
|
||||
preventDefault(event);
|
||||
const pos = save();
|
||||
const wrapText = pos.start == pos.end ? '' : getSelection().toString();
|
||||
const text = event.key + wrapText + close[open.indexOf(event.key)];
|
||||
insert(text);
|
||||
pos.start++;
|
||||
pos.end++;
|
||||
restore(pos);
|
||||
}
|
||||
}
|
||||
function handleTabCharacters(event) {
|
||||
if (event.key === 'Tab') {
|
||||
preventDefault(event);
|
||||
if (event.shiftKey) {
|
||||
const before = beforeCursor();
|
||||
let [padding, start,] = findPadding(before);
|
||||
if (padding.length > 0) {
|
||||
const pos = save();
|
||||
// Remove full length tab or just remaining padding
|
||||
const len = Math.min(options.tab.length, padding.length);
|
||||
restore({ start, end: start + len });
|
||||
document.execCommand('delete');
|
||||
pos.start -= len;
|
||||
pos.end -= len;
|
||||
restore(pos);
|
||||
}
|
||||
}
|
||||
else {
|
||||
insert(options.tab);
|
||||
}
|
||||
}
|
||||
}
|
||||
function handleUndoRedo(event) {
|
||||
if (isUndo(event)) {
|
||||
preventDefault(event);
|
||||
at--;
|
||||
const record = history[at];
|
||||
if (record) {
|
||||
editor.innerHTML = record.html;
|
||||
restore(record.pos);
|
||||
}
|
||||
if (at < 0)
|
||||
at = 0;
|
||||
}
|
||||
if (isRedo(event)) {
|
||||
preventDefault(event);
|
||||
at++;
|
||||
const record = history[at];
|
||||
if (record) {
|
||||
editor.innerHTML = record.html;
|
||||
restore(record.pos);
|
||||
}
|
||||
if (at >= history.length)
|
||||
at--;
|
||||
}
|
||||
}
|
||||
function recordHistory() {
|
||||
if (!focus)
|
||||
return;
|
||||
const html = editor.innerHTML;
|
||||
const pos = save();
|
||||
const lastRecord = history[at];
|
||||
if (lastRecord) {
|
||||
if (lastRecord.html === html
|
||||
&& lastRecord.pos.start === pos.start
|
||||
&& lastRecord.pos.end === pos.end)
|
||||
return;
|
||||
}
|
||||
at++;
|
||||
history[at] = { html, pos };
|
||||
history.splice(at + 1);
|
||||
const maxHistory = 300;
|
||||
if (at > maxHistory) {
|
||||
at = maxHistory;
|
||||
history.splice(0, 1);
|
||||
}
|
||||
}
|
||||
function handlePaste(event) {
|
||||
preventDefault(event);
|
||||
const text = (event.originalEvent || event)
|
||||
.clipboardData
|
||||
.getData('text/plain')
|
||||
.replace(/\r/g, '');
|
||||
const pos = save();
|
||||
insert(text);
|
||||
highlight(editor);
|
||||
restore({
|
||||
start: Math.min(pos.start, pos.end) + text.length,
|
||||
end: Math.min(pos.start, pos.end) + text.length,
|
||||
dir: '<-',
|
||||
});
|
||||
}
|
||||
function visit(editor, visitor) {
|
||||
const queue = [];
|
||||
if (editor.firstChild)
|
||||
queue.push(editor.firstChild);
|
||||
let el = queue.pop();
|
||||
while (el) {
|
||||
if (visitor(el) === 'stop')
|
||||
break;
|
||||
if (el.nextSibling)
|
||||
queue.push(el.nextSibling);
|
||||
if (el.firstChild)
|
||||
queue.push(el.firstChild);
|
||||
el = queue.pop();
|
||||
}
|
||||
}
|
||||
function isCtrl(event) {
|
||||
return event.metaKey || event.ctrlKey;
|
||||
}
|
||||
function isUndo(event) {
|
||||
return isCtrl(event) && !event.shiftKey && event.code === 'KeyZ';
|
||||
}
|
||||
function isRedo(event) {
|
||||
return isCtrl(event) && event.shiftKey && event.code === 'KeyZ';
|
||||
}
|
||||
function insert(text) {
|
||||
text = text
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
document.execCommand('insertHTML', false, text);
|
||||
}
|
||||
function debounce(cb, wait) {
|
||||
let timeout = 0;
|
||||
return (...args) => {
|
||||
clearTimeout(timeout);
|
||||
timeout = window.setTimeout(() => cb(...args), wait);
|
||||
};
|
||||
}
|
||||
function findPadding(text) {
|
||||
// Find beginning of previous line.
|
||||
let i = text.length - 1;
|
||||
while (i >= 0 && text[i] !== '\n')
|
||||
i--;
|
||||
i++;
|
||||
// Find padding of the line.
|
||||
let j = i;
|
||||
while (j < text.length && /[ \t]/.test(text[j]))
|
||||
j++;
|
||||
return [text.substring(i, j) || '', i, j];
|
||||
}
|
||||
function toString() {
|
||||
return editor.textContent || '';
|
||||
}
|
||||
function preventDefault(event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
function getSelection() {
|
||||
var _a;
|
||||
if (((_a = editor.parentNode) === null || _a === void 0 ? void 0 : _a.nodeType) == Node.DOCUMENT_FRAGMENT_NODE) {
|
||||
return editor.parentNode.getSelection();
|
||||
}
|
||||
return window.getSelection();
|
||||
}
|
||||
return {
|
||||
updateOptions(newOptions) {
|
||||
Object.assign(options, newOptions);
|
||||
},
|
||||
updateCode(code) {
|
||||
editor.textContent = code;
|
||||
highlight(editor);
|
||||
},
|
||||
onUpdate(cb) {
|
||||
callback = cb;
|
||||
},
|
||||
toString,
|
||||
save,
|
||||
restore,
|
||||
recordHistory,
|
||||
destroy() {
|
||||
for (let [type, fn] of listeners) {
|
||||
editor.removeEventListener(type, fn);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
87
system/typemill/author/js/vue-account.js
Normal file
87
system/typemill/author/js/vue-account.js
Normal file
@@ -0,0 +1,87 @@
|
||||
const app = Vue.createApp({
|
||||
template: `<Transition name="initial" appear>
|
||||
<div class="w-full">
|
||||
<form class="w-full my-8">
|
||||
<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>
|
||||
<button type="submit" @click.prevent="save()" class="w-full p-3 my-1 bg-stone-700 hover:bg-stone-900 text-white cursor-pointer transition duration-100">Save</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</Transition>`,
|
||||
data() {
|
||||
return {
|
||||
formDefinitions: data.userfields,
|
||||
formData: data.userdata,
|
||||
userroles: data.userroles,
|
||||
message: '',
|
||||
messageClass: '',
|
||||
errors: {},
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
eventBus.$on('forminput', formdata => {
|
||||
this.formData[formdata.name] = formdata.value;
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
selectComponent: function(type)
|
||||
{
|
||||
return 'component-'+type;
|
||||
},
|
||||
save: function()
|
||||
{
|
||||
this.reset();
|
||||
var self = this;
|
||||
|
||||
tmaxios.put('/api/v1/account',{
|
||||
'csrf_name': document.getElementById("csrf_name").value,
|
||||
'csrf_value': document.getElementById("csrf_value").value,
|
||||
'userdata': this.formData
|
||||
})
|
||||
.then(function (response)
|
||||
{
|
||||
self.messageClass = 'bg-teal-500';
|
||||
self.message = response.data.message;
|
||||
})
|
||||
.catch(function (error)
|
||||
{
|
||||
self.messageClass = 'bg-rose-500';
|
||||
self.message = error.response.data.message;
|
||||
if(error.response.data.errors !== undefined)
|
||||
{
|
||||
self.errors = error.response.data.errors;
|
||||
}
|
||||
});
|
||||
},
|
||||
reset: function()
|
||||
{
|
||||
this.errors = {};
|
||||
this.message = '';
|
||||
this.messageClass = '';
|
||||
}
|
||||
},
|
||||
})
|
31
system/typemill/author/js/vue-eventbus.js
Normal file
31
system/typemill/author/js/vue-eventbus.js
Normal file
@@ -0,0 +1,31 @@
|
||||
class Event{
|
||||
constructor(){
|
||||
this.events = {};
|
||||
}
|
||||
|
||||
$on(eventName, fn) {
|
||||
this.events[eventName] = this.events[eventName] || [];
|
||||
this.events[eventName].push(fn);
|
||||
}
|
||||
|
||||
$off(eventName, fn) {
|
||||
if (this.events[eventName]) {
|
||||
for (var i = 0; i < this.events[eventName].length; i++) {
|
||||
if (this.events[eventName][i] === fn) {
|
||||
this.events[eventName].splice(i, 1);
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
$emit(eventName, data) {
|
||||
if (this.events[eventName]) {
|
||||
this.events[eventName].forEach(function(fn) {
|
||||
fn(data);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const eventBus = new Event();
|
118
system/typemill/author/js/vue-plugins.js
Normal file
118
system/typemill/author/js/vue-plugins.js
Normal file
@@ -0,0 +1,118 @@
|
||||
const app = Vue.createApp({
|
||||
template: `<Transition name="initial" appear>
|
||||
<div class="w-full">
|
||||
<ul>
|
||||
<li v-for="(theme,themename) in formDefinitions" class="w-full my-4 bg-stone-100">
|
||||
<div class="w-full p-8">
|
||||
<div class="w-full">
|
||||
<h2 class="text-xl font-bold mb-3">{{theme.name}}</h2>
|
||||
<div class="text-xs my-3">author: <a :href="theme.homepage" class="hover:underline text-teal-500">{{theme.author}}</a> | version: {{theme.version}} | {{theme.licence}}</div>
|
||||
<p>{{theme.description}}</p>
|
||||
</div>
|
||||
<div class="w-full mt-6 flex justify-between">
|
||||
<button @click="setCurrent(themename)" class="w-half p-3 bg-stone-700 hover:bg-stone-900 text-white cursor-pointer transition duration-100">Configure</button>
|
||||
<button class="w-half p-3 bg-teal-500 hover:bg-teal-600 text-white cursor-pointer transition duration-100">Donate/Buy</button>
|
||||
</div>
|
||||
</div>
|
||||
<form class="w-full p-8" v-if="current == themename">
|
||||
<div v-for="(fieldDefinition, fieldname) in theme.forms.fields">
|
||||
<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[themename][subfieldname]"
|
||||
v-bind="subfieldDefinition">
|
||||
</component>
|
||||
</fieldset>
|
||||
<component v-else
|
||||
:key="fieldname"
|
||||
:is="selectComponent(fieldDefinition.type)"
|
||||
:errors="errors"
|
||||
:name="fieldname"
|
||||
:userroles="userroles"
|
||||
:value="formData[themename][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>
|
||||
<div class="w-full">
|
||||
<button type="submit" @click.prevent="save()" class="w-full p-3 my-1 bg-stone-700 hover:bg-stone-900 text-white cursor-pointer transition duration-100">Save</button>
|
||||
<button @click.prevent="" class="w-full p-3 my-1 bg-teal-500 hover:bg-teal-600 text-white cursor-pointer transition duration-100">Donate/Buy</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Transition>`,
|
||||
data() {
|
||||
return {
|
||||
current: '',
|
||||
formDefinitions: data.plugins,
|
||||
formData: data.settings,
|
||||
message: '',
|
||||
messageClass: '',
|
||||
errors: {},
|
||||
userroles: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
eventBus.$on('forminput', formdata => {
|
||||
this.formData[this.current][formdata.name] = formdata.value;
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
setCurrent: function(name)
|
||||
{
|
||||
if(this.current == name)
|
||||
{
|
||||
this.current = '';
|
||||
}
|
||||
else
|
||||
{
|
||||
this.current = name;
|
||||
}
|
||||
},
|
||||
selectComponent: function(type)
|
||||
{
|
||||
return 'component-'+type;
|
||||
},
|
||||
save: function()
|
||||
{
|
||||
this.reset();
|
||||
var self = this;
|
||||
|
||||
tmaxios.post('/api/v1/plugin',{
|
||||
'csrf_name': document.getElementById("csrf_name").value,
|
||||
'csrf_value': document.getElementById("csrf_value").value,
|
||||
'plugin': this.current,
|
||||
'settings': this.formData[this.current]
|
||||
})
|
||||
.then(function (response)
|
||||
{
|
||||
self.messageClass = 'bg-teal-500';
|
||||
self.message = response.data.message;
|
||||
})
|
||||
.catch(function (error)
|
||||
{
|
||||
self.messageClass = 'bg-rose-500';
|
||||
self.message = error.response.data.message;
|
||||
if(error.response.data.errors !== undefined)
|
||||
{
|
||||
self.errors = error.response.data.errors;
|
||||
}
|
||||
});
|
||||
},
|
||||
reset: function()
|
||||
{
|
||||
this.errors = {};
|
||||
this.message = '';
|
||||
this.messageClass = '';
|
||||
}
|
||||
},
|
||||
})
|
1457
system/typemill/author/js/vue-shared.js
Normal file
1457
system/typemill/author/js/vue-shared.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,79 +1,98 @@
|
||||
const { createApp } = Vue
|
||||
|
||||
createApp({
|
||||
template: `<div><form>MyForm Here message
|
||||
<div v-for="(field, legend) in formDefinitions">
|
||||
{{ legend }}
|
||||
<fieldset v-if="field.type == 'fieldset'" class="fs-formbuilder"><legend>{{field.legend}}</legend>
|
||||
<component v-for="(subfield, index) in field.fields "
|
||||
:key="index"
|
||||
:is="selectComponent(subfield)"
|
||||
:errors="errors"
|
||||
:name="index"
|
||||
:userroles="userroles"
|
||||
v-model="formdata[index]"
|
||||
v-bind="subfield">
|
||||
</component>
|
||||
</fieldset>
|
||||
<component v-else
|
||||
:key="index"
|
||||
:is="selectComponent(field)"
|
||||
:errors="errors"
|
||||
:name="index"
|
||||
:userroles="userroles"
|
||||
v-model="formData[index]"
|
||||
v-bind="field">
|
||||
</component>
|
||||
</div>
|
||||
</form></div>`,
|
||||
const app = Vue.createApp({
|
||||
template: `<Transition name="initial" appear>
|
||||
<form class="inline-block w-full">
|
||||
<ul class="flex mt-4 mb-4">
|
||||
<li v-for="tab in tabs" class="">
|
||||
<button class="px-2 py-2 border-b-2 border-stone-200 hover:border-b-4 hover:bg-stone-200 hover:border-stone-700 transition duration-100" :class="(tab == currentTab) ? 'border-b-4 border-stone-700 bg-stone-200' : ''" @click.prevent="activateTab(tab)">{{tab}}</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div v-for="(fieldDefinition, fieldname) in formDefinitions">
|
||||
<fieldset class="flex flex-wrap justify-between" :class="(fieldDefinition.legend == currentTab) ? 'block' : 'hidden'" v-if="fieldDefinition.type == 'fieldset'">
|
||||
<component v-for="(subfieldDefinition, fieldname) in fieldDefinition.fields"
|
||||
:key="fieldname"
|
||||
:is="selectComponent(subfieldDefinition.type)"
|
||||
:errors="errors"
|
||||
:name="fieldname"
|
||||
:userroles="userroles"
|
||||
:value="formData[fieldname]"
|
||||
v-bind="subfieldDefinition">
|
||||
</component>
|
||||
</fieldset>
|
||||
</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() {
|
||||
return {
|
||||
message: 'Add system forms with vue here',
|
||||
root: document.getElementById("main").dataset.url,
|
||||
currentTab: 'System',
|
||||
tabs: [],
|
||||
formDefinitions: data.system.fields,
|
||||
formDefinitions: data.system,
|
||||
formData: data.settings,
|
||||
formErrors: {},
|
||||
formErrorsReset: {},
|
||||
item: false,
|
||||
userroles: false,
|
||||
saved: false,
|
||||
message: '',
|
||||
messageClass: '',
|
||||
errors: {},
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentTabComponent: function () {
|
||||
if(this.currentTab == 'Content')
|
||||
{
|
||||
editor.showEditor = 'show';
|
||||
posts.showPosts = 'show';
|
||||
}
|
||||
else
|
||||
{
|
||||
editor.showEditor = 'hidden';
|
||||
posts.showPosts = 'hidden';
|
||||
return 'tab-' + this.currentTab.toLowerCase()
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
mounted() {
|
||||
|
||||
for (var key in this.formDefinitions)
|
||||
{
|
||||
if (this.formDefinitions.hasOwnProperty(key))
|
||||
{
|
||||
this.tabs.push(key);
|
||||
this.formErrors[key] = false;
|
||||
this.tabs.push(this.formDefinitions[key].legend);
|
||||
this.errors[key] = false;
|
||||
}
|
||||
}
|
||||
this.formErrorsReset = this.formErrors;
|
||||
|
||||
eventBus.$on('forminput', formdata => {
|
||||
this.formData[formdata.name] = formdata.value;
|
||||
});
|
||||
|
||||
},
|
||||
methods: {
|
||||
selectComponent: function(field)
|
||||
selectComponent: function(type)
|
||||
{
|
||||
return 'component-'+field.type;
|
||||
return 'component-'+type;
|
||||
},
|
||||
},
|
||||
}).mount('#systemsettings')
|
||||
activateTab: function(tab){
|
||||
this.currentTab = tab;
|
||||
},
|
||||
save: function()
|
||||
{
|
||||
this.reset();
|
||||
var self = this;
|
||||
|
||||
tmaxios.post('/api/v1/settings',{
|
||||
'csrf_name': document.getElementById("csrf_name").value,
|
||||
'csrf_value': document.getElementById("csrf_value").value,
|
||||
'settings': this.formData
|
||||
})
|
||||
.then(function (response)
|
||||
{
|
||||
self.messageClass = 'bg-teal-500';
|
||||
self.message = response.data.message;
|
||||
})
|
||||
.catch(function (error)
|
||||
{
|
||||
self.messageClass = 'bg-rose-500';
|
||||
self.message = error.response.data.message;
|
||||
if(error.response.data.errors !== undefined)
|
||||
{
|
||||
self.errors = error.response.data.errors;
|
||||
}
|
||||
});
|
||||
},
|
||||
reset: function()
|
||||
{
|
||||
this.errors = {};
|
||||
this.message = '';
|
||||
this.messageClass = '';
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
/*
|
||||
|
130
system/typemill/author/js/vue-themes.js
Normal file
130
system/typemill/author/js/vue-themes.js
Normal file
@@ -0,0 +1,130 @@
|
||||
const app = Vue.createApp({
|
||||
template: `<Transition name="initial" appear>
|
||||
<div class="w-full">
|
||||
<ul>
|
||||
<li v-for="(theme,themename) in formDefinitions" class="w-full my-4 bg-stone-100">
|
||||
<div class="w-full flex p-8">
|
||||
<div class="w-1/2 h-64 overflow-hidden">
|
||||
<img :src="theme.preview" class="w-full">
|
||||
</div>
|
||||
<div class="w-1/2 pl-8 flex flex-col">
|
||||
<div class="w-full">
|
||||
<h2 class="text-xl font-bold mb-3">{{theme.name}}</h2>
|
||||
<div class="text-xs my-3">author: <a :href="theme.homepage" class="hover:underline text-teal-500">{{theme.author}}</a> | version: {{theme.version}} | {{theme.licence}}</div>
|
||||
<p>{{theme.description}}</p>
|
||||
</div>
|
||||
<div class="w-full mt-auto flex justify-between">
|
||||
<button @click="setCurrent(themename)" class="w-half p-3 bg-stone-700 hover:bg-stone-900 text-white cursor-pointer transition duration-100">Configure</button>
|
||||
<button class="w-half p-3 bg-teal-500 hover:bg-teal-600 text-white cursor-pointer transition duration-100">Donate/Buy</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form class="w-full p-8" v-if="current == themename">
|
||||
<div v-for="(fieldDefinition, fieldname) in theme.forms.fields">
|
||||
<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[themename][subfieldname]"
|
||||
v-bind="subfieldDefinition">
|
||||
</component>
|
||||
</fieldset>
|
||||
<component v-else
|
||||
:key="fieldname"
|
||||
:is="selectComponent(fieldDefinition.type)"
|
||||
:errors="errors"
|
||||
:name="fieldname"
|
||||
:userroles="userroles"
|
||||
:value="formData[themename][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>
|
||||
<div class="w-full">
|
||||
<button type="submit" @click.prevent="save()" class="w-full p-3 my-1 bg-stone-700 hover:bg-stone-900 text-white cursor-pointer transition duration-100">Save</button>
|
||||
<button @click.prevent="" class="w-full p-3 my-1 bg-teal-500 hover:bg-teal-600 text-white cursor-pointer transition duration-100">Donate/Buy</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Transition>`,
|
||||
data() {
|
||||
return {
|
||||
current: '',
|
||||
formDefinitions: data.themes,
|
||||
formData: data.settings,
|
||||
message: '',
|
||||
messageClass: '',
|
||||
errors: {},
|
||||
userroles: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
eventBus.$on('forminput', formdata => {
|
||||
this.formData[this.current][formdata.name] = formdata.value;
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
setCurrent: function(name)
|
||||
{
|
||||
if(this.current == name)
|
||||
{
|
||||
this.current = '';
|
||||
}
|
||||
else
|
||||
{
|
||||
this.current = name;
|
||||
}
|
||||
},
|
||||
selectComponent: function(type)
|
||||
{
|
||||
return 'component-'+type;
|
||||
},
|
||||
save: function()
|
||||
{
|
||||
this.reset();
|
||||
var self = this;
|
||||
|
||||
tmaxios.post('/api/v1/theme',{
|
||||
'csrf_name': document.getElementById("csrf_name").value,
|
||||
'csrf_value': document.getElementById("csrf_value").value,
|
||||
'theme': this.current,
|
||||
'settings': this.formData[this.current]
|
||||
})
|
||||
.then(function (response)
|
||||
{
|
||||
self.messageClass = 'bg-teal-500';
|
||||
self.message = response.data.message;
|
||||
|
||||
self.updateCSS();
|
||||
})
|
||||
.catch(function (error)
|
||||
{
|
||||
self.messageClass = 'bg-rose-500';
|
||||
self.message = error.response.data.message;
|
||||
if(error.response.data.errors !== undefined)
|
||||
{
|
||||
self.errors = error.response.data.errors;
|
||||
}
|
||||
});
|
||||
},
|
||||
updateCSS: function()
|
||||
{
|
||||
/* check if css has been modified */
|
||||
/* if so, send to api endpoint */
|
||||
},
|
||||
reset: function()
|
||||
{
|
||||
this.errors = {};
|
||||
this.message = '';
|
||||
this.messageClass = '';
|
||||
}
|
||||
},
|
||||
})
|
13
system/typemill/author/js/vue-translate.js
Normal file
13
system/typemill/author/js/vue-translate.js
Normal file
@@ -0,0 +1,13 @@
|
||||
app.config.globalProperties.$filters = {
|
||||
translate(value)
|
||||
{
|
||||
if (!value) return ''
|
||||
translation_key = value.replace(/[ ]/g,"_").replace(/[.]/g, "_").replace(/[,]/g, "_").replace(/[-]/g, "_").replace(/[,]/g,"_").toUpperCase()
|
||||
translation_value = data.labels[translation_key]
|
||||
if(!translation_value || translation_value.length === 0){
|
||||
return value
|
||||
} else {
|
||||
return data.labels[translation_key]
|
||||
}
|
||||
}
|
||||
}
|
407
system/typemill/author/js/vue-users.js
Normal file
407
system/typemill/author/js/vue-users.js
Normal file
@@ -0,0 +1,407 @@
|
||||
const app = Vue.createApp({
|
||||
template: `<div class="w-full">
|
||||
<Transition name="initial" appear>
|
||||
<searchbox :error="error"></searchbox>
|
||||
</Transition>
|
||||
</div>
|
||||
<div class="w-full overflow-auto">
|
||||
<Transition name="initial" appear>
|
||||
<usertable :userdata="userdata"></usertable>
|
||||
</Transition>
|
||||
</div>
|
||||
<ul class="w-full flex mt-4" v-if="showpagination">
|
||||
<pagination
|
||||
v-for="page in pages"
|
||||
v-bind:key="page"
|
||||
v-bind:page="page"
|
||||
></pagination>
|
||||
</ul>`,
|
||||
data: function () {
|
||||
return {
|
||||
usernames: data.usernames,
|
||||
holdusernames: data.usernames,
|
||||
userdata: data.userdata,
|
||||
holduserdata: data.userdata,
|
||||
userroles: data.userroles,
|
||||
pagenumber: 1,
|
||||
pagesize: 10,
|
||||
pages: 0,
|
||||
error: false,
|
||||
}
|
||||
},
|
||||
mounted: function(){
|
||||
this.calculatepages();
|
||||
},
|
||||
computed: {
|
||||
showpagination: function () {
|
||||
return this.pages != 1;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clear: function(filter)
|
||||
{
|
||||
this.usernames = this.holdusernames;
|
||||
this.userdata = this.holduserdata;
|
||||
this.calculatepages();
|
||||
if(this.pages == 1)
|
||||
{
|
||||
this.showpagination = false;
|
||||
}
|
||||
},
|
||||
calculatepages: function()
|
||||
{
|
||||
this.pages = Math.ceil(this.usernames.length / this.pagesize);
|
||||
this.pagenumber = 1;
|
||||
},
|
||||
getusernamesforpage: function() {
|
||||
// human-readable page numbers usually start with 1, so we reduce 1 in the first argument
|
||||
return this.usernames.slice((this.pagenumber - 1) * this.pagesize, this.pagenumber * this.pagesize);
|
||||
},
|
||||
getuserdata: function(usernames)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
tmaxios.get('/api/v1/users/getbynames',{
|
||||
params: {
|
||||
'csrf_name': document.getElementById("csrf_name").value,
|
||||
'csrf_value': document.getElementById("csrf_value").value,
|
||||
'usernames': usernames,
|
||||
}
|
||||
})
|
||||
.then(function (response) {
|
||||
self.userdata = response.data.userdata;
|
||||
})
|
||||
.catch(function (error)
|
||||
{
|
||||
self.messageClass = 'bg-rose-500';
|
||||
self.message = error.response.data.message;
|
||||
});
|
||||
},
|
||||
search: function(term,filter)
|
||||
{
|
||||
if(filter == 'username')
|
||||
{
|
||||
this.usernames = this.filterItems(this.holdusernames, term);
|
||||
this.userdata = [];
|
||||
this.calculatepages();
|
||||
|
||||
if(this.usernames.length > 0)
|
||||
{
|
||||
let usernames = this.getusernamesforpage();
|
||||
|
||||
this.getuserdata(usernames);
|
||||
}
|
||||
}
|
||||
else if(filter == 'usermail')
|
||||
{
|
||||
this.usernames = [];
|
||||
this.userdata = [];
|
||||
this.calculatepages();
|
||||
|
||||
var self = this;
|
||||
|
||||
tmaxios.get('/api/v1/users/getbyemail',{
|
||||
params: {
|
||||
'csrf_name': document.getElementById("csrf_name").value,
|
||||
'csrf_value': document.getElementById("csrf_value").value,
|
||||
'email': term,
|
||||
}
|
||||
})
|
||||
.then(function (response)
|
||||
{
|
||||
self.userdata = response.data.userdata;
|
||||
for(var x = 0; x <= self.userdata.length; x++)
|
||||
{
|
||||
self.usernames.push(self.userdata[x].username);
|
||||
}
|
||||
self.calculatepages();
|
||||
})
|
||||
.catch(function (error)
|
||||
{
|
||||
self.messageClass = 'bg-rose-500';
|
||||
self.message = error.response.data.message;
|
||||
});
|
||||
}
|
||||
else if(filter == 'userrole')
|
||||
{
|
||||
this.usernames = [];
|
||||
this.userdata = [];
|
||||
this.calculatepages();
|
||||
|
||||
var self = this;
|
||||
|
||||
tmaxios.get('/api/v1/users/getbyrole',{
|
||||
params: {
|
||||
'csrf_name': document.getElementById("csrf_name").value,
|
||||
'csrf_value': document.getElementById("csrf_value").value,
|
||||
'role': term,
|
||||
}
|
||||
})
|
||||
.then(function (response)
|
||||
{
|
||||
self.userdata = response.data.userdata;
|
||||
for(var x = 0; x <= self.userdata.length; x++)
|
||||
{
|
||||
self.usernames.push(self.userdata[x].username);
|
||||
}
|
||||
self.calculatepages();
|
||||
})
|
||||
.catch(function (error)
|
||||
{
|
||||
self.messageClass = 'bg-rose-500';
|
||||
self.message = error.response.data.message;
|
||||
});
|
||||
}
|
||||
},
|
||||
filterItems: function(arr, query)
|
||||
{
|
||||
return arr.filter(function(el){
|
||||
return el.toLowerCase().indexOf(query.toLowerCase()) !== -1
|
||||
})
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
app.component('searchbox', {
|
||||
props: ['usernames', 'error'],
|
||||
data: function () {
|
||||
return {
|
||||
filter: 'username',
|
||||
searchterm: '',
|
||||
userroles: data.userroles,
|
||||
}
|
||||
},
|
||||
template: `<div>
|
||||
<div>
|
||||
<button @click.prevent="setFilter('username')" :class="checkActive('username')" class="px-2 py-2 border-b-4 hover:bg-stone-200 hover:border-stone-700 transition duration-100">username</button>
|
||||
<button @click.prevent="setFilter('userrole')" :class="checkActive('userrole')" class="px-2 py-2 border-b-4 hover:bg-stone-200 hover:border-stone-700 transition duration-100">userrole</button>
|
||||
<button @click.prevent="setFilter('usermail')" :class="checkActive('usermail')" class="px-2 py-2 border-b-4 hover:bg-stone-200 hover:border-stone-700 transition duration-100">e-mail</button>
|
||||
</div>
|
||||
<div class="w-100 flex">
|
||||
<select v-if="this.filter == 'userrole'" v-model="searchterm" class="w-3/4 h-12 px-2 py-3 border border-stone-300 bg-stone-200">
|
||||
<option v-for="role in userroles">{{role}}</option>
|
||||
</select>
|
||||
<input v-else type="text" class="usersearch" v-model="searchterm" class="w-3/4 h-12 px-2 py-3 border border-stone-300 bg-stone-200">
|
||||
<div class="w-1/4 flex justify-around">
|
||||
<button class="w-half bg-stone-200 hover:bg-stone-100" @click.prevent="clearSearch()">Clear</button>
|
||||
<button class="w-half bg-stone-700 hover:bg-stone-900 text-white" @click.prevent="startSearch()">Search</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="error" class="error pt1 f6">{{error}}</div>
|
||||
<div v-if="this.filter == \'usermail\'" class="text-xs">You can use the asterisk (*) wildcard to search for name@* or *@domain.com.</div>
|
||||
</div>`,
|
||||
methods: {
|
||||
startSearch: function()
|
||||
{
|
||||
this.$root.error = false;
|
||||
|
||||
if(this.searchterm.trim() != '')
|
||||
{
|
||||
if(this.searchterm.trim().length < 3)
|
||||
{
|
||||
this.$root.error = 'Please enter at least 3 characters';
|
||||
return;
|
||||
}
|
||||
this.$root.search(this.searchterm, this.filter);
|
||||
}
|
||||
},
|
||||
clearSearch: function()
|
||||
{
|
||||
this.$root.error = false;
|
||||
this.searchterm = '';
|
||||
this.$root.clear(this.filter);
|
||||
},
|
||||
setFilter: function(filter)
|
||||
{
|
||||
this.searchterm = '';
|
||||
this.filter = filter;
|
||||
if(filter == 'userrole')
|
||||
{
|
||||
this.searchterm = this.userroles[0];
|
||||
}
|
||||
},
|
||||
checkActive: function(filter)
|
||||
{
|
||||
if(this.filter == filter)
|
||||
{
|
||||
return 'border-stone-700 bg-stone-200';
|
||||
}
|
||||
return 'border-stone-100 bg-stone-100';
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
app.component('usertable', {
|
||||
props: ['userdata'],
|
||||
template: `<table class="w-full mt-8" cellspacing="0">
|
||||
<tr>
|
||||
<th class="p-3 bg-stone-200 border-2 border-stone-50">Username</th>
|
||||
<th class="p-3 bg-stone-200 border-2 border-stone-50">Userrole</th>
|
||||
<th class="p-3 bg-stone-200 border-2 border-stone-50">E-Mail</th>
|
||||
<th class="p-3 bg-stone-200 border-2 border-stone-50">Edit</th>
|
||||
</tr>
|
||||
<tr v-for="user,index in userdata" key="username">
|
||||
<td class="p-3 bg-stone-100 border-2 border-white">{{ user.username }}</td>
|
||||
<td class="p-3 bg-stone-100 border-2 border-white">{{ user.userrole }}</td>
|
||||
<td class="p-3 bg-stone-100 border-2 border-white">{{ user.email }}</td>
|
||||
<td class="p-3 bg-stone-100 border-2 border-white"><a :href="getEditLink(user.username)" class="link tm-red no-underline underline-hover">edit</a></td>
|
||||
</tr>
|
||||
</table>`,
|
||||
methods: {
|
||||
getEditLink: function(username){
|
||||
return this.$root.$data.root + '/tm/user/' + username;
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
app.component('pagination', {
|
||||
props: ['page'],
|
||||
template: '<li><button class="p-1 border-2 border-stone-50 hover:bg-stone-200" :class="checkActive()" @click="goto(page)">{{ page }}</button></li>',
|
||||
methods: {
|
||||
goto: function(page){
|
||||
|
||||
this.$root.$data.pagenumber = page;
|
||||
let usernames = this.$root.getusernamesforpage();
|
||||
this.$root.getuserdata(usernames);
|
||||
},
|
||||
checkActive: function()
|
||||
{
|
||||
if(this.page == this.$root.$data.pagenumber)
|
||||
{
|
||||
return 'bg-stone-200';
|
||||
}
|
||||
return 'bg-stone-100';
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
const app = Vue.createApp({
|
||||
template: `<Transition name="initial" appear>
|
||||
<div class="w-full">
|
||||
<ul>
|
||||
<li v-for="(theme,themename) in formDefinitions" class="w-full my-4 bg-stone-100">
|
||||
<div class="w-full flex p-8">
|
||||
<div class="w-1/2 h-64 overflow-hidden">
|
||||
<img :src="theme.preview" class="w-full">
|
||||
</div>
|
||||
<div class="w-1/2 pl-8 flex flex-col">
|
||||
<div class="w-full">
|
||||
<h2 class="text-xl font-bold mb-3">{{theme.name}}</h2>
|
||||
<div class="text-xs my-3">author: <a :href="theme.homepage" class="hover:underline text-teal-500">{{theme.author}}</a> | version: {{theme.version}} | {{theme.licence}}</div>
|
||||
<p>{{theme.description}}</p>
|
||||
</div>
|
||||
<div class="w-full mt-auto flex justify-between">
|
||||
<button @click="setCurrent(themename)" class="w-half p-3 bg-stone-700 hover:bg-stone-900 text-white cursor-pointer transition duration-100">Configure</button>
|
||||
<button class="w-half p-3 bg-teal-500 hover:bg-teal-600 text-white cursor-pointer transition duration-100">Donate/Buy</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form class="w-full p-8" v-if="current == themename">
|
||||
<div v-for="(fieldDefinition, fieldname) in theme.forms.fields">
|
||||
<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[themename][subfieldname]"
|
||||
v-bind="subfieldDefinition">
|
||||
</component>
|
||||
</fieldset>
|
||||
<component v-else
|
||||
:key="fieldname"
|
||||
:is="selectComponent(fieldDefinition.type)"
|
||||
:errors="errors"
|
||||
:name="fieldname"
|
||||
:userroles="userroles"
|
||||
:value="formData[themename][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>
|
||||
<div class="w-full">
|
||||
<button type="submit" @click.prevent="save()" class="w-full p-3 my-1 bg-stone-700 hover:bg-stone-900 text-white cursor-pointer transition duration-100">Save</button>
|
||||
<button @click.prevent="" class="w-full p-3 my-1 bg-teal-500 hover:bg-teal-600 text-white cursor-pointer transition duration-100">Donate/Buy</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</Transition>`,
|
||||
data() {
|
||||
return {
|
||||
current: '',
|
||||
formDefinitions: data.themes,
|
||||
formData: data.settings,
|
||||
message: '',
|
||||
messageClass: '',
|
||||
errors: {},
|
||||
userroles: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
eventBus.$on('forminput', formdata => {
|
||||
this.formData[this.current][formdata.name] = formdata.value;
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
setCurrent: function(name)
|
||||
{
|
||||
if(this.current == name)
|
||||
{
|
||||
this.current = '';
|
||||
}
|
||||
else
|
||||
{
|
||||
this.current = name;
|
||||
}
|
||||
},
|
||||
selectComponent: function(type)
|
||||
{
|
||||
return 'component-'+type;
|
||||
},
|
||||
save: function()
|
||||
{
|
||||
this.reset();
|
||||
var self = this;
|
||||
|
||||
tmaxios.post('/api/v1/theme',{
|
||||
'csrf_name': document.getElementById("csrf_name").value,
|
||||
'csrf_value': document.getElementById("csrf_value").value,
|
||||
'theme': this.current,
|
||||
'settings': this.formData[this.current]
|
||||
})
|
||||
.then(function (response)
|
||||
{
|
||||
self.messageClass = 'bg-teal-500';
|
||||
self.message = response.data.message;
|
||||
|
||||
self.updateCSS();
|
||||
})
|
||||
.catch(function (error)
|
||||
{
|
||||
self.messageClass = 'bg-rose-500';
|
||||
self.message = error.response.data.message;
|
||||
if(error.response.data.errors !== undefined)
|
||||
{
|
||||
self.errors = error.response.data.errors;
|
||||
}
|
||||
});
|
||||
},
|
||||
reset: function()
|
||||
{
|
||||
this.errors = {};
|
||||
this.message = '';
|
||||
this.messageClass = '';
|
||||
}
|
||||
},
|
||||
})
|
||||
*/
|
@@ -19,9 +19,6 @@
|
||||
<link rel="stylesheet" href="{{ base_url() }}/system/typemill/author/css/output.css?v={{ settings.version }}" />
|
||||
<link rel="stylesheet" href="{{ base_url() }}/system/typemill/author/css/custom.css?v={{ settings.version }}" />
|
||||
|
||||
<!-- <link rel="stylesheet" href="{{ base_url() }}/system/author/css/tachyons.min.css?v={{ settings.version }}" />
|
||||
<link rel="stylesheet" href="{{ base_url() }}/system/author/css/style.css?v={{ settings.version }}" />
|
||||
-->
|
||||
{{ assets.renderCSS() }}
|
||||
|
||||
</head>
|
||||
@@ -39,7 +36,7 @@
|
||||
<aside class="w-1/4">
|
||||
{% include 'partials/systemNavi.twig' %}
|
||||
</aside>
|
||||
<article class="w-3/4 bg-stone-50 drop-shadow-md">
|
||||
<article class="w-3/4 bg-stone-50 drop-shadow-md p-8">
|
||||
{% block content %}{% endblock %}
|
||||
</article>
|
||||
</div>
|
||||
@@ -52,36 +49,21 @@
|
||||
|
||||
<script>
|
||||
|
||||
var data = {{ jsdata | json_encode() | raw }}
|
||||
|
||||
/* var eventBus = false; */
|
||||
console.info(data.settings);
|
||||
const data = {{ jsdata | json_encode() | raw }}
|
||||
const labels = {{ translations|json_encode() }};
|
||||
|
||||
</script>
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/axios.min.js?v={{ settings.version }}"></script>
|
||||
<script>
|
||||
const tmaxios = axios.create();
|
||||
tmaxios.defaults.baseURL = "{{ base_url() }}";
|
||||
/* tmaxios.defaults.headers.common['Authorization'] = "Basic {{ basicauth }}"; */
|
||||
/* header for session authentication in middleware */
|
||||
tmaxios.defaults.headers.common['X-Session-Auth'] = "true";
|
||||
/* tmaxios.defaults.headers.common['Authorization'] = "Basic {{ basicauth }}"; */
|
||||
</script>
|
||||
<!-- <script src="{{ base_url() }}/system/typemill/author/js/autosize.min.js?v={{ settings.version }}"></script> -->
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue.js?v={{ settings.version }}"></script>
|
||||
<script>
|
||||
const { eventBus } = Vue;
|
||||
</script>
|
||||
<!--
|
||||
<script>
|
||||
var eventBus = false;
|
||||
const FormBus = new Vue();
|
||||
</script>
|
||||
<script src="{{ base_url() }}/system/author/js/vue-shared.js?v={{ settings.version }}"></script>
|
||||
<script src="{{ base_url() }}/system/author/js/author.js?v={{ settings.version }}"></script>
|
||||
<script src="{{ base_url() }}/system/author/js/typemillutils.js?v={{ settings.version }}"></script>
|
||||
<script>
|
||||
typemillUtilities.start()
|
||||
</script>
|
||||
-->
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-eventbus.js?v={{ settings.version }}"></script>
|
||||
|
||||
{% block javascript %}{% endblock %}
|
||||
|
||||
{{ assets.renderJS() }}
|
||||
|
@@ -5,7 +5,7 @@
|
||||
<ul class="flex border-l-2 border-stone-200">
|
||||
{% for name,navitem in mainnavi %}
|
||||
<li class="border-r-2 border-stone-200">
|
||||
<a class="inline-block px-4 pt-4 pb-3 border-b-4 hover:border-stone-700 hover:bg-stone-50 focus:bg-stone-50 active:bg-stone-50{{ navitem.active ? ' bg-stone-50 border-stone-700' : ' border-stone-100' }}" href="{{ url_for(navitem.routename) }}">{{ translate(navitem.title)|capitalize }}</a>
|
||||
<a class="inline-block px-4 pt-4 pb-3 border-b-4 hover:border-stone-700 hover:bg-stone-50 focus:bg-stone-50 active:bg-stone-50 transition duration-100{{ navitem.active ? ' bg-stone-50 border-stone-700' : ' border-stone-100' }}" href="{{ url_for(navitem.routename) }}">{{ translate(navitem.title)|capitalize }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
@@ -1,10 +1,10 @@
|
||||
<nav id="sidebar-menu" class="sidebar-menu">
|
||||
<div id="mobile-menu" class="menu-action">{{ translate('Menu') }} <span class="button-arrow"></span></div>
|
||||
<div id="mobile-menu" class="hidden menu-action">{{ translate('Menu') }} <span class="button-arrow"></span></div>
|
||||
<ul class="mr-4">
|
||||
|
||||
{% for name,navitem in systemnavi %}
|
||||
<li class="mb-1">
|
||||
<a class="block p-2 border-l-4 border-slate-200 hover:bg-stone-50 hover:border-cyan-500{{ navitem.active ? ' active' : '' }}" href="{{ url_for(navitem.routename) }}">
|
||||
<a class="block p-2 border-l-4 hover:bg-stone-50 hover:border-teal-500 transition duration-100{{ navitem.active ? ' active bg-stone-50 border-cyan-500' : ' border-slate-200' }}" href="{{ url_for(navitem.routename) }}">
|
||||
<svg class="icon {{ navitem.icon }} mr-2"><use xlink:href="#{{ navitem.icon }}"></use></svg> {{ translate(name) }}
|
||||
</a>
|
||||
</li>
|
||||
|
21
system/typemill/author/system/account.twig
Normal file
21
system/typemill/author/system/account.twig
Normal file
@@ -0,0 +1,21 @@
|
||||
{% extends 'layouts/layoutSystem.twig' %}
|
||||
{% block title %}{{ translate('Account') }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1 class="text-3xl font-bold mb-4">{{ translate('Account') }} </h1>
|
||||
|
||||
<div id="account" v-cloak></div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-account.js?v={{ settings.version }}"></script>
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-translate.js?v={{ settings.version }}"></script>
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-shared.js?v={{ settings.version }}"></script>
|
||||
<script>
|
||||
app.mount('#account');
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
21
system/typemill/author/system/plugins.twig
Normal file
21
system/typemill/author/system/plugins.twig
Normal file
@@ -0,0 +1,21 @@
|
||||
{% extends 'layouts/layoutSystem.twig' %}
|
||||
{% block title %}{{ translate('Plugin Settings') }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1 class="text-3xl font-bold mb-4">{{ translate('Plugins') }} </h1>
|
||||
|
||||
<div id="plugins" v-cloak></div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-plugins.js?v={{ settings.version }}"></script>
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-translate.js?v={{ settings.version }}"></script>
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-shared.js?v={{ settings.version }}"></script>
|
||||
<script>
|
||||
app.mount('#plugins');
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
@@ -3,32 +3,19 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="formWrapper">
|
||||
|
||||
<h1>{{ translate('System') }} </h1>
|
||||
|
||||
<div id="systemsettings" v-cloak>
|
||||
|
||||
<systemsettings :userdata="userdata">${ message }</systemsettings>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<h1 class="text-3xl font-bold mb-4">{{ translate('System') }} </h1>
|
||||
|
||||
<div id="system" v-cloak></div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-system.js?v={{ settings.version }}"></script>
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-translate.js?v={{ settings.version }}"></script>
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-shared.js?v={{ settings.version }}"></script>
|
||||
<script>
|
||||
tmaxios.get('/api/v1/mainnavi',{
|
||||
'csrf_name': document.getElementById("csrf_name").value,
|
||||
'csrf_value': document.getElementById("csrf_value").value,
|
||||
})
|
||||
.then(function (response) {
|
||||
console.info(response);
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.info(error);
|
||||
});
|
||||
</script>
|
||||
app.mount('#system');
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
|
21
system/typemill/author/system/themes.twig
Normal file
21
system/typemill/author/system/themes.twig
Normal file
@@ -0,0 +1,21 @@
|
||||
{% extends 'layouts/layoutSystem.twig' %}
|
||||
{% block title %}{{ translate('Theme Settings') }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1 class="text-3xl font-bold mb-4">{{ translate('Themes') }} </h1>
|
||||
|
||||
<div id="themes" v-cloak></div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-themes.js?v={{ settings.version }}"></script>
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-translate.js?v={{ settings.version }}"></script>
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-shared.js?v={{ settings.version }}"></script>
|
||||
<script>
|
||||
app.mount('#themes');
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
21
system/typemill/author/system/users.twig
Normal file
21
system/typemill/author/system/users.twig
Normal file
@@ -0,0 +1,21 @@
|
||||
{% extends 'layouts/layoutSystem.twig' %}
|
||||
{% block title %}{{ translate('Users') }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1 class="text-3xl font-bold mb-4">{{ translate('Users') }} </h1>
|
||||
|
||||
<div id="users" v-cloak></div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-users.js?v={{ settings.version }}"></script>
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-translate.js?v={{ settings.version }}"></script>
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-shared.js?v={{ settings.version }}"></script>
|
||||
<script>
|
||||
app.mount('#users');
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
@@ -1,7 +1,23 @@
|
||||
<?php
|
||||
|
||||
use Typemill\Controllers\ControllerSystemApi;
|
||||
use Typemill\Middleware\RestrictApiAccess;
|
||||
use Typemill\Controllers\ControllerApiGlobals;
|
||||
use Typemill\Controllers\ControllerApiSystemSettings;
|
||||
use Typemill\Controllers\ControllerApiSystemThemes;
|
||||
use Typemill\Controllers\ControllerApiSystemPlugins;
|
||||
use Typemill\Controllers\ControllerApiSystemUsers;
|
||||
|
||||
$app->get('/api/v1/systemnavi', ControllerApiGlobals::class . ':getSystemnavi')->setName('api.systemnavi.get')->add(new RestrictApiAccess());
|
||||
$app->get('/api/v1/mainnavi', ControllerApiGlobals::class . ':getMainnavi')->setName('api.mainnavi.get')->add(new RestrictApiAccess());
|
||||
$app->get('/api/v1/settings', ControllerApiSystemSettings::class . ':getSettings')->setName('api.settings.get')->add(new RestrictApiAccess());
|
||||
$app->post('/api/v1/settings', ControllerApiSystemSettings::class . ':updateSettings')->setName('api.settings.set')->add(new RestrictApiAccess());
|
||||
$app->post('/api/v1/theme', ControllerApiSystemThemes::class . ':updateTheme')->setName('api.theme.set')->add(new RestrictApiAccess());
|
||||
$app->post('/api/v1/plugin', ControllerApiSystemPlugins::class . ':updatePlugin')->setName('api.plugin.set')->add(new RestrictApiAccess());
|
||||
$app->get('/api/v1/users/getbynames', ControllerApiSystemUsers::class . ':getUsersByNames')->setName('api.usersbynames')->add(new RestrictApiAccess());
|
||||
$app->get('/api/v1/users/getbyemail', ControllerApiSystemUsers::class . ':getUsersByEmail')->setName('api.usersbyemail')->add(new RestrictApiAccess());
|
||||
$app->get('/api/v1/users/getbyrole', ControllerApiSystemUsers::class . ':getUsersByRole')->setName('api.usersbyrole')->add(new RestrictApiAccess());
|
||||
$app->put('/api/v1/account', ControllerApiSystemUsers::class . ':updateUser')->setName('api.user.update')->add(new RestrictApiAccess());
|
||||
|
||||
|
||||
|
||||
# https://stackoverflow.blog/2021/10/06/best-practices-for-authentication-and-authorization-for-rest-apis/
|
||||
@@ -19,11 +35,6 @@ use Typemill\Middleware\RestrictApiAccess;
|
||||
# AUTHORIZATION: apikey username.apikey
|
||||
|
||||
|
||||
$app->get('/api/v1/settings', ControllerSystemApi::class . ':getSettings')->setName('api.settings.get')->add(new RestrictApiAccess());
|
||||
$app->get('/api/v1/systemnavi', ControllerSystemApi::class . ':getSystemnavi')->setName('api.systemnavi.get')->add(new RestrictApiAccess());
|
||||
$app->get('/api/v1/mainnavi', ControllerSystemApi::class . ':getMainnavi')->setName('api.mainnavi.get')->add(new RestrictApiAccess());
|
||||
|
||||
|
||||
/*
|
||||
use Typemill\Controllers\ControllerAuthorArticleApi;
|
||||
use Typemill\Controllers\ControllerAuthorBlockApi;
|
||||
|
@@ -1,21 +1,30 @@
|
||||
<?php
|
||||
|
||||
use Typemill\Controllers\ControllerWebFrontend;
|
||||
#use Typemill\Controllers\ControllerWebFrontend;
|
||||
use Typemill\Controllers\ControllerWebLogin;
|
||||
use Typemill\Controllers\ControllerSystem;
|
||||
use Typemill\Controllers\ControllerWebSystem;
|
||||
use Typemill\Middleware\RedirectIfAuthenticated;
|
||||
use Typemill\Middleware\RedirectIfUnauthenticated;
|
||||
use Slim\Views\TwigMiddleware;
|
||||
#use Slim\Views\TwigMiddleware;
|
||||
|
||||
$app->get('/tm/login', ControllerWebLogin::class . ':show')->setName('auth.show')->add(new RedirectIfAuthenticated($routeParser, $settings));
|
||||
$app->post('/tm/login', ControllerWebLogin::class . ':login')->setName('auth.login')->add(new RedirectIfAuthenticated($routeParser, $settings));
|
||||
|
||||
$app->get('/tm/system', ControllerSystem::class . ':showSettings')->setName('settings.show')->add(new RedirectIfUnauthenticated($routeParser));
|
||||
$app->get('/tm/themes', ControllerSystem::class . ':showThemes')->setName('themes.show');
|
||||
$app->get('/tm/plugins', ControllerSystem::class . ':showPlugins')->setName('plugins.show');
|
||||
$app->get('/tm/account', ControllerSystem::class . ':showAccount')->setName('user.account');
|
||||
$app->get('/tm/user/new', ControllerSystem::class . ':newUser')->setName('user.new');
|
||||
$app->get('/tm/users', ControllerSystem::class . ':listUser')->setName('user.list');
|
||||
$app->get('/tm/system', ControllerWebSystem::class . ':showSettings')->setName('settings.show')->add(new RedirectIfUnauthenticated($routeParser));
|
||||
$app->get('/tm/themes', ControllerWebSystem::class . ':showThemes')->setName('themes.show')->add(new RedirectIfUnauthenticated($routeParser));
|
||||
$app->get('/tm/plugins', ControllerWebSystem::class . ':showPlugins')->setName('plugins.show')->add(new RedirectIfUnauthenticated($routeParser));
|
||||
$app->get('/tm/account', ControllerWebSystem::class . ':showAccount')->setName('user.account')->add(new RedirectIfUnauthenticated($routeParser));
|
||||
$app->get('/tm/users', ControllerWebSystem::class . ':showUsers')->setName('users.show')->add(new RedirectIfUnauthenticated($routeParser));
|
||||
|
||||
|
||||
|
||||
|
||||
# $app->get('/tm/user/new', ControllerSystem::class . ':newUser')->setName('user.new');
|
||||
# $app->get('/tm/users', ControllerSystem::class . ':listUser')->setName('user.list');
|
||||
# $app->post('/tm/user/create', ControllerSettings::class . ':createUser')->setName('user.create')->add(new accessMiddleware($container['router'], $container['acl'], 'user', 'create'));
|
||||
# $app->post('/tm/user/update', ControllerSettings::class . ':updateUser')->setName('user.update')->add(new accessMiddleware($container['router'], $container['acl'], 'user', 'update'));
|
||||
# $app->post('/tm/user/delete', ControllerSettings::class . ':deleteUser')->setName('user.delete')->add(new accessMiddleware($container['router'], $container['acl'], 'user', 'delete'));
|
||||
# $app->get('/tm/user/{username}', ControllerSettings::class . ':showUser')->setName('user.show')->add(new accessMiddleware($container['router'], $container['acl'], 'user', 'view'));
|
||||
|
||||
|
||||
|
||||
@@ -55,8 +64,6 @@ else
|
||||
$app->post('/tm/formpost', ControllerFrontendForms::class . ':savePublicForm')->setName('form.save');
|
||||
|
||||
$app->get('/tm', ControllerFrontendAuth::class . ':redirect');
|
||||
$app->get('/tm/login', ControllerFrontendAuth::class . ':show')->setName('auth.show')->add(new RedirectIfAuthenticated($container['router'], $container['settings']));
|
||||
$app->post('/tm/login', ControllerFrontendAuth::class . ':login')->setName('auth.login')->add(new RedirectIfAuthenticated($container['router'], $container['settings']));
|
||||
$app->get('/tm/logout', ControllerFrontendAuth::class . ':logout')->setName('auth.logout')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
|
||||
|
||||
if(isset($settings['settings']['recoverpw']) && $settings['settings']['recoverpw'])
|
||||
@@ -67,22 +74,22 @@ if(isset($settings['settings']['recoverpw']) && $settings['settings']['recoverpw
|
||||
$app->post('/tm/recoverpwnew', ControllerFrontendAuth::class . ':createrecoverpasswordnew')->setName('auth.recoverpwnew')->add(new RedirectIfAuthenticated($container['router'], $container['settings']));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
|
||||
MIGRATED
|
||||
|
||||
$app->get('/tm/settings', ControllerSettings::class . ':showSettings')->setName('settings.show')->add(new accessMiddleware($container['router'], $container['acl'], 'system', 'view'));
|
||||
$app->post('/tm/settings', ControllerSettings::class . ':saveSettings')->setName('settings.save')->add(new accessMiddleware($container['router'], $container['acl'], 'system', 'update'));
|
||||
|
||||
$app->get('/tm/themes', ControllerSettings::class . ':showThemes')->setName('themes.show')->add(new accessMiddleware($container['router'], $container['acl'], 'system', 'view'));
|
||||
$app->post('/tm/themes', ControllerSettings::class . ':saveThemes')->setName('themes.save')->add(new accessMiddleware($container['router'], $container['acl'], 'system', 'update'));
|
||||
|
||||
$app->get('/tm/plugins', ControllerSettings::class . ':showPlugins')->setName('plugins.show')->add(new accessMiddleware($container['router'], $container['acl'], 'system', 'view'));
|
||||
$app->post('/tm/plugins', ControllerSettings::class . ':savePlugins')->setName('plugins.save')->add(new accessMiddleware($container['router'], $container['acl'], 'system', 'update'));
|
||||
|
||||
$app->get('/tm/account', ControllerSettings::class . ':showAccount')->setName('user.account')->add(new accessMiddleware($container['router'], $container['acl'], 'user', 'view'));
|
||||
$app->get('/tm/user/new', ControllerSettings::class . ':newUser')->setName('user.new')->add(new accessMiddleware($container['router'], $container['acl'], 'user', 'create'));
|
||||
$app->post('/tm/user/create', ControllerSettings::class . ':createUser')->setName('user.create')->add(new accessMiddleware($container['router'], $container['acl'], 'user', 'create'));
|
||||
$app->post('/tm/user/update', ControllerSettings::class . ':updateUser')->setName('user.update')->add(new accessMiddleware($container['router'], $container['acl'], 'user', 'update'));
|
||||
$app->post('/tm/user/delete', ControllerSettings::class . ':deleteUser')->setName('user.delete')->add(new accessMiddleware($container['router'], $container['acl'], 'user', 'delete'));
|
||||
$app->get('/tm/user/{username}', ControllerSettings::class . ':showUser')->setName('user.show')->add(new accessMiddleware($container['router'], $container['acl'], 'user', 'view'));
|
||||
$app->get('/tm/users', ControllerSettings::class . ':listUser')->setName('user.list')->add(new accessMiddleware($container['router'], $container['acl'], 'userlist', 'view'));
|
||||
$app->get('/tm/login', ControllerFrontendAuth::class . ':show')->setName('auth.show')->add(new RedirectIfAuthenticated($container['router'], $container['settings']));
|
||||
$app->post('/tm/login', ControllerFrontendAuth::class . ':login')->setName('auth.login')->add(new RedirectIfAuthenticated($container['router'], $container['settings']));
|
||||
|
||||
|
||||
|
||||
$app->get('/tm/content/raw[/{params:.*}]', ControllerAuthorEditor::class . ':showContent')->setName('content.raw')->add(new accessMiddleware($container['router'], $container['acl'], 'content', 'view'));
|
||||
$app->get('/tm/content/visual[/{params:.*}]', ControllerAuthorEditor::class . ':showBlox')->setName('content.visual')->add(new accessMiddleware($container['router'], $container['acl'], 'content', 'view'));
|
||||
|
@@ -7,6 +7,7 @@ copyright: 'Copyright'
|
||||
language: 'en'
|
||||
langattr: 'en'
|
||||
themeFolder: 'themes'
|
||||
pluginFolder: 'plugins'
|
||||
settingsFolder: 'settings'
|
||||
contentFolder: 'content'
|
||||
authorFolder: '/system/typemill/author'
|
||||
|
@@ -1,50 +1,262 @@
|
||||
system:
|
||||
fieldsetsystem:
|
||||
type: fieldset
|
||||
legend: System
|
||||
fields:
|
||||
fieldsetsystem:
|
||||
type: fieldset
|
||||
legend: System
|
||||
fields:
|
||||
title:
|
||||
type: text
|
||||
label: Website Title
|
||||
class: medium
|
||||
maxlength: 60
|
||||
author:
|
||||
type: text
|
||||
label: Author
|
||||
class: medium
|
||||
maxlength: 60
|
||||
copyright:
|
||||
type: text
|
||||
label: Copyright
|
||||
class: medium
|
||||
maxlength: 60
|
||||
year:
|
||||
type: text
|
||||
label: Year
|
||||
class: medium
|
||||
maxlength: 60
|
||||
fieldsetmedia:
|
||||
type: fieldset
|
||||
legend: Media
|
||||
fields:
|
||||
title:
|
||||
type: text
|
||||
label: Website Title
|
||||
class: medium
|
||||
maxlength: 60
|
||||
author:
|
||||
type: text
|
||||
label: Author
|
||||
class: medium
|
||||
maxlength: 60
|
||||
copyright:
|
||||
type: text
|
||||
label: Copyright
|
||||
class: medium
|
||||
maxlength: 60
|
||||
year:
|
||||
type: text
|
||||
label: Year
|
||||
class: medium
|
||||
maxlength: 60
|
||||
title:
|
||||
type: text
|
||||
label: Website Title
|
||||
maxlength: 60
|
||||
css: w-half
|
||||
author:
|
||||
type: text
|
||||
label: Author
|
||||
css: w-half
|
||||
maxlength: 60
|
||||
copyright:
|
||||
type: select
|
||||
label: Copyright
|
||||
css: w-half
|
||||
maxlength: 60
|
||||
options:
|
||||
'©': '©'
|
||||
'CC-BY': 'CC-BY'
|
||||
'CC-BY-NC': 'CC-BY-NC'
|
||||
'CC-BY-NC-ND': 'CC-BY-NC-ND'
|
||||
'CC-BY-NC-SA': 'CC-BY-NC-SA'
|
||||
'CC-BY-ND': 'CC-BY-ND'
|
||||
'CC-BY-SA': 'CC-BY-SA'
|
||||
year:
|
||||
type: text
|
||||
label: Year
|
||||
css: w-half
|
||||
maxlength: 4
|
||||
language:
|
||||
type: select
|
||||
label: Language (admin ui)
|
||||
css: w-half
|
||||
maxlength: 60
|
||||
options:
|
||||
'en': 'English'
|
||||
'ru': 'Russian'
|
||||
'nl': 'Dutch, Flemish'
|
||||
'de': 'German'
|
||||
'it': 'Italian'
|
||||
'fr': 'French'
|
||||
langattr:
|
||||
type: text
|
||||
label: Language attribute (website)
|
||||
css: w-half
|
||||
maxlength: 5
|
||||
description: Please use ISO 639-1 codes like "en"
|
||||
sitemap:
|
||||
type: text
|
||||
label: Google sitemap (readonly)
|
||||
css: w-half
|
||||
readonly: true
|
||||
description: You can ping the sitemap with the following links to google and bing
|
||||
pingsitemap:
|
||||
type: checkbox
|
||||
label: Ping sitemap
|
||||
css: w-half
|
||||
checkboxlabel: Ping sitemap automatically after publishing a page
|
||||
fieldsetmedia:
|
||||
type: fieldset
|
||||
legend: Media
|
||||
fields:
|
||||
logo:
|
||||
type: text
|
||||
label: Logo
|
||||
css: w-half
|
||||
maxlength: 60
|
||||
favicon:
|
||||
type: text
|
||||
label: Favicon
|
||||
css: w-half
|
||||
maxlength: 60
|
||||
width:
|
||||
type: text
|
||||
label: Standard width for images
|
||||
description: This applies only for future images in the content area.
|
||||
css: w-half
|
||||
maxlength: 60
|
||||
height:
|
||||
type: text
|
||||
label: Standard height for images
|
||||
description: If you add a value for the height, then the image will be cropped.
|
||||
css: w-half
|
||||
maxlength: 60
|
||||
svg:
|
||||
type: checkbox
|
||||
label: Upload svg images
|
||||
checkboxlabel: Allow upload of svg images (svg can be malicious, use trusted sources)
|
||||
css: w-full
|
||||
maxuploadsize:
|
||||
type: number
|
||||
label: Maximum size for file-uploads in MB
|
||||
description: The maximum file size might be limited by your server settings.
|
||||
css: w-half
|
||||
fieldsetwriting:
|
||||
type: fieldset
|
||||
legend: Writing
|
||||
fields:
|
||||
editor:
|
||||
type: radio
|
||||
label: Standard editor mode
|
||||
css: w-half
|
||||
options:
|
||||
'visual': 'visual editor'
|
||||
'raw': 'raw editor'
|
||||
formats:
|
||||
type: checkboxlist
|
||||
label: Format options for visual editor
|
||||
css: w-half
|
||||
options:
|
||||
'markdown': 'markdown'
|
||||
'headline': 'headline'
|
||||
'ulist': 'numbered list'
|
||||
'olist': 'bullet list'
|
||||
'table': 'table'
|
||||
'quote': 'quote'
|
||||
'notice': 'notice'
|
||||
'image': 'image'
|
||||
'video': 'video'
|
||||
'file': 'file'
|
||||
'toc': 'table of contents'
|
||||
'hr': 'horizontal line'
|
||||
'definition': 'definition list'
|
||||
'code': 'code'
|
||||
'shortcode': 'shortcode'
|
||||
headlineanchors:
|
||||
type: checkbox
|
||||
label: Headline anchors
|
||||
checkboxlabel: Show anchors next to headline in frontend
|
||||
css: w-full
|
||||
urlschemes:
|
||||
type: text
|
||||
label: Url schemes
|
||||
description: Add more url schemes for external links e.g. like dict:// (comma separated list)
|
||||
css: w-full
|
||||
maxlength: 60
|
||||
fieldsetaccess:
|
||||
type: fieldset
|
||||
legend: Restrictions
|
||||
fields:
|
||||
access:
|
||||
type: checkbox
|
||||
label: Website restriction
|
||||
checkboxlabel: Show the website only to authenticated users and redirect all other users to the login page.
|
||||
css: w-full
|
||||
pageaccess:
|
||||
type: checkbox
|
||||
label: Page restriction
|
||||
checkboxlabel: Activate individual restrictions for pages in the meta-tab of each page.
|
||||
css: w-full
|
||||
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).
|
||||
css: w-full
|
||||
restrictionnotice:
|
||||
type: textarea
|
||||
label: Restriction notice (use markdown)
|
||||
css: w-full
|
||||
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)
|
||||
css: w-full
|
||||
fieldsetrecovery:
|
||||
type: fieldset
|
||||
legend: PW recovery
|
||||
fields:
|
||||
recoverpw:
|
||||
type: checkbox
|
||||
label: Recover password
|
||||
checkboxlabel: Activate the password recovery.
|
||||
css: w-full
|
||||
recoverfrom:
|
||||
type: text
|
||||
label: Sender email
|
||||
placeholder: your@email.org
|
||||
css: w-full
|
||||
maxlength: 60
|
||||
recoversubject:
|
||||
type: text
|
||||
label: Email subject
|
||||
placeholder: Recover your password
|
||||
css: w-full
|
||||
maxlength: 60
|
||||
recovermessage:
|
||||
type: textarea
|
||||
label: Text before recover link in email message
|
||||
css: w-full
|
||||
maxlength: 2000
|
||||
fieldsetdeveloper:
|
||||
type: fieldset
|
||||
legend: Developer
|
||||
fields:
|
||||
displayErrorDetails:
|
||||
type: checkbox
|
||||
label: Error reporting
|
||||
checkboxlabel: Display application errors
|
||||
css: w-full
|
||||
securitylog:
|
||||
type: checkbox
|
||||
label: Security log
|
||||
checkboxlabel: Track spam and suspicious actions in a logfile
|
||||
css: w-full
|
||||
twigcache:
|
||||
type: checkbox
|
||||
label: Twig cache
|
||||
checkboxlabel: Activate the cache for twig templates
|
||||
css: w-full
|
||||
refreshcache:
|
||||
type: checkbox
|
||||
label: Refresh cache
|
||||
checkboxlabel: Refresh the cache every 10 minutes. Use this option if you change content-files via FTP.
|
||||
css: w-full
|
||||
proxy:
|
||||
type: checkbox
|
||||
label: Proxy
|
||||
checkboxlabel: Use x-forwarded-header.
|
||||
css: w-half
|
||||
trustedproxies:
|
||||
type: text
|
||||
label: Trusted IPs for proxies (comma separated)
|
||||
css: w-half
|
||||
headersoff:
|
||||
type: checkbox
|
||||
label: Disable headers
|
||||
checkboxlabel: Disable the typemill headers and send your owwn
|
||||
css: w-full
|
||||
fieldsetapi:
|
||||
type: fieldset
|
||||
legend: API
|
||||
fields:
|
||||
api:
|
||||
type: checkbox
|
||||
label: Activate api
|
||||
checkboxlabel: Activate the api
|
||||
css: w-full
|
||||
apikey:
|
||||
type: text
|
||||
label: Api key
|
||||
css: w-full
|
||||
apirole:
|
||||
type: select
|
||||
label: Api role
|
||||
css: w-full
|
||||
options:
|
||||
'admin': 'admin'
|
||||
'editor': 'editor'
|
||||
'author': 'author'
|
||||
'member': 'member'
|
||||
fieldsetlicence:
|
||||
type: fieldset
|
||||
legend: Licence
|
||||
fields:
|
||||
licencekey:
|
||||
type: text
|
||||
label: Your licence for maker or business features
|
||||
css: w-full
|
@@ -24,7 +24,7 @@
|
||||
'aclprivilege': 'view'
|
||||
'users':
|
||||
'title': 'Users'
|
||||
'routename': 'user.list'
|
||||
'routename': 'users.show'
|
||||
'icon': 'icon-group'
|
||||
'aclresource': 'userlist'
|
||||
'aclprivilege': 'view'
|
34
system/typemill/settings/user.yaml
Normal file
34
system/typemill/settings/user.yaml
Normal file
@@ -0,0 +1,34 @@
|
||||
username:
|
||||
name: username
|
||||
label: 'Username (read only)'
|
||||
type: 'text'
|
||||
readonly: true
|
||||
firstname:
|
||||
name: firstname
|
||||
label: 'First Name'
|
||||
type: 'text'
|
||||
lastname:
|
||||
name: lastname
|
||||
label: 'Last Name'
|
||||
type: 'text'
|
||||
email:
|
||||
name: email
|
||||
label: 'E-Mail'
|
||||
type: 'text'
|
||||
required: true
|
||||
userrole:
|
||||
name: userrole
|
||||
label: 'Role'
|
||||
type: 'text'
|
||||
readonly: true
|
||||
password:
|
||||
name: password
|
||||
label: 'Actual Password'
|
||||
type: 'password'
|
||||
autocomplete: 'new-password'
|
||||
newpassword:
|
||||
name: newpassword
|
||||
label: 'New Password'
|
||||
type: 'password'
|
||||
autocomplete: 'new-password'
|
||||
generator: true
|
@@ -17,6 +17,7 @@ use Typemill\Static\Plugins;
|
||||
use Typemill\Static\Translations;
|
||||
use Typemill\Static\Permissions;
|
||||
use Typemill\Static\Session;
|
||||
use Typemill\Static\Helpers;
|
||||
use Typemill\Events\OnSettingsLoaded;
|
||||
use Typemill\Events\OnPluginsLoaded;
|
||||
use Typemill\Events\OnSessionSegmentsLoaded;
|
||||
@@ -29,8 +30,6 @@ use Typemill\Extensions\TwigUrlExtension;
|
||||
use Typemill\Extensions\TwigUserExtension;
|
||||
use Typemill\Models\StorageWrapper;
|
||||
|
||||
# require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$timer = [];
|
||||
$timer['start'] = microtime(true);
|
||||
|
||||
@@ -65,10 +64,7 @@ if(isset($settings['displayErrorDetails']) && $settings['displayErrorDetails'])
|
||||
# ADD THEM TO THE SETTINGS AND YOU HAVE THEM EVERYWHERE??
|
||||
$uriFactory = new UriFactory();
|
||||
$uri = $uriFactory->createFromGlobals($_SERVER);
|
||||
|
||||
$settings['fullpath'] = $uri->getPath();
|
||||
$settings['basepath'] = preg_replace('/(.*)\/.*/', '$1', $_SERVER['SCRIPT_NAME']);
|
||||
$settings['routepath'] = str_replace($settings['basepath'], '', $settings['fullpath']);
|
||||
$urlinfo = Helpers::urlInfo($uri);
|
||||
|
||||
$timer['settings'] = microtime(true);
|
||||
|
||||
@@ -89,8 +85,11 @@ $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);
|
||||
|
||||
# in slim 4 you alsways have to set application basepath
|
||||
$app->setBasePath($settings['basepath']);
|
||||
$app->setBasePath($urlinfo['basepath']);
|
||||
|
||||
$timer['container'] = microtime(true);
|
||||
|
||||
@@ -189,7 +188,7 @@ $timer['permissions'] = microtime(true);
|
||||
if( ( isset($settings['access']) && $settings['access'] ) || ( isset($settings['pageaccess']) && $settings['pageaccess'] ) )
|
||||
{
|
||||
# activate session for all routes
|
||||
$session_segments = [$settings['routepath']];
|
||||
$session_segments = [$urlinfo['route']];
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -201,7 +200,7 @@ else
|
||||
}
|
||||
|
||||
# start session
|
||||
Session::startSessionForSegments($session_segments, $settings['routepath']);
|
||||
Session::startSessionForSegments($session_segments, $urlinfo['route']);
|
||||
|
||||
$timer['session segments'] = microtime(true);
|
||||
|
||||
@@ -209,11 +208,15 @@ $timer['session segments'] = microtime(true);
|
||||
* OTHER CONTAINER ITEMS *
|
||||
****************************/
|
||||
|
||||
# translations
|
||||
$translations = Translations::loadTranslations($settings, $urlinfo['route']);
|
||||
$container->set('translations', $translations);
|
||||
|
||||
# dispatcher to container
|
||||
$container->set('dispatcher', function() use ($dispatcher){ return $dispatcher; });
|
||||
|
||||
# asset function for plugins
|
||||
$container->set('assets', function() use ($settings){ return new \Typemill\Assets($settings['basepath']); });
|
||||
$container->set('assets', function() use ($urlinfo){ return new \Typemill\Assets($urlinfo['basepath']); });
|
||||
|
||||
# Register Middleware On Container
|
||||
$csrf = false;
|
||||
@@ -230,13 +233,12 @@ if(isset($_SESSION))
|
||||
# $app->add(new ValidationErrors($container->get('view')));
|
||||
}
|
||||
|
||||
|
||||
/****************************
|
||||
* TWIG TO CONTAINER *
|
||||
****************************/
|
||||
|
||||
$container->set('view', function() use ($settings, $csrf, $uri) {
|
||||
|
||||
$container->set('view', function() use ($settings, $csrf, $urlinfo, $translations) {
|
||||
|
||||
$twig = Twig::create(
|
||||
[
|
||||
# path to templates
|
||||
@@ -256,7 +258,7 @@ $container->set('view', function() use ($settings, $csrf, $uri) {
|
||||
# add extensions
|
||||
$twig->addExtension(new DebugExtension());
|
||||
$twig->addExtension(new TwigUserExtension());
|
||||
$twig->addExtension(new TwigUrlExtension($uri, $settings['basepath']));
|
||||
$twig->addExtension(new TwigUrlExtension($urlinfo));
|
||||
|
||||
# $twig->addExtension(new \Nquire\Extensions\TwigUserExtension());
|
||||
|
||||
@@ -265,9 +267,6 @@ $container->set('view', function() use ($settings, $csrf, $uri) {
|
||||
$twig->addExtension(new TwigCsrfExtension($csrf));
|
||||
}
|
||||
|
||||
# translations
|
||||
$translations = Translations::loadTranslations($settings);
|
||||
# $twig->getEnvironment()->addGlobal('translations', $translations);
|
||||
$twig->addExtension(new Typemill\Extensions\TwigLanguageExtension( $translations ));
|
||||
|
||||
return $twig;
|
||||
|
@@ -2,7 +2,11 @@
|
||||
module.exports = {
|
||||
content: ["./system/typemill/author/**/*.{html,js,twig}"],
|
||||
theme: {
|
||||
extend: {},
|
||||
extend: {
|
||||
width: {
|
||||
'half': '48%',
|
||||
}
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
}
|
@@ -18,31 +18,29 @@ settings:
|
||||
forms:
|
||||
fields:
|
||||
|
||||
layoutsize:
|
||||
type: select
|
||||
label: Layout Size
|
||||
options:
|
||||
standard: Standard
|
||||
large: Large
|
||||
full: Full Width
|
||||
|
||||
posts:
|
||||
layout:
|
||||
type: fieldset
|
||||
legend: Configure Posts
|
||||
legend: General Layout
|
||||
fields:
|
||||
layoutsize:
|
||||
type: select
|
||||
label: Layout Size
|
||||
options:
|
||||
standard: Standard
|
||||
large: Large
|
||||
full: Full Width
|
||||
blogimage:
|
||||
type: checkbox
|
||||
label: Post-Images
|
||||
checkboxlabel: Generally show hero images in all lists of posts
|
||||
|
||||
blog:
|
||||
type: checkbox
|
||||
checkboxlabel: Activate a list of posts on the homepage
|
||||
|
||||
bloghomepage:
|
||||
type: fieldset
|
||||
legend: Posts on Homepage
|
||||
fields:
|
||||
blog:
|
||||
type: checkbox
|
||||
checkboxlabel: Activate a list of posts on the homepage
|
||||
blogintro:
|
||||
type: checkbox
|
||||
label: Intro Content
|
||||
@@ -52,9 +50,13 @@ forms:
|
||||
label: Enter the folder path with the posts
|
||||
placeholder: /blog
|
||||
|
||||
landingpage:
|
||||
type: checkbox
|
||||
checkboxlabel: Activate a landing page with segments on the homepage
|
||||
landing:
|
||||
type: fieldset
|
||||
legend: Landingpage
|
||||
fields:
|
||||
landingpage:
|
||||
type: checkbox
|
||||
checkboxlabel: Activate a landing page with segments on the homepage
|
||||
|
||||
landingpageIntro:
|
||||
type: fieldset
|
||||
@@ -64,11 +66,13 @@ forms:
|
||||
type: number
|
||||
label: Position of Intro Segment
|
||||
description: Use 0 to disable the section
|
||||
css: 'w-half'
|
||||
introTitle:
|
||||
type: text
|
||||
label: Title for your landingpage intro
|
||||
placeholder: Typemill
|
||||
description: Leave empty to use the title of your base content page.
|
||||
css: 'w-half'
|
||||
introMarkdown:
|
||||
type: textarea
|
||||
label: Text for your landingpage intro (use markdown)
|
||||
@@ -77,10 +81,12 @@ forms:
|
||||
type: text
|
||||
label: Link for startbutton
|
||||
placeholder: /my/deeplink
|
||||
css: 'w-half'
|
||||
introButtonLabel:
|
||||
type: text
|
||||
label: Label for startbutton
|
||||
placeholder: my label
|
||||
css: 'w-half'
|
||||
introFullsize:
|
||||
type: checkbox
|
||||
label: Full Screen
|
||||
@@ -117,45 +123,51 @@ forms:
|
||||
teaser1title:
|
||||
type: text
|
||||
label: Teaser 1 Title
|
||||
css: 'w-half'
|
||||
teaser1text:
|
||||
type: text
|
||||
label: Teaser 1 Text
|
||||
css: 'w-half'
|
||||
teaser1link:
|
||||
type: text
|
||||
label: Teaser 1 Link
|
||||
fieldsize: half
|
||||
css: 'w-half'
|
||||
teaser1label:
|
||||
type: text
|
||||
label: Teaser 1 Label
|
||||
fieldsize: half
|
||||
css: 'w-half'
|
||||
teaser2title:
|
||||
type: text
|
||||
label: Teaser 2 Title
|
||||
css: 'w-half'
|
||||
teaser2text:
|
||||
type: text
|
||||
label: Teaser 2 Text
|
||||
css: 'w-half'
|
||||
teaser2link:
|
||||
type: text
|
||||
label: Teaser 2 Link
|
||||
fieldsize: half
|
||||
css: 'w-half'
|
||||
teaser2label:
|
||||
type: text
|
||||
label: Teaser 2 Label
|
||||
fieldsize: half
|
||||
css: 'w-half'
|
||||
teaser3title:
|
||||
type: text
|
||||
label: Teaser 3 Title
|
||||
css: 'w-half'
|
||||
teaser3text:
|
||||
type: text
|
||||
label: Teaser 3 Text
|
||||
css: 'w-half'
|
||||
teaser3link:
|
||||
type: text
|
||||
label: Teaser 3 Link
|
||||
fieldsize: half
|
||||
css: 'w-half'
|
||||
teaser3label:
|
||||
type: text
|
||||
label: Teaser 3 Label
|
||||
fieldsize: half
|
||||
css: 'w-half'
|
||||
|
||||
landingpageContrast:
|
||||
type: fieldset
|
||||
@@ -174,9 +186,11 @@ forms:
|
||||
contrastLink:
|
||||
type: text
|
||||
label: Button Link
|
||||
css: 'w-half'
|
||||
contrastLabel:
|
||||
type: text
|
||||
label: Button Label
|
||||
css: 'w-half'
|
||||
|
||||
landingpageNavi:
|
||||
type: fieldset
|
||||
@@ -201,19 +215,23 @@ forms:
|
||||
type: number
|
||||
label: Position of News Segment
|
||||
description: Use 0 to disable the section
|
||||
css: 'w-half'
|
||||
newsHeadline:
|
||||
type: text
|
||||
label: Headline for news-segment
|
||||
placeholder: News
|
||||
css: 'w-half'
|
||||
newsFolder:
|
||||
type: text
|
||||
label: List entries from folder
|
||||
placeholder: /blog
|
||||
description: Add a path to a folder from which you want to list entries
|
||||
css: 'w-half'
|
||||
newsLabel:
|
||||
type: text
|
||||
label: Label for read more link
|
||||
placeholder: All News
|
||||
css: 'w-half'
|
||||
|
||||
fieldsetAuthor:
|
||||
type: fieldset
|
||||
@@ -246,10 +264,12 @@ forms:
|
||||
type: text
|
||||
label: Date intro text
|
||||
placeholder: Last Updated
|
||||
css: 'w-half'
|
||||
|
||||
dateFormat:
|
||||
type: select
|
||||
label: Date format
|
||||
css: 'w-half'
|
||||
options:
|
||||
'm/d/Y': 01/20/2020
|
||||
'd.m.Y': 20.01.2020
|
||||
@@ -318,18 +338,22 @@ forms:
|
||||
type: text
|
||||
label: Label for expand button
|
||||
placeholder: expand navigation
|
||||
css: 'w-half'
|
||||
collapse:
|
||||
type: text
|
||||
label: Label for collapse button
|
||||
placeholder: collapse navigation
|
||||
css: 'w-half'
|
||||
next:
|
||||
type: text
|
||||
label: Label for next link
|
||||
placeholder: next
|
||||
css: 'w-half'
|
||||
previous:
|
||||
type: text
|
||||
label: Label for previous link
|
||||
placeholder: previous
|
||||
css: 'w-half'
|
||||
|
||||
fieldsetfooter:
|
||||
type: fieldset
|
||||
@@ -420,6 +444,7 @@ forms:
|
||||
calisto,serif: calisto (serif)
|
||||
garamond,serif: garamond (serif)
|
||||
baskerville,serif: baskerville (serif)
|
||||
|
||||
fieldsetColors:
|
||||
type: fieldset
|
||||
legend: Colors
|
||||
@@ -428,83 +453,83 @@ forms:
|
||||
type: text
|
||||
label: Background color for body
|
||||
placeholder: 'leightseagreen'
|
||||
fieldsize: half
|
||||
css: 'w-half'
|
||||
fontcolorprimary:
|
||||
type: text
|
||||
label: Font color for body
|
||||
placeholder: 'white'
|
||||
fieldsize: half
|
||||
css: 'w-half'
|
||||
newsbackground:
|
||||
type: text
|
||||
label: Background color for news-box
|
||||
placeholder: 'white'
|
||||
fieldsize: half
|
||||
css: 'w-half'
|
||||
newscolor:
|
||||
type: text
|
||||
label: Font color for news-box
|
||||
placeholder: '#333'
|
||||
fieldsize: half
|
||||
css: 'w-half'
|
||||
brandcolortertiary:
|
||||
type: text
|
||||
label: Background color for buttons
|
||||
placeholder: 'lightseagreen'
|
||||
fieldsize: half
|
||||
css: 'w-half'
|
||||
fontcolortertiary:
|
||||
type: text
|
||||
label: Font color for buttons
|
||||
placeholder: '#F7F7F7'
|
||||
fieldsize: half
|
||||
css: 'w-half'
|
||||
bordercolortertiary:
|
||||
type: text
|
||||
label: Border color for buttons
|
||||
placeholder: '#F7F7F7'
|
||||
fieldsize: half
|
||||
css: 'w-half'
|
||||
fontcolorlink:
|
||||
type: text
|
||||
label: Font color for content links
|
||||
placeholder: '#007F7F'
|
||||
fieldsize: half
|
||||
css: 'w-half'
|
||||
brandcolorsecondary:
|
||||
type: text
|
||||
label: Background color for content
|
||||
placeholder: '#f7f7f7'
|
||||
fieldsize: half
|
||||
css: 'w-half'
|
||||
fontcolorsecondary:
|
||||
type: text
|
||||
label: Font color for content
|
||||
placeholder: '#333'
|
||||
fieldsize: half
|
||||
css: 'w-half'
|
||||
codebackground:
|
||||
type: text
|
||||
label: Background color for code
|
||||
placeholder: '#ddd'
|
||||
fieldsize: half
|
||||
css: 'w-half'
|
||||
codecolor:
|
||||
type: text
|
||||
label: Font color for code
|
||||
placeholder: '#333'
|
||||
fieldsize: half
|
||||
css: 'w-half'
|
||||
contentnavihoverbackground:
|
||||
type: text
|
||||
label: Background color for hover of content navigation
|
||||
placeholder: 'lightseagreen'
|
||||
fieldsize: half
|
||||
css: 'w-half'
|
||||
contentnavihovercolor:
|
||||
type: text
|
||||
label: Font color for hover of content navigation
|
||||
placeholder: 'white'
|
||||
fieldsize: half
|
||||
css: 'w-half'
|
||||
thinbordercolor:
|
||||
type: text
|
||||
label: Thin border color
|
||||
placeholder: 'lightgray'
|
||||
description: Used for content navigation, table and horizontal line
|
||||
fieldsize: half
|
||||
css: 'w-half'
|
||||
noticecolors:
|
||||
type: checkbox
|
||||
label: Color for notices
|
||||
checkboxlabel: Use grayscale color schema for notices
|
||||
fieldsize: half
|
||||
css: 'w-half'
|
||||
|
||||
metatabs:
|
||||
meta:
|
||||
|
Reference in New Issue
Block a user