mirror of
https://github.com/typemill/typemill.git
synced 2025-07-26 00:31:38 +02:00
demo frontend events, plugin api and web-routes, filesystem security
This commit is contained in:
@@ -9,6 +9,8 @@
|
||||
* DONE: License feature
|
||||
* ToDo: Enhance with plugins
|
||||
|
||||
----
|
||||
|
||||
## Visual Editor
|
||||
|
||||
* DONE: Refactor and redesign
|
||||
|
2
content/02-folder/index.yaml
Normal file
2
content/02-folder/index.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
meta:
|
||||
navtitle: folder
|
File diff suppressed because one or more lines are too long
@@ -70,3 +70,9 @@
|
||||
noindex: false
|
||||
path: /01-cyanine-theme/03-content-elements.md
|
||||
keyPath: '1.3'
|
||||
/folder:
|
||||
navtitle: folder
|
||||
hide: false
|
||||
noindex: false
|
||||
path: /02-folder
|
||||
keyPath: 2
|
||||
|
1
data/navigation/navi-live.txt
Normal file
1
data/navigation/navi-live.txt
Normal file
File diff suppressed because one or more lines are too long
@@ -54,7 +54,15 @@ class ControllerApiAuthorArticle extends Controller
|
||||
# publish content
|
||||
$content = new Content($urlinfo['baseurl']);
|
||||
$draftMarkdown = $content->getDraftMarkdown($item);
|
||||
$content->publishMarkdown($item, $draftMarkdown);
|
||||
$publish = $content->publishMarkdown($item, $draftMarkdown);
|
||||
if($publish !== true)
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => $publish,
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(404);
|
||||
}
|
||||
|
||||
# refresh navigation and item
|
||||
$navigation->clearNavigation();
|
||||
@@ -848,12 +856,12 @@ class ControllerApiAuthorArticle extends Controller
|
||||
}
|
||||
|
||||
# check if it is a folder and if the folder has published pages.
|
||||
if($item->elementType == 'folder' && isset($item->folderContent))
|
||||
if($item->elementType == 'folder')
|
||||
{
|
||||
if(count($item->folderContent > 0))
|
||||
if($this->folderHasPublishedPages($item))
|
||||
{
|
||||
$response->getBody()->write(json_encode([
|
||||
'message' => 'This folder contains pages, please delete the pages first.',
|
||||
'message' => 'The folder contains published pages. Please unpublish or delete the pages first.',
|
||||
]));
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(422);
|
||||
@@ -889,4 +897,21 @@ class ControllerApiAuthorArticle extends Controller
|
||||
|
||||
return $response->withHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
|
||||
private function folderHasPublishedPages($folder)
|
||||
{
|
||||
if(isset($folder->folderContent) && is_array($folder->folderContent) && count($folder->folderContent) > 0 )
|
||||
{
|
||||
foreach($folder->folderContent as $page)
|
||||
{
|
||||
if($page->status == 'published')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@@ -360,385 +360,4 @@ class ControllerWebFrontend extends Controller
|
||||
|
||||
return $restrictionNotice;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
# Initiate Variables
|
||||
$contentHTML = false;
|
||||
$item = false;
|
||||
$home = false;
|
||||
$breadcrumb = false;
|
||||
$currentpage = false;
|
||||
$this->pathToContent = $this->settings['rootPath'] . $this->settings['contentFolder'];
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
$this->base_url = $this->uri->getBaseUrl();
|
||||
|
||||
# if there is no structure at all, the content folder is probably empty
|
||||
if(!$this->setStructureLive())
|
||||
{
|
||||
return $this->render($response, '/index.twig', array( 'content' => '<h1>No Content</h1><p>Your content folder is empty.</p>' ));
|
||||
}
|
||||
|
||||
# we can create an initial sitemap here, but makes no sense for every pagecall. Sitemap will be created on first author interaction (publish/delete/channge page).
|
||||
# $this->checkSitemap();
|
||||
|
||||
# if the admin activated to refresh the cache automatically each 10 minutes (e.g. use without admin area)
|
||||
if(isset($this->settings['refreshcache']) && $this->settings['refreshcache'] && !$this->writeCache->validate('cache', 'lastCache.txt', 600))
|
||||
{
|
||||
# delete the cache
|
||||
$dir = $this->settings['basePath'] . 'cache';
|
||||
$this->writeCache->deleteCacheFiles($dir);
|
||||
|
||||
# update the internal structure
|
||||
$this->setFreshStructureDraft();
|
||||
|
||||
# update the public structure
|
||||
$this->setFreshStructureLive();
|
||||
|
||||
# update the navigation
|
||||
$this->setFreshNavigation();
|
||||
|
||||
# update the sitemap
|
||||
$this->updateSitemap();
|
||||
}
|
||||
|
||||
# dispatch event and let others manipulate the structure
|
||||
$this->structureLive = $this->c->dispatcher->dispatch('onPagetreeLoaded', new OnPagetreeLoaded($this->structureLive))->getData();
|
||||
|
||||
# check if there is a custom theme css
|
||||
$theme = $this->settings['theme'];
|
||||
$customcss = $this->writeCache->checkFile('cache', $theme . '-custom.css');
|
||||
if($customcss)
|
||||
{
|
||||
$this->c->assets->addCSS($this->base_url . '/cache/' . $theme . '-custom.css');
|
||||
}
|
||||
|
||||
$logo = false;
|
||||
if(isset($this->settings['logo']) && $this->settings['logo'] != '')
|
||||
{
|
||||
# check if logo exists
|
||||
if(file_exists($this->settings['rootPath'] . 'media/live/' . $this->settings['logo']))
|
||||
{
|
||||
$logo = 'media/live/' . $this->settings['logo'];
|
||||
}
|
||||
elseif(file_exists($this->settings['rootPath'] . 'media/files/' . $this->settings['logo']))
|
||||
{
|
||||
$logo = 'media/files/' . $this->settings['logo'];
|
||||
}
|
||||
}
|
||||
|
||||
$favicon = false;
|
||||
if(isset($this->settings['favicon']) && $this->settings['favicon'] != '')
|
||||
{
|
||||
$favicon = true;
|
||||
$this->c->assets->addMeta('tilecolor','<meta name="msapplication-TileColor" content="#F9F8F6" />');
|
||||
$this->c->assets->addMeta('tileimage','<meta name="msapplication-TileImage" content="' . $this->base_url . '/media/files/favicon-144.png" />');
|
||||
$this->c->assets->addMeta('icon16','<link rel="icon" type="image/png" href="' . $this->base_url . '/media/files/favicon-16.png" sizes="16x16" />');
|
||||
$this->c->assets->addMeta('icon32','<link rel="icon" type="image/png" href="' . $this->base_url . '/media/files/favicon-32.png" sizes="32x32" />');
|
||||
$this->c->assets->addMeta('icon72','<link rel="apple-touch-icon" sizes="72x72" href="' . $this->base_url . '/media/files/favicon-72.png" />');
|
||||
$this->c->assets->addMeta('icon114','<link rel="apple-touch-icon" sizes="114x114" href="' . $this->base_url . '/media/files/favicon-114.png" />');
|
||||
$this->c->assets->addMeta('icon144','<link rel="apple-touch-icon" sizes="144x144" href="' . $this->base_url . '/media/files/favicon-144.png" />');
|
||||
$this->c->assets->addMeta('icon180','<link rel="apple-touch-icon" sizes="180x180" href="' . $this->base_url . '/media/files/favicon-180.png" />');
|
||||
}
|
||||
|
||||
# the navigation is a copy of the structure without the hidden pages
|
||||
# hint: if the navigation has been deleted from the cache, then we do not recreate it here to save performace.
|
||||
# Instead you have to recreate cache in admin or change a page (publish/unpublish/delete/move)
|
||||
$navigation = $this->writeCache->getCache('cache', 'navigation.txt');
|
||||
if(!$navigation)
|
||||
{
|
||||
# use the structure if there is no cached navigation
|
||||
$navigation = $this->structureLive;
|
||||
}
|
||||
|
||||
# start pagination
|
||||
if(isset($args['params']))
|
||||
{
|
||||
$argSegments = explode("/", $args['params']);
|
||||
|
||||
# check if the last url segment is a number
|
||||
$pageNumber = array_pop($argSegments);
|
||||
if(is_numeric($pageNumber) && $pageNumber < 10000)
|
||||
{
|
||||
# then check if the segment before the page is a "p" that indicates a paginator
|
||||
$pageIndicator = array_pop($argSegments);
|
||||
if($pageIndicator == "p")
|
||||
{
|
||||
# use page number as current page variable
|
||||
$currentpage = $pageNumber;
|
||||
|
||||
# set empty args for startpage
|
||||
$args = [];
|
||||
|
||||
# if there are still params
|
||||
if(!empty($argSegments))
|
||||
{
|
||||
# add them to the args again
|
||||
$args['params'] = implode("/", $argSegments);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# if the user is on startpage
|
||||
$home = false;
|
||||
if(empty($args))
|
||||
{
|
||||
$home = true;
|
||||
$item = Folder::getItemForUrl($navigation, $this->uri->getBasePath(), $this->uri->getBaseUrl(), NULL, $home);
|
||||
$urlRel = $this->uri->getBasePath();
|
||||
}
|
||||
else
|
||||
{
|
||||
# get the request url, trim args so physical folders have no trailing slash
|
||||
$urlRel = $this->uri->getBasePath() . '/' . trim($args['params'], "/");
|
||||
|
||||
# find the url in the content-item-tree and return the item-object for the file
|
||||
# important to use the structure here so it is found, even if the item is hidden.
|
||||
$item = Folder::getItemForUrl($this->structureLive, $urlRel, $this->uri->getBasePath());
|
||||
|
||||
# if the item is a folder and if that folder is not hidden
|
||||
if($item && $item->elementType == 'folder' && isset($item->hide) && !$item->hide)
|
||||
{
|
||||
# use the navigation instead of the structure so that hidden elements are erased
|
||||
$item = Folder::getItemForUrl($navigation, $urlRel, $this->uri->getBaseUrl(), NULL, $home);
|
||||
}
|
||||
|
||||
# if there is still no item, return a 404-page
|
||||
if(!$item)
|
||||
{
|
||||
return $this->render404($response, array(
|
||||
'navigation' => $navigation,
|
||||
'settings' => $this->settings,
|
||||
'base_url' => $this->base_url,
|
||||
'title' => false,
|
||||
'content' => false,
|
||||
'item' => false,
|
||||
'breadcrumb' => false,
|
||||
'metatabs' => false,
|
||||
'image' => false,
|
||||
'logo' => $logo,
|
||||
'favicon' => $favicon
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
###
|
||||
if(isset($item->hide))
|
||||
{
|
||||
# if it is a hidden page
|
||||
if($item->hide)
|
||||
{
|
||||
# get breadcrumb for page and set pages active
|
||||
# use structure here because the hidden item is not part of the navigation
|
||||
$breadcrumb = Folder::getBreadcrumb($this->structureLive, $item->keyPathArray);
|
||||
$breadcrumb = $this->c->dispatcher->dispatch('onBreadcrumbLoaded', new OnBreadcrumbLoaded($breadcrumb))->getData();
|
||||
|
||||
# add the paging to the item
|
||||
$item = Folder::getPagingForItem($this->structureLive, $item);
|
||||
}
|
||||
else
|
||||
{
|
||||
# get breadcrumb for page and set pages active
|
||||
# use navigation, because it is used for frontend
|
||||
$breadcrumb = Folder::getBreadcrumb($navigation, $item->keyPathArray);
|
||||
$breadcrumb = $this->c->dispatcher->dispatch('onBreadcrumbLoaded', new OnBreadcrumbLoaded($breadcrumb))->getData();
|
||||
|
||||
# add the paging to the item
|
||||
$item = Folder::getPagingForItem($navigation, $item);
|
||||
}
|
||||
}
|
||||
###
|
||||
|
||||
|
||||
|
||||
# dispatch the item
|
||||
$item = $this->c->dispatcher->dispatch('onItemLoaded', new OnItemLoaded($item))->getData();
|
||||
|
||||
# set the filepath
|
||||
$filePath = $this->pathToContent . $item->path;
|
||||
|
||||
# check if url is a folder and add index.md
|
||||
if($item->elementType == 'folder')
|
||||
{
|
||||
$filePath = $filePath . DIRECTORY_SEPARATOR . 'index.md';
|
||||
}
|
||||
|
||||
# read the content of the file
|
||||
$contentMD = file_exists($filePath) ? file_get_contents($filePath) : false;
|
||||
|
||||
# dispatch the original content without plugin-manipulations for case anyone wants to use it
|
||||
$this->c->dispatcher->dispatch('onOriginalLoaded', new OnOriginalLoaded($contentMD));
|
||||
|
||||
# initiate object for metadata
|
||||
$writeMeta = new WriteMeta();
|
||||
|
||||
# makes sure that you always have the full meta with title, description and all the rest.
|
||||
$metatabs = $writeMeta->completePageMeta($contentMD, $this->settings, $item);
|
||||
|
||||
# write meta
|
||||
if(isset($metatabs['meta']['noindex']) && $metatabs['meta']['noindex'])
|
||||
{
|
||||
$this->c->assets->addMeta('noindex','<meta name="robots" content="noindex">');
|
||||
}
|
||||
|
||||
$this->c->assets->addMeta('og_site_name','<meta property="og:site_name" content="' . $this->settings['title'] . '">');
|
||||
$this->c->assets->addMeta('og_title','<meta property="og:title" content="' . $metatabs['meta']['title'] . '">');
|
||||
$this->c->assets->addMeta('og_description','<meta property="og:description" content="' . $metatabs['meta']['description'] . '">');
|
||||
$this->c->assets->addMeta('og_type','<meta property="og:type" content="article">');
|
||||
$this->c->assets->addMeta('og_url','<meta property="og:url" content="' . $item->urlAbs . '">');
|
||||
|
||||
# dispatch meta
|
||||
$metatabs = $this->c->dispatcher->dispatch('onMetaLoaded', new OnMetaLoaded($metatabs))->getData();
|
||||
|
||||
# dispatch content
|
||||
$contentMD = $this->c->dispatcher->dispatch('onMarkdownLoaded', new OnMarkdownLoaded($contentMD))->getData();
|
||||
|
||||
$itemUrl = isset($item->urlRel) ? $item->urlRel : false;
|
||||
|
||||
/* initialize parsedown
|
||||
$parsedown = new ParsedownExtension($this->base_url, $this->settings, $this->c->dispatcher);
|
||||
|
||||
/* set safe mode to escape javascript and html in markdown
|
||||
$parsedown->setSafeMode(true);
|
||||
|
||||
|
||||
|
||||
####
|
||||
# check access restriction here
|
||||
$restricted = $this->checkRestrictions($metatabs['meta']);
|
||||
if($restricted)
|
||||
{
|
||||
# convert markdown into array of markdown block-elements
|
||||
$markdownBlocks = $parsedown->markdownToArrayBlocks($contentMD);
|
||||
|
||||
# infos that plugins need to add restriction content
|
||||
$restrictions = [
|
||||
'restricted' => $restricted,
|
||||
'defaultContent' => true,
|
||||
'markdownBlocks' => $markdownBlocks,
|
||||
];
|
||||
|
||||
# dispatch the data
|
||||
$restrictions = $this->c->dispatcher->dispatch('onRestrictionsLoaded', new OnRestrictionsLoaded( $restrictions ))->getData();
|
||||
|
||||
# use the returned markdown
|
||||
$markdownBlocks = $restrictions['markdownBlocks'];
|
||||
|
||||
# if no plugin has disabled the default behavior
|
||||
if($restrictions['defaultContent'])
|
||||
{
|
||||
# cut the restricted content
|
||||
$shortenedPage = $this->cutRestrictedContent($markdownBlocks);
|
||||
|
||||
# check if there is customized content
|
||||
$restrictionnotice = $this->prepareRestrictionNotice();
|
||||
|
||||
# add notice to shortened content
|
||||
$shortenedPage[] = $restrictionnotice;
|
||||
|
||||
# Use the shortened page
|
||||
$markdownBlocks = $shortenedPage;
|
||||
}
|
||||
|
||||
# finally transform the markdown blocks back to pure markdown text
|
||||
$contentMD = $parsedown->arrayBlocksToMarkdown($markdownBlocks);
|
||||
}
|
||||
###
|
||||
|
||||
|
||||
|
||||
/* parse markdown-file to content-array
|
||||
$contentArray = $parsedown->text($contentMD);
|
||||
$contentArray = $this->c->dispatcher->dispatch('onContentArrayLoaded', new OnContentArrayLoaded($contentArray))->getData();
|
||||
|
||||
/* parse markdown-content-array to content-string
|
||||
$contentHTML = $parsedown->markup($contentArray);
|
||||
$contentHTML = $this->c->dispatcher->dispatch('onHtmlLoaded', new OnHtmlLoaded($contentHTML))->getData();
|
||||
|
||||
/* extract the h1 headline
|
||||
$contentParts = explode("</h1>", $contentHTML, 2);
|
||||
$title = isset($contentParts[0]) ? strip_tags($contentParts[0]) : $this->settings['title'];
|
||||
|
||||
$contentHTML = isset($contentParts[1]) ? $contentParts[1] : $contentHTML;
|
||||
|
||||
# get the first image from content array
|
||||
$img_url = isset($metatabs['meta']['heroimage']) ? $metatabs['meta']['heroimage'] : false;
|
||||
$img_alt = isset($metatabs['meta']['heroimagealt']) ? $metatabs['meta']['heroimagealt'] : false;
|
||||
|
||||
# get url and alt-tag for first image, if exists
|
||||
if(!$img_url OR $img_url == '')
|
||||
{
|
||||
# extract first image from content
|
||||
$firstImageMD = $this->getFirstImage($contentArray);
|
||||
|
||||
if($firstImageMD)
|
||||
{
|
||||
preg_match('#\((.*?)\)#', $firstImageMD, $img_url_result);
|
||||
$img_url = isset($img_url_result[1]) ? $img_url_result[1] : false;
|
||||
|
||||
if($img_url)
|
||||
{
|
||||
preg_match('#\[(.*?)\]#', $firstImageMD, $img_alt_result);
|
||||
$img_alt = isset($img_alt_result[1]) ? $img_alt_result[1] : false;
|
||||
}
|
||||
}
|
||||
elseif($logo)
|
||||
{
|
||||
$img_url = $logo;
|
||||
$pathinfo = pathinfo($this->settings['logo']);
|
||||
$img_alt = $pathinfo['filename'];
|
||||
}
|
||||
}
|
||||
|
||||
$firstImage = false;
|
||||
if($img_url)
|
||||
{
|
||||
$firstImage = array('img_url' => $this->base_url . '/' . $img_url, 'img_alt' => $img_alt);
|
||||
|
||||
$this->c->assets->addMeta('og_image','<meta property="og:image" content="' . $this->base_url . '/' . $img_url . '">');
|
||||
$this->c->assets->addMeta('twitter_image_alt','<meta name="twitter:image:alt" content="' . $img_alt . '">');
|
||||
$this->c->assets->addMeta('twitter_card','<meta name="twitter:card" content="summary_large_image">');
|
||||
}
|
||||
|
||||
$route = empty($args) && isset($this->settings['themes'][$theme]['cover']) ? '/cover.twig' : '/index.twig';
|
||||
|
||||
return $this->render($response, $route, [
|
||||
'home' => $home,
|
||||
'navigation' => $navigation,
|
||||
'title' => $title,
|
||||
'content' => $contentHTML,
|
||||
'item' => $item,
|
||||
'breadcrumb' => $breadcrumb,
|
||||
'settings' => $this->settings,
|
||||
'base_url' => $this->base_url,
|
||||
'metatabs' => $metatabs,
|
||||
'image' => $firstImage,
|
||||
'logo' => $logo,
|
||||
'favicon' => $favicon,
|
||||
'currentpage' => $currentpage
|
||||
]);
|
||||
}
|
||||
|
||||
*/
|
||||
|
||||
}
|
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Typemill\Controllers;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Typemill\Models\Navigation;
|
||||
use Typemill\Models\Extension;
|
||||
use Typemill\Models\User;
|
||||
@@ -10,7 +12,7 @@ use Typemill\Models\Settings;
|
||||
|
||||
class ControllerWebSystem extends Controller
|
||||
{
|
||||
public function showSettings($request, $response, $args)
|
||||
public function showSettings(Request $request, Response $response, $args)
|
||||
{
|
||||
$navigation = new Navigation();
|
||||
$mainNavigation = $navigation->getMainNavigation(
|
||||
@@ -49,7 +51,7 @@ class ControllerWebSystem extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function showThemes($request, $response, $args)
|
||||
public function showThemes(Request $request, Response $response, $args)
|
||||
{
|
||||
$navigation = new Navigation();
|
||||
$mainNavigation = $navigation->getMainNavigation(
|
||||
@@ -101,7 +103,7 @@ class ControllerWebSystem extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function showPlugins($request, $response, $args)
|
||||
public function showPlugins(Request $request, Response $response, $args)
|
||||
{
|
||||
$navigation = new Navigation();
|
||||
$mainNavigation = $navigation->getMainNavigation(
|
||||
@@ -152,7 +154,7 @@ class ControllerWebSystem extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function showLicense($request, $response, $args)
|
||||
public function showLicense(Request $request, Response $response, $args)
|
||||
{
|
||||
$navigation = new Navigation();
|
||||
$mainNavigation = $navigation->getMainNavigation(
|
||||
@@ -192,7 +194,7 @@ class ControllerWebSystem extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function showAccount($request, $response, $args)
|
||||
public function showAccount(Request $request, Response $response, $args)
|
||||
{
|
||||
$navigation = new Navigation();
|
||||
$mainNavigation = $navigation->getMainNavigation(
|
||||
@@ -230,7 +232,7 @@ class ControllerWebSystem extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function showUsers($request, $response, $args)
|
||||
public function showUsers(Request $request, Response $response, $args)
|
||||
{
|
||||
$navigation = new Navigation();
|
||||
$mainNavigation = $navigation->getMainNavigation(
|
||||
@@ -274,7 +276,7 @@ class ControllerWebSystem extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function showUser($request, $response, $args)
|
||||
public function showUser(Request $request, Response $response, $args)
|
||||
{
|
||||
$navigation = new Navigation();
|
||||
$mainNavigation = $navigation->getMainNavigation(
|
||||
@@ -316,7 +318,7 @@ class ControllerWebSystem extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function newUser($request, $response, $args)
|
||||
public function newUser(Request $request, Response $response, $args)
|
||||
{
|
||||
$navigation = new Navigation();
|
||||
$mainNavigation = $navigation->getMainNavigation(
|
||||
@@ -345,24 +347,37 @@ class ControllerWebSystem extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
public function showBlank($request, $response, $args)
|
||||
public function blankSystemPage(Request $request, Response $response, $args)
|
||||
{
|
||||
$user = new User();
|
||||
$settings = $this->c->get('settings');
|
||||
$route = $request->getAttribute('route');
|
||||
$navigation = $this->getMainNavigation();
|
||||
$navigation = new Navigation();
|
||||
$mainNavigation = $navigation->getMainNavigation(
|
||||
$userrole = $request->getAttribute('c_userrole'),
|
||||
$acl = $this->c->get('acl'),
|
||||
$urlinfo = $this->c->get('urlinfo'),
|
||||
$editor = $this->settings['editor']
|
||||
);
|
||||
$userrole = $request->getAttribute('c_userrole');
|
||||
$acl = $this->c->get('acl');
|
||||
$urlinfo = $this->c->get('urlinfo');
|
||||
$editor = $this->settings['editor'];
|
||||
|
||||
$content = '<h1>Hello</h1><p>I am the showBlank method from the settings controller</p><p>In most cases I have been called from a plugin. But if you see this content, then the plugin does not work or has forgotten to inject its own content.</p>';
|
||||
$systemNavigation = $navigation->getSystemNavigation(
|
||||
$userrole = $request->getAttribute('c_userrole'),
|
||||
$acl = $this->c->get('acl'),
|
||||
$urlinfo = $this->c->get('urlinfo'),
|
||||
$dispatcher = $this->c->get('dispatcher')
|
||||
);
|
||||
|
||||
return $this->render($response, 'settings/blank.twig', array(
|
||||
'settings' => $settings,
|
||||
'acl' => $this->c->acl,
|
||||
'navigation' => $navigation,
|
||||
'content' => $content,
|
||||
'route' => $route->getName()
|
||||
));
|
||||
}
|
||||
*/
|
||||
return $this->c->get('view')->render($response, 'layouts/layoutSystemBlank.twig', [
|
||||
'settings' => $this->settings,
|
||||
'mainnavi' => $mainNavigation,
|
||||
'systemnavi' => $systemNavigation,
|
||||
'jsdata' => [
|
||||
'settings' => $this->settings,
|
||||
'labels' => $this->c->get('translations'),
|
||||
'urlinfo' => $this->c->get('urlinfo'),
|
||||
'acl' => $this->c->get('acl'),
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
@@ -113,22 +113,31 @@ class Content
|
||||
return $this->storage->getError();
|
||||
}
|
||||
|
||||
public function deletePage($item)
|
||||
public function deletePage($item, $result = NULL)
|
||||
{
|
||||
$extensions = ['.md', '.txt', '.yaml'];
|
||||
|
||||
$result = true;
|
||||
if($item->elementType == 'folder' && isset($item->folderContent) && is_array($item->folderContent))
|
||||
{
|
||||
foreach($item->folderContent as $content)
|
||||
{
|
||||
$result = $this->deletePage($content);
|
||||
|
||||
if($result !== true){ break; }
|
||||
}
|
||||
}
|
||||
|
||||
foreach($extensions as $extension)
|
||||
{
|
||||
$result = $this->storage->deleteFile('contentFolder', '', $item->pathWithoutType . $extension);
|
||||
}
|
||||
|
||||
if($result)
|
||||
if($result !== true)
|
||||
{
|
||||
return true;
|
||||
return $this->storage->getError();
|
||||
}
|
||||
|
||||
return $this->storage->getError();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function addDraftHtml($markdownArray)
|
||||
|
@@ -37,6 +37,10 @@ class Storage
|
||||
protected $translationFolder = false;
|
||||
|
||||
protected $systemSettings = false;
|
||||
|
||||
protected $isReadable = [];
|
||||
|
||||
protected $isWrtiable = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -69,6 +73,19 @@ class Storage
|
||||
$this->translationFolder = $this->basepath . 'system' . DIRECTORY_SEPARATOR . 'typemill' . DIRECTORY_SEPARATOR . 'author' . DIRECTORY_SEPARATOR . 'translations' . DIRECTORY_SEPARATOR;
|
||||
|
||||
$this->systemSettings = $this->basepath . 'system' . DIRECTORY_SEPARATOR . 'typemill' . DIRECTORY_SEPARATOR . 'settings';
|
||||
|
||||
$this->isWritable = [
|
||||
'tmpFolder' => true,
|
||||
'originalFolder' => true,
|
||||
'liveFolder' => true,
|
||||
'thumbsFolder' => true,
|
||||
'customFolder' => true,
|
||||
'fileFolder' => true,
|
||||
'contentFolder' => true,
|
||||
'dataFolder' => true,
|
||||
'cacheFolder' => true,
|
||||
'settingsFolder' => true
|
||||
];
|
||||
}
|
||||
|
||||
public function getError()
|
||||
@@ -78,26 +95,31 @@ class Storage
|
||||
|
||||
public function getFolderPath($location, $folder = NULL)
|
||||
{
|
||||
# security: remove ../ from location
|
||||
# security: make sure user does not write into basepath
|
||||
# security: write only into certain folders
|
||||
|
||||
if(isset($this->$location))
|
||||
{
|
||||
$path = rtrim($this->$location, DIRECTORY_SEPARATOR);
|
||||
$path .= DIRECTORY_SEPARATOR;
|
||||
if($folder && $folder != '')
|
||||
|
||||
# check if folder is no hack like "../"
|
||||
if($folder && $folder != '' && preg_match('/^(?:[\/\\a-z0-9_-]|\.(?!\.))+$/iD', $folder))
|
||||
{
|
||||
$folder = trim($folder, DIRECTORY_SEPARATOR);
|
||||
$path .= $folder . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
# echo '<pre>';
|
||||
# echo $path;
|
||||
elseif($location == 'basepath')
|
||||
{
|
||||
# do not allow direct access to basepath files
|
||||
|
||||
$this->error = "Access to basepath is not allowed.";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
$this->error = "We could not find a folderPath for $location";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -152,7 +174,7 @@ class Storage
|
||||
{
|
||||
if($this->checkFile($location, $folder, $filename))
|
||||
{
|
||||
$filepath = $this->getFolderPath($location) . $folder . DIRECTORY_SEPARATOR . $filename;
|
||||
$filepath = $this->getFolderPath($location, $folder) . $filename;
|
||||
|
||||
$fileContent = file_get_contents($filepath);
|
||||
|
||||
@@ -184,6 +206,13 @@ class Storage
|
||||
|
||||
public function writeFile($location, $folder, $filename, $data, $method = NULL)
|
||||
{
|
||||
if(!isset($this->isWritable[$location]))
|
||||
{
|
||||
$this->error = "It is not allowed to write into $location";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
# CLEAN EVERYTHING UP FUNCTION
|
||||
$folder = trim($folder, DIRECTORY_SEPARATOR);
|
||||
$folder = ($folder == '') ? '' : $folder . DIRECTORY_SEPARATOR;
|
||||
@@ -197,8 +226,8 @@ class Storage
|
||||
}
|
||||
}
|
||||
|
||||
$filepath = $this->getFolderPath($location) . $folder . $filename;
|
||||
|
||||
$filepath = $this->getFolderPath($location, $folder) . $filename;
|
||||
|
||||
$openfile = @fopen($filepath, "w");
|
||||
if(!$openfile)
|
||||
{
|
||||
@@ -228,6 +257,13 @@ class Storage
|
||||
|
||||
public function renameFile($location, $folder, $oldname, $newname)
|
||||
{
|
||||
if(!isset($this->isWritable[$location]))
|
||||
{
|
||||
$this->error = "It is not allowed to write into $location";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$folder = trim($folder, DIRECTORY_SEPARATOR);
|
||||
|
||||
$oldFilePath = $this->getFolderPath($location) . $folder . DIRECTORY_SEPARATOR . $oldname;
|
||||
@@ -249,12 +285,47 @@ class Storage
|
||||
return true;
|
||||
}
|
||||
|
||||
public function deleteFolder($location, $folder, $filename)
|
||||
{
|
||||
if(!isset($this->isWritable[$location]))
|
||||
{
|
||||
$this->error = "It is not allowed to write into $location";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$filepath = $this->getFolderPath($location, $folder) . $filename;
|
||||
|
||||
if(is_dir($filepath))
|
||||
{
|
||||
if(rmdir($dir))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->error = "We found the folder but could not delete $filepath";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->error = "The path $filepath is not a folder.";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function deleteFile($location, $folder, $filename)
|
||||
{
|
||||
if(!isset($this->isWritable[$location]))
|
||||
{
|
||||
$this->error = "It is not allowed to write into $location";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if($this->checkFile($location, $folder, $filename))
|
||||
{
|
||||
$filepath = $this->getFolderPath($location) . $folder . DIRECTORY_SEPARATOR . $filename;
|
||||
|
||||
|
||||
if(unlink($filepath))
|
||||
{
|
||||
return true;
|
||||
@@ -329,6 +400,13 @@ class Storage
|
||||
|
||||
public function updateYaml($location, $folder, $filename, $contentArray)
|
||||
{
|
||||
if(!isset($this->isWritable[$location]))
|
||||
{
|
||||
$this->error = "It is not allowed to write into $location";
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$yaml = \Symfony\Component\Yaml\Yaml::dump($contentArray,6);
|
||||
if($this->writeFile($location, $folder, $filename, $yaml))
|
||||
{
|
||||
|
@@ -45,28 +45,16 @@ class Plugins
|
||||
{
|
||||
# add the routes
|
||||
$pluginRoutes = $className::addNewRoutes();
|
||||
|
||||
# multi-dimensional or simple array of routes
|
||||
if(isset($pluginRoutes[0]))
|
||||
|
||||
foreach($pluginRoutes as $pluginRoute)
|
||||
{
|
||||
# if they are properly formatted, add them to routes array
|
||||
foreach($pluginRoutes as $pluginRoute)
|
||||
if(self::checkRouteArray($routes,$pluginRoute))
|
||||
{
|
||||
if(self::checkRouteArray($routes,$pluginRoute))
|
||||
{
|
||||
$pluginRoute['route'] = strtolower($pluginRoute['route']);
|
||||
$routes[] = $pluginRoute;
|
||||
}
|
||||
$routeType = (substr($pluginRoute['route'], 0,5) == '/api/') ? 'api' : 'web';
|
||||
$pluginRoute['route'] = strtolower($pluginRoute['route']);
|
||||
$routes[$routeType][] = $pluginRoute;
|
||||
}
|
||||
}
|
||||
elseif(is_array($routes))
|
||||
{
|
||||
if(self::checkRouteArray($routes,$pluginRoutes))
|
||||
{
|
||||
$pluginRoutes['route'] = strtolower($pluginRoutes['route']);
|
||||
$routes[] = $pluginRoutes;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $routes;
|
||||
@@ -113,7 +101,9 @@ class Plugins
|
||||
if(
|
||||
isset($route['httpMethod']) AND in_array($route['httpMethod'], array('get','post','put','delete','head','patch','options'))
|
||||
AND isset($route['route']) AND is_string($route['route'])
|
||||
AND isset($route['class']) AND is_string($route['class']))
|
||||
AND isset($route['class']) AND is_string($route['class'])
|
||||
AND isset($route['name']) AND is_string($route['name'])
|
||||
)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
@@ -265,7 +265,8 @@ const publisher = Vue.createApp({
|
||||
{
|
||||
if(error.response)
|
||||
{
|
||||
alert(error.response);
|
||||
self.message = error.response.data.message;
|
||||
self.messageClass = "bg-rose-500";
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -301,7 +302,8 @@ const publisher = Vue.createApp({
|
||||
{
|
||||
if(error.response)
|
||||
{
|
||||
alert(error.response);
|
||||
self.message = error.response.data.message;
|
||||
self.messageClass = "bg-rose-500";
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -326,7 +328,8 @@ const publisher = Vue.createApp({
|
||||
{
|
||||
if(error.response)
|
||||
{
|
||||
alert(error.response);
|
||||
self.message = error.response.data.message;
|
||||
self.messageClass = "bg-rose-500";
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -356,8 +359,10 @@ const publisher = Vue.createApp({
|
||||
{
|
||||
if(error.response)
|
||||
{
|
||||
alert(error.response);
|
||||
}
|
||||
self.showModal = false;
|
||||
self.message = error.response.data.message;
|
||||
self.messageClass = "bg-rose-500";
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
|
@@ -43,10 +43,6 @@
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<footer class="max-w-6xl m-auto bg-stone-50">
|
||||
Typemill version xyz
|
||||
</footer>
|
||||
|
||||
<!-- < csrf() | raw > -->
|
||||
|
||||
<script>
|
||||
|
82
system/typemill/author/layouts/layoutSystemBlank.twig
Normal file
82
system/typemill/author/layouts/layoutSystemBlank.twig
Normal file
@@ -0,0 +1,82 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<meta name="description" content="TYPEMILL system extension"/>
|
||||
|
||||
<meta name="msapplication-TileColor" content="#F9F8F6" />
|
||||
<meta name="msapplication-TileImage" content="{{ base_url() }}/system/author/img/favicon-144.png" />
|
||||
<link rel="icon" type="image/png" href="{{ base_url() }}/system/author/img/favicon-16.png" sizes="16x16" />
|
||||
<link rel="icon" type="image/png" href="{{ base_url() }}/system/author/img/favicon-32.png" sizes="32x32" />
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="{{ base_url() }}/system/author/img/favicon-72.png" />
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="{{ base_url() }}/system/author/img/favicon-114.png" />
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="{{ base_url() }}/system/author/img/favicon-144.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ base_url() }}/system/author/img/favicon-180.png" />
|
||||
|
||||
<link rel="stylesheet" href="{{ base_url() }}/system/typemill/author/css/output.css?v={{ settings.version }}" />
|
||||
<link rel="stylesheet" href="{{ base_url() }}/system/typemill/author/css/custom.css?v={{ settings.version }}" />
|
||||
|
||||
{% block stylesheet %}{% endblock %}
|
||||
|
||||
{{ assets.renderCSS() }}
|
||||
|
||||
</head>
|
||||
<body class="bg-stone-100">
|
||||
|
||||
{% include 'partials/symbols.twig' %}
|
||||
|
||||
<header class="border-b-2 border-stone-200">
|
||||
{% include 'partials/mainNavi.twig' %}
|
||||
</header>
|
||||
|
||||
<div class="max-w-6xl m-auto mt-7 flex justify-between" id="main" data-url="{{ base_url() }}">
|
||||
|
||||
<aside class="w-1/4">
|
||||
{% include 'partials/systemNavi.twig' %}
|
||||
</aside>
|
||||
|
||||
<article class="w-3/4 bg-stone-50 shadow-md p-8">
|
||||
|
||||
<h1 class="text-3xl font-bold mb-4">{{ translate('System') }} </h1>
|
||||
|
||||
<div id="system" v-cloak></div>
|
||||
|
||||
</article>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
const data = {{ jsdata | json_encode() | raw }};
|
||||
|
||||
</script>
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/axios.min.js?v={{ settings.version }}"></script>
|
||||
<script>
|
||||
const tmaxios = axios.create();
|
||||
tmaxios.defaults.baseURL = "{{ base_url() }}";
|
||||
tmaxios.defaults.headers.common['X-Session-Auth'] = "true";
|
||||
</script>
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/highlight.min.js?v={{ settings.version }}"></script>
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue.js?v={{ settings.version }}"></script>
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-eventbus.js?v={{ settings.version }}"></script>
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-shared.js?v={{ settings.version }}"></script>
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-medialib.js?v={{ settings.version }}"></script>
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-forms.js?v={{ settings.version }}"></script>
|
||||
|
||||
{% block javascript %}{% endblock %}
|
||||
|
||||
{{ assets.renderJS() }}
|
||||
|
||||
<script>
|
||||
app.config.globalProperties.$filters = translatefilter;
|
||||
app.mount('#system');
|
||||
|
||||
/* kixote.mount('#kixote'); */
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
@@ -82,4 +82,26 @@ $app->group('/api/v1', function (RouteCollectorProxy $group) use ($acl) {
|
||||
$group->get('/meta', ControllerApiAuthorMeta::class . ':getMeta')->setName('api.meta.get')->add(new ApiAuthorization($acl, 'mycontent', 'view'));
|
||||
$group->post('/meta', ControllerApiAuthorMeta::class . ':updateMeta')->setName('api.metadata.update')->add(new ApiAuthorization($acl, 'mycontent', 'update'));
|
||||
|
||||
})->add(new ApiAuthentication());
|
||||
})->add(new ApiAuthentication());
|
||||
|
||||
# api-routes from plugins
|
||||
foreach($routes['api'] as $pluginRoute)
|
||||
{
|
||||
$method = $pluginRoute['httpMethod'] ?? false;
|
||||
$route = $pluginRoute['route'] ?? false;
|
||||
$class = $pluginRoute['class'] ?? false;
|
||||
$name = $pluginRoute['name'] ?? false;
|
||||
$resource = $pluginRoute['resource'] ?? false;
|
||||
$privilege = $pluginRoute['privilege'] ?? false;
|
||||
|
||||
if($resources && $privilege)
|
||||
{
|
||||
# protected api requires authentication and authorization
|
||||
$app->{$method}($route, $class)->setName($name)->add(new ApiAuthorization($acl, $resource, $privilege))->add(new ApiAuthentication());
|
||||
}
|
||||
else
|
||||
{
|
||||
# public api routes
|
||||
$app->{$method}($route, $class)->setName($name);
|
||||
}
|
||||
}
|
||||
|
@@ -54,34 +54,24 @@ $app->redirect('/tm/', $routeParser->urlFor('auth.show'), 302);
|
||||
# downloads
|
||||
$app->get('/media/files[/{params:.*}]', ControllerWebDownload::class . ':download')->setName('download.file');
|
||||
|
||||
foreach($routes as $pluginRoute)
|
||||
# web-routes from plugins
|
||||
foreach($routes['web'] as $pluginRoute)
|
||||
{
|
||||
$method = $pluginRoute['httpMethod'];
|
||||
$route = $pluginRoute['route'];
|
||||
$class = $pluginRoute['class'];
|
||||
# $resource = isset($pluginRoute['resource']) ? $pluginRoute['resource'] : NULL;
|
||||
# $privilege = isset($pluginRoute['privilege']) ? $pluginRoute['privilege'] : NULL;
|
||||
$method = $pluginRoute['httpMethod'] ?? false;
|
||||
$route = $pluginRoute['route'] ?? false;
|
||||
$class = $pluginRoute['class'] ?? false;
|
||||
$name = $pluginRoute['name'] ?? false;
|
||||
$resource = $pluginRoute['resource'] ?? false;
|
||||
$privilege = $pluginRoute['privilege'] ?? false;
|
||||
|
||||
# echo '<br>';
|
||||
# echo 'method: ' . $method . ' -> route: ' . $route . ' -> class: ' . $class;
|
||||
|
||||
if(isset($pluginRoute['name']))
|
||||
if($resources && $privilege)
|
||||
{
|
||||
# $app->{$method}($route, $class)->setName($pluginRoute['name'])->add(new accessMiddleware($container['router'], $container['acl'], $resource, $privilege));
|
||||
$app->{$method}($route, $class)->setName($pluginRoute['name']);
|
||||
$app->{$method}($route, $class)->setName($name)->add(new WebAuthorization($routeParser, $acl, $resource, $privilege))->add(new WebRedirectIfUnauthenticated($routeParser));
|
||||
}
|
||||
else
|
||||
{
|
||||
# $app->{$method}($route, $class)->add(new accessMiddleware($container['router'], $container['acl'], $resource, $privilege));
|
||||
$app->{$method}($route, $class);
|
||||
$app->{$method}($route, $class)->setName($name);
|
||||
}
|
||||
|
||||
# if api and if authorization
|
||||
# ->add(new ApiAuthorization($acl, 'account', 'view'));
|
||||
|
||||
# if web and if authorization
|
||||
# ->add(new WebAuthorization($acl, 'account', 'view'));
|
||||
}
|
||||
# die();
|
||||
# website
|
||||
|
||||
$app->get('/[{route:.*}]', ControllerWebFrontend::class . ':index')->setName('home');
|
Reference in New Issue
Block a user