mirror of
https://github.com/typemill/typemill.git
synced 2025-08-06 06:07:31 +02:00
old tm-dev stuff
This commit is contained in:
304
system/typemill/Assets.php
Normal file
304
system/typemill/Assets.php
Normal file
@@ -0,0 +1,304 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill;
|
||||
|
||||
use Typemill\Models\ProcessImage;
|
||||
|
||||
# this class is available to the container and to all plugins
|
||||
class Assets
|
||||
{
|
||||
public $baseUrl;
|
||||
|
||||
public function __construct($baseUrl)
|
||||
{
|
||||
$this->baseUrl = $baseUrl;
|
||||
$this->JS = array();
|
||||
$this->CSS = array();
|
||||
$this->inlineJS = array();
|
||||
$this->inlineCSS = array();
|
||||
$this->editorJS = array();
|
||||
$this->editorCSS = array();
|
||||
$this->editorInlineJS = array();
|
||||
$this->svgSymbols = array();
|
||||
$this->meta = array();
|
||||
$this->imageUrl = false;
|
||||
$this->imageFolder = 'original';
|
||||
}
|
||||
|
||||
public function setUri($uri)
|
||||
{
|
||||
$this->uri = $uri;
|
||||
}
|
||||
|
||||
public function setBaseUrl($baseUrl)
|
||||
{
|
||||
$this->baseUrl = $baseUrl;
|
||||
}
|
||||
|
||||
public function image($url)
|
||||
{
|
||||
$this->imageUrl = $url;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function resize($width,$height)
|
||||
{
|
||||
$pathinfo = pathinfo($this->imageUrl);
|
||||
$extension = strtolower($pathinfo['extension']);
|
||||
$imageName = $pathinfo['filename'];
|
||||
|
||||
$desiredSizes = ['custom' => []];
|
||||
|
||||
$resize = '-';
|
||||
|
||||
if(is_int($width) && $width < 10000)
|
||||
{
|
||||
$resize .= $width;
|
||||
$desiredSizes['custom']['width'] = $width;
|
||||
}
|
||||
|
||||
$resize .= 'x';
|
||||
|
||||
if(is_int($height) && $height < 10000)
|
||||
{
|
||||
$resize .= $height;
|
||||
$desiredSizes['custom']['height'] = $height;
|
||||
}
|
||||
|
||||
$processImage = new ProcessImage($desiredSizes);
|
||||
|
||||
$processImage->checkFolders('images');
|
||||
|
||||
$imageNameResized = $imageName . $resize;
|
||||
$imagePathResized = $processImage->customFolder . $imageNameResized . '.' . $extension;
|
||||
$imageUrlResized = 'media/custom/' . $imageNameResized . '.' . $extension;
|
||||
|
||||
if(!file_exists( $imagePathResized ))
|
||||
{
|
||||
# if custom version does not exist, use original version for resizing
|
||||
$imageFolder = ($this->imageFolder == 'original') ? $processImage->originalFolder : $processImage->customFolder;
|
||||
|
||||
$imagePath = $imageFolder . $pathinfo['basename'];
|
||||
|
||||
$resizedImage = $processImage->generateSizesFromImageFile($imageUrlResized, $imagePath);
|
||||
|
||||
$savedImage = $processImage->saveImage($processImage->customFolder, $resizedImage['custom'], $imageNameResized, $extension);
|
||||
|
||||
if(!$savedImage)
|
||||
{
|
||||
# return old image url without resize
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
# set folder to custom, so that the next method uses the correct (resized) version
|
||||
$this->imageFolder = 'custom';
|
||||
|
||||
$this->imageUrl = $imageUrlResized;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function grayscale()
|
||||
{
|
||||
$pathinfo = pathinfo($this->imageUrl);
|
||||
$extension = strtolower($pathinfo['extension']);
|
||||
$imageName = $pathinfo['filename'];
|
||||
|
||||
$processImage = new ProcessImage([]);
|
||||
|
||||
$processImage->checkFolders('images');
|
||||
|
||||
$imageNameGrayscale = $imageName . '-grayscale';
|
||||
$imagePathGrayscale = $processImage->customFolder . $imageNameGrayscale . '.' . $extension;
|
||||
$imageUrlGrayscale = 'media/custom/' . $imageNameGrayscale . '.' . $extension;
|
||||
|
||||
if(!file_exists( $imagePathGrayscale ))
|
||||
{
|
||||
# if custom-version does not exist, use live-version for grayscale-manipulation.
|
||||
$imageFolder = ($this->imageFolder == 'original') ? $processImage->liveFolder : $processImage->customFolder;
|
||||
|
||||
$imagePath = $imageFolder . $pathinfo['basename'];
|
||||
|
||||
$grayscaleImage = $processImage->grayscale($imagePath, $extension);
|
||||
|
||||
$savedImage = $processImage->saveImage($processImage->customFolder, $grayscaleImage, $imageNameGrayscale, $extension);
|
||||
|
||||
if(!$savedImage)
|
||||
{
|
||||
# return old image url without resize
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
# set folder to custom, so that the next method uses the correct (resized) version
|
||||
$this->imageFolder = 'custom';
|
||||
|
||||
$this->imageUrl = $imageUrlGrayscale;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function src()
|
||||
{
|
||||
# when we finish it, we shoud reset all settings
|
||||
$imagePath = $this->baseUrl . '/' . $this->imageUrl;
|
||||
$this->imageUrl = false;
|
||||
$this->imageFolder = 'original';
|
||||
|
||||
return $imagePath;
|
||||
}
|
||||
|
||||
public function addCSS($CSS)
|
||||
{
|
||||
$CSSfile = $this->getFileUrl($CSS);
|
||||
|
||||
if($CSSfile)
|
||||
{
|
||||
$this->CSS[] = '<link rel="stylesheet" href="' . $CSSfile . '" />';
|
||||
}
|
||||
}
|
||||
|
||||
public function addInlineCSS($CSS)
|
||||
{
|
||||
$this->inlineCSS[] = '<style>' . $CSS . '</style>';
|
||||
}
|
||||
|
||||
public function addJS($JS)
|
||||
{
|
||||
$JSfile = $this->getFileUrl($JS);
|
||||
|
||||
if($JSfile)
|
||||
{
|
||||
$this->JS[] = '<script src="' . $JSfile . '"></script>';
|
||||
}
|
||||
}
|
||||
|
||||
public function addInlineJS($JS)
|
||||
{
|
||||
$this->inlineJS[] = '<script>' . $JS . '</script>';
|
||||
}
|
||||
|
||||
public function activateVue()
|
||||
{
|
||||
$vueUrl = '<script src="' . $this->baseUrl . '/system/author/js/vue.min.js"></script>';
|
||||
if(!in_array($vueUrl, $this->JS))
|
||||
{
|
||||
$this->JS[] = $vueUrl;
|
||||
}
|
||||
}
|
||||
|
||||
public function activateAxios()
|
||||
{
|
||||
$axiosUrl = '<script src="' . $this->baseUrl . '/system/author/js/axios.min.js"></script>';
|
||||
if(!in_array($axiosUrl, $this->JS))
|
||||
{
|
||||
$this->JS[] = $axiosUrl;
|
||||
|
||||
$axios = '<script>const myaxios = axios.create({ baseURL: \'' . $this->baseUrl . '\' });</script>';
|
||||
$this->JS[] = $axios;
|
||||
}
|
||||
}
|
||||
|
||||
public function activateTachyons()
|
||||
{
|
||||
$tachyonsUrl = '<link rel="stylesheet" href="' . $this->baseUrl . '/system/author/css/tachyons.min.css" />';
|
||||
if(!in_array($tachyonsUrl, $this->CSS))
|
||||
{
|
||||
$this->CSS[] = $tachyonsUrl;
|
||||
}
|
||||
}
|
||||
|
||||
public function addSvgSymbol($symbol)
|
||||
{
|
||||
$this->svgSymbols[] = $symbol;
|
||||
}
|
||||
|
||||
# add JS to enhance the blox-editor in author area
|
||||
public function addEditorJS($JS)
|
||||
{
|
||||
$JSfile = $this->getFileUrl($JS);
|
||||
|
||||
if($JSfile)
|
||||
{
|
||||
$this->editorJS[] = '<script src="' . $JSfile . '"></script>';
|
||||
}
|
||||
}
|
||||
|
||||
public function addEditorInlineJS($JS)
|
||||
{
|
||||
$this->editorInlineJS[] = '<script>' . $JS . '</script>';
|
||||
}
|
||||
|
||||
public function addEditorCSS($CSS)
|
||||
{
|
||||
$CSSfile = $this->getFileUrl($CSS);
|
||||
|
||||
if($CSSfile)
|
||||
{
|
||||
$this->editorCSS[] = '<link rel="stylesheet" href="' . $CSSfile . '" />';
|
||||
}
|
||||
}
|
||||
|
||||
public function addMeta($key,$meta)
|
||||
{
|
||||
$this->meta[$key] = $meta;
|
||||
}
|
||||
|
||||
public function renderEditorJS()
|
||||
{
|
||||
return implode("\n", $this->editorJS) . implode("\n", $this->editorInlineJS);
|
||||
}
|
||||
|
||||
public function renderEditorCSS()
|
||||
{
|
||||
return implode("\n", $this->editorCSS);
|
||||
}
|
||||
|
||||
public function renderCSS()
|
||||
{
|
||||
return implode("\n", $this->CSS) . implode("\n", $this->inlineCSS);
|
||||
}
|
||||
|
||||
public function renderJS()
|
||||
{
|
||||
return implode("\n", $this->JS) . implode("\n", $this->inlineJS);
|
||||
}
|
||||
|
||||
public function renderSvg()
|
||||
{
|
||||
return implode('', $this->svgSymbols);
|
||||
}
|
||||
|
||||
public function renderMeta()
|
||||
{
|
||||
$metaLines = '';
|
||||
foreach($this->meta as $meta)
|
||||
{
|
||||
$metaLines .= "\n";
|
||||
$metaLines .= $meta;
|
||||
}
|
||||
return $metaLines;
|
||||
}
|
||||
/**
|
||||
* Checks, if a string is a valid internal or external ressource like js-file or css-file
|
||||
* @params $path string
|
||||
* @return string or false
|
||||
*/
|
||||
public function getFileUrl($path)
|
||||
{
|
||||
# check system path of file without parameter for fingerprinting
|
||||
$internalFile = __DIR__ . '/../../plugins' . strtok($path, "?");
|
||||
|
||||
if(file_exists($internalFile))
|
||||
{
|
||||
return $this->baseUrl . '/plugins' . $path;
|
||||
}
|
||||
|
||||
return $path;
|
||||
|
||||
if(fopen($path, "r"))
|
||||
{
|
||||
return $path;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
516
system/typemill/Controllers/ControllerApiAuthorMeta.php
Normal file
516
system/typemill/Controllers/ControllerApiAuthorMeta.php
Normal file
@@ -0,0 +1,516 @@
|
||||
<?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\Content;
|
||||
use Typemill\Models\Navigation;
|
||||
use Typemill\Models\Meta;
|
||||
use Typemill\Static\Slug;
|
||||
|
||||
class ControllerApiAuthorMeta extends Controller
|
||||
{
|
||||
public function getMeta(Request $request, Response $response, $args)
|
||||
{
|
||||
$validRights = $this->validateRights($request->getAttribute('c_userrole'), 'content', 'update');
|
||||
if(!$validRights)
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'You do not have enough rights.',
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
|
||||
}
|
||||
|
||||
$url = $request->getQueryParams()['url'] ?? false;
|
||||
|
||||
$navigation = new Navigation();
|
||||
$urlinfo = $this->c->get('urlinfo');
|
||||
$item = $this->getItem($navigation, $url, $urlinfo);
|
||||
if(!$item)
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'page not found',
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(404);
|
||||
}
|
||||
|
||||
$meta = new Meta();
|
||||
|
||||
$metadata = $meta->getMetaData($item);
|
||||
|
||||
if(!$metadata)
|
||||
{
|
||||
die('no page meta');
|
||||
# $pagemeta = $writeMeta->getPageMetaBlank($this->content, $this->settings, $this->item);
|
||||
}
|
||||
|
||||
# if item is a folder
|
||||
if($item->elementType == "folder" && isset($item->contains))
|
||||
{
|
||||
$metadata['meta']['contains'] = isset($pagemeta['meta']['contains']) ? $pagemeta['meta']['contains'] : $item->contains;
|
||||
|
||||
# get global metadefinitions
|
||||
$metadefinitions = $meta->getMetaDefinitions($this->settings, $folder = true);
|
||||
}
|
||||
else
|
||||
{
|
||||
# get global metadefinitions
|
||||
$metadefinitions = $meta->getMetaDefinitions($this->settings, $folder = false);
|
||||
}
|
||||
|
||||
# cleanup metadata to the current metadefinitions (e.g. strip out deactivated plugins)
|
||||
$metacleared = [];
|
||||
|
||||
# store the metadata-scheme for frontend, so frontend does not use obsolete data
|
||||
$metascheme = [];
|
||||
|
||||
foreach($metadefinitions as $tabname => $tabfields )
|
||||
{
|
||||
# add userroles and other datasets
|
||||
$metadefinitions[$tabname]['fields'] = $this->addDatasets($tabfields['fields']);
|
||||
|
||||
$tabfields = $this->flattenTabFields($tabfields['fields'],[]);
|
||||
|
||||
$metacleared[$tabname] = [];
|
||||
|
||||
foreach($tabfields as $fieldname => $fielddefinitions)
|
||||
{
|
||||
$metascheme[$tabname][$fieldname] = true;
|
||||
|
||||
$metacleared[$tabname][$fieldname] = isset($metadata[$tabname][$fieldname]) ? $metadata[$tabname][$fieldname] : null;
|
||||
}
|
||||
}
|
||||
|
||||
# store the metascheme in cache for frontend
|
||||
# $writeMeta->updateYaml('cache', 'metatabs.yaml', $metascheme);
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'metadata' => $metacleared,
|
||||
'metadefinitions' => $metadefinitions,
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
public function updateMetaData(Request $request, Response $response, $args)
|
||||
{
|
||||
$validRights = $this->validateRights($request->getAttribute('c_userrole'), 'content', 'update');
|
||||
if(!$validRights)
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'You do not have enough rights.',
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
|
||||
}
|
||||
|
||||
$params = $request->getParsedBody();
|
||||
$validate = new Validation();
|
||||
$validInput = $validate->metaInput($params);
|
||||
if($validInput !== true)
|
||||
{
|
||||
$errors = $validate->returnFirstValidationErrors($validInput);
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => reset($errors),
|
||||
'errors' => $errors
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
|
||||
}
|
||||
|
||||
$navigation = new Navigation();
|
||||
$urlinfo = $this->c->get('urlinfo');
|
||||
$item = $this->getItem($navigation, $params['url'], $urlinfo);
|
||||
if(!$item)
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'page not found',
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(404);
|
||||
}
|
||||
|
||||
$meta = new Meta();
|
||||
|
||||
# if item is a folder
|
||||
if($item->elementType == "folder" && isset($item->contains))
|
||||
{
|
||||
$metadata['meta']['contains'] = isset($pagemeta['meta']['contains']) ? $pagemeta['meta']['contains'] : $item->contains;
|
||||
|
||||
# get global metadefinitions
|
||||
$metadefinitions = $meta->getMetaDefinitions($this->settings, $folder = true);
|
||||
}
|
||||
else
|
||||
{
|
||||
# get global metadefinitions
|
||||
$metadefinitions = $meta->getMetaDefinitions($this->settings, $folder = false);
|
||||
}
|
||||
|
||||
$tabdefinitions = $metadefinitions[$params['tab']] ?? false;
|
||||
if(!$tabdefinitions)
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'Tab not found',
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(404);
|
||||
}
|
||||
$tabdefinitions['fields'] = $this->addDatasets($tabdefinitions['fields']);
|
||||
$tabdefinitions = $this->flattenTabFields($tabdefinitions['fields'], []);
|
||||
|
||||
# create validation object
|
||||
$errors = false;
|
||||
|
||||
# take the user input data and iterate over all fields and values
|
||||
foreach($params['data'] as $fieldname => $fieldvalue)
|
||||
{
|
||||
# get the corresponding field definition from original plugin settings
|
||||
$fielddefinition = $tabdefinitions[$fieldname] ?? false;
|
||||
|
||||
if(!$fielddefinition)
|
||||
{
|
||||
$errors[$tab][$fieldname] = 'This field is not defined';
|
||||
}
|
||||
else
|
||||
{
|
||||
# validate user input for this field
|
||||
$result = $validate->field($fieldname, $fieldvalue, $fielddefinition);
|
||||
|
||||
if($result !== true)
|
||||
{
|
||||
$errors[$params['tab']][$fieldname] = $result[$fieldname][0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# return validation errors
|
||||
if($errors)
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'Please correct the errors.',
|
||||
'errors' => $errors
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
|
||||
}
|
||||
|
||||
$pageMeta = $meta->getMetaData($item);
|
||||
|
||||
# extended
|
||||
$navigation = new Navigation();
|
||||
|
||||
$extended = $navigation->getExtendedNavigation($urlinfo, $this->settings['langattr']);
|
||||
|
||||
if($params['tab'] == 'meta')
|
||||
{
|
||||
# if manual date has been modified
|
||||
if( $this->hasChanged($params['data'], $pageMeta['meta'], 'manualdate'))
|
||||
{
|
||||
# update the time
|
||||
$params['data']['time'] = date('H-i-s', time());
|
||||
|
||||
# if it is a post, then rename the post
|
||||
if($item->elementType == "file" && strlen($item->order) == 12)
|
||||
{
|
||||
# create file-prefix with date
|
||||
$metadate = $params['data']['manualdate'];
|
||||
if($metadate == '')
|
||||
{
|
||||
$metadate = $pageMeta['meta']['created'];
|
||||
}
|
||||
$datetime = $metadate . '-' . $params['data']['time'];
|
||||
$datetime = implode(explode('-', $datetime));
|
||||
$datetime = substr($datetime,0,12);
|
||||
|
||||
# create the new filename
|
||||
$pathWithoutFile = str_replace($item->originalName, "", $item->path);
|
||||
$newPathWithoutType = $pathWithoutFile . $datetime . '-' . $item->slug;
|
||||
|
||||
$meta->renamePost($item->pathWithoutType, $newPathWithoutType);
|
||||
|
||||
$navigation->clearNavigation();
|
||||
}
|
||||
}
|
||||
|
||||
# if folder has changed and contains pages instead of posts or posts instead of pages
|
||||
if($item->elementType == "folder" && isset($params['data']['contains']) && isset($pageMeta['meta']['contains']) && $this->hasChanged($params['data'], $pageMeta['meta'], 'contains'))
|
||||
{
|
||||
if($meta->folderContainsFolders($item))
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'The folder contains another folder so we cannot transform it. Please make sure there are only files in this folder.',
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
|
||||
}
|
||||
|
||||
if($params['data']['contains'] == "posts" && !$meta->transformPagesToPosts($item))
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'One or more files could not be transformed.',
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
|
||||
}
|
||||
|
||||
if($params['data']['contains'] == "pages" && !$meta->transformPostsToPages($item))
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'One or more files could not be transformed.',
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
|
||||
}
|
||||
|
||||
$navigation->clearNavigation();
|
||||
}
|
||||
|
||||
# normalize the meta-input
|
||||
$params['data']['navtitle'] = (isset($params['data']['navtitle']) && $params['data']['navtitle'] !== null )? $params['data']['navtitle'] : '';
|
||||
$params['data']['hide'] = (isset($params['data']['hide']) && $params['data']['hide'] !== null) ? $params['data']['hide'] : false;
|
||||
$params['data']['noindex'] = (isset($params['data']['noindex']) && $params['data']['noindex'] !== null) ? $params['data']['noindex'] : false;
|
||||
|
||||
# input values are empty but entry in structure exists
|
||||
if(
|
||||
!$params['data']['hide']
|
||||
&& $params['data']['navtitle'] == ""
|
||||
&& isset($extended[$item->urlRelWoF])
|
||||
)
|
||||
{
|
||||
$navigation->clearNavigation();
|
||||
}
|
||||
elseif(
|
||||
# check if navtitle or hide-value has been changed
|
||||
($this->hasChanged($params['data'], $pageMeta['meta'], 'navtitle'))
|
||||
OR
|
||||
($this->hasChanged($params['data'], $pageMeta['meta'], 'hide'))
|
||||
OR
|
||||
($this->hasChanged($params['data'], $pageMeta['meta'], 'noindex'))
|
||||
)
|
||||
{
|
||||
$navigation->clearNavigation();
|
||||
}
|
||||
}
|
||||
|
||||
# add the new/edited metadata
|
||||
$pageMeta[$params['tab']] = $params['data'];
|
||||
|
||||
# store the metadata
|
||||
$store = $meta->updateMeta($pageMeta, $item);
|
||||
|
||||
if($store === true)
|
||||
{
|
||||
$draftNavigation = $navigation->getDraftNavigation($urlinfo, $this->settings['langattr']);
|
||||
$draftNavigation = $navigation->setActiveNaviItems($draftNavigation, $item->keyPathArray);
|
||||
$item = $navigation->getItemWithKeyPath($draftNavigation, $item->keyPathArray);
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'navigation' => $draftNavigation,
|
||||
'item' => $item
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => $store,
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
# get the standard meta-definitions and the meta-definitions from plugins (same for all sites)
|
||||
public function aggregateMetaDefinitions($folder = null)
|
||||
{
|
||||
$metatabs = $this->meta->getMetaDefinitions();
|
||||
|
||||
# the fields for user or role based access
|
||||
if(!isset($this->settings['pageaccess']) || $this->settings['pageaccess'] === NULL )
|
||||
{
|
||||
unset($metatabs['meta']['fields']['fieldsetrights']);
|
||||
}
|
||||
|
||||
# add radio buttons to choose posts or pages for folder.
|
||||
if(!$folder)
|
||||
{
|
||||
unset($metatabs['meta']['fields']['contains']);
|
||||
}
|
||||
|
||||
echo '<pre>';
|
||||
print_r($metatabs);
|
||||
die();
|
||||
|
||||
# loop through all plugins
|
||||
if(!empty($this->settings['plugins']))
|
||||
{
|
||||
foreach($this->settings['plugins'] as $name => $plugin)
|
||||
{
|
||||
if($plugin['active'])
|
||||
{
|
||||
$pluginSettings = \Typemill\Settings::getObjectSettings('plugins', $name);
|
||||
if($pluginSettings && isset($pluginSettings['metatabs']))
|
||||
{
|
||||
$metatabs = array_merge_recursive($metatabs, $pluginSettings['metatabs']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# add the meta from theme settings here
|
||||
$themeSettings = \Typemill\Settings::getObjectSettings('themes', $this->settings['theme']);
|
||||
|
||||
if($themeSettings && isset($themeSettings['metatabs']))
|
||||
{
|
||||
$metatabs = array_merge_recursive($metatabs, $themeSettings['metatabs']);
|
||||
}
|
||||
|
||||
# dispatch meta
|
||||
# $metatabs = $this->c->dispatcher->dispatch('onMetaDefinitionsLoaded', new OnMetaDefinitionsLoaded($metatabs))->getData();
|
||||
|
||||
return $metatabs;
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
public function publishArticle(Request $request, Response $response, $args)
|
||||
{
|
||||
$validRights = $this->validateRights($request->getAttribute('c_userrole'), 'content', 'update');
|
||||
if(!$validRights)
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'You do not have enough rights.',
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
|
||||
}
|
||||
|
||||
$params = $request->getParsedBody();
|
||||
$validate = new Validation();
|
||||
$validInput = $validate->articlePublish($params);
|
||||
if($validInput !== true)
|
||||
{
|
||||
$errors = $validate->returnFirstValidationErrors($validInput);
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => reset($errors),
|
||||
'errors' => $errors
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(400);
|
||||
}
|
||||
|
||||
$navigation = new Navigation();
|
||||
$urlinfo = $this->c->get('urlinfo');
|
||||
$item = $this->getItem($navigation, $params['url'], $urlinfo);
|
||||
if(!$item)
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'page not found',
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(404);
|
||||
}
|
||||
|
||||
# publish content
|
||||
$content = new Content($urlinfo['baseurl']);
|
||||
$draftMarkdown = $content->getDraftMarkdown($item);
|
||||
$content->publishMarkdown($item, $draftMarkdown);
|
||||
|
||||
# refresh navigation and item
|
||||
$navigation->clearNavigation();
|
||||
$draftNavigation = $navigation->getDraftNavigation($urlinfo, $this->settings['langattr']);
|
||||
$draftNavigation = $navigation->setActiveNaviItems($draftNavigation, $item->keyPathArray);
|
||||
$item = $navigation->getItemWithKeyPath($draftNavigation, $item->keyPathArray);
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'navigation' => $draftNavigation,
|
||||
'item' => $item
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
# get the standard meta-definitions and the meta-definitions from plugins (same for all sites)
|
||||
public function getMetaDefinitions(Request $request, Response $response, $args)
|
||||
{
|
||||
$validRights = $this->validateRights($request->getAttribute('c_userrole'), 'content', 'update');
|
||||
if(!$validRights)
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'You do not have enough rights.',
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
|
||||
}
|
||||
|
||||
$metatabs = $this->aggregateMetaDefinitions();
|
||||
|
||||
$response->getBody()->write(json_encode([
|
||||
'definitions' => $metatabs
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
# we have to flatten field definitions for tabs if there are fieldsets in it
|
||||
public function flattenTabFields($tabfields, $flattab, $fieldset = null)
|
||||
{
|
||||
foreach($tabfields as $name => $field)
|
||||
{
|
||||
if($field['type'] == 'fieldset')
|
||||
{
|
||||
$flattab = $this->flattenTabFields($field['fields'], $flattab, $name);
|
||||
}
|
||||
else
|
||||
{
|
||||
# add the name of the fieldset so we know to which fieldset it belongs for references
|
||||
if($fieldset)
|
||||
{
|
||||
$field['fieldset'] = $fieldset;
|
||||
}
|
||||
$flattab[$name] = $field;
|
||||
}
|
||||
}
|
||||
return $flattab;
|
||||
}
|
||||
|
||||
protected function hasChanged($input, $page, $field)
|
||||
{
|
||||
if(isset($input[$field]) && isset($page[$field]) && $input[$field] == $page[$field])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if(!isset($input[$field]) && !isset($input[$field]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
149
system/typemill/Controllers/ControllerWebDownload.php
Normal file
149
system/typemill/Controllers/ControllerWebDownload.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Controllers;
|
||||
|
||||
use Typemill\Models\StorageWrapper;
|
||||
|
||||
class ControllerWebDownload extends Controller
|
||||
{
|
||||
public function download($request, $response, $args)
|
||||
{
|
||||
$filename = isset($args['params']) ? $args['params'] : false;
|
||||
if(!$filename)
|
||||
{
|
||||
die('the requested file does not exist.');
|
||||
}
|
||||
|
||||
$storage = new StorageWrapper('\Typemill\Models\Storage');
|
||||
$restrictions = $storage->getYaml('fileFolder', '', 'filerestrictions.yaml');
|
||||
|
||||
$filepath = $storage->getFolderPath('fileFolder');
|
||||
$filefolder = 'media/files/';
|
||||
|
||||
# validate
|
||||
$allowedFiletypes = [];
|
||||
if(!$this->validate($filepath, $filename, $allowedFiletypes))
|
||||
{
|
||||
die('the requested filetype is not allowed.');
|
||||
}
|
||||
|
||||
if($restrictions && isset($restrictions[$filefolder . $filename]))
|
||||
{
|
||||
$userrole = $request->getAttribute('c_userrole');
|
||||
$allowedrole = $restrictions[$filefolder . $filename];
|
||||
|
||||
if(!$userrole)
|
||||
{
|
||||
$this->c->get('flash')->addMessage('error', "You have to be an authenticated $allowedrole to download this file.");
|
||||
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
|
||||
}
|
||||
elseif(
|
||||
$userrole != 'administrator'
|
||||
AND $userrole != $allowedrole
|
||||
AND !$this->c->get('acl')->inheritsRole($userrole, $allowedrole)
|
||||
)
|
||||
{
|
||||
$this->c->get('flash')->addMessage('error', "You have to be a $allowedrole to download this file.");
|
||||
|
||||
return $response->withHeader('Location', $this->routeParser->urlFor('auth.show'))->withStatus(302);
|
||||
}
|
||||
}
|
||||
|
||||
$file = $filepath . $filename;
|
||||
|
||||
# for now we only allow one download
|
||||
$this->sendDownload($file);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate if the file exists and if
|
||||
* there is a permission (download dir) to download this file
|
||||
*
|
||||
* You should ALWAYS call this method if you don't want
|
||||
* somebody to download files not intended to be for the public.
|
||||
*
|
||||
* @param string $file GET parameter
|
||||
* @param array $allowedFiletypes (defined in the head of this file)
|
||||
* @return bool true if validation was successfull
|
||||
*/
|
||||
private function validate($path, $filename, $allowedFiletypes)
|
||||
{
|
||||
$filepath = $path . $filename;
|
||||
|
||||
# check if file exists
|
||||
if (!isset($filepath) || empty($filepath) || !file_exists($filepath) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
# check allowed filetypes
|
||||
if(!empty($allowedFiletypes))
|
||||
{
|
||||
$fileAllowed = false;
|
||||
foreach ($allowedFiletypes as $filetype)
|
||||
{
|
||||
if (strpos($filename, $filetype) === (strlen($filename) - strlen($filetype)))
|
||||
{
|
||||
$fileAllowed = true; //ends with $filetype
|
||||
}
|
||||
}
|
||||
|
||||
if (!$fileAllowed) return false;
|
||||
}
|
||||
|
||||
# check download directory
|
||||
if (strpos($filename, '..') !== false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download function.
|
||||
* Sets the HTTP header and supplies the given file
|
||||
* as a download to the browser.
|
||||
*
|
||||
* @param string $file path to file
|
||||
*/
|
||||
private function sendDownload($file)
|
||||
{
|
||||
# Parse information
|
||||
$pathinfo = pathinfo($file);
|
||||
$extension = strtolower($pathinfo['extension']);
|
||||
$mimetype = null;
|
||||
|
||||
# Get mimetype for extension
|
||||
# This list can be extended as you need it.
|
||||
# A good start to find mimetypes is the apache mime.types list
|
||||
# http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
|
||||
switch ($extension) {
|
||||
case 'zip': $mimetype = "application/zip"; break;
|
||||
default: $mimetype = "application/force-download";
|
||||
}
|
||||
|
||||
# Required for some browsers like Safari and IE
|
||||
if (ini_get('zlib.output_compression'))
|
||||
{
|
||||
ini_set('zlib.output_compression', 'Off');
|
||||
}
|
||||
|
||||
header('Pragma: public');
|
||||
header('Content-Encoding: none');
|
||||
header('Accept-Ranges: bytes'); # Allow support for download resume
|
||||
header('Expires: 0');
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', filemtime($file)) . ' GMT');
|
||||
header_remove("Last-Modified");
|
||||
header('Cache-Control: max-age=0, no-cache, no-store, must-revalidate');
|
||||
header('Cache-Control: private', false); # required for some browsers
|
||||
header('Content-Type: ' . $mimetype);
|
||||
header('Content-Disposition: attachment; filename="'.basename($file).'";'); # Make the browser display the Save As dialog
|
||||
header('Content-Transfer-Encoding: binary');
|
||||
header('Content-Length: '.filesize($file));
|
||||
ob_end_flush();
|
||||
readfile($file); # This is necessary in order to get it to actually download the file, otherwise it will be 0Kb
|
||||
}
|
||||
}
|
26
system/typemill/Extensions/TwigMarkdownExtension.php
Normal file
26
system/typemill/Extensions/TwigMarkdownExtension.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Extensions;
|
||||
|
||||
use Twig\Extension\AbstractExtension;
|
||||
use Twig\TwigFunction;
|
||||
use Typemill\Extensions\ParsedownExtension;
|
||||
|
||||
class TwigMarkdownExtension extends AbstractExtension
|
||||
{
|
||||
public function getFunctions()
|
||||
{
|
||||
return [
|
||||
new TwigFunction('markdown', array($this, 'renderMarkdown' ))
|
||||
];
|
||||
}
|
||||
|
||||
public function renderMarkdown($markdown)
|
||||
{
|
||||
$parsedown = new ParsedownExtension();
|
||||
|
||||
$markdownArray = $parsedown->text($markdown);
|
||||
|
||||
return $parsedown->markup($markdownArray);
|
||||
}
|
||||
}
|
41
system/typemill/Middleware/AssetMiddleware.php
Normal file
41
system/typemill/Middleware/AssetMiddleware.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Middleware;
|
||||
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
|
||||
use Slim\Psr7\Response;
|
||||
|
||||
class AssetMiddleware implements MiddlewareInterface
|
||||
{
|
||||
protected $assets;
|
||||
|
||||
protected $view;
|
||||
|
||||
public function __construct($assets, $view)
|
||||
{
|
||||
$this->assets = $assets;
|
||||
|
||||
$this->view = $view;
|
||||
}
|
||||
|
||||
public function process(Request $request, RequestHandler $handler) :response
|
||||
{
|
||||
# get url from request
|
||||
|
||||
# update the asset object in the container (for plugins) with the new url
|
||||
# $this->container->assets->setBaseUrl($uri->getBaseUrl());
|
||||
|
||||
# add the asset object to twig-frontend for themes
|
||||
$this->view->getEnvironment()->addGlobal('assets', $this->assets);
|
||||
|
||||
# use {{ base_url() }} in twig templates
|
||||
# $this->container['view']['base_url'] = $uri->getBaseUrl();
|
||||
# $this->container['view']['current_url'] = $uri->getPath();
|
||||
|
||||
$response = $handler->handle($request);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
55
system/typemill/Middleware/SessionMiddleware.php
Normal file
55
system/typemill/Middleware/SessionMiddleware.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Middleware;
|
||||
|
||||
use Psr\Http\Server\MiddlewareInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
|
||||
use Slim\Psr7\Response;
|
||||
use Typemill\Static\Session;
|
||||
use Typemill\Models\User;
|
||||
|
||||
class SessionMiddleware implements MiddlewareInterface
|
||||
{
|
||||
protected $segments;
|
||||
|
||||
protected $route;
|
||||
|
||||
public function __construct($segments, $route)
|
||||
{
|
||||
$this->segments = $segments;
|
||||
|
||||
$this->route = $route;
|
||||
}
|
||||
|
||||
public function process(Request $request, RequestHandler $handler) :response
|
||||
{
|
||||
# start session for routes
|
||||
Session::startSessionForSegments($this->segments, $this->route);
|
||||
|
||||
$authenticated = (
|
||||
(isset($_SESSION['username'])) &&
|
||||
(isset($_SESSION['login']))
|
||||
)
|
||||
? true : false;
|
||||
|
||||
if($authenticated)
|
||||
{
|
||||
# add userdata to the request for later use
|
||||
$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;
|
||||
}
|
||||
}
|
404
system/typemill/Models/Meta.php
Normal file
404
system/typemill/Models/Meta.php
Normal file
@@ -0,0 +1,404 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Models;
|
||||
|
||||
use Typemill\Models\StorageWrapper;
|
||||
use Typemill\Models\Content;
|
||||
|
||||
class Meta
|
||||
{
|
||||
private $storage;
|
||||
|
||||
public function __construct($baseurl = NULL)
|
||||
{
|
||||
$this->storage = new StorageWrapper('\Typemill\Models\Storage');
|
||||
}
|
||||
|
||||
# used by contentApiController (backend) and pageController (frontend) and TwigMetaExtension (list pages)
|
||||
public function getMetaData($item)
|
||||
{
|
||||
$metadata = $this->storage->getYaml('contentFolder', '', $item->pathWithoutType . '.yaml');
|
||||
|
||||
return $metadata;
|
||||
|
||||
|
||||
|
||||
# compare with meta that are in use right now (e.g. changed theme, disabled plugin)
|
||||
$metascheme = $this->getYaml('cache', 'metatabs.yaml');
|
||||
|
||||
if($metascheme)
|
||||
{
|
||||
$meta = $this->whitelistMeta($meta,$metascheme);
|
||||
}
|
||||
|
||||
return $meta;
|
||||
}
|
||||
|
||||
public function getMetaDefinitions($settings, $folder)
|
||||
{
|
||||
$metadefinitions = $this->storage->getYaml('systemSettings', '', 'metatabs.yaml');
|
||||
|
||||
# loop through all plugins
|
||||
if(!empty($settings['plugins']))
|
||||
{
|
||||
foreach($settings['plugins'] as $name => $plugin)
|
||||
{
|
||||
if($plugin['active'])
|
||||
{
|
||||
$pluginSettings = \Typemill\Static\Settings::getObjectSettings('plugins', $name);
|
||||
if($pluginSettings && isset($pluginSettings['metatabs']))
|
||||
{
|
||||
$metadefinitions = array_merge_recursive($metadefinitions, $pluginSettings['metatabs']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# add the meta from theme settings here
|
||||
$themeSettings = \Typemill\Static\Settings::getObjectSettings('themes', $settings['theme']);
|
||||
|
||||
if($themeSettings && isset($themeSettings['metatabs']))
|
||||
{
|
||||
$metadefinitions = array_merge_recursive($metadefinitions, $themeSettings['metatabs']);
|
||||
}
|
||||
|
||||
# conditional fieldset for user or role based access
|
||||
if(!isset($settings['pageaccess']) || $settings['pageaccess'] === NULL )
|
||||
{
|
||||
unset($metadefinitions['meta']['fields']['fieldsetrights']);
|
||||
}
|
||||
|
||||
# conditional fieldset for folders
|
||||
if(!$folder)
|
||||
{
|
||||
unset($metadefinitions['meta']['fields']['fieldsetfolder']);
|
||||
}
|
||||
|
||||
# dispatch meta
|
||||
# $metatabs = $this->c->dispatcher->dispatch('onMetaDefinitionsLoaded', new OnMetaDefinitionsLoaded($metatabs))->getData();
|
||||
|
||||
return $metadefinitions;
|
||||
}
|
||||
|
||||
public function updateMeta($meta, $item)
|
||||
{
|
||||
$filename = $item->pathWithoutType . '.yaml';
|
||||
|
||||
if($this->storage->updateYaml('contentFolder', '', $filename, $meta))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->storage->getError();
|
||||
}
|
||||
|
||||
public function addMetaDefaults($meta, $item, $authorFromSettings, $currentuser = false)
|
||||
{
|
||||
$modified = false;
|
||||
|
||||
if(!isset($meta['meta']['owner']))
|
||||
{
|
||||
$meta['meta']['owner'] = $currentuser ? $currentuser : false;
|
||||
$modified = true;
|
||||
}
|
||||
|
||||
if(!isset($meta['meta']['author']))
|
||||
{
|
||||
$meta['meta']['owner'] = $currentuser ? $currentuser : $authorFromSettings;
|
||||
$modified = true;
|
||||
}
|
||||
|
||||
if(!isset($meta['meta']['created']))
|
||||
{
|
||||
$meta['meta']['created'] = date("Y-m-d");
|
||||
$modified = true;
|
||||
}
|
||||
|
||||
if(!isset($meta['meta']['time']))
|
||||
{
|
||||
$meta['meta']['time'] = date("H-i-s");
|
||||
$modified = true;
|
||||
}
|
||||
|
||||
if(!isset($meta['meta']['navtitle']))
|
||||
{
|
||||
$meta['meta']['navtitle'] = $item->name;
|
||||
$modified = true;
|
||||
}
|
||||
|
||||
if($modified)
|
||||
{
|
||||
$this->updateMeta($meta, $item);
|
||||
}
|
||||
|
||||
$filePath = $item->path;
|
||||
if($item->elementType == 'folder')
|
||||
{
|
||||
$filePath = $item->path . DIRECTORY_SEPARATOR . 'index.md';
|
||||
}
|
||||
$meta['meta']['modified'] = $this->storage->getFileTime('contentFolder', '', $filePath);
|
||||
|
||||
return $meta;
|
||||
}
|
||||
|
||||
public function addMetaTitleDescription(array $meta, $item, array $markdown)
|
||||
{
|
||||
$title = (isset($meta['meta']['title']) && $meta['meta']['title'] != '') ? $meta['meta']['title'] : false;
|
||||
$description = (isset($meta['meta']['description']) && $meta['meta']['description'] != '') ? $meta['meta']['description'] : false;
|
||||
|
||||
if(!$title OR !$description)
|
||||
{
|
||||
$content = new Content();
|
||||
|
||||
if(!$title)
|
||||
{
|
||||
$meta['meta']['title'] = $content->getTitle($markdown);
|
||||
}
|
||||
|
||||
if(!$description)
|
||||
{
|
||||
$meta['meta']['description'] = $content->getDescription($markdown);
|
||||
}
|
||||
|
||||
$this->updateMeta($meta, $item);
|
||||
}
|
||||
|
||||
return $meta;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public function getNavtitle($url)
|
||||
{
|
||||
# get the extended structure where the navigation title is stored
|
||||
$extended = $this->getYaml('cache', 'structure-extended.yaml');
|
||||
|
||||
if(isset($extended[$url]['navtitle']))
|
||||
{
|
||||
return $extended[$url]['navtitle'];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
# used by articleApiController and pageController to add title and description if an article is published
|
||||
public function completePageMeta($content, $settings, $item)
|
||||
{
|
||||
$meta = $this->getPageMeta($settings, $item);
|
||||
|
||||
if(!$meta)
|
||||
{
|
||||
return $this->getPageMetaDefaults($content, $settings, $item);
|
||||
}
|
||||
|
||||
$title = (isset($meta['meta']['title']) AND $meta['meta']['title'] !== '') ? true : false;
|
||||
$description = (isset($meta['meta']['description']) AND $meta['meta']['description'] !== '') ? true : false;
|
||||
|
||||
if($title && $description)
|
||||
{
|
||||
return $meta;
|
||||
}
|
||||
|
||||
# initialize parsedown extension
|
||||
$parsedown = new ParsedownExtension();
|
||||
|
||||
# if content is not an array, then transform it
|
||||
if(!is_array($content))
|
||||
{
|
||||
# turn markdown into an array of markdown-blocks
|
||||
$content = $parsedown->markdownToArrayBlocks($content);
|
||||
}
|
||||
|
||||
# delete markdown from title
|
||||
if(!$title && isset($content[0]))
|
||||
{
|
||||
$meta['meta']['title'] = trim($content[0], "# ");
|
||||
}
|
||||
|
||||
if(!$description && isset($content[1]))
|
||||
{
|
||||
$meta['meta']['description'] = $this->generateDescription($content, $parsedown, $item);
|
||||
}
|
||||
|
||||
$this->updateYaml($settings['contentFolder'], $item->pathWithoutType . '.yaml', $meta);
|
||||
|
||||
return $meta;
|
||||
}
|
||||
|
||||
private function whitelistMeta($meta, $metascheme)
|
||||
{
|
||||
# we have only 2 dimensions, so no recursive needed
|
||||
foreach($meta as $tab => $values)
|
||||
{
|
||||
if(!isset($metascheme[$tab]))
|
||||
{
|
||||
unset($meta[$tab]);
|
||||
}
|
||||
foreach($values as $key => $value)
|
||||
{
|
||||
if(!isset($metascheme[$tab][$key]))
|
||||
{
|
||||
unset($meta[$tab][$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $meta;
|
||||
}
|
||||
|
||||
public function generateDescription($content, $parsedown, $item)
|
||||
{
|
||||
$description = isset($content[1]) ? $content[1] : '';
|
||||
|
||||
# create description or abstract from content
|
||||
if($description !== '')
|
||||
{
|
||||
$firstLineArray = $parsedown->text($description);
|
||||
$description = strip_tags($parsedown->markup($firstLineArray, $item->urlAbs));
|
||||
|
||||
# if description is very short
|
||||
if(strlen($description) < 100 && isset($content[2]))
|
||||
{
|
||||
$secondLineArray = $parsedown->text($content[2]);
|
||||
$description .= ' ' . strip_tags($parsedown->markup($secondLineArray, $item->urlAbs));
|
||||
}
|
||||
|
||||
# if description is too long
|
||||
if(strlen($description) > 300)
|
||||
{
|
||||
$description = substr($description, 0, 300);
|
||||
$lastSpace = strrpos($description, ' ');
|
||||
$description = substr($description, 0, $lastSpace);
|
||||
}
|
||||
}
|
||||
return $description;
|
||||
}
|
||||
|
||||
public function transformPagesToPosts($folder)
|
||||
{
|
||||
$filetypes = array('md', 'txt', 'yaml');
|
||||
$result = true;
|
||||
|
||||
foreach($folder->folderContent as $page)
|
||||
{
|
||||
# create old filename without filetype
|
||||
$oldFile = $this->basePath . 'content' . $page->pathWithoutType;
|
||||
|
||||
# set default date
|
||||
$date = date('Y-m-d', time());
|
||||
$time = date('H-i', time());
|
||||
|
||||
$meta = $this->getYaml('content', $page->pathWithoutType . '.yaml');
|
||||
|
||||
if($meta)
|
||||
{
|
||||
# get dates from meta
|
||||
if(isset($meta['meta']['manualdate'])){ $date = $meta['meta']['manualdate']; }
|
||||
elseif(isset($meta['meta']['created'])){ $date = $meta['meta']['created']; }
|
||||
elseif(isset($meta['meta']['modified'])){ $date = $meta['meta']['modified']; }
|
||||
|
||||
# set time
|
||||
if(isset($meta['meta']['time']))
|
||||
{
|
||||
$time = $meta['meta']['time'];
|
||||
}
|
||||
}
|
||||
|
||||
$datetime = $date . '-' . $time;
|
||||
$datetime = implode(explode('-', $datetime));
|
||||
$datetime = substr($datetime,0,12);
|
||||
|
||||
# create new file-name without filetype
|
||||
$newFile = $this->basePath . 'content' . $folder->path . DIRECTORY_SEPARATOR . $datetime . '-' . $page->slug;
|
||||
|
||||
foreach($filetypes as $filetype)
|
||||
{
|
||||
$oldFilePath = $oldFile . '.' . $filetype;
|
||||
$newFilePath = $newFile . '.' . $filetype;
|
||||
|
||||
#check if file with filetype exists and rename
|
||||
if($oldFilePath != $newFilePath && file_exists($oldFilePath))
|
||||
{
|
||||
if(@rename($oldFilePath, $newFilePath))
|
||||
{
|
||||
$result = $result;
|
||||
}
|
||||
else
|
||||
{
|
||||
$result = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function transformPostsToPages($folder)
|
||||
{
|
||||
$filetypes = array('md', 'txt', 'yaml');
|
||||
$index = 0;
|
||||
$result = true;
|
||||
|
||||
foreach($folder->folderContent as $page)
|
||||
{
|
||||
# create old filename without filetype
|
||||
$oldFile = $this->basePath . 'content' . $page->pathWithoutType;
|
||||
|
||||
$order = $index;
|
||||
|
||||
if($index < 10)
|
||||
{
|
||||
$order = '0' . $index;
|
||||
}
|
||||
|
||||
# create new file-name without filetype
|
||||
$newFile = $this->basePath . 'content' . $folder->path . DIRECTORY_SEPARATOR . $order . '-' . $page->slug;
|
||||
|
||||
foreach($filetypes as $filetype)
|
||||
{
|
||||
$oldFilePath = $oldFile . '.' . $filetype;
|
||||
$newFilePath = $newFile . '.' . $filetype;
|
||||
|
||||
#check if file with filetype exists and rename
|
||||
if($oldFilePath != $newFilePath && file_exists($oldFilePath))
|
||||
{
|
||||
if(@rename($oldFilePath, $newFilePath))
|
||||
{
|
||||
$result = $result;
|
||||
}
|
||||
else
|
||||
{
|
||||
$result = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$index++;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function folderContainsFolders($folder)
|
||||
{
|
||||
foreach($folder->folderContent as $page)
|
||||
{
|
||||
if($page->elementType == 'folder')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
66
system/typemill/author/js/vue-forms-local.js
Normal file
66
system/typemill/author/js/vue-forms-local.js
Normal file
@@ -0,0 +1,66 @@
|
||||
const textcomponent = {
|
||||
props: ['id', 'description', 'maxlength', 'hidden', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'value', 'css', 'errors'],
|
||||
template: `<div :class="css ? css : 'w-full'" class="mt-5 mb-5">
|
||||
<label :for="name" class="block mb-1 font-medium">{{ $filters.translate(label) }}</label>
|
||||
<input type="text" class="h-12 w-full border px-2 py-3" :class="errors[name] ? ' border-red-500 bg-red-100' : ' border-stone-300 bg-stone-200'"
|
||||
:id="id"
|
||||
:maxlength="maxlength"
|
||||
:readonly="readonly"
|
||||
:hidden="hidden"
|
||||
:required="required"
|
||||
:disabled="disabled"
|
||||
:name="name"
|
||||
:placeholder="placeholder"
|
||||
:value="value"
|
||||
@input="update($event, name)">
|
||||
<p v-if="errors[name]" class="text-xs text-red-500">{{ errors[name] }}</p>
|
||||
<p v-else class="text-xs">{{ $filters.translate(description) }}</p>
|
||||
</div>`,
|
||||
methods: {
|
||||
update: function($event, name)
|
||||
{
|
||||
eventBus.$emit('forminput', {'name': name, 'value': $event.target.value});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const textareacomponent = {
|
||||
props: ['id', 'description', 'maxlength', 'readonly', 'required', 'disabled', 'placeholder', 'label', 'name', 'type', 'css', 'value', 'errors'],
|
||||
template: `<div :class="css ? css : 'w-full'" class="mt-5 mb-5">
|
||||
<label :for="name" class="block mb-1 font-medium">{{ $filters.translate(label) }}</label>
|
||||
<textarea rows="8" class="w-full border border-stone-300 bg-stone-200 px-2 py-3"
|
||||
:id="id"
|
||||
:class="css"
|
||||
:readonly="readonly"
|
||||
:required="required"
|
||||
:disabled="disabled"
|
||||
:name="name"
|
||||
:placeholder="placeholder"
|
||||
:value="value"
|
||||
@input="update($event, name)"></textarea>
|
||||
<p v-if="errors[name]" class="text-xs text-red-500">{{ errors[name] }}</p>
|
||||
<p v-else class="text-xs">{{ $filters.translate(description) }}</p>
|
||||
</div>`,
|
||||
methods: {
|
||||
update: function($event, name)
|
||||
{
|
||||
eventBus.$emit('forminput', {'name': name, 'value': $event.target.value});
|
||||
},
|
||||
formatValue: function(value)
|
||||
{
|
||||
/*
|
||||
if(value !== null && typeof value === 'object')
|
||||
{
|
||||
this.textareaclass = 'codearea';
|
||||
return JSON.stringify(value, undefined, 4);
|
||||
}
|
||||
return value;
|
||||
*/
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const formcomponents = {
|
||||
'component-text' : textcomponent,
|
||||
'component-textarea' : textareacomponent
|
||||
};
|
289
system/typemill/author/js/vue-meta.js
Normal file
289
system/typemill/author/js/vue-meta.js
Normal file
@@ -0,0 +1,289 @@
|
||||
const app = Vue.createApp({
|
||||
template: `<div>
|
||||
|
||||
<button
|
||||
v-for="tab in tabs"
|
||||
v-on:click="currentTab = tab"
|
||||
:key="tab"
|
||||
class="px-4 py-2 border-b-2 border-stone-200 hover:border-stone-700 hover:bg-stone-50 transition duration-100"
|
||||
:class="(tab == currentTab) ? 'bg-stone-50 border-stone-700' : ''"
|
||||
>
|
||||
{{ $filters.translate(tab) }}
|
||||
</button>
|
||||
|
||||
<component
|
||||
:class="css"
|
||||
:is="currentTabComponent"
|
||||
:saved="saved"
|
||||
:errors="formErrors[currentTab]"
|
||||
:message="message"
|
||||
:messageClass="messageClass"
|
||||
:formDefinitions="formDefinitions[currentTab]"
|
||||
:formData="formData[currentTab]"
|
||||
:item="item"
|
||||
v-on:saveform="saveForm">
|
||||
</component>
|
||||
|
||||
</div>`,
|
||||
data: function () {
|
||||
return {
|
||||
item: data.item,
|
||||
currentTab: 'Content',
|
||||
tabs: ['Content'],
|
||||
formDefinitions: [],
|
||||
formData: [],
|
||||
formErrors: {},
|
||||
formErrorsReset: {},
|
||||
message: false,
|
||||
messageClass: false,
|
||||
css: "px-16 py-16 bg-stone-50 shadow-md mb-16",
|
||||
saved: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentTabComponent: function ()
|
||||
{
|
||||
if(this.currentTab == 'Content')
|
||||
{
|
||||
eventBus.$emit("showEditor");
|
||||
}
|
||||
else
|
||||
{
|
||||
eventBus.$emit("hideEditor");
|
||||
return 'tab-' + this.currentTab.toLowerCase()
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted: function(){
|
||||
|
||||
var self = this;
|
||||
|
||||
tmaxios.get('/api/v1/meta',{
|
||||
params: {
|
||||
'url': data.urlinfo.route,
|
||||
}
|
||||
})
|
||||
.then(function (response){
|
||||
|
||||
var formdefinitions = response.data.metadefinitions;
|
||||
|
||||
for (var key in formdefinitions)
|
||||
{
|
||||
if (formdefinitions.hasOwnProperty(key))
|
||||
{
|
||||
self.tabs.push(key);
|
||||
self.formErrors[key] = false;
|
||||
}
|
||||
}
|
||||
|
||||
self.formErrorsReset = self.formErrors;
|
||||
self.formDefinitions = formdefinitions;
|
||||
|
||||
self.formData = response.data.metadata;
|
||||
|
||||
/*
|
||||
self.userroles = response.data.userroles;
|
||||
self.item = response.data.item;
|
||||
if(self.item.elementType == "folder" && self.item.contains == "posts")
|
||||
{
|
||||
posts.posts = self.item.folderContent;
|
||||
posts.folderid = self.item.keyPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
posts.posts = false;
|
||||
}
|
||||
*/
|
||||
})
|
||||
.catch(function (error)
|
||||
{
|
||||
if(error.response)
|
||||
{
|
||||
}
|
||||
});
|
||||
|
||||
eventBus.$on('forminput', formdata => {
|
||||
this.formData[this.currentTab][formdata.name] = formdata.value;
|
||||
});
|
||||
|
||||
/*
|
||||
update values that are objects
|
||||
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
|
||||
|
||||
eventBus.$on('forminputobject', formdata => {
|
||||
this.formData[this.currentTab][formdata.name] = Object.assign({}, this.formData[this.currentTab][formdata.name], formdata.value);
|
||||
});
|
||||
*/
|
||||
},
|
||||
methods: {
|
||||
saveForm: function()
|
||||
{
|
||||
this.saved = false;
|
||||
|
||||
self = this;
|
||||
tmaxios.post('/api/v1/metadata',{
|
||||
'url': data.urlinfo.route,
|
||||
'tab': self.currentTab,
|
||||
'data': self.formData[self.currentTab]
|
||||
})
|
||||
.then(function (response){
|
||||
|
||||
self.saved = true;
|
||||
self.message = 'saved successfully';
|
||||
self.messageClass = 'bg-teal-500';
|
||||
self.formErrors = self.formErrorsReset;
|
||||
|
||||
if(response.data.navigation)
|
||||
{
|
||||
eventBus.$emit('navigation', response.data.navigation);
|
||||
}
|
||||
if(response.data.item)
|
||||
{
|
||||
eventBus.$emit('item', response.data.item);
|
||||
}
|
||||
})
|
||||
.catch(function (error)
|
||||
{
|
||||
if(error.response)
|
||||
{
|
||||
self.formErrors = error.response.data.errors;
|
||||
self.message = 'please correct the errors above';
|
||||
self.messageClass = 'bg-rose-500';
|
||||
}
|
||||
});
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
app.component('tab-meta', {
|
||||
props: ['item', 'formData', 'formDefinitions', 'saved', 'errors', 'message', 'messageClass'],
|
||||
data: function () {
|
||||
return {
|
||||
slug: false,
|
||||
originalSlug: false,
|
||||
slugerror: false,
|
||||
disabled: true,
|
||||
}
|
||||
},
|
||||
template: `<section>
|
||||
<form>
|
||||
<div v-if="slug !== false">
|
||||
<div class="w-full relative">
|
||||
<label class="block mb-1 font-medium">{{ $filters.translate('Slug') }}</label>
|
||||
<div class="flex">
|
||||
<input
|
||||
class="h-12 w-3/4 border px-2 py-3 border-stone-300 bg-stone-200"
|
||||
type="text"
|
||||
v-model="slug"
|
||||
pattern="[a-z0-9\- ]"
|
||||
@input="changeSlug()"
|
||||
/>
|
||||
<button
|
||||
class="w-1/4 px-2 py-3 ml-2 text-stone-50 bg-stone-700 hover:bg-stone-900 hover:text-white transition duration-100 cursor-pointer disabled:cursor-not-allowed disabled:bg-stone-200 disabled:text-stone-800"
|
||||
@click.prevent="storeSlug()"
|
||||
:disabled="disabled"
|
||||
>
|
||||
{{ $filters.translate('change slug') }}
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="slugerror" class="f6 tm-red mt1">{{ slugerror }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="(fieldDefinition, fieldname) in formDefinitions.fields">
|
||||
<fieldset class="flex flex-wrap justify-between border-2 border-stone-200 p-4 my-8" v-if="fieldDefinition.type == 'fieldset'">
|
||||
<legend class="text-lg font-medium">{{ fieldDefinition.legend }}</legend>
|
||||
<component v-for="(subfieldDefinition, subfieldname) in fieldDefinition.fields"
|
||||
:key="subfieldname"
|
||||
:is="selectComponent(subfieldDefinition.type)"
|
||||
:errors="errors"
|
||||
:name="subfieldname"
|
||||
:userroles="userroles"
|
||||
:value="formData[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">{{ $filters.translate(message) }}</div>
|
||||
<input type="submit" @click.prevent="saveInput()" :value="$filters.translate('save')" class="w-full p-3 my-1 bg-stone-700 hover:bg-stone-900 text-white cursor-pointer transition duration-100">
|
||||
</div>
|
||||
</form>
|
||||
</section>`,
|
||||
mounted: function()
|
||||
{
|
||||
if(this.item.slug != '')
|
||||
{
|
||||
this.slug = this.item.slug;
|
||||
this.originalSlug = this.item.slug;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectComponent: function(type)
|
||||
{
|
||||
return 'component-' + type;
|
||||
},
|
||||
saveInput: function()
|
||||
{
|
||||
this.$emit('saveform');
|
||||
},
|
||||
changeSlug: function()
|
||||
{
|
||||
if(this.slug == this.originalSlug)
|
||||
{
|
||||
this.slugerror = false;
|
||||
this.disabled = true;
|
||||
return;
|
||||
}
|
||||
if(this.slug == '')
|
||||
{
|
||||
this.slugerror = 'empty slugs are not allowed';
|
||||
this.disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.slug = this.slug.replace(/ /g, '-');
|
||||
this.slug = this.slug.toLowerCase();
|
||||
|
||||
if(this.slug.match(/^[a-z0-9\-]*$/))
|
||||
{
|
||||
this.slugerror = false;
|
||||
this.disabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.slugerror = 'Only lowercase a-z and 0-9 and "-" is allowed for slugs.';
|
||||
this.disabled = true;
|
||||
}
|
||||
},
|
||||
storeSlug: function()
|
||||
{
|
||||
if(this.slug.match(/^[a-z0-9\-]*$/) && this.slug != this.originalSlug)
|
||||
{
|
||||
var self = this;
|
||||
|
||||
tmaxios.post('/api/v1/article/rename',{
|
||||
'url': data.urlinfo.route,
|
||||
'slug': this.slug,
|
||||
'oldslug': this.originalSlug,
|
||||
})
|
||||
.then(function (response)
|
||||
{
|
||||
window.location.replace(response.data.url);
|
||||
})
|
||||
.catch(function (error)
|
||||
{
|
||||
eventBus.$emit('publishermessage', error.response.data.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
Reference in New Issue
Block a user