mirror of
https://github.com/typemill/typemill.git
synced 2025-07-30 19:00:32 +02:00
Typemill Version 2 first milestone system area
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -18,6 +18,7 @@ content/01-cyanine-theme/01-colors-and-fonts.yaml
|
||||
content/01-cyanine-theme/02-footer.yaml
|
||||
content/01-cyanine-theme/03-content-elements.yaml
|
||||
settings/settings.yaml
|
||||
settings/license.yaml
|
||||
settings/users
|
||||
system/vendor
|
||||
plugins/demo
|
||||
|
7
system/author/css/a11y-dark.min.css
vendored
Normal file
7
system/author/css/a11y-dark.min.css
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
||||
Theme: a11y-dark
|
||||
Author: @ericwbailey
|
||||
Maintainer: @ericwbailey
|
||||
|
||||
Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css
|
||||
*/.hljs{background:#2b2b2b;color:#f8f8f2}.hljs-comment,.hljs-quote{color:#d4d0ab}.hljs-deletion,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#ffa07a}.hljs-built_in,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-type{color:#f5ab35}.hljs-attribute{color:gold}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:#abe338}.hljs-section,.hljs-title{color:#00e0e0}.hljs-keyword,.hljs-selector-tag{color:#dcc6e0}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}@media screen and (-ms-high-contrast:active){.hljs-addition,.hljs-attribute,.hljs-built_in,.hljs-bullet,.hljs-comment,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-quote,.hljs-string,.hljs-symbol,.hljs-type{color:highlight}.hljs-keyword,.hljs-selector-tag{font-weight:700}}
|
709
system/author/js/highlight.min.js
vendored
Normal file
709
system/author/js/highlight.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
335
system/typemill/Controllers/ControllerApiImage.php
Normal file
335
system/typemill/Controllers/ControllerApiImage.php
Normal file
@@ -0,0 +1,335 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Controllers;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Typemill\Models\ProcessImage;
|
||||
use Typemill\Models\StorageWrapper;
|
||||
|
||||
|
||||
# use Typemill\Models\ProcessFile;
|
||||
# use Typemill\Models\Yaml;
|
||||
# use Typemill\Controllers\ControllerAuthorBlockApi;
|
||||
|
||||
class ControllerApiImage extends ControllerData
|
||||
{
|
||||
|
||||
# MISSING
|
||||
#
|
||||
# solution for logo
|
||||
# return error messages and display in image component
|
||||
# check if resized is bigger than original, then use original
|
||||
# use original size checkbox
|
||||
|
||||
|
||||
public function saveImage(Request $request, Response $response, $args)
|
||||
{
|
||||
$params = $request->getParsedBody();
|
||||
|
||||
if(!isset($params['image']) OR !isset($params['name']))
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'Image or name is missing.',
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
|
||||
}
|
||||
|
||||
$img = new ProcessImage();
|
||||
|
||||
if($this->settingActive('allowsvg'))
|
||||
{
|
||||
$img->addAllowedExtension('svg');
|
||||
}
|
||||
|
||||
# prepare the image
|
||||
if(!$img->prepareImage($params['image'], $params['name']))
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => $img->errors[0],
|
||||
'fullerrors' => $img->errors,
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
|
||||
}
|
||||
|
||||
# check if image name already exisits in live folder and create an unique name (do not overwrite existing files)
|
||||
$storage = new StorageWrapper('\Typemill\Models\Storage');
|
||||
$uniqueImageName = $storage->createUniqueImageName($img->getFilename(), $img->getExtension());
|
||||
$img->setFilename($uniqueImageName);
|
||||
|
||||
# store the original image
|
||||
if(!$img->storeOriginalToTmp())
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => $img->errors[0],
|
||||
'fullerrors' => $img->errors,
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
|
||||
}
|
||||
|
||||
# if image is not resizable (animated gif or svg)
|
||||
if(!$img->isResizable())
|
||||
{
|
||||
if($img->saveOriginalForAll())
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'Image saved successfully',
|
||||
'name' => 'media/live/' . $img->getFullName(),
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => $img->errors[0],
|
||||
'fullerrors' => $img->errors,
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
|
||||
}
|
||||
|
||||
# for all other image types, check if they should be transformed to webp
|
||||
if($this->settingActive('convertwebp'))
|
||||
{
|
||||
$img->setExtension('webp');
|
||||
}
|
||||
|
||||
if(!$img->storeRenditionsToTmp($this->settings['images']))
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => $img->errors[0],
|
||||
'fullerrors' => $img->errors,
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(500);
|
||||
}
|
||||
|
||||
/*
|
||||
if(isset($params['publish']) && $params['publish'])
|
||||
{
|
||||
if(!$img->publishImage($img->getFullName()))
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => $img->errors[0],
|
||||
'fullerrors' => $img->errors,
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(500);
|
||||
}
|
||||
}
|
||||
*/
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'Image saved successfully',
|
||||
'name' => 'media/tmp/' . $img->getFullName(),
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public function publishImage(Request $request, Response $response, $args)
|
||||
{
|
||||
$params = $request->getParsedBody();
|
||||
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
if(!$imageProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
# check the resize modifier in the image markdown, set it to true and delete it from markdown
|
||||
$noresize = false;
|
||||
$markdown = isset($params['markdown']) ? $params['markdown'] : false;
|
||||
|
||||
if($markdown && (strlen($markdown) > 9) && (substr($markdown, -9) == '|noresize') )
|
||||
{
|
||||
$noresize = true;
|
||||
$params['markdown'] = substr($markdown,0,-9);
|
||||
}
|
||||
|
||||
if($imageProcessor->publishImage($noresize))
|
||||
{
|
||||
$request = $request->withParsedBody($params);
|
||||
|
||||
$block = new ControllerAuthorBlockApi($this->c);
|
||||
if($params['new'])
|
||||
{
|
||||
return $block->addBlock($request, $response, $args);
|
||||
}
|
||||
return $block->updateBlock($request, $response, $args);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => 'could not store image to media folder'],500);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public function getMediaLibImages(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParsedBody();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
if(!$imageProcessor->checkFolders('images'))
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
$imagelist = $imageProcessor->scanMediaFlat();
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'images' => $imagelist
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
|
||||
}
|
||||
|
||||
public function getImage(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParsedBody();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
|
||||
$this->setStructureDraft();
|
||||
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
if(!$imageProcessor->checkFolders('images'))
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
$imageDetails = $imageProcessor->getImageDetails($this->params['name'], $this->structureDraft);
|
||||
|
||||
if($imageDetails)
|
||||
{
|
||||
return $response->withJson(['image' => $imageDetails]);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => 'Image not found or image name not valid.'], 404);
|
||||
}
|
||||
|
||||
public function deleteImage(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
|
||||
# minimum permission is that user is allowed to delete content
|
||||
if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'delete'))
|
||||
{
|
||||
return $response->withJson(array('data' => false, 'errors' => 'You are not allowed to delete images.'), 403);
|
||||
}
|
||||
|
||||
if(!isset($this->params['name']))
|
||||
{
|
||||
return $response->withJson(['errors' => 'image name is missing'],500);
|
||||
}
|
||||
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
if(!$imageProcessor->checkFolders('images'))
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
if($imageProcessor->deleteImage($this->params['name']))
|
||||
{
|
||||
return $response->withJson(['errors' => false]);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => 'Oops, looks like we could not delete all sizes of that image.'], 500);
|
||||
}
|
||||
|
||||
|
||||
public function saveVideoImage(Request $request, Response $response, $args)
|
||||
{
|
||||
/* get params from call */
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
$class = false;
|
||||
|
||||
$imageUrl = $this->params['markdown'];
|
||||
|
||||
if(strpos($imageUrl, 'https://www.youtube.com/watch?v=') !== false)
|
||||
{
|
||||
$videoID = str_replace('https://www.youtube.com/watch?v=', '', $imageUrl);
|
||||
$videoID = strpos($videoID, '&') ? substr($videoID, 0, strpos($videoID, '&')) : $videoID;
|
||||
$class = 'youtube';
|
||||
}
|
||||
if(strpos($imageUrl, 'https://youtu.be/') !== false)
|
||||
{
|
||||
$videoID = str_replace('https://youtu.be/', '', $imageUrl);
|
||||
$videoID = strpos($videoID, '?') ? substr($videoID, 0, strpos($videoID, '?')) : $videoID;
|
||||
$class = 'youtube';
|
||||
}
|
||||
|
||||
if($class == 'youtube')
|
||||
{
|
||||
$videoURLmaxres = 'https://i1.ytimg.com/vi/' . $videoID . '/maxresdefault.jpg';
|
||||
$videoURL0 = 'https://i1.ytimg.com/vi/' . $videoID . '/0.jpg';
|
||||
}
|
||||
|
||||
$ctx = stream_context_create(array(
|
||||
'https' => array(
|
||||
'timeout' => 1
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$imageData = @file_get_contents($videoURLmaxres, 0, $ctx);
|
||||
if($imageData === false)
|
||||
{
|
||||
$imageData = @file_get_contents($videoURL0, 0, $ctx);
|
||||
if($imageData === false)
|
||||
{
|
||||
return $response->withJson(array('errors' => 'could not get the video image'));
|
||||
}
|
||||
}
|
||||
|
||||
$imageData64 = 'data:image/jpeg;base64,' . base64_encode($imageData);
|
||||
$desiredSizes = ['live' => ['width' => 560, 'height' => 315]];
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
if(!$imageProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Please check if your media-folder exists and all folders inside are writable.']], 500);
|
||||
}
|
||||
|
||||
$tmpImage = $imageProcessor->createImage($imageData64, $videoID, $desiredSizes);
|
||||
|
||||
if(!$tmpImage)
|
||||
{
|
||||
return $response->withJson(array('errors' => 'could not create temporary image'));
|
||||
}
|
||||
|
||||
$imageUrl = $imageProcessor->publishImage();
|
||||
if($imageUrl)
|
||||
{
|
||||
$this->params['markdown'] = '{#' . $videoID. ' .' . $class . '}';
|
||||
|
||||
$request = $request->withParsedBody($this->params);
|
||||
$block = new ControllerAuthorBlockApi($this->c);
|
||||
if($this->params['new'])
|
||||
{
|
||||
return $block->addBlock($request, $response, $args);
|
||||
}
|
||||
return $block->updateBlock($request, $response, $args);
|
||||
}
|
||||
|
||||
return $response->withJson(array('errors' => 'could not store the preview image'));
|
||||
}
|
||||
}
|
663
system/typemill/Controllers/ControllerApiMediaOld.php
Normal file
663
system/typemill/Controllers/ControllerApiMediaOld.php
Normal file
@@ -0,0 +1,663 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Controllers;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Typemill\Models\ProcessImage;
|
||||
use Typemill\Models\ProcessFile;
|
||||
use Typemill\Models\Yaml;
|
||||
use Typemill\Controllers\ControllerAuthorBlockApi;
|
||||
|
||||
class ControllerApiMedia extends ControllerData
|
||||
{
|
||||
public function createImage(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$params = $request->getParsedBody();
|
||||
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
|
||||
if(!$imageProcessor->checkFolders('images'))
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'Please check if your media-folder exists and all folders inside are writable.'
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(403);
|
||||
}
|
||||
|
||||
$imageParts = explode(";base64,", $params['image']);
|
||||
$imageType = explode("/", $imageParts[0]);
|
||||
|
||||
if(!isset($imageType[1]))
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'We did not find an image type, the file might be corrupted.'
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(403);
|
||||
}
|
||||
|
||||
$acceptedTypes = [
|
||||
'png' => true,
|
||||
'jpg' => true,
|
||||
'jpeg' => true,
|
||||
'gif' => true,
|
||||
'webp' => true,
|
||||
];
|
||||
|
||||
if(isset($this->settings['svg']) && $this->settings['svg'])
|
||||
{
|
||||
$acceptedTypes['svg+xml'] = true;
|
||||
}
|
||||
|
||||
if(!isset($acceptedTypes[$imageType[1]]))
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'The image type is not supported.'
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(403);
|
||||
}
|
||||
|
||||
$imageResult = $imageProcessor->createImage($params['image'], $params['name'], $this->settings['images']);
|
||||
|
||||
if($imageResult)
|
||||
{
|
||||
if(is_array($imageResult) && isset($imageResult['errors']))
|
||||
{
|
||||
return $response->withJson($imageResult,422);
|
||||
}
|
||||
|
||||
# publish image directly, used for example by image field for meta-tabs
|
||||
if($params['publish'])
|
||||
{
|
||||
$imageProcessor->publishImage();
|
||||
}
|
||||
return $response->withJson(['name' => 'media/live/' . $imageProcessor->getFullName(),'errors' => false]);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'could not store image to temporary folder.'
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(403);
|
||||
}
|
||||
|
||||
public function getMediaLibImages(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParsedBody();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
if(!$imageProcessor->checkFolders('images'))
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
$imagelist = $imageProcessor->scanMediaFlat();
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'images' => $imagelist
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
|
||||
}
|
||||
|
||||
public function getMediaLibFiles(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParsedBody();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
|
||||
$fileProcessor = new ProcessFile();
|
||||
if(!$fileProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
$filelist = $fileProcessor->scanFilesFlat();
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'files' => $filelist
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
|
||||
}
|
||||
|
||||
public function getImage(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParsedBody();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
|
||||
$this->setStructureDraft();
|
||||
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
if(!$imageProcessor->checkFolders('images'))
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
$imageDetails = $imageProcessor->getImageDetails($this->params['name'], $this->structureDraft);
|
||||
|
||||
if($imageDetails)
|
||||
{
|
||||
return $response->withJson(['image' => $imageDetails]);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => 'Image not found or image name not valid.'], 404);
|
||||
}
|
||||
|
||||
public function getFile(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
|
||||
$this->setStructureDraft();
|
||||
|
||||
$fileProcessor = new ProcessFile();
|
||||
if(!$fileProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
$fileDetails = $fileProcessor->getFileDetails($this->params['name'], $this->structureDraft);
|
||||
|
||||
if($fileDetails)
|
||||
{
|
||||
return $response->withJson(['file' => $fileDetails]);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => 'file not found or file name invalid'],404);
|
||||
}
|
||||
|
||||
public function getFileRestrictions(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
$restriction = 'all';
|
||||
|
||||
$userroles = $this->c->acl->getRoles();
|
||||
|
||||
if(isset($this->params['filename']) && $this->params['filename'] != '')
|
||||
{
|
||||
$writeYaml = new WriteYaml();
|
||||
$restrictions = $writeYaml->getYaml('media' . DIRECTORY_SEPARATOR . 'files', 'filerestrictions.yaml');
|
||||
if(isset($restrictions[$this->params['filename']]))
|
||||
{
|
||||
$restriction = $restrictions[$this->params['filename']];
|
||||
}
|
||||
}
|
||||
|
||||
return $response->withJson(['userroles' => $userroles, 'restriction' => $restriction]);
|
||||
}
|
||||
|
||||
public function updateFileRestrictions(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
$filename = isset($this->params['filename']) ? $this->params['filename'] : false;
|
||||
$role = isset($this->params['role']) ? $this->params['role'] : false;
|
||||
|
||||
if(!$filename OR !$role)
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Filename or userrole is missing.']], 422);
|
||||
}
|
||||
|
||||
$userroles = $this->c->acl->getRoles();
|
||||
|
||||
if($role != 'all' AND !in_array($role, $userroles))
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Userrole is unknown.']], 422);
|
||||
}
|
||||
|
||||
$writeYaml = new WriteYaml();
|
||||
$restrictions = $writeYaml->getYaml('media' . DIRECTORY_SEPARATOR . 'files', 'filerestrictions.yaml');
|
||||
if(!$restrictions)
|
||||
{
|
||||
$restrictions = [];
|
||||
}
|
||||
|
||||
if($role == 'all')
|
||||
{
|
||||
unset($restrictions[$filename]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$restrictions[$filename] = $role;
|
||||
}
|
||||
|
||||
$writeYaml->updateYaml('media' . DIRECTORY_SEPARATOR . 'files', 'filerestrictions.yaml', $restrictions);
|
||||
|
||||
return $response->withJson(['restrictions' => $restrictions]);
|
||||
}
|
||||
|
||||
|
||||
public function uploadFile(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
|
||||
if (!isset($this->params['file']))
|
||||
{
|
||||
return $response->withJson(['errors' => 'No file found.'],404);
|
||||
}
|
||||
|
||||
$size = (int) (strlen(rtrim($this->params['file'], '=')) * 3 / 4);
|
||||
$extension = pathinfo($this->params['name'], PATHINFO_EXTENSION);
|
||||
$finfo = finfo_open( FILEINFO_MIME_TYPE );
|
||||
$mtype = @finfo_file( $finfo, $this->params['file'] );
|
||||
finfo_close($finfo);
|
||||
|
||||
if ($size === 0)
|
||||
{
|
||||
return $response->withJson(['errors' => 'File is empty.'],422);
|
||||
}
|
||||
|
||||
# 20 MB (1 byte * 1024 * 1024 * 20 (for 20 MB))
|
||||
if ($size > 20971520)
|
||||
{
|
||||
return $response->withJson(['errors' => 'File is bigger than 20MB.'],422);
|
||||
}
|
||||
|
||||
# check extension first
|
||||
if (!$this->checkAllowedExtensions($extension))
|
||||
{
|
||||
return $response->withJson(['errors' => 'File is not allowed.'],422);
|
||||
}
|
||||
|
||||
# check mimetype and extension if there is a mimetype.
|
||||
# in some environments the finfo_file does not work with a base64 string.
|
||||
if($mtype)
|
||||
{
|
||||
if(!$this->checkAllowedMimeTypes($mtype, $extension))
|
||||
{
|
||||
return $response->withJson(['errors' => 'The mime-type or file extension is not allowed.'],422);
|
||||
}
|
||||
}
|
||||
|
||||
$fileProcessor = new ProcessFile();
|
||||
|
||||
if(!$fileProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
$fileinfo = $fileProcessor->storeFile($this->params['file'], $this->params['name']);
|
||||
|
||||
if($fileinfo)
|
||||
{
|
||||
# if the previous check of the mtype with the base64 string failed, then do it now again with the temporary file
|
||||
if(!$mtype)
|
||||
{
|
||||
$filePath = str_replace('media/files', 'media/tmp', $fileinfo['url']);
|
||||
$fullPath = $this->settings['rootPath'] . $filePath;
|
||||
$finfo = finfo_open( FILEINFO_MIME_TYPE );
|
||||
$mtype = @finfo_file( $finfo, $fullPath );
|
||||
finfo_close($finfo);
|
||||
|
||||
if(!$mtype OR !$this->checkAllowedMimeTypes($mtype, $extension))
|
||||
{
|
||||
$fileProcessor->clearTempFolder();
|
||||
|
||||
return $response->withJson(['errors' => 'The mime-type is missing, not allowed or does not fit to the file extension.'],422);
|
||||
}
|
||||
}
|
||||
|
||||
# publish file directly, used for example by file field for meta-tabs
|
||||
if(isset($this->params['publish']) && $this->params['publish'])
|
||||
{
|
||||
$fileProcessor->publishFile();
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => false, 'info' => $fileinfo]);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => 'could not store file to temporary folder'],500);
|
||||
}
|
||||
|
||||
public function publishImage(Request $request, Response $response, $args)
|
||||
{
|
||||
$params = $request->getParsedBody();
|
||||
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
if(!$imageProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
# check the resize modifier in the image markdown, set it to true and delete it from markdown
|
||||
$noresize = false;
|
||||
$markdown = isset($params['markdown']) ? $params['markdown'] : false;
|
||||
|
||||
if($markdown && (strlen($markdown) > 9) && (substr($markdown, -9) == '|noresize') )
|
||||
{
|
||||
$noresize = true;
|
||||
$params['markdown'] = substr($markdown,0,-9);
|
||||
}
|
||||
|
||||
if($imageProcessor->publishImage($noresize))
|
||||
{
|
||||
$request = $request->withParsedBody($params);
|
||||
|
||||
$block = new ControllerAuthorBlockApi($this->c);
|
||||
if($params['new'])
|
||||
{
|
||||
return $block->addBlock($request, $response, $args);
|
||||
}
|
||||
return $block->updateBlock($request, $response, $args);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => 'could not store image to media folder'],500);
|
||||
}
|
||||
|
||||
public function publishFile(Request $request, Response $response, $args)
|
||||
{
|
||||
$params = $request->getParsedBody();
|
||||
|
||||
$fileProcessor = new ProcessFile();
|
||||
if(!$fileProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
if($fileProcessor->publishFile())
|
||||
{
|
||||
$request = $request->withParsedBody($params);
|
||||
|
||||
$block = new ControllerAuthorBlockApi($this->c);
|
||||
if($params['new'])
|
||||
{
|
||||
return $block->addBlock($request, $response, $args);
|
||||
}
|
||||
return $block->updateBlock($request, $response, $args);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => 'could not store file to media folder'],500);
|
||||
}
|
||||
|
||||
public function deleteImage(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
|
||||
# minimum permission is that user is allowed to delete content
|
||||
if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'delete'))
|
||||
{
|
||||
return $response->withJson(array('data' => false, 'errors' => 'You are not allowed to delete images.'), 403);
|
||||
}
|
||||
|
||||
if(!isset($this->params['name']))
|
||||
{
|
||||
return $response->withJson(['errors' => 'image name is missing'],500);
|
||||
}
|
||||
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
if(!$imageProcessor->checkFolders('images'))
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
if($imageProcessor->deleteImage($this->params['name']))
|
||||
{
|
||||
return $response->withJson(['errors' => false]);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => 'Oops, looks like we could not delete all sizes of that image.'], 500);
|
||||
}
|
||||
|
||||
public function deleteFile(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
|
||||
# minimum permission is that user is allowed to delete content
|
||||
if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'delete'))
|
||||
{
|
||||
return $response->withJson(array('data' => false, 'errors' => 'You are not allowed to delete files.'), 403);
|
||||
}
|
||||
|
||||
if(!isset($this->params['name']))
|
||||
{
|
||||
return $response->withJson(['errors' => 'file name is missing'],500);
|
||||
}
|
||||
|
||||
$fileProcessor = new ProcessFile();
|
||||
|
||||
if($fileProcessor->deleteFile($this->params['name']))
|
||||
{
|
||||
return $response->withJson(['errors' => false]);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => 'could not delete the file'],500);
|
||||
}
|
||||
|
||||
public function saveVideoImage(Request $request, Response $response, $args)
|
||||
{
|
||||
/* get params from call */
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
$class = false;
|
||||
|
||||
$imageUrl = $this->params['markdown'];
|
||||
|
||||
if(strpos($imageUrl, 'https://www.youtube.com/watch?v=') !== false)
|
||||
{
|
||||
$videoID = str_replace('https://www.youtube.com/watch?v=', '', $imageUrl);
|
||||
$videoID = strpos($videoID, '&') ? substr($videoID, 0, strpos($videoID, '&')) : $videoID;
|
||||
$class = 'youtube';
|
||||
}
|
||||
if(strpos($imageUrl, 'https://youtu.be/') !== false)
|
||||
{
|
||||
$videoID = str_replace('https://youtu.be/', '', $imageUrl);
|
||||
$videoID = strpos($videoID, '?') ? substr($videoID, 0, strpos($videoID, '?')) : $videoID;
|
||||
$class = 'youtube';
|
||||
}
|
||||
|
||||
if($class == 'youtube')
|
||||
{
|
||||
$videoURLmaxres = 'https://i1.ytimg.com/vi/' . $videoID . '/maxresdefault.jpg';
|
||||
$videoURL0 = 'https://i1.ytimg.com/vi/' . $videoID . '/0.jpg';
|
||||
}
|
||||
|
||||
$ctx = stream_context_create(array(
|
||||
'https' => array(
|
||||
'timeout' => 1
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$imageData = @file_get_contents($videoURLmaxres, 0, $ctx);
|
||||
if($imageData === false)
|
||||
{
|
||||
$imageData = @file_get_contents($videoURL0, 0, $ctx);
|
||||
if($imageData === false)
|
||||
{
|
||||
return $response->withJson(array('errors' => 'could not get the video image'));
|
||||
}
|
||||
}
|
||||
|
||||
$imageData64 = 'data:image/jpeg;base64,' . base64_encode($imageData);
|
||||
$desiredSizes = ['live' => ['width' => 560, 'height' => 315]];
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
if(!$imageProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Please check if your media-folder exists and all folders inside are writable.']], 500);
|
||||
}
|
||||
|
||||
$tmpImage = $imageProcessor->createImage($imageData64, $videoID, $desiredSizes);
|
||||
|
||||
if(!$tmpImage)
|
||||
{
|
||||
return $response->withJson(array('errors' => 'could not create temporary image'));
|
||||
}
|
||||
|
||||
$imageUrl = $imageProcessor->publishImage();
|
||||
if($imageUrl)
|
||||
{
|
||||
$this->params['markdown'] = '{#' . $videoID. ' .' . $class . '}';
|
||||
|
||||
$request = $request->withParsedBody($this->params);
|
||||
$block = new ControllerAuthorBlockApi($this->c);
|
||||
if($this->params['new'])
|
||||
{
|
||||
return $block->addBlock($request, $response, $args);
|
||||
}
|
||||
return $block->updateBlock($request, $response, $args);
|
||||
}
|
||||
|
||||
return $response->withJson(array('errors' => 'could not store the preview image'));
|
||||
}
|
||||
|
||||
# https://www.sitepoint.com/mime-types-complete-list/
|
||||
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
|
||||
# https://wiki.selfhtml.org/wiki/MIME-Type/%C3%9Cbersicht
|
||||
# http://www.mime-type.net/application/x-latex/
|
||||
private function getAllowedMtypes()
|
||||
{
|
||||
return array(
|
||||
'application/vnd.oasis.opendocument.chart' => 'odc',
|
||||
'application/vnd.oasis.opendocument.formula' => 'odf',
|
||||
'application/vnd.oasis.opendocument.graphics' => 'odg',
|
||||
'application/vnd.oasis.opendocument.image' => 'odi',
|
||||
'application/vnd.oasis.opendocument.presentation' => 'odp',
|
||||
'application/vnd.oasis.opendocument.spreadsheet' => 'ods',
|
||||
'application/vnd.oasis.opendocument.text' => 'odt',
|
||||
'application/vnd.oasis.opendocument.text-master' => 'odm',
|
||||
|
||||
'application/powerpoint' => 'ppt',
|
||||
'application/mspowerpoint' => ['ppt','ppz','pps','pot'],
|
||||
'application/x-mspowerpoint' => 'ppt',
|
||||
'application/vnd.ms-powerpoint' => 'ppt',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx',
|
||||
|
||||
'application/x-visio' => ['vsd','vst','msw'],
|
||||
'application/vnd.visio' => ['vsd','vst','msw'],
|
||||
'application/x-project' => ['mpc','mpt','mpv','mpx'],
|
||||
'application/vnd.ms-project' => 'mpp',
|
||||
|
||||
'application/excel' => ['xla','xlb','xlc','xld','xlk','xll','xlm','xls','xlt','xlv','xlw'],
|
||||
'application/msexcel' => ['xls','xla'],
|
||||
'application/x-excel' => ['xla','xlb','xlc','xld','xlk','xll','xlm','xls','xlt','xlv','xlw'],
|
||||
'application/x-msexcel' => ['xls', 'xla','xlw'],
|
||||
'application/vnd.ms-excel' => ['xlb','xlc','xll','xlm','xls','xlw'],
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx',
|
||||
|
||||
'application/mshelp' => ['hlp','chm'],
|
||||
'application/msword' => ['doc','dot'],
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',
|
||||
|
||||
'application/vnd.apple.keynote' => 'key',
|
||||
'application/vnd.apple.numbers' => 'numbers',
|
||||
'application/vnd.apple.pages' => 'pages',
|
||||
|
||||
'application/x-latex' => ['ltx','latex'],
|
||||
'application/pdf' => 'pdf',
|
||||
|
||||
'application/vnd.amazon.mobi8-ebook' => 'azw3',
|
||||
'application/x-mobipocket-ebook' => 'mobi',
|
||||
'application/epub+zip' => 'epub',
|
||||
|
||||
'application/x-gtar' => 'gtar',
|
||||
'application/x-tar' => 'tar',
|
||||
'application/zip' => 'zip',
|
||||
'application/gzip' => 'gz',
|
||||
'application/x-gzip' => ['gz', 'gzip'],
|
||||
'application/x-compressed' => ['gz','tgz','z','zip'],
|
||||
'application/x-zip-compressed' => 'zip',
|
||||
'application/vnd.rar' => 'rar',
|
||||
'application/x-7z-compressed' => '7z',
|
||||
|
||||
'application/rtf' => 'rtf',
|
||||
'application/x-rtf' => 'rtf',
|
||||
|
||||
'text/calendar' => 'ics',
|
||||
'text/comma-separated-values' => 'csv',
|
||||
'text/css' => 'css',
|
||||
'text/plain' => 'txt',
|
||||
'text/richtext' => 'rtx',
|
||||
'text/rtf' => 'rtf',
|
||||
|
||||
'audio/basic' => ['au','snd'],
|
||||
'audio/mpeg' => 'mp3',
|
||||
'audio/mp4' => 'mp4',
|
||||
'audio/ogg' => 'ogg',
|
||||
'audio/wav' => 'wav',
|
||||
'audio/x-aiff' => ['aif','aiff','aifc'],
|
||||
'audio/x-midi' => ['mid','midi'],
|
||||
'audio/x-mpeg' => 'mp2',
|
||||
'audio/x-pn-realaudio' => ['ram','ra'],
|
||||
|
||||
'image/png' => 'png',
|
||||
'image/jpeg' => ['jpeg','jpe','jpg'],
|
||||
'image/gif' => 'gif',
|
||||
'image/tiff' => ['tiff','tif'],
|
||||
'image/svg+xml' => 'svg',
|
||||
'image/x-icon' => 'ico',
|
||||
'image/webp' => 'webp',
|
||||
|
||||
'video/mpeg' => ['mpeg','mpg','mpe'],
|
||||
'video/mp4' => 'mp4',
|
||||
'video/ogg' => ['ogg','ogv'],
|
||||
'video/quicktime' => ['qt','mov'],
|
||||
'video/vnd.vivo' => ['viv','vivo'],
|
||||
'video/webm' => 'webm',
|
||||
'video/x-msvideo' => 'avi',
|
||||
'video/x-sgi-movie' => 'movie',
|
||||
'video/3gpp' => '3gp',
|
||||
);
|
||||
}
|
||||
|
||||
protected function checkAllowedMimeTypes($mtype, $extension)
|
||||
{
|
||||
$allowedMimes = $this->getAllowedMtypes();
|
||||
|
||||
if(!isset($allowedMimes[$mtype]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(
|
||||
(is_array($allowedMimes[$mtype]) && !in_array($extension, $allowedMimes[$mtype])) OR
|
||||
(!is_array($allowedMimes[$mtype]) && $allowedMimes[$mtype] != $extension )
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function checkAllowedExtensions($extension)
|
||||
{
|
||||
$mtypes = $this->getAllowedMtypes();
|
||||
foreach($mtypes as $mtExtension)
|
||||
{
|
||||
if(is_array($mtExtension))
|
||||
{
|
||||
if(in_array($extension, $mtExtension))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if($extension == $mtExtension)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
59
system/typemill/Controllers/ControllerApiSystemLicense.php
Normal file
59
system/typemill/Controllers/ControllerApiSystemLicense.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?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\StorageWrapper;
|
||||
use Typemill\Models\License;
|
||||
|
||||
class ControllerApiSystemLicense extends ControllerData
|
||||
{
|
||||
public function createLicense(Request $request, Response $response)
|
||||
{
|
||||
$params = $request->getParsedBody();
|
||||
|
||||
if(!isset($params['license']) OR !is_array($params['license']))
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'License data missing.',
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
|
||||
}
|
||||
|
||||
# validate input
|
||||
$validate = new Validation();
|
||||
$validationresult = $validate->newLicense($params['license']);
|
||||
if($validationresult !== true)
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'Please correct errors in form.',
|
||||
'errors' => $validate->returnFirstValidationErrors($validationresult)
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
|
||||
}
|
||||
|
||||
$license = new License();
|
||||
|
||||
$licensedata = $license->activateLicense($params['license']);
|
||||
|
||||
if(!$licensedata)
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => $license->getMessage()
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'Licence has been stored',
|
||||
'licensedata' => $license->getLicenseData($this->c->get('urlinfo'))
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
|
||||
}
|
||||
}
|
117
system/typemill/Controllers/ControllerWebAuth.php
Normal file
117
system/typemill/Controllers/ControllerWebAuth.php
Normal file
@@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Controllers;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Slim\Routing\RouteContext;
|
||||
use Typemill\Models\Validation;
|
||||
use Typemill\Models\User;
|
||||
|
||||
class ControllerWebAuth extends Controller
|
||||
{
|
||||
public function show(Request $request, Response $response)
|
||||
{
|
||||
return $this->c->get('view')->render($response, 'auth/login.twig', [
|
||||
#'captcha' => $this->checkIfAddCaptcha(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function login(Request $request, Response $response)
|
||||
{
|
||||
if( ( null !== $request->getattribute('csrf_result') ) OR ( $request->getattribute('csrf_result') === false ) )
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'The form has a timeout, please try again.');
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'));
|
||||
}
|
||||
|
||||
$input = $request->getParsedBody();
|
||||
$validation = new Validation();
|
||||
$settings = $this->c->get('settings');
|
||||
|
||||
if($validation->signin($input))
|
||||
{
|
||||
$user = new User();
|
||||
|
||||
if(!$user->setUserWithPassword($input['username']))
|
||||
{
|
||||
# return error
|
||||
}
|
||||
|
||||
$userdata = $user->getUserData();
|
||||
|
||||
if($userdata && password_verify($input['password'], $userdata['password']))
|
||||
{
|
||||
# check if user has confirmed the account
|
||||
if(isset($userdata['optintoken']) && $userdata['optintoken'])
|
||||
{
|
||||
$this->c->get('flash')->addMessage('error', 'Your registration is not confirmed yet. Please check your e-mails and use the confirmation link.');
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
|
||||
}
|
||||
|
||||
$user->login();
|
||||
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('settings.show'))->withStatus(302);
|
||||
|
||||
/*
|
||||
# if user is allowed to view content-area
|
||||
$acl = $this->c->get('acl');
|
||||
if($acl->hasRole($userdata['userrole']) && $acl->isAllowed($userdata['userrole'], 'content', 'view'))
|
||||
{
|
||||
$editor = (isset($this->settings['editor']) && $this->settings['editor'] == 'visual') ? 'visual' : 'raw';
|
||||
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('content.' . $editor))->withStatus(302);
|
||||
}
|
||||
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('user.account'))->withStatus(302);
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($this->settings['securitylog']) && $this->settings['securitylog'])
|
||||
{
|
||||
\Typemill\Static\Helpers::addLogEntry('wrong login');
|
||||
}
|
||||
|
||||
$this->c->get('flash')->addMessage('error', 'Ups, wrong password or username, please try again.');
|
||||
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
|
||||
}
|
||||
|
||||
/**
|
||||
* log out a user
|
||||
*
|
||||
* @param obj $request the slim request object
|
||||
* @param obj $response the slim response object
|
||||
* @return obje $response with redirect to route
|
||||
*/
|
||||
|
||||
public function logout(Request $request, Response $response)
|
||||
{
|
||||
# check https://www.php.net/session_destroy
|
||||
if(isset($_SESSION))
|
||||
{
|
||||
# Unset all of the session variables.
|
||||
$_SESSION = array();
|
||||
|
||||
# If it's desired to kill the session, also delete the session cookie. This will destroy the session, and not just the session data!
|
||||
if (ini_get("session.use_cookies"))
|
||||
{
|
||||
$params = session_get_cookie_params();
|
||||
|
||||
setcookie(
|
||||
session_name(),
|
||||
'',
|
||||
time() - 42000,
|
||||
$params["path"], $params["domain"],
|
||||
$params["secure"], $params["httponly"]
|
||||
);
|
||||
}
|
||||
|
||||
# Finally, destroy the session.
|
||||
session_destroy();
|
||||
}
|
||||
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
|
||||
}
|
||||
}
|
144
system/typemill/Middleware/ApiAuthentication.php
Normal file
144
system/typemill/Middleware/ApiAuthentication.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Middleware;
|
||||
|
||||
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 ApiAuthentication
|
||||
{
|
||||
public function __invoke(Request $request, RequestHandler $handler)
|
||||
{
|
||||
$routeContext = RouteContext::fromRequest($request);
|
||||
$baseURL = $routeContext->getBasePath();
|
||||
|
||||
# check if it is a session based authentication
|
||||
if ($request->hasHeader('X-Session-Auth'))
|
||||
{
|
||||
session_start();
|
||||
|
||||
$authenticated = (
|
||||
(isset($_SESSION['username'])) &&
|
||||
(isset($_SESSION['login']))
|
||||
)
|
||||
? true : false;
|
||||
|
||||
if($authenticated)
|
||||
{
|
||||
# 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('c_username', $userdata['username']);
|
||||
$request = $request->withAttribute('c_userrole', $userdata['userrole']);
|
||||
|
||||
$response = $handler->handle($request);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
# return error message
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# api authentication with basic auth
|
||||
# inspired by tuupola
|
||||
$host = $request->getUri()->getHost();
|
||||
$scheme = $request->getUri()->getScheme();
|
||||
$server_params = $request->getServerParams();
|
||||
|
||||
/*
|
||||
# HTTP allowed only if secure is false or server is in relaxed array.
|
||||
# use own logic for https proto forwarding
|
||||
if($scheme !== "https" && $this->options["secure"] !== true)
|
||||
{
|
||||
$allowedHost = in_array($host, $this->options["relaxed"]);
|
||||
|
||||
# if 'headers' is in the 'relaxed' key, then we check for forwarding
|
||||
$allowedForward = false;
|
||||
if (in_array("headers", $this->options["relaxed"]))
|
||||
{
|
||||
if ( $request->getHeaderLine("X-Forwarded-Proto") === "https" && $request->getHeaderLine('X-Forwarded-Port') === "443")
|
||||
{
|
||||
$allowedForward = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!($allowedHost || $allowedForward))
|
||||
{
|
||||
$message = sprintf("Insecure use of middleware over %s denied by configuration.", strtoupper($scheme));
|
||||
throw new \RuntimeException($message);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
$params = [];
|
||||
|
||||
if (preg_match("/Basic\s+(.*)$/i", $request->getHeaderLine("Authorization"), $matches))
|
||||
{
|
||||
$explodedCredential = explode(":", base64_decode($matches[1]), 2);
|
||||
if (count($explodedCredential) == 2)
|
||||
{
|
||||
[$params["user"], $params["password"]] = $explodedCredential;
|
||||
}
|
||||
}
|
||||
|
||||
if(!empty($params))
|
||||
{
|
||||
# load userdata
|
||||
$user = new User();
|
||||
|
||||
if($user->setUserWithPassword($params['user']))
|
||||
{
|
||||
$userdata = $user->getUserData();
|
||||
|
||||
# this might be unsecure, check for === comparator
|
||||
$apiaccess = ( isset($userdata['apiaccess']) && $userdata['apiaccess'] == true ) ? true : false;
|
||||
|
||||
if($userdata && $apiaccess && password_verify($params['password'], $userdata['password']))
|
||||
{
|
||||
$request = $request->withAttribute('c_username', $userdata['username']);
|
||||
$request = $request->withAttribute('c_userrole', $userdata['userrole']);
|
||||
|
||||
# this executes code from routes first and then executes middleware
|
||||
$response = $handler->handle($request);
|
||||
|
||||
return $response;
|
||||
}
|
||||
else
|
||||
{
|
||||
# if basic auth is set but with wrong credentials
|
||||
$response = new Response();
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'Authentication failed.'
|
||||
]));
|
||||
|
||||
return $response->withHeader('WWW-Authenticate', 'Basic realm=')->withStatus(401);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# elseif ($request->getHeaderLine('X-Requested-With') === 'XMLHttpRequest') {
|
||||
# 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??)
|
||||
# }
|
||||
|
||||
$response = new Response();
|
||||
|
||||
$response->getBody()->write('Zugriff nicht erlaubt.');
|
||||
|
||||
return $response->withStatus(401);
|
||||
}
|
||||
}
|
39
system/typemill/Middleware/ApiAuthorization.php
Normal file
39
system/typemill/Middleware/ApiAuthorization.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Middleware;
|
||||
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Slim\Routing\RouteParser;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
|
||||
use Slim\Psr7\Response;
|
||||
|
||||
class ApiAuthorization implements MiddlewareInterface
|
||||
{
|
||||
public function __construct($acl, string $resource = NULL, string $action = NULL)
|
||||
{
|
||||
$this->acl = $acl;
|
||||
$this->resource = $resource;
|
||||
$this->action = $action;
|
||||
}
|
||||
|
||||
public function process(Request $request, RequestHandler $handler) :Response
|
||||
{
|
||||
if(!$this->acl->isAllowed($request->getAttribute('c_userrole'), $this->resource, $this->action))
|
||||
{
|
||||
|
||||
$message = 'userrole: ' . $request->getAttribute('c_userrole') . ' resource: ' . $this->resource . ' action: ' . $this->action;
|
||||
$response = new Response();
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => $message
|
||||
]));
|
||||
|
||||
return $response->withStatus(401);
|
||||
}
|
||||
|
||||
$response = $handler->handle($request);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
34
system/typemill/Middleware/WebAuthorization.php
Normal file
34
system/typemill/Middleware/WebAuthorization.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Middleware;
|
||||
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Slim\Routing\RouteParser;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
|
||||
use Slim\Psr7\Response;
|
||||
|
||||
class WebAuthorization implements MiddlewareInterface
|
||||
{
|
||||
public function __construct(RouteParser $router, $acl, string $resource = NULL, string $action = NULL)
|
||||
{
|
||||
$this->router = $router;
|
||||
$this->acl = $acl;
|
||||
$this->resource = $resource;
|
||||
$this->action = $action;
|
||||
}
|
||||
|
||||
public function process(Request $request, RequestHandler $handler) :Response
|
||||
{
|
||||
if(!$this->acl->isAllowed($request->getAttribute('c_userrole'), $this->resource, $this->action))
|
||||
{
|
||||
$response = new Response();
|
||||
|
||||
return $response->withHeader('Location', $this->router->urlFor('user.account'))->withStatus(302);
|
||||
}
|
||||
|
||||
$response = $handler->handle($request);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
41
system/typemill/Middleware/WebRedirectIfAuthenticated.php
Normal file
41
system/typemill/Middleware/WebRedirectIfAuthenticated.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Middleware;
|
||||
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Slim\Routing\RouteParser;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
|
||||
use Slim\Psr7\Response;
|
||||
|
||||
class WebRedirectIfAuthenticated implements MiddlewareInterface
|
||||
{
|
||||
public function __construct(RouteParser $router, $settings)
|
||||
{
|
||||
$this->router = $router;
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
public function process(Request $request, RequestHandler $handler) :Response
|
||||
{
|
||||
$authenticated = (
|
||||
(isset($_SESSION['username'])) &&
|
||||
(isset($_SESSION['login']))
|
||||
)
|
||||
? true : false;
|
||||
|
||||
if($authenticated)
|
||||
{
|
||||
|
||||
$editor = (isset($this->settings['editor']) && $this->settings['editor'] == 'visual') ? 'visual' : 'raw';
|
||||
|
||||
$response = new Response();
|
||||
|
||||
return $response->withHeader('Location', $this->router->urlFor('content.' . $editor))->withStatus(302);
|
||||
}
|
||||
|
||||
$response = $handler->handle($request);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
51
system/typemill/Middleware/WebRedirectIfUnauthenticated.php
Normal file
51
system/typemill/Middleware/WebRedirectIfUnauthenticated.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Middleware;
|
||||
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
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 WebRedirectIfUnauthenticated implements MiddlewareInterface
|
||||
{
|
||||
public function __construct(RouteParser $router)
|
||||
{
|
||||
$this->router = $router;
|
||||
}
|
||||
|
||||
public function process(Request $request, RequestHandler $handler) :response
|
||||
{
|
||||
# session authentication
|
||||
if(
|
||||
(isset($_SESSION['username'])) &&
|
||||
(isset($_SESSION['login']))
|
||||
)
|
||||
{
|
||||
# load userdata
|
||||
$user = new User();
|
||||
|
||||
if($user->setUser($_SESSION['username']))
|
||||
{
|
||||
|
||||
# pass username and userrole
|
||||
$userdata = $user->getUserData();
|
||||
|
||||
$request = $request->withAttribute('c_username', $userdata['username']);
|
||||
$request = $request->withAttribute('c_userrole', $userdata['userrole']);
|
||||
|
||||
# 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);
|
||||
}
|
||||
}
|
258
system/typemill/Models/License.php
Normal file
258
system/typemill/Models/License.php
Normal file
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Models;
|
||||
|
||||
use Typemill\Models\StorageWrapper;
|
||||
|
||||
class License
|
||||
{
|
||||
public $message = '';
|
||||
|
||||
private $plans = [
|
||||
'44699' => [
|
||||
'name' => 'MAKER',
|
||||
'scope' => ['MAKER' => true]
|
||||
],
|
||||
'33334' => [
|
||||
'name' => 'BUSINESS',
|
||||
'scope' => ['MAKER' => true, 'BUSINESS' => true]
|
||||
]
|
||||
];
|
||||
|
||||
public function getMessage()
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
# used for license management in admin settings
|
||||
public function getLicenseData(array $urlinfo)
|
||||
{
|
||||
# returns data for settings page
|
||||
$licensedata = $this->checkLicense();
|
||||
if($licensedata)
|
||||
{
|
||||
$licensedata['plan'] = $this->plans[$licensedata['plan']]['name'];
|
||||
$licensedata['domaincheck'] = $this->checkLicenseDomain($licensedata['domain'], $urlinfo);
|
||||
$licensedata['datecheck'] = $this->checkLicenseDate($licensedata['payed_until']);
|
||||
|
||||
return $licensedata;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
# used to activate or deactivate features that require a license
|
||||
public function getLicenseScope(array $urlinfo)
|
||||
{
|
||||
$licensedata = $this->checkLicense();
|
||||
|
||||
if(!$licensedata)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$domain = $this->checkLicenseDomain($licensedata['domain'], $urlinfo);
|
||||
$date = $this->checkLicenseDate($licensedata['payed_until']);
|
||||
|
||||
$domain = true;
|
||||
|
||||
if($domain && $date)
|
||||
{
|
||||
return $this->plans[$licensedata['plan']]['scope'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function refreshLicense()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
# check the local licence file (like pem or pub)
|
||||
private function checkLicense()
|
||||
{
|
||||
$storage = new StorageWrapper('\Typemill\Models\Storage');
|
||||
|
||||
$licensedata = $storage->getYaml('settings', 'license.yaml');
|
||||
|
||||
if(!$licensedata)
|
||||
{
|
||||
$this->message = 'no license found';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!isset($licensedata['license'],$licensedata['email'],$licensedata['domain'],$licensedata['plan'],$licensedata['payed_until'],$licensedata['signature']))
|
||||
{
|
||||
$this->message = 'License data incomplete';
|
||||
|
||||
return false;
|
||||
}
|
||||
$licenseStatus = $this->validateLicense($licensedata);
|
||||
|
||||
if($licenseStatus === true)
|
||||
{
|
||||
unset($licensedata['signature']);
|
||||
|
||||
# check here if payed until is in past
|
||||
return $licensedata;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function validateLicense($data)
|
||||
{
|
||||
$public_key_pem = $this->getPublicKeyPem();
|
||||
|
||||
$binary_signature = base64_decode($data['signature']);
|
||||
|
||||
$data['email'] = $this->hashMail($data['email']);
|
||||
unset($data['signature']);
|
||||
|
||||
# test manipulate data
|
||||
#$data['plan'] = 'wrong';
|
||||
|
||||
$data = json_encode($data);
|
||||
|
||||
# Check signature
|
||||
$verified = openssl_verify($data, $binary_signature, $public_key_pem, OPENSSL_ALGO_SHA256);
|
||||
|
||||
if ($verified == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
elseif ($verified == 0)
|
||||
{
|
||||
$this->message = 'License data are invalid';
|
||||
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->message = 'There was an error checking the license signature';
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function activateLicense($params)
|
||||
{
|
||||
# prepare data for call to licence server
|
||||
$licensedata = [
|
||||
'license' => $params['license'],
|
||||
'email' => $this->hashMail($params['email']),
|
||||
'domain' => $params['domain']
|
||||
];
|
||||
|
||||
$postdata = http_build_query($licensedata);
|
||||
|
||||
$authstring = $this->getPublicKeyPem();
|
||||
$authstring = hash('sha256', substr($authstring, 0, 50));
|
||||
|
||||
$options = array (
|
||||
'http' => array (
|
||||
'method' => 'POST',
|
||||
'ignore_errors' => true,
|
||||
'header' => "Content-Type: application/x-www-form-urlencoded\r\n" .
|
||||
"Accept: application/json\r\n" .
|
||||
"Authorization: $authstring\r\n" .
|
||||
"Connection: close\r\n",
|
||||
'content' => $postdata
|
||||
)
|
||||
);
|
||||
|
||||
$context = stream_context_create($options);
|
||||
|
||||
$response = file_get_contents('https://service.typemill.net/api/v2/activate', false, $context);
|
||||
|
||||
if(substr($http_response_header[0], -6) != "200 OK")
|
||||
{
|
||||
$this->message = 'the license server responded with: ' . $http_response_header[0];
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$signedLicense = json_decode($response,true);
|
||||
|
||||
if(isset($signedLicense['code']))
|
||||
{
|
||||
# $this->message = 'Something went wrong. Please check your input data or contact the support.';
|
||||
$this->message = $signedLicense['code'];
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
# check for positive and validate response data
|
||||
if($signedLicense['license'])
|
||||
{
|
||||
$this->message = ;
|
||||
}
|
||||
*/
|
||||
$signedLicense['license']['email'] = trim($params['email']);
|
||||
$storage = new StorageWrapper('\Typemill\Models\Storage');
|
||||
|
||||
$storage->updateYaml('settings', 'license.yaml', $signedLicense['license']);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function updateLicence()
|
||||
{
|
||||
# todo
|
||||
}
|
||||
|
||||
private function checkLicenseDomain(string $licensedomain, array $urlinfo)
|
||||
{
|
||||
$licensehost = parse_url($licensedomain, PHP_URL_HOST);
|
||||
$licensehost = str_replace("www.", "", $licensehost);
|
||||
|
||||
$thishost = parse_url($urlinfo['baseurl'], PHP_URL_HOST);
|
||||
$thishost = str_replace("www.", "", $thishost);
|
||||
|
||||
$whitelist = ['localhost', '127.0.0.1', 'typemilltest.', $licensehost];
|
||||
|
||||
foreach($whitelist as $domain)
|
||||
{
|
||||
if(substr($thishost, 0, strlen($domain)) == $domain)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function checkLicenseDate(string $payed_until)
|
||||
{
|
||||
if(strtotime($payed_until) > strtotime(date('Y-m-d')))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function hashMail(string $mail)
|
||||
{
|
||||
return hash('sha256', trim($mail) . 'TYla5xa8JUur');
|
||||
}
|
||||
|
||||
private function getPublicKeyPem()
|
||||
{
|
||||
$pkeyfile = getcwd() . DIRECTORY_SEPARATOR . 'settings' . DIRECTORY_SEPARATOR . "public_key.pem";
|
||||
|
||||
if(file_exists($pkeyfile) && is_readable($pkeyfile))
|
||||
{
|
||||
# fetch public key from file and ready it
|
||||
$fp = fopen($pkeyfile, "r");
|
||||
$public_key_pem = fread($fp, 8192);
|
||||
fclose($fp);
|
||||
|
||||
return $public_key_pem;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
243
system/typemill/Models/ProcessAssets.php
Normal file
243
system/typemill/Models/ProcessAssets.php
Normal file
@@ -0,0 +1,243 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Models;
|
||||
|
||||
use Typemill\Models\Folder;
|
||||
|
||||
class ProcessAssets
|
||||
{
|
||||
# holds the path to the temporary image folder
|
||||
public $basepath = false;
|
||||
|
||||
public $tmpFolder = false;
|
||||
|
||||
public $errors = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
ini_set('memory_limit', '512M');
|
||||
|
||||
$this->basepath = getcwd() . DIRECTORY_SEPARATOR;
|
||||
|
||||
$this->tmpFolder = $this->basepath . 'media' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
public function clearTempFolder()
|
||||
{
|
||||
$files = scandir($this->tmpFolder);
|
||||
$now = time();
|
||||
$result = true;
|
||||
|
||||
foreach($files as $file)
|
||||
{
|
||||
if (!in_array($file, array(".","..")))
|
||||
{
|
||||
$filelink = $this->tmpFolder . $file;
|
||||
if(file_exists($filelink))
|
||||
{
|
||||
$filetime = filemtime($filelink);
|
||||
if($now - $filetime > 1800)
|
||||
{
|
||||
if(!unlink($filelink))
|
||||
{
|
||||
$result = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*
|
||||
public function checkFolders($forassets = null)
|
||||
{
|
||||
|
||||
$folders = [$this->mediaFolder, $this->tmpFolder, $this->fileFolder];
|
||||
|
||||
if($forassets == 'images')
|
||||
{
|
||||
$folders = [$this->mediaFolder, $this->tmpFolder, $this->originalFolder, $this->liveFolder, $this->thumbFolder, $this->customFolder];
|
||||
}
|
||||
|
||||
foreach($folders as $folder)
|
||||
{
|
||||
if(!file_exists($folder) && !is_dir( $folder ))
|
||||
{
|
||||
if(!mkdir($folder, 0755, true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if($folder == $this->thumbFolder)
|
||||
{
|
||||
# cleanup old systems
|
||||
$this->cleanupLiveFolder();
|
||||
|
||||
# generate thumbnails from live folder
|
||||
$this->generateThumbs();
|
||||
}
|
||||
}
|
||||
elseif(!is_writeable($folder) OR !is_readable($folder))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
# check if thumb-folder is empty, then generate thumbs from live folder
|
||||
if($folder == $this->thumbFolder && $this->is_dir_empty($folder))
|
||||
{
|
||||
# cleanup old systems
|
||||
$this->cleanupLiveFolder();
|
||||
|
||||
# generate thumbnails from live folder
|
||||
$this->generateThumbs();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
|
||||
public function is_dir_empty($dir)
|
||||
{
|
||||
return (count(scandir($dir)) == 2);
|
||||
}
|
||||
|
||||
/*
|
||||
public function setFileName($originalname, $type, $overwrite = NULL)
|
||||
{
|
||||
$pathinfo = pathinfo($originalname);
|
||||
$this->extension = isset($pathinfo['extension']) ? strtolower($pathinfo['extension']) : null;
|
||||
$this->filename = Folder::createSlug($pathinfo['filename']);
|
||||
|
||||
$filename = $this->filename;
|
||||
|
||||
# check if file name is
|
||||
if(!$overwrite)
|
||||
{
|
||||
$suffix = 1;
|
||||
|
||||
$destination = $this->liveFolder;
|
||||
if($type == 'file')
|
||||
{
|
||||
$destination = $this->fileFolder;
|
||||
}
|
||||
|
||||
while(file_exists($destination . $filename . '.' . $this->extension))
|
||||
{
|
||||
$filename = $this->filename . '-' . $suffix;
|
||||
$suffix++;
|
||||
}
|
||||
}
|
||||
|
||||
$this->filename = $filename;
|
||||
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
public function getName()
|
||||
{
|
||||
return $this->filename;
|
||||
}
|
||||
|
||||
public function setExtension($extension)
|
||||
{
|
||||
$this->extension = $extension;
|
||||
}
|
||||
|
||||
public function getExtension()
|
||||
{
|
||||
return $this->extension;
|
||||
}
|
||||
|
||||
public function getFullName()
|
||||
{
|
||||
return $this->filename . '.' . $this->extension;
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
public function cleanupLiveFolder()
|
||||
{
|
||||
# delete all old thumbs mlibrary in live folder
|
||||
foreach(glob($this->liveFolder . '*mlibrary*') as $filename)
|
||||
{
|
||||
unlink($filename);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
*/
|
||||
|
||||
public function findPagesWithUrl($structure, $url, $result)
|
||||
{
|
||||
foreach ($structure as $key => $item)
|
||||
{
|
||||
if($item->elementType == 'folder')
|
||||
{
|
||||
$result = $this->findPagesWithUrl($item->folderContent, $url, $result);
|
||||
}
|
||||
else
|
||||
{
|
||||
$live = getcwd() . DIRECTORY_SEPARATOR . 'content' . $item->pathWithoutType . '.md';
|
||||
$draft = getcwd() . DIRECTORY_SEPARATOR . 'content' . $item->pathWithoutType . '.txt';
|
||||
|
||||
# check live first
|
||||
if(file_exists($live))
|
||||
{
|
||||
$content = file_get_contents($live);
|
||||
|
||||
if (stripos($content, $url) !== false)
|
||||
{
|
||||
$result[] = $item->urlRelWoF;
|
||||
}
|
||||
# if not in live, check in draft
|
||||
elseif(file_exists($draft))
|
||||
{
|
||||
$content = file_get_contents($draft);
|
||||
|
||||
if (stripos($content, $url) !== false)
|
||||
{
|
||||
$result[] = $item->urlRelWoF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function formatSizeUnits($bytes)
|
||||
{
|
||||
if ($bytes >= 1073741824)
|
||||
{
|
||||
$bytes = number_format($bytes / 1073741824, 2) . ' GB';
|
||||
}
|
||||
elseif ($bytes >= 1048576)
|
||||
{
|
||||
$bytes = number_format($bytes / 1048576, 2) . ' MB';
|
||||
}
|
||||
elseif ($bytes >= 1024)
|
||||
{
|
||||
$bytes = number_format($bytes / 1024, 2) . ' KB';
|
||||
}
|
||||
elseif ($bytes > 1)
|
||||
{
|
||||
$bytes = $bytes . ' bytes';
|
||||
}
|
||||
elseif ($bytes == 1)
|
||||
{
|
||||
$bytes = $bytes . ' byte';
|
||||
}
|
||||
else
|
||||
{
|
||||
$bytes = '0 bytes';
|
||||
}
|
||||
|
||||
return $bytes;
|
||||
}
|
||||
}
|
40
system/typemill/Models/ProcessFile.php
Normal file
40
system/typemill/Models/ProcessFile.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Models;
|
||||
|
||||
class Yaml extends StorageWrapper
|
||||
{
|
||||
/**
|
||||
* Get the a yaml file.
|
||||
* @param string $fileName is the name of the Yaml Folder.
|
||||
* @param string $yamlFileName is the name of the Yaml File.
|
||||
*/
|
||||
public function getYaml($folderName, $yamlFileName)
|
||||
{
|
||||
$yaml = $this->getFile($folderName, $yamlFileName);
|
||||
|
||||
if($yaml)
|
||||
{
|
||||
return \Symfony\Component\Yaml\Yaml::parse($yaml);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a yaml file.
|
||||
* @param string $fileName is the name of the Yaml Folder.
|
||||
* @param string $yamlFileName is the name of the Yaml File.
|
||||
* @param array $contentArray is the content as an array.
|
||||
*/
|
||||
public function updateYaml($folderName, $yamlFileName, $contentArray)
|
||||
{
|
||||
$yaml = \Symfony\Component\Yaml\Yaml::dump($contentArray,6);
|
||||
if($this->writeFile($folderName, $yamlFileName, $yaml))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
585
system/typemill/Models/ProcessImage.php
Normal file
585
system/typemill/Models/ProcessImage.php
Normal file
@@ -0,0 +1,585 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Models;
|
||||
|
||||
#use Slim\Http\UploadedFile;
|
||||
use Typemill\Static\Slug;
|
||||
|
||||
class ProcessImage extends ProcessAssets
|
||||
{
|
||||
protected $imgstring = false;
|
||||
protected $type = false;
|
||||
protected $extension = false;
|
||||
protected $allowedExtensions = ['png' => true, 'jpg' => true, 'jpeg' => true, 'webp' => true];
|
||||
protected $filename = false;
|
||||
protected $animated = false;
|
||||
protected $resizable = true;
|
||||
protected $sizes = [];
|
||||
|
||||
public function prepareImage($image, $name)
|
||||
{
|
||||
# change clear tmp folder and delete only old ones
|
||||
$this->clearTempFolder();
|
||||
#$this->checkFolders('image');
|
||||
$this->decode($image);
|
||||
$this->setPathInfo($name);
|
||||
$this->checkAllowedExtension();
|
||||
|
||||
if(empty($this->errors))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function storeOriginalToTmp()
|
||||
{
|
||||
# $this->saveName();
|
||||
$this->saveOriginal();
|
||||
|
||||
if(empty($this->errors))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function storeRenditionsToTmp($sizes)
|
||||
{
|
||||
# transform image-stream into image
|
||||
$image = $this->createImage();
|
||||
|
||||
$originalsize = $this->getImageSize($image);
|
||||
|
||||
foreach($sizes as $destinationfolder => $desiredsize)
|
||||
{
|
||||
$desiredsize = $this->calculateSize($originalsize, $desiredsize);
|
||||
|
||||
$resizedImage = $this->resizeImage($image, $desiredsize, $originalsize);
|
||||
|
||||
$this->saveResizedImage($resizedImage, $destinationfolder, $this->extension);
|
||||
|
||||
imagedestroy($resizedImage);
|
||||
}
|
||||
|
||||
imagedestroy($image);
|
||||
|
||||
if(empty($this->errors))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
# decode a base64 image string from js image components
|
||||
public function decode(string $image)
|
||||
{
|
||||
$imageParts = explode(";base64,", $image);
|
||||
|
||||
if(!isset($imageParts[0]) OR !isset($imageParts[1]))
|
||||
{
|
||||
$this->errors[] = 'Could not decode image, probably not a base64 encoding.';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$type = explode("/", $imageParts[0]);
|
||||
$this->type = strtolower($type[0]);
|
||||
$this->imgstring = base64_decode($imageParts[1]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
# set the pathinfo (name and extension) and slugify a unique name if option to overwrite existing files is false
|
||||
public function setPathInfo(string $name)
|
||||
{
|
||||
$pathinfo = pathinfo($name);
|
||||
if(!$pathinfo)
|
||||
{
|
||||
$this->errors[] = 'Could not read pathinfo.';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->extension = isset($pathinfo['extension']) ? strtolower($pathinfo['extension']) : false;
|
||||
$this->filename = Slug::createSlug($pathinfo['filename']);
|
||||
|
||||
if(!$this->extension OR !$this->filename)
|
||||
{
|
||||
$this->errors[] = 'Extension or filename are missing.';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getExtension()
|
||||
{
|
||||
return $this->extension;
|
||||
}
|
||||
|
||||
public function getFilename()
|
||||
{
|
||||
return $this->filename;
|
||||
}
|
||||
|
||||
public function setFilename($filename)
|
||||
{
|
||||
$this->filename = $filename;
|
||||
}
|
||||
|
||||
public function getFullName()
|
||||
{
|
||||
return $this->filename . '.' . $this->extension;
|
||||
}
|
||||
|
||||
# add an allowed image extension like svg
|
||||
public function addAllowedExtension(string $extension)
|
||||
{
|
||||
$this->allowedExtensions[$extension] = true;
|
||||
}
|
||||
|
||||
|
||||
# force an image type like webp
|
||||
public function setExtension(string $extension)
|
||||
{
|
||||
$this->extension = $extension;
|
||||
}
|
||||
|
||||
public function checkAllowedExtension()
|
||||
{
|
||||
if(!isset($this->allowedExtensions[$this->extension]))
|
||||
{
|
||||
$this->errors[] = 'Images with this extension are not allowed.';
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
# check if image should not be resized (animated gif and svg)
|
||||
public function isResizable()
|
||||
{
|
||||
if($this->type == 'gif' && $this->detectAnimatedGif())
|
||||
{
|
||||
$this->resizable = false;
|
||||
}
|
||||
|
||||
if($this->type == 'svg+xml')
|
||||
{
|
||||
$this->resizable = false;
|
||||
}
|
||||
|
||||
return $this->resizable;
|
||||
}
|
||||
|
||||
public function detectAnimatedGif()
|
||||
{
|
||||
$is_animated = preg_match('#(\x00\x21\xF9\x04.{4}\x00\x2C.*){2,}#s', $this->imgstring);
|
||||
if ($is_animated == 1)
|
||||
{
|
||||
$this->animated = true;
|
||||
}
|
||||
|
||||
return $this->animated;
|
||||
}
|
||||
|
||||
# save the original image to temp folder
|
||||
public function saveOriginal($destinationfolder = 'ORIGINAL')
|
||||
{
|
||||
$path = $this->tmpFolder . $destinationfolder . '+' . $this->filename . '.' . $this->extension;
|
||||
|
||||
if(!file_put_contents($path, $this->imgstring))
|
||||
{
|
||||
$this->errors[] = 'could not store the image in the temporary folder';
|
||||
}
|
||||
}
|
||||
|
||||
# save the original image for all sizes/folders
|
||||
public function saveOriginalForAll()
|
||||
{
|
||||
$this->saveOriginal('LIVE');
|
||||
$this->saveOriginal('THUMBS');
|
||||
}
|
||||
|
||||
public function createImage()
|
||||
{
|
||||
return imagecreatefromstring($this->imgstring);
|
||||
}
|
||||
|
||||
public function getImageSize($image)
|
||||
{
|
||||
return ['width' => imagesx($image), 'height' => imagesy($image)];
|
||||
}
|
||||
|
||||
public function calculateSize(array $originalsize, array $desiredsize)
|
||||
{
|
||||
# if desired size is bigger than the actual image, then drop the desired sizes and use the actual image size instead
|
||||
if($desiredsize['width'] > $originalsize['width'])
|
||||
{
|
||||
return $originalsize;
|
||||
}
|
||||
|
||||
if(!isset($desiredsize['height']))
|
||||
{
|
||||
$resizeFactor = $originalsize['width'] / $desiredsize['width'];
|
||||
$desiredsize['height'] = round( ($originalsize['height'] / $resizeFactor), 0);
|
||||
}
|
||||
|
||||
return $desiredsize;
|
||||
}
|
||||
|
||||
public function resizeImage($image, array $desired, array $original)
|
||||
{
|
||||
# resize
|
||||
$ratio = max($desired['width']/$original['width'], $desired['height']/$original['height']);
|
||||
$h = $desired['height'] / $ratio;
|
||||
$x = ($original['width'] - $desired['width'] / $ratio) / 2;
|
||||
$y = ($original['height'] - $desired['height'] / $ratio) / 2;
|
||||
$w = $desired['width'] / $ratio;
|
||||
|
||||
$resizedImage = imagecreatetruecolor($desired['width'], $desired['height']);
|
||||
|
||||
# preserve transparency
|
||||
if($this->extension == "gif" or $this->extension == "png" or $this->extension == "webp")
|
||||
{
|
||||
imagecolortransparent($resizedImage, imagecolorallocatealpha($resizedImage, 0, 0, 0, 127));
|
||||
imagealphablending($resizedImage, false);
|
||||
imagesavealpha($resizedImage, true);
|
||||
}
|
||||
|
||||
imagecopyresampled($resizedImage, $image, 0, 0, $x, $y, $desired['width'], $desired['height'], $w, $h);
|
||||
|
||||
return $resizedImage;
|
||||
}
|
||||
|
||||
public function saveResizedImage($resizedImage, string $destinationfolder, string $extension)
|
||||
{
|
||||
$destinationfolder = strtoupper($destinationfolder);
|
||||
|
||||
switch($extension)
|
||||
{
|
||||
case "png":
|
||||
$storedImage = imagepng( $resizedImage, $this->tmpFolder . $destinationfolder . '+' . $this->filename . '.png', 9 );
|
||||
break;
|
||||
case "gif":
|
||||
$storedImage = imagegif( $resizedImage, $this->tmpFolder . $destinationfolder . '+' . $this->filename . '.gif' );
|
||||
break;
|
||||
case "webp":
|
||||
$storedImage = imagewebp( $resizedImage, $this->tmpFolder . $destinationfolder . '+' . $this->filename . '.webp', 80);
|
||||
break;
|
||||
case "jpg":
|
||||
case "jpeg":
|
||||
$storedImage = imagejpeg( $resizedImage, $this->tmpFolder . $destinationfolder . '+' . $this->filename . '.' . $extension, 80);
|
||||
break;
|
||||
default:
|
||||
$storedImage = false;
|
||||
}
|
||||
|
||||
if(!$storedImage)
|
||||
{
|
||||
$failedImage = $this->tmpFolder . $destinationfolder . '+' . $this->filename . '.' . $extension;
|
||||
|
||||
$this->errors[] = "Could not store the resized version $failedImage";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
# publish image function is moved to storage model
|
||||
|
||||
|
||||
# MOVE TO STORAGE ??
|
||||
public function deleteImage($name)
|
||||
{
|
||||
# validate name
|
||||
$name = basename($name);
|
||||
|
||||
if(!file_exists($this->originalFolder . $name) OR !unlink($this->originalFolder . $name))
|
||||
{
|
||||
$this->errors[] = "We could not delete the original image";
|
||||
}
|
||||
|
||||
if(!file_exists($this->liveFolder . $name) OR !unlink($this->liveFolder . $name))
|
||||
{
|
||||
$this->errors[] = "We could not delete the live image";
|
||||
}
|
||||
|
||||
if(!file_exists($this->thumbFolder . $name) OR !unlink($this->thumbFolder . $name))
|
||||
{
|
||||
$this->errors[] = "we could not delete the thumb image";
|
||||
}
|
||||
|
||||
# delete custom images (resized and grayscaled) array_map('unlink', glob("some/dir/*.txt"));
|
||||
$pathinfo = pathinfo($name);
|
||||
foreach(glob($this->customFolder . $pathinfo['filename'] . '\-*.' . $pathinfo['extension']) as $image)
|
||||
{
|
||||
# you could check if extension is the same here
|
||||
if(!unlink($image))
|
||||
{
|
||||
$this->errors[] = "we could not delete a custom image (grayscale or resized)";
|
||||
}
|
||||
}
|
||||
|
||||
if(empty($this->errors))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
# in use ??
|
||||
public function deleteImageWithName($name)
|
||||
{
|
||||
# e.g. delete $name = 'logo...';
|
||||
|
||||
$name = basename($name);
|
||||
|
||||
if($name != '' && !in_array($name, array(".","..")))
|
||||
{
|
||||
foreach(glob($this->liveFolder . $name) as $file)
|
||||
{
|
||||
unlink($file);
|
||||
}
|
||||
foreach(glob($this->originalFolder . $name) as $file)
|
||||
{
|
||||
unlink($file);
|
||||
}
|
||||
foreach(glob($this->thumbFolder . $name) as $file)
|
||||
{
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# in use ??
|
||||
public function copyImage($name,$sourcefolder,$targetfolder)
|
||||
{
|
||||
copy($sourcefolder . $name, $targetfolder . $name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Moves the uploaded file to the upload directory. Only used for settings / NON VUE.JS uploads
|
||||
*
|
||||
* @param string $directory directory to which the file is moved
|
||||
* @param UploadedFile $uploadedFile file uploaded file to move
|
||||
* @return string filename of moved file
|
||||
*/
|
||||
public function moveUploadedImage(UploadedFile $uploadedFile, $overwrite = false, $name = false, $folder = NULL)
|
||||
{
|
||||
$this->setFileName($uploadedFile->getClientFilename(), 'file');
|
||||
|
||||
if($name)
|
||||
{
|
||||
$this->setFileName($name . '.' . $this->extension, 'file', $overwrite);
|
||||
}
|
||||
|
||||
if(!$folder)
|
||||
{
|
||||
$folder = $this->liveFolder;
|
||||
}
|
||||
|
||||
$uploadedFile->moveTo($folder . $this->getFullName());
|
||||
|
||||
return $this->getFullName();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
# save the image name as txt to temp folder
|
||||
public function saveName()
|
||||
{
|
||||
$path = $this->tmpFolder . $this->filename . '.txt';
|
||||
|
||||
if(!fopen($path, "w"))
|
||||
{
|
||||
$this->errors[] = 'could not store the filename in the temporary folder';
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* scans content of a folder (without recursion)
|
||||
* vars: folder path as string
|
||||
* returns: one-dimensional array with names of folders and files
|
||||
*/
|
||||
public function scanMediaFlat()
|
||||
{
|
||||
$thumbs = array_diff(scandir($this->thumbFolder), array('..', '.'));
|
||||
$imagelist = array();
|
||||
|
||||
foreach ($thumbs as $key => $name)
|
||||
{
|
||||
if (file_exists($this->liveFolder . $name))
|
||||
{
|
||||
$imagelist[] = [
|
||||
'name' => $name,
|
||||
'timestamp' => filemtime($this->liveFolder . $name),
|
||||
'src_thumb' => 'media/thumbs/' . $name,
|
||||
'src_live' => 'media/live/' . $name,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$imagelist = Helpers::array_sort($imagelist, 'timestamp', SORT_DESC);
|
||||
|
||||
return $imagelist;
|
||||
}
|
||||
|
||||
|
||||
# get details from existing image for media library
|
||||
public function getImageDetails($name, $structure)
|
||||
{
|
||||
$name = basename($name);
|
||||
|
||||
if (!in_array($name, array(".","..")) && file_exists($this->liveFolder . $name))
|
||||
{
|
||||
$imageinfo = getimagesize($this->liveFolder . $name);
|
||||
|
||||
if(!$imageinfo && pathinfo($this->liveFolder . $name, PATHINFO_EXTENSION) == 'svg')
|
||||
{
|
||||
$imagedetails = [
|
||||
'name' => $name,
|
||||
'timestamp' => filemtime($this->liveFolder . $name),
|
||||
'bytes' => filesize($this->liveFolder . $name),
|
||||
'width' => '---',
|
||||
'height' => '---',
|
||||
'type' => 'svg',
|
||||
'src_thumb' => 'media/thumbs/' . $name,
|
||||
'src_live' => 'media/live/' . $name,
|
||||
'pages' => $this->findPagesWithUrl($structure, $name, $result = [])
|
||||
];
|
||||
}
|
||||
else
|
||||
{
|
||||
$imagedetails = [
|
||||
'name' => $name,
|
||||
'timestamp' => filemtime($this->liveFolder . $name),
|
||||
'bytes' => filesize($this->liveFolder . $name),
|
||||
'width' => $imageinfo[0],
|
||||
'height' => $imageinfo[1],
|
||||
'type' => $imageinfo['mime'],
|
||||
'src_thumb' => 'media/thumbs/' . $name,
|
||||
'src_live' => 'media/live/' . $name,
|
||||
'pages' => $this->findPagesWithUrl($structure, $name, $result = [])
|
||||
];
|
||||
}
|
||||
|
||||
return $imagedetails;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function generateThumbs()
|
||||
{
|
||||
# generate images from live folder to 'tmthumbs'
|
||||
$liveImages = scandir($this->liveFolder);
|
||||
|
||||
$result = false;
|
||||
|
||||
foreach ($liveImages as $key => $name)
|
||||
{
|
||||
if (!in_array($name, array(".","..")))
|
||||
{
|
||||
$result = $this->generateThumbFromImageFile($name);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function generateThumbFromImageFile($filename)
|
||||
{
|
||||
$this->setFileName($filename, 'image', $overwrite = true);
|
||||
|
||||
$image = $this->createImageFromPath($this->liveFolder . $filename, $this->extension);
|
||||
|
||||
$originalSize = $this->getImageSize($image);
|
||||
|
||||
$thumbSize = $this->desiredSizes['thumbs'];
|
||||
|
||||
$thumb = $this->imageResize($image, $originalSize, ['thumbs' => $thumbSize ], $this->extension);
|
||||
|
||||
$saveImage = $this->saveImage($this->thumbFolder, $thumb['thumbs'], $this->filename, $this->extension);
|
||||
if($saveImage)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
# filename and imagepath can be a tmp-version after upload.
|
||||
public function generateSizesFromImageFile($filename, $imagePath)
|
||||
{
|
||||
$this->setFileName($filename, 'image');
|
||||
|
||||
$image = $this->createImageFromPath($imagePath, $this->extension);
|
||||
|
||||
$originalSize = $this->getImageSize($image);
|
||||
|
||||
$resizedImages = $this->imageResize($image, $originalSize, $this->desiredSizes, $this->extension);
|
||||
|
||||
return $resizedImages;
|
||||
}
|
||||
|
||||
public function grayscale($imagePath, $extension)
|
||||
{
|
||||
$image = $this->createImageFromPath($imagePath, $extension);
|
||||
|
||||
imagefilter($image, IMG_FILTER_GRAYSCALE);
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
public function createImageFromPath($imagePath, $extension)
|
||||
{
|
||||
switch($extension)
|
||||
{
|
||||
case 'gif': $image = imagecreatefromgif($imagePath); break;
|
||||
case 'jpg' :
|
||||
case 'jpeg': $image = imagecreatefromjpeg($imagePath); break;
|
||||
case 'png': $image = imagecreatefrompng($imagePath); break;
|
||||
case 'webp': $image = imagecreatefromwebp($imagePath); break;
|
||||
default: return 'image type not supported';
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
}
|
229
system/typemill/Static/License.php
Normal file
229
system/typemill/Static/License.php
Normal file
@@ -0,0 +1,229 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Static;
|
||||
use Typemill\Models\StorageWrapper;
|
||||
|
||||
class License
|
||||
{
|
||||
public static function getLicenseData()
|
||||
{
|
||||
# returns data for settings page
|
||||
}
|
||||
|
||||
public static function getLicensePlan()
|
||||
{
|
||||
# returns plan for plugins
|
||||
}
|
||||
|
||||
# check the local licence file (like pem or pub)
|
||||
public static function checkLicense()
|
||||
{
|
||||
$storage = new StorageWrapper('\Typemill\Models\Storage');
|
||||
|
||||
$licensedata = $storage->getYaml('settings', 'license.yaml');
|
||||
|
||||
if(!$licensedata)
|
||||
{
|
||||
return ['result' => false, 'message' => 'no license found'];
|
||||
}
|
||||
|
||||
if(!isset($licensedata['license'],$licensedata['email'],$licensedata['domain'],$licensedata['plan'],$licensedata['payed_until'],$licensedata['signature']))
|
||||
{
|
||||
return ['result' => false, 'message' => 'License data not complete'];
|
||||
}
|
||||
|
||||
$licenseStatus = self::validateLicense($licensedata);
|
||||
|
||||
unset($licensedata['signature']);
|
||||
|
||||
if($licenseStatus === false)
|
||||
{
|
||||
return ['result' => false, 'message' => 'License data are invalid'];
|
||||
}
|
||||
elseif($licenseStatus === true)
|
||||
{
|
||||
echo '<pre>';
|
||||
print_r($licensedata);
|
||||
die();
|
||||
}
|
||||
else
|
||||
{
|
||||
die('error checking signature');
|
||||
}
|
||||
}
|
||||
|
||||
public static function validateLicense($data)
|
||||
{
|
||||
$public_key_pem = self::getPublicKeyPem();
|
||||
|
||||
$binary_signature = base64_decode($data['signature']);
|
||||
|
||||
$data['email'] = self::hashMail($data['email']);
|
||||
unset($data['signature']);
|
||||
|
||||
# manipulate data
|
||||
# $data['product'] = 'business';
|
||||
|
||||
$data = json_encode($data);
|
||||
|
||||
# Check signature
|
||||
$verified = openssl_verify($data, $binary_signature, $public_key_pem, OPENSSL_ALGO_SHA256);
|
||||
|
||||
if ($verified == 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
elseif ($verified == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
die("ugly, error checking signature");
|
||||
}
|
||||
}
|
||||
|
||||
public static function hashMail($mail)
|
||||
{
|
||||
return hash('sha256', trim($mail) . 'TYla5xa8JUur');
|
||||
}
|
||||
|
||||
public static function getPublicKeyPem()
|
||||
{
|
||||
$pkeyfile = getcwd() . DIRECTORY_SEPARATOR . 'settings' . DIRECTORY_SEPARATOR . "public_key.pem";
|
||||
|
||||
if(file_exists($pkeyfile) && is_readable($pkeyfile))
|
||||
{
|
||||
# fetch public key from file and ready it
|
||||
$fp = fopen($pkeyfile, "r");
|
||||
$public_key_pem = fread($fp, 8192);
|
||||
fclose($fp);
|
||||
|
||||
return $public_key_pem;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* KIRBY -> source -> cms -> system.php
|
||||
|
||||
/**
|
||||
* Loads the license file and returns
|
||||
* the license information if available
|
||||
*
|
||||
* @return string|bool License key or `false` if the current user has
|
||||
* permissions for access.settings, otherwise just a
|
||||
* boolean that tells whether a valid license is active
|
||||
|
||||
public function license()
|
||||
{
|
||||
try {
|
||||
$license = Json::read($this->app->root('license'));
|
||||
} catch (Throwable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check for all required fields for the validation
|
||||
if (isset(
|
||||
$license['license'],
|
||||
$license['order'],
|
||||
$license['date'],
|
||||
$license['email'],
|
||||
$license['domain'],
|
||||
$license['signature']
|
||||
) !== true) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// build the license verification data
|
||||
$data = [
|
||||
'license' => $license['license'],
|
||||
'order' => $license['order'],
|
||||
'email' => hash('sha256', $license['email'] . 'kwAHMLyLPBnHEskzH9pPbJsBxQhKXZnX'),
|
||||
'domain' => $license['domain'],
|
||||
'date' => $license['date']
|
||||
];
|
||||
|
||||
|
||||
// get the public key
|
||||
$pubKey = F::read($this->app->root('kirby') . '/kirby.pub');
|
||||
|
||||
// verify the license signature
|
||||
$data = json_encode($data);
|
||||
$signature = hex2bin($license['signature']);
|
||||
if (openssl_verify($data, $signature, $pubKey, 'RSA-SHA256') !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// verify the URL
|
||||
if ($this->licenseUrl() !== $this->licenseUrl($license['domain'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// only return the actual license key if the
|
||||
// current user has appropriate permissions
|
||||
if ($this->app->user()?->isAdmin() === true) {
|
||||
return $license['license'];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Validates the license key
|
||||
* and adds it to the .license file in the config
|
||||
* folder if possible.
|
||||
*
|
||||
* @throws \Kirby\Exception\Exception
|
||||
* @throws \Kirby\Exception\InvalidArgumentException
|
||||
*
|
||||
public function register(string $license = null, string $email = null): bool
|
||||
{
|
||||
if (Str::startsWith($license, 'K3-PRO-') === false) {
|
||||
throw new InvalidArgumentException(['key' => 'license.format']);
|
||||
}
|
||||
|
||||
if (V::email($email) === false) {
|
||||
throw new InvalidArgumentException(['key' => 'license.email']);
|
||||
}
|
||||
|
||||
// @codeCoverageIgnoreStart
|
||||
$response = Remote::get('https://hub.getkirby.com/register', [
|
||||
'data' => [
|
||||
'license' => $license,
|
||||
'email' => Str::lower(trim($email)),
|
||||
'domain' => $this->indexUrl()
|
||||
]
|
||||
]);
|
||||
|
||||
if ($response->code() !== 200) {
|
||||
throw new Exception($response->content());
|
||||
}
|
||||
|
||||
// decode the response
|
||||
$json = Json::decode($response->content());
|
||||
|
||||
// replace the email with the plaintext version
|
||||
$json['email'] = $email;
|
||||
|
||||
// where to store the license file
|
||||
$file = $this->app->root('license');
|
||||
|
||||
// save the license information
|
||||
Json::write($file, $json);
|
||||
|
||||
if ($this->license() === false) {
|
||||
throw new InvalidArgumentException([
|
||||
'key' => 'license.verification'
|
||||
]);
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
*/
|
45
system/typemill/Static/Slug.php
Normal file
45
system/typemill/Static/Slug.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Static;
|
||||
|
||||
use \URLify;
|
||||
|
||||
class Slug
|
||||
{
|
||||
public static function getStringParts($name)
|
||||
{
|
||||
return preg_split('/[\-\.\_\=\+\?\!\*\#\(\)\/ ]/',$name);
|
||||
}
|
||||
|
||||
public static function getFileType($fileName)
|
||||
{
|
||||
$parts = preg_split('/\./',$fileName);
|
||||
return end($parts);
|
||||
}
|
||||
|
||||
public static function splitFileName($fileName)
|
||||
{
|
||||
$parts = preg_split('/\./',$fileName);
|
||||
return $parts;
|
||||
}
|
||||
public static function getNameWithoutType($fileName)
|
||||
{
|
||||
$parts = preg_split('/\./',$fileName);
|
||||
return $parts[0];
|
||||
}
|
||||
|
||||
public static function createSlug($name, $language = 'en')
|
||||
{
|
||||
$name = iconv(mb_detect_encoding($name, mb_detect_order(), true), "UTF-8", $name);
|
||||
|
||||
return URLify::filter(
|
||||
$name,
|
||||
$length = 60,
|
||||
$language,
|
||||
$file_name = false,
|
||||
$use_remove_list = false,
|
||||
$lower_case = true,
|
||||
$treat_underscore_as_space = true
|
||||
);
|
||||
}
|
||||
}
|
7
system/typemill/author/css/a11y-dark.min.css
vendored
Normal file
7
system/typemill/author/css/a11y-dark.min.css
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
|
||||
Theme: a11y-dark
|
||||
Author: @ericwbailey
|
||||
Maintainer: @ericwbailey
|
||||
|
||||
Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css
|
||||
*/.hljs{background:#2b2b2b;color:#f8f8f2}.hljs-comment,.hljs-quote{color:#d4d0ab}.hljs-deletion,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#ffa07a}.hljs-built_in,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-type{color:#f5ab35}.hljs-attribute{color:gold}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:#abe338}.hljs-section,.hljs-title{color:#00e0e0}.hljs-keyword,.hljs-selector-tag{color:#dcc6e0}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}@media screen and (-ms-high-contrast:active){.hljs-addition,.hljs-attribute,.hljs-built_in,.hljs-bullet,.hljs-comment,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-quote,.hljs-string,.hljs-symbol,.hljs-type{color:highlight}.hljs-keyword,.hljs-selector-tag{font-weight:700}}
|
709
system/typemill/author/js/highlight.min.js
vendored
Normal file
709
system/typemill/author/js/highlight.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
131
system/typemill/author/js/vue-license.js
Normal file
131
system/typemill/author/js/vue-license.js
Normal file
@@ -0,0 +1,131 @@
|
||||
const app = Vue.createApp({
|
||||
template: `<Transition name="initial" appear>
|
||||
<div v-if="licenseData.license">
|
||||
<div>
|
||||
<p v-if="!licenseData.datecheck" class="bg-rose-500 text-white p-2 text-center">Your license is out of date. Please check if the payments for your subscription were successfull.</p>
|
||||
<p v-else-if="!licenseData.domaincheck" class="bg-rose-500 text-white p-2 text-center">Your license is only valid for the domain listed in your license data below.</p>
|
||||
<p v-else>Congratulations! Your license is ok and you can enjoy all features.</p>
|
||||
</div>
|
||||
<div class="flex flex-wrap justify-between">
|
||||
<div class="w-2/5 border-2 border-stone-200 my-8 text-center flex flex-col">
|
||||
<div class="p-8 grow flex justify-center items-center">
|
||||
<img class="mx-auto" :src="src" width="150" height="150">
|
||||
</div>
|
||||
<div class="p-8 bg-teal-500">
|
||||
<p class="font-medium text-white">{{ licenseData.plan }}-LICENSE</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-3/5 border-2 border-stone-200 p-8 my-8">
|
||||
<p class="mb-1 font-medium">License-key:</p>
|
||||
<p class="w-full border p-2 bg-stone-100">{{ licenseData.license }}</p>
|
||||
<p class="mb-1 mt-3 font-medium">Domain:</p>
|
||||
<p class="w-full border p-2 bg-stone-100">{{ licenseData.domain }}</p>
|
||||
<p class="mb-1 mt-3 font-medium">E-Mail:</p>
|
||||
<p class="w-full border p-2 bg-stone-100">{{ licenseData.email }}</p>
|
||||
<p class="mb-1 mt-3 font-medium">Payed until:</p>
|
||||
<p class="w-full border p-2 bg-stone-100">{{ licenseData.payed_until }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form v-else class="inline-block w-full">
|
||||
<div>
|
||||
<p>Buy a typemill-license and enjoy our flatrate-model for premium-plugins and -themes.</p><p>We offer two types of subscription-based licenses:</p>
|
||||
<div class="flex flex-wrap justify-between">
|
||||
<div class="w-half border-2 border-stone-200 p-4 my-8 text-center">
|
||||
<h2 class="text-3 font-bold mb-4">Maker License</h2>
|
||||
<p class="mb-4">Use all maker-prodcuts (plugins and themes) for one year. The subscription will automatically renewed after a year.</p>
|
||||
<a href="#!" class="paddle_button" data-product="44699">Buy now for 29,00 €/year</a>
|
||||
</div>
|
||||
<div class="w-half border-2 border-stone-200 p-4 my-8 text-center">
|
||||
<h2 class="text-3 font-bold mb-4">Business License</h2>
|
||||
<p class="mb-4">Use all business- and maker-products (plugins, themes, services) for one year. The subscription will automatically renewed after a year.</p>
|
||||
<a href="#!" class="paddle_button" data-product="44700">Buy now for 229,00 €/year</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="(fieldDefinition, fieldname) in formDefinitions">
|
||||
<fieldset class="flex flex-wrap justify-between border-2 border-stone-200 p-4 my-8" v-if="fieldDefinition.type == 'fieldset'">
|
||||
<legend class="text-lg font-medium">{{ fieldDefinition.legend }}</legend>
|
||||
<component v-for="(subfieldDefinition, subfieldname) in fieldDefinition.fields"
|
||||
:key="subfieldname"
|
||||
:is="selectComponent(subfieldDefinition.type)"
|
||||
:errors="errors"
|
||||
:name="subfieldname"
|
||||
:userroles="userroles"
|
||||
:value="formData[subfieldname]"
|
||||
v-bind="subfieldDefinition">
|
||||
</component>
|
||||
</fieldset>
|
||||
<component v-else
|
||||
:key="fieldname"
|
||||
:is="selectComponent(fieldDefinition.type)"
|
||||
:errors="errors"
|
||||
:name="fieldname"
|
||||
:userroles="userroles"
|
||||
:value="formData[fieldname]"
|
||||
v-bind="fieldDefinition">
|
||||
</component>
|
||||
</div>
|
||||
<div class="my-5">
|
||||
<div :class="messageClass" class="block w-full h-8 px-3 py-1 my-1 text-white transition duration-100">{{ message }}</div>
|
||||
<input type="submit" @click.prevent="save()" value="save" class="w-full p-3 my-1 bg-stone-700 hover:bg-stone-900 text-white cursor-pointer transition duration-100">
|
||||
</div>
|
||||
</form>
|
||||
</Transition>`,
|
||||
data() {
|
||||
return {
|
||||
licenseData: data.licensedata,
|
||||
formDefinitions: data.licensefields,
|
||||
formData: {},
|
||||
message: '',
|
||||
messageClass: '',
|
||||
errors: {},
|
||||
src: tmaxios.defaults.baseURL + "/system/author/img/favicon-144.png"
|
||||
}
|
||||
},
|
||||
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.post('/api/v1/license',{
|
||||
'csrf_name': document.getElementById("csrf_name").value,
|
||||
'csrf_value': document.getElementById("csrf_value").value,
|
||||
'license': this.formData
|
||||
})
|
||||
.then(function (response)
|
||||
{
|
||||
self.messageClass = 'bg-teal-500';
|
||||
self.message = response.data.message;
|
||||
self.licenseData = response.data.licensedata;
|
||||
})
|
||||
.catch(function (error)
|
||||
{
|
||||
self.messageClass = 'bg-rose-500';
|
||||
self.message = error.response.data.message;
|
||||
|
||||
/* form validation errors */
|
||||
if(error.response.data.errors !== undefined)
|
||||
{
|
||||
self.errors = error.response.data.errors;
|
||||
}
|
||||
});
|
||||
},
|
||||
reset: function()
|
||||
{
|
||||
this.errors = {};
|
||||
this.message = '';
|
||||
this.messageClass = '';
|
||||
}
|
||||
},
|
||||
})
|
136
system/typemill/author/js/vue-user.js
Normal file
136
system/typemill/author/js/vue-user.js
Normal file
@@ -0,0 +1,136 @@
|
||||
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 class="my-5 text-center">
|
||||
<button @click.prevent="showModal = true" class="p-3 px-4 text-rose-500 border border-rose-100 hover:border-rose-500 cursor-pointer transition duration-100">delete user</button>
|
||||
<modal v-if="showModal" @close="showModal = false">
|
||||
<template #header>
|
||||
<h3>Delete user</h3>
|
||||
</template>
|
||||
<template #body>
|
||||
<p>Do you really want to delete this user?</p>
|
||||
</template>
|
||||
<template #button>
|
||||
<button @click="deleteuser()" class="focus:outline-none px-4 p-3 mr-3 text-white bg-rose-500 hover:bg-rose-700 transition duration-100">delete user</button>
|
||||
</template>
|
||||
</modal>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>`,
|
||||
data() {
|
||||
return {
|
||||
formDefinitions: data.userfields,
|
||||
formData: data.userdata,
|
||||
userroles: data.userroles,
|
||||
message: '',
|
||||
messageClass: '',
|
||||
errors: {},
|
||||
showModal: false,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
eventBus.$on('forminput', formdata => {
|
||||
this.formData[formdata.name] = formdata.value;
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
selectComponent: function(type)
|
||||
{
|
||||
return 'component-'+type;
|
||||
},
|
||||
changeForm: function()
|
||||
{
|
||||
/* change input form if user role changed */
|
||||
},
|
||||
save: function()
|
||||
{
|
||||
this.reset();
|
||||
var self = this;
|
||||
|
||||
tmaxios.put('/api/v1/user',{
|
||||
'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;
|
||||
}
|
||||
});
|
||||
},
|
||||
deleteuser: function()
|
||||
{
|
||||
this.reset();
|
||||
var self = this;
|
||||
|
||||
tmaxios.delete('/api/v1/user',{
|
||||
data: {
|
||||
'csrf_name': document.getElementById("csrf_name").value,
|
||||
'csrf_value': document.getElementById("csrf_value").value,
|
||||
'username': this.formData.username
|
||||
}
|
||||
})
|
||||
.then(function (response)
|
||||
{
|
||||
self.showModal = false;
|
||||
self.messageClass = 'bg-teal-500';
|
||||
self.message = response.data.message;
|
||||
/* redirect to userlist */
|
||||
})
|
||||
.catch(function (error)
|
||||
{
|
||||
self.showModal = false;
|
||||
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 = '';
|
||||
}
|
||||
},
|
||||
})
|
126
system/typemill/author/js/vue-usernew.js
Normal file
126
system/typemill/author/js/vue-usernew.js
Normal file
@@ -0,0 +1,126 @@
|
||||
const app = Vue.createApp({
|
||||
template: `<Transition name="initial" appear>
|
||||
<div class="w-full">
|
||||
<div class="mt-5 mb-5">
|
||||
<label for="roleselector" class="block mb-1 font-medium">{{ $filters.translate("Select a role") }}</label>
|
||||
<select class="form-select block w-full border border-stone-300 bg-stone-200 px-2 py-3 h-12 transition ease-in-out"
|
||||
v-model="selectedrole"
|
||||
@change="generateForm()">
|
||||
<option disabled value="">Please select</option>
|
||||
<option v-for="option,optionkey in userroles">{{option}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<form v-if="formDefinitions" 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 {
|
||||
selectedrole: false,
|
||||
formDefinitions: false,
|
||||
formData: {},
|
||||
userroles: data.userroles,
|
||||
message: '',
|
||||
messageClass: '',
|
||||
errors: {},
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
eventBus.$on('forminput', formdata => {
|
||||
this.formData[formdata.name] = formdata.value;
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
selectComponent: function(type)
|
||||
{
|
||||
return 'component-'+type;
|
||||
},
|
||||
generateForm: function()
|
||||
{
|
||||
this.reset();
|
||||
var self = this;
|
||||
|
||||
tmaxios.get('/api/v1/userform',{
|
||||
params: {
|
||||
'csrf_name': document.getElementById("csrf_name").value,
|
||||
'csrf_value': document.getElementById("csrf_value").value,
|
||||
'userrole': this.selectedrole
|
||||
}
|
||||
})
|
||||
.then(function (response)
|
||||
{
|
||||
self.formDefinitions = response.data.userform;
|
||||
self.formData.userrole = self.selectedrole;
|
||||
})
|
||||
.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;
|
||||
}
|
||||
});
|
||||
},
|
||||
save: function()
|
||||
{
|
||||
this.reset();
|
||||
var self = this;
|
||||
|
||||
tmaxios.post('/api/v1/user',{
|
||||
'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;
|
||||
|
||||
window.location = tmaxios.defaults.baseURL + '/tm/user/' + self.formData.username;
|
||||
})
|
||||
.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 = '';
|
||||
}
|
||||
},
|
||||
})
|
27
system/typemill/author/system/license.twig
Normal file
27
system/typemill/author/system/license.twig
Normal file
@@ -0,0 +1,27 @@
|
||||
{% extends 'layouts/layoutSystem.twig' %}
|
||||
{% block title %}{{ translate('License') }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1 class="text-3xl font-bold mb-4">{{ translate('License') }} </h1>
|
||||
|
||||
<div id="license" v-cloak></div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-license.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('#license');
|
||||
</script>
|
||||
<script src="https://cdn.paddle.com/paddle/paddle.js"></script>
|
||||
<script type="text/javascript">
|
||||
|
||||
Paddle.Environment.set('sandbox');
|
||||
Paddle.Setup({ vendor: 10629 });
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
21
system/typemill/author/system/user.twig
Normal file
21
system/typemill/author/system/user.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('User') }} </h1>
|
||||
|
||||
<div id="account" v-cloak></div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-user.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/usernew.twig
Normal file
21
system/typemill/author/system/usernew.twig
Normal file
@@ -0,0 +1,21 @@
|
||||
{% extends 'layouts/layoutSystem.twig' %}
|
||||
{% block title %}{{ translate('Create user') }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1 class="text-3xl font-bold mb-4">{{ translate('Create user') }} </h1>
|
||||
|
||||
<div id="newuser" v-cloak></div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-usernew.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('#newuser');
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
15
system/typemill/settings/license.yaml
Normal file
15
system/typemill/settings/license.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
license:
|
||||
name: license
|
||||
label: 'Your license key'
|
||||
type: 'text'
|
||||
required: true
|
||||
email:
|
||||
name: email
|
||||
label: 'Your email'
|
||||
type: 'text'
|
||||
required: true
|
||||
domain:
|
||||
name: domain
|
||||
label: 'Domain for license'
|
||||
type: 'text'
|
||||
required: true
|
Reference in New Issue
Block a user