1
0
mirror of https://github.com/typemill/typemill.git synced 2025-07-30 02:40:42 +02:00

failed try with middleware

This commit is contained in:
trendschau
2022-07-12 22:08:06 +02:00
parent 0888a8167f
commit a5df46c6f7
62 changed files with 3531 additions and 85 deletions

56
composer.lock generated
View File

@@ -1742,16 +1742,16 @@
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.25.0",
"version": "v1.26.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "30885182c981ab175d4d034db0f6f469898070ab"
"reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/30885182c981ab175d4d034db0f6f469898070ab",
"reference": "30885182c981ab175d4d034db0f6f469898070ab",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
"reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4",
"shasum": ""
},
"require": {
@@ -1766,7 +1766,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.23-dev"
"dev-main": "1.26-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -1804,7 +1804,7 @@
"portable"
],
"support": {
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.25.0"
"source": "https://github.com/symfony/polyfill-ctype/tree/v1.26.0"
},
"funding": [
{
@@ -1820,20 +1820,20 @@
"type": "tidelift"
}
],
"time": "2021-10-20T20:35:02+00:00"
"time": "2022-05-24T11:49:31+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.25.0",
"version": "v1.26.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825"
"reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/0abb51d2f102e00a4eefcf46ba7fec406d245825",
"reference": "0abb51d2f102e00a4eefcf46ba7fec406d245825",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
"reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e",
"shasum": ""
},
"require": {
@@ -1848,7 +1848,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.23-dev"
"dev-main": "1.26-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -1887,7 +1887,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.25.0"
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.26.0"
},
"funding": [
{
@@ -1903,20 +1903,20 @@
"type": "tidelift"
}
],
"time": "2021-11-30T18:21:41+00:00"
"time": "2022-05-24T11:49:31+00:00"
},
{
"name": "symfony/polyfill-php80",
"version": "v1.25.0",
"version": "v1.26.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
"reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c"
"reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/4407588e0d3f1f52efb65fbe92babe41f37fe50c",
"reference": "4407588e0d3f1f52efb65fbe92babe41f37fe50c",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace",
"reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace",
"shasum": ""
},
"require": {
@@ -1925,7 +1925,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.23-dev"
"dev-main": "1.26-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -1970,7 +1970,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.25.0"
"source": "https://github.com/symfony/polyfill-php80/tree/v1.26.0"
},
"funding": [
{
@@ -1986,20 +1986,20 @@
"type": "tidelift"
}
],
"time": "2022-03-04T08:16:47+00:00"
"time": "2022-05-10T07:21:04+00:00"
},
{
"name": "symfony/polyfill-php81",
"version": "v1.25.0",
"version": "v1.26.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php81.git",
"reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f"
"reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/5de4ba2d41b15f9bd0e19b2ab9674135813ec98f",
"reference": "5de4ba2d41b15f9bd0e19b2ab9674135813ec98f",
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/13f6d1271c663dc5ae9fb843a8f16521db7687a1",
"reference": "13f6d1271c663dc5ae9fb843a8f16521db7687a1",
"shasum": ""
},
"require": {
@@ -2008,7 +2008,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.23-dev"
"dev-main": "1.26-dev"
},
"thanks": {
"name": "symfony/polyfill",
@@ -2049,7 +2049,7 @@
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php81/tree/v1.25.0"
"source": "https://github.com/symfony/polyfill-php81/tree/v1.26.0"
},
"funding": [
{
@@ -2065,7 +2065,7 @@
"type": "tidelift"
}
],
"time": "2021-09-13T13:58:11+00:00"
"time": "2022-05-24T11:49:31+00:00"
},
{
"name": "symfony/yaml",

View File

@@ -1,3 +1,5 @@
<?php
require __DIR__ . '/../system/typemill/system.php';
$rootpath = __DIR__ . DIRECTORY_SEPARATOR . '..';
require $rootpath . '/system/typemill/system.php';

View File

@@ -1,33 +1,18 @@
const naviBus = new Vue();
const navcomponent = Vue.component('navigation', {
template: '#navigation-template',
props: ['homepage', 'name', 'hide', 'newItem', 'parent', 'active', 'filetype', 'status', 'elementtype', 'contains', 'element', 'folder', 'level', 'url', 'root', 'freeze'],
props: ['homepage', 'name', 'hide', 'newItem', 'parent', 'active', 'filetype', 'status', 'elementtype', 'contains', 'element', 'folder', 'level', 'url', 'root', 'freeze', 'collapse'],
data: function () {
return {
showForm: false,
revert: false,
collapse: [],
}
},
mounted: function(){
collapse = localStorage.getItem('collapse');
if(collapse !== null)
{
this.collapse = collapse.split(',');
}
},
methods: {
toggleCollapse: function(name)
callToggle: function(name)
{
var index = this.collapse.indexOf(name);
if (index > -1)
{
this.collapse.splice(index, 1);
}
else
{
this.collapse.push(name);
}
localStorage.setItem("collapse", this.collapse.toString());
naviBus.$emit('toggleNavi', name);
},
isCollapsed: function(name)
{
@@ -222,9 +207,46 @@ let navi = new Vue({
folderName: '',
showForm: false,
newItem: '',
collapse: [],
}
},
mounted: function(){
var collapse = localStorage.getItem('collapse');
if(collapse !== null)
{
var collapseArray = collapse.split(',');
var collapseLength = collapseArray.length;
var cleanCollapseArray = [];
for(var i = 0; i < collapseLength; i++)
{
if(typeof collapseArray[i] === 'string' && collapseArray[i] != '')
{
cleanCollapseArray.push(collapseArray[i]);
}
}
this.collapse = collapse.split(',');
}
naviBus.$on('toggleNavi', this.toggleNavigation);
},
methods:{
clearToggle: function()
{
this.collapse = [];
localStorage.removeItem('collapse');
},
toggleNavigation: function(name)
{
var index = this.collapse.indexOf(name);
if (index > -1)
{
this.collapse.splice(index, 1);
}
else
{
this.collapse.push(name);
}
localStorage.setItem("collapse", this.collapse.toString());
},
checkMove: function(evt){
/* this.$refs.draggit[0].checkMove(evt); */
if(evt.dragged.classList.contains('folder') && evt.from.parentNode.id != evt.to.parentNode.id)

View File

@@ -4,6 +4,7 @@
<div id="navi" class="content-navi" :value.sync="freeze" v-cloak>
<div class="navi-list">
<div class="navi-item folder">
<div class="foldertoggle" @click="clearToggle"><svg class="icon icon-enlarge2"><use xlink:href="#icon-enlarge2"></use></svg></div>
<div class="status" :class="homepage.status"></div>
<a href="{{ base_url }}/tm/content/{{ settings.editor }}" :class="homepage.active"><span><span class="iconwrapper"><svg class="icon icon-home"><use xlink:href="#icon-home"></use></svg></span><span class="level-1">{{ __('Homepage') }}</span></a>
</div>
@@ -15,7 +16,7 @@
:move="checkMove"
group="file"
animation="150"
:disabled="freeze">
:disabled="freeze">
<navigation
v-for="item in items"
ref="draggit"
@@ -34,6 +35,7 @@
:filetype="item.fileType"
:status="item.status"
:folder="item.folderContent"
:collapse="collapse"
></navigation>
</draggable>
<ul class="navi-list addBaseItem">
@@ -58,7 +60,7 @@
{% verbatim %}
<template id="navigation-template">
<li class="navi-item" :class="elementtype">
<div v-if="folder" class="foldertoggle" @click="toggleCollapse(name)"><svg v-if="isCollapsed(name)" class="icon icon-shrink2"><use xlink:href="#icon-shrink2"></use></svg><svg v-else class="icon icon-enlarge2"><use xlink:href="#icon-enlarge2"></use></svg></div>
<div v-if="folder" class="foldertoggle" @click="callToggle(name)"><svg v-if="isCollapsed(name)" class="icon icon-shrink2"><use xlink:href="#icon-shrink2"></use></svg><svg v-else class="icon icon-enlarge2"><use xlink:href="#icon-enlarge2"></use></svg></div>
<div class="status" :class="status"></div>
<a v-bind:href="getUrl(root, url)" :class="checkActive(active,parent)"><span class="iconwrapper"><svg class="icon" :class="getIconClass(elementtype, filetype, hide)"><use :xlink:href="getIcon(elementtype, filetype, hide)"></use></svg></span><span :class="getLevel(level)">{{ name }}</span><span class="movewrapper"><svg class="icon icon-arrows-v"><use xlink:href="#icon-arrows-v"></use></svg></span></a>
<draggable v-if="folder" v-show="!isCollapsed(name)" class="navi-list" tag="ul"
@@ -88,6 +90,7 @@
:elementtype="item.elementType"
:contains="item.contains"
:folder="item.folderContent"
:collapse="collapse"
></navigation>
</draggable>
<ul v-if="folder && contains == 'pages'" class="navi-list">

View File

@@ -0,0 +1,499 @@
<?php
namespace Typemill\Controllers;
use DI\Container;
use Slim\Routing\RouteContext;
# use Psr\Container\ContainerInterface;
# use Typemill\Models\Folder;
# use Typemill\Models\WriteCache;
# use Typemill\Models\WriteYaml;
# use Typemill\Events\OnPageReady;
# use Typemill\Events\OnPagetreeLoaded;
use Typemill\Events\OnTwigLoaded;
abstract class Controller
{
# holds the container
protected $c;
# holds the settings
protected $settings;
public function __construct(Container $container)
{
$this->c = $container;
$this->routeParser = $container->get('routeParser');
$this->settings = $container->get('settings');
}
/*
# holds the pimple container
protected $c;
# holds the settings
protected $settings;
# holds the write cache object
protected $writeCache;
# holds the structure of content folder as a serialized array of objects
protected $structureDraft = false;
# holds the structure of content folder as a serialized array of objects
protected $structureLive = false;
# holds the name of the structure-file with drafts for author environment
protected $structureDraftName = 'structure-draft.txt';
# holds the name of the structure-file without drafts for live site
protected $structureLiveName = 'structure.txt';
# holds the frontend navigation without hidden pages
protected $navigation = false;
# holds the list of pages with navigation titles and hidden pages. It extends the structures and navigations
protected $extended = false;
public function __construct(ContainerInterface $c)
{
$this->c = $c;
$this->settings = $this->c->get('settings');
# used everywhere so instantiate it
$this->writeCache = new writeCache();
$this->c->dispatcher->dispatch('onTwigLoaded');
}
# render page for frontend
protected function render($response, $route, $data)
{
# why commented this out??
$data = $this->c->dispatcher->dispatch('onPageReady', new OnPageReady($data))->getData();
if(isset($_SESSION['old']))
{
unset($_SESSION['old']);
}
$response = $response->withoutHeader('Server');
$response = $response->withAddedHeader('X-Powered-By', 'Typemill');
if(!isset($this->settings['headersoff']) or !$this->settings['headersoff'])
{
$response = $response->withAddedHeader('X-Content-Type-Options', 'nosniff');
$response = $response->withAddedHeader('X-Frame-Options', 'SAMEORIGIN');
$response = $response->withAddedHeader('X-XSS-Protection', '1;mode=block');
$response = $response->withAddedHeader('Referrer-Policy', 'no-referrer-when-downgrade');
if($this->c->request->getUri()->getScheme() == 'https')
{
$response = $response->withAddedHeader('Strict-Transport-Security', 'max-age=63072000');
}
}
return $this->c->view->render($response, $route, $data);
}
# render 404 for frontend
protected function render404($response, $data = NULL)
{
return $this->c->view->render($response->withStatus(404), '/404.twig', $data);
}
# render page for authors (admin-area)
protected function renderIntern($response, $route, $data)
{
if(isset($_SESSION['old']))
{
unset($_SESSION['old']);
}
$response = $response->withoutHeader('Server');
$response = $response->withAddedHeader('X-Powered-By', 'Typemill');
if(!isset($this->settings['headersoff']) or !$this->settings['headersoff'])
{
$response = $response->withAddedHeader('X-Content-Type-Options', 'nosniff');
$response = $response->withAddedHeader('X-Frame-Options', 'SAMEORIGIN');
$response = $response->withAddedHeader('X-XSS-Protection', '1;mode=block');
$response = $response->withAddedHeader('Referrer-Policy', 'no-referrer-when-downgrade');
if($this->c->request->getUri()->getScheme() == 'https')
{
$response = $response->withAddedHeader('Strict-Transport-Security', 'max-age=63072000');
}
}
return $this->c->view->render($response, $route, $data);
}
# render 404 for authors
protected function renderIntern404($response, $data = NULL)
{
return $this->c->view->render($response->withStatus(404), '/intern404.twig', $data);
}
# reads the cached structure with published and non-published pages for the author
protected function setStructureDraft()
{
# get the cached structure
$this->structureDraft = $this->writeCache->getCache('cache', $this->structureDraftName);
# if there is no cached structure
if(!$this->structureDraft)
{
return $this->setFreshStructureDraft();
}
return true;
}
# creates a fresh structure with published and non-published pages for the author
protected function setFreshStructureDraft()
{
# scan the content of the folder
$pagetreeDraft = Folder::scanFolder($this->settings['rootPath'] . $this->settings['contentFolder'], $draft = true );
# if there is content, then get the content details
if(count($pagetreeDraft) > 0)
{
# get the extended structure files with changes like navigation title or hidden pages
$yaml = new writeYaml();
$extended = $this->getExtended();
# create an array of object with the whole content of the folder and changes from extended file
$this->structureDraft = Folder::getFolderContentDetails($pagetreeDraft, $extended, $this->settings, $this->uri->getBaseUrl(), $this->uri->getBasePath());
# cache structure draft
$this->writeCache->updateCache('cache', $this->structureDraftName, 'lastCache.txt', $this->structureDraft);
return true;
}
return false;
}
# reads the cached structure of published pages
protected function setStructureLive()
{
# get the cached structure
$this->structureLive = $this->writeCache->getCache('cache', $this->structureLiveName);
# if there is no cached structure
if(!$this->structureLive)
{
return $this->setFreshStructureLive();
}
return true;
}
# creates a fresh structure with published pages
protected function setFreshStructureLive()
{
# scan the content of the folder
$pagetreeLive = Folder::scanFolder($this->settings['rootPath'] . $this->settings['contentFolder'], $draft = false );
# if there is content, then get the content details
if($pagetreeLive && count($pagetreeLive) > 0)
{
# get the extended structure files with changes like navigation title or hidden pages
$yaml = new writeYaml();
$extended = $this->getExtended();
# create an array of object with the whole content of the folder and changes from extended file
$this->structureLive = Folder::getFolderContentDetails($pagetreeLive, $extended, $this->settings, $this->uri->getBaseUrl(), $this->uri->getBasePath());
# cache structure live
$this->writeCache->updateCache('cache', $this->structureLiveName, 'lastCache.txt', $this->structureLive);
return true;
}
return false;
}
# reads the live navigation from cache (live structure without hidden pages)
protected function setNavigation()
{
# get the cached structure
$this->navigation = $this->writeCache->getCache('cache', 'navigation.txt');
# if there is no cached structure
if(!$this->navigation)
{
return $this->setFreshNavigation();
}
return true;
}
# creates a fresh live navigation (live structure without hidden pages)
protected function setFreshNavigation()
{
if(!$this->extended)
{
$extended = $this->getExtended();
}
if($this->containsHiddenPages($this->extended))
{
if(!$this->structureLive)
{
$this->setStructureLive();
}
$structureLive = $this->c->dispatcher->dispatch('onPagetreeLoaded', new OnPagetreeLoaded($this->structureLive))->getData();
$this->navigation = $this->createNavigation($structureLive);
# cache navigation
$this->writeCache->updateCache('cache', 'navigation.txt', false, $this->navigation);
return true;
}
# make sure no old navigation file is left
$this->writeCache->deleteFileWithPath('cache' . DIRECTORY_SEPARATOR . 'navigation.txt');
return false;
}
# create navigation from structure
protected function createNavigation($structureLive)
{
foreach ($structureLive as $key => $element)
{
if($element->hide === true)
{
unset($structureLive[$key]);
}
elseif(isset($element->folderContent))
{
$structureLive[$key]->folderContent = $this->createNavigation($element->folderContent);
}
}
return $structureLive;
}
# controllerFrontendWebsite, but not in use, makes no sense to check on each page load
public function checkSitemap()
{
if(!$this->writeCache->getCache('cache', 'sitemap.xml'))
{
if(!$this->structureLive)
{
$this->setStructureLive();
}
$this->updateSitemap();
}
return true;
}
public function updateSitemap($ping = false)
{
$sitemap = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
$sitemap .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
$sitemap = $this->addUrlSet($sitemap, $this->uri->getBaseUrl());
$sitemap .= $this->generateUrlSets($this->structureLive);
$sitemap .= '</urlset>';
$this->writeCache->writeFile('cache', 'sitemap.xml', $sitemap);
if($ping && isset($this->settings['pingsitemap']) && $this->settings['pingsitemap'])
{
$sitemapUrl = $this->uri->getBaseUrl() . '/cache/sitemap.xml';
$pingGoogleUrl = 'http://www.google.com/ping?sitemap=' . urlencode($sitemapUrl);
$pingBingUrl = 'http://www.bing.com/ping?sitemap=' . urlencode($sitemapUrl);
$opts = array(
'http'=>array(
'method'=>"GET",
'ignore_errors' => true,
'timeout' => 5
)
);
$context = stream_context_create($opts);
$responseBing = file_get_contents($pingBingUrl, false, $context);
$responseGoogle = file_get_contents($pingGoogleUrl, false, $context);
}
}
public function generateUrlSets($structureLive)
{
$urlset = '';
foreach($structureLive as $item)
{
if($item->elementType == 'folder' && isset($item->noindex) && $item->noindex === true)
{
$urlset .= $this->generateUrlSets($item->folderContent, $urlset);
}
elseif($item->elementType == 'folder')
{
$urlset = $this->addUrlSet($urlset, $item->urlAbs);
$urlset .= $this->generateUrlSets($item->folderContent, $urlset);
}
elseif(isset($item->noindex) && $item->noindex === true )
{
continue;
}
else
{
$urlset = $this->addUrlSet($urlset, $item->urlAbs);
}
}
return $urlset;
}
public function addUrlSet($urlset, $url)
{
$urlset .= ' <url>' . "\n";
$urlset .= ' <loc>' . $url . '</loc>' . "\n";
$urlset .= ' </url>' . "\n";
return $urlset;
}
protected function getExtended()
{
$yaml = new writeYaml();
if(!$this->extended)
{
$this->extended = $yaml->getYaml('cache', 'structure-extended.yaml');
}
if(!$this->extended)
{
# scan the content of the folder
$pagetreeDraft = Folder::scanFolder($this->settings['rootPath'] . $this->settings['contentFolder'], $draft = true );
# if there is content, then get the content details
if(count($pagetreeDraft) == 0)
{
return false;
}
# create an array of object with the whole content of the folder and changes from extended file
$structureDraft = Folder::getFolderContentDetails($pagetreeDraft, $extended = false, $this->settings, $this->uri->getBaseUrl(), $this->uri->getBasePath());
$this->extended = $this->createExtended($this->settings['rootPath'] . $this->settings['contentFolder'], $yaml, $structureDraft);
$yaml->updateYaml('cache', 'structure-extended.yaml', $this->extended);
}
return $this->extended;
}
# creates a file that holds all hide flags and navigation titles
# reads all meta-files and creates an array with url => ['hide' => bool, 'navtitle' => 'bla']
public function createExtended($contentPath, $yaml, $structureLive, $extended = NULL)
{
if(!$extended)
{
$extended = [];
}
foreach ($structureLive as $key => $item)
{
# $filename = ($item->elementType == 'folder') ? DIRECTORY_SEPARATOR . 'index.yaml' : $item->pathWithoutType . '.yaml';
$filename = $item->pathWithoutType . '.yaml';
if(file_exists($contentPath . $filename))
{
# read file
$meta = $yaml->getYaml('content', $filename);
$extended[$item->urlRelWoF]['hide'] = isset($meta['meta']['hide']) ? $meta['meta']['hide'] : false;
$extended[$item->urlRelWoF]['navtitle'] = isset($meta['meta']['navtitle']) ? $meta['meta']['navtitle'] : '';
}
if ($item->elementType == 'folder')
{
$extended = $this->createExtended($contentPath, $yaml, $item->folderContent, $extended);
}
}
return $extended;
}
# only backoffice
protected function renameExtended($item, $newFolder)
{
# get the extended structure files with changes like navigation title or hidden pages
$yaml = new writeYaml();
$extended = $yaml->getYaml('cache', 'structure-extended.yaml');
if(isset($extended[$item->urlRelWoF]))
{
$newUrl = $newFolder->urlRelWoF . '/' . $item->slug;
$entry = $extended[$item->urlRelWoF];
unset($extended[$item->urlRelWoF]);
$extended[$newUrl] = $entry;
$yaml->updateYaml('cache', 'structure-extended.yaml', $extended);
}
return true;
}
# only backoffice
protected function deleteFromExtended()
{
# get the extended structure files with changes like navigation title or hidden pages
$yaml = new writeYaml();
$extended = $yaml->getYaml('cache', 'structure-extended.yaml');
if($this->item->elementType == "file" && isset($extended[$this->item->urlRelWoF]))
{
unset($extended[$this->item->urlRelWoF]);
$yaml->updateYaml('cache', 'structure-extended.yaml', $extended);
}
if($this->item->elementType == "folder")
{
$changed = false;
# delete all entries with that folder url
foreach($extended as $url => $entries)
{
if( strpos($url, $this->item->urlRelWoF) !== false )
{
$changed = true;
unset($extended[$url]);
}
}
if($changed)
{
$yaml->updateYaml('cache', 'structure-extended.yaml', $extended);
}
}
}
# checks if there is a hidden page, returns true on first find
protected function containsHiddenPages($extended)
{
foreach($extended as $element)
{
if(isset($element['hide']) && $element['hide'] === true)
{
return true;
}
}
return false;
}
*/
}

View File

@@ -0,0 +1,54 @@
<?php
namespace Typemill\Controllers;
use DI\Container;
use Slim\Views\Twig;
use Typemill\Events\OnTwigLoaded;
class ControllerWeb extends Controller
{
public function __construct(Container $container)
{
parent::__construct($container);
echo '<br>add twig';
$settings = $this->settings;
$csrf = isset($_SESSION) ? $this->c->get('csrf') : false;
$this->c->set('view', function() use ($settings, $csrf)
{
$twig = Twig::create(
[
# path to templates
$settings['rootPath'] . $settings['authorFolder'],
$settings['rootPath'] . DIRECTORY_SEPARATOR . 'themes' . DIRECTORY_SEPARATOR . $settings['theme'],
],
[
# settings
'cache' => ( isset($settings['twigcache']) && $settings['twigcache'] ) ? $settings['rootPath'] . '/cache/twig' : false,
'debug' => isset($settings['displayErrorDetails'])
]
);
# placeholder for flash and errors, will be filled later with middleware
$twig->getEnvironment()->addGlobal('errors', NULL);
$twig->getEnvironment()->addGlobal('flash', NULL);
# add extensions
$twig->addExtension(new \Twig\Extension\DebugExtension());
# $twig->addExtension(new \Nquire\Extensions\TwigUserExtension());
if($csrf)
{
$twig->addExtension(new \Typemill\Extensions\TwigCsrfExtension($csrf));
}
return $twig;
});
$this->c->get('dispatcher')->dispatch(new OnTwigLoaded(false), 'onTwigLoaded');
}
}

View File

@@ -0,0 +1,503 @@
<?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\Folder;
use Typemill\Models\WriteMeta;
use Typemill\Extensions\ParsedownExtension;
use Typemill\Events\OnPagetreeLoaded;
use Typemill\Events\OnBreadcrumbLoaded;
use Typemill\Events\OnItemLoaded;
use Typemill\Events\OnOriginalLoaded;
use Typemill\Events\OnMetaLoaded;
use Typemill\Events\OnMarkdownLoaded;
use Typemill\Events\OnContentArrayLoaded;
use Typemill\Events\OnHtmlLoaded;
use Typemill\Events\OnRestrictionsLoaded;
*/
class ControllerWebFrontend extends ControllerWeb
{
public function index(Request $request, Response $response)
{
die('hallo');
return $this->c->get('view')->render($response, 'home.twig', [
'title' => 'Typemill Version 2',
'description' => 'Typemill Version 2 wird noch besser als Version 1.'
]);
# 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
]);
}
protected function getFirstImage(array $contentBlocks)
{
foreach($contentBlocks as $block)
{
/* is it a paragraph? */
if(isset($block['name']) && $block['name'] == 'p')
{
if(isset($block['handler']['argument']) && substr($block['handler']['argument'], 0, 2) == '![' )
{
return $block['handler']['argument'];
}
}
}
return false;
}
# checks if a page has a restriction in meta and if the current user is blocked by that restriction
protected function checkRestrictions($meta)
{
# check if content restrictions are active
if(isset($this->settings['pageaccess']) && $this->settings['pageaccess'])
{
# check if page is restricted to certain user
if(isset($meta['alloweduser']) && $meta['alloweduser'] && $meta['alloweduser'] !== '' )
{
$alloweduser = array_map('trim', explode(",", $meta['alloweduser']));
if(isset($_SESSION['user']) && in_array($_SESSION['user'], $alloweduser))
{
# user has access to the page, so there are no restrictions
return false;
}
# otherwise return array with type of restriction and allowed username
return [ 'alloweduser' => $meta['alloweduser'] ];
}
# check if page is restricted to certain userrole
if(isset($meta['allowedrole']) && $meta['allowedrole'] && $meta['allowedrole'] !== '' )
{
# var_dump($this->c->acl->inheritsRole('editor', 'member'));
# die();
if(
isset($_SESSION['role'])
AND (
$_SESSION['role'] == 'administrator'
OR $_SESSION['role'] == $meta['allowedrole']
OR $this->c->acl->inheritsRole($_SESSION['role'], $meta['allowedrole'])
)
)
{
# role has access to page, so there are no restrictions
return false;
}
return [ 'allowedrole' => $meta['allowedrole'] ];
}
}
return false;
}
protected function cutRestrictedContent($markdown)
{
#initially add only the title of the page.
$restrictedMarkdown = [$markdown[0]];
unset($markdown[0]);
if(isset($this->settings['hrdelimiter']) && $this->settings['hrdelimiter'] !== NULL )
{
foreach ($markdown as $block)
{
$firstCharacters = substr($block, 0, 3);
if($firstCharacters == '---' OR $firstCharacters == '***')
{
return $restrictedMarkdown;
}
$restrictedMarkdown[] = $block;
}
# no delimiter found, so use the title only
$restrictedMarkdown = [$restrictedMarkdown[0]];
}
return $restrictedMarkdown;
}
protected function prepareRestrictionNotice()
{
if( isset($this->settings['restrictionnotice']) && $this->settings['restrictionnotice'] != '' )
{
$restrictionNotice = $this->settings['restrictionnotice'];
}
else
{
$restrictionNotice = 'You are not allowed to access this content.';
}
if( isset($this->settings['wraprestrictionnotice']) && $this->settings['wraprestrictionnotice'] )
{
# standardize line breaks
$text = str_replace(array("\r\n", "\r"), "\n", $restrictionNotice);
# remove surrounding line breaks
$text = trim($text, "\n");
# split text into lines
$lines = explode("\n", $text);
$restrictionNotice = '';
foreach($lines as $key => $line)
{
$restrictionNotice .= "!!!! " . $line . "\n";
}
}
return $restrictionNotice;
}
}

View File

@@ -0,0 +1,108 @@
<?php
namespace Typemill\Controllers;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use Slim\Routing\RouteContext;
use Slim\Views\Twig;
use Typemill\Models\Validation;
use Typemill\Models\User;
use Typemill\Models\WriteYaml;
use Typemill\Extensions\ParsedownExtension;
class ControllerWebLogin extends ControllerWeb
{
# redirect if visit /setup route
public function redirect(Request $request, Response $response)
{
if(isset($_SESSION['login']))
{
return $response->withRedirect($this->c->router->pathFor('content.raw'));
}
else
{
return $response->withRedirect($this->c->router->pathFor('auth.show'));
}
}
/**
* show login form
*
* @param obj $request the slim request object.
* @param obj $response the slim response object.
* @param array $args with arguments past to the slim router
* @return obj $response and string route.
*/
public function show(Request $request, Response $response, $args)
{
return $this->c->get('view')->render($response, 'login.twig', [
#'captcha' => $this->checkIfAddCaptcha(),
#'url' => $this->urlCollection,
]);
# $settings = $this->c->get('settings');
# return $this->render($response, '/auth/login.twig', ['settings' => $settings]);
}
/**
* signin an existing user
*
* @param obj $request the slim request object with form data in the post params.
* @param obj $response the slim response object.
* @return obj $response with redirect to route.
*/
public function login(Request $request, Response $response)
{
if( ( null !== $request->getattribute('csrf_result') ) OR ( $request->getattribute('csrf_result') === false ) )
{
$this->c->flash->addMessage('error', 'The form has a timeout, please try again.');
return $response->withRedirect($this->c->router->pathFor('auth.show'));
}
/* authentication */
$params = $request->getParams();
$validation = new Validation();
$settings = $this->c->get('settings');
if($validation->signin($params))
{
$user = new User();
$userdata = $user->getUser($params['username']);
if($userdata && password_verify($params['password'], $userdata['password']))
{
# check if user has confirmed the account
if(isset($userdata['optintoken']) && $userdata['optintoken'])
{
$this->c->flash->addMessage('error', 'Your registration is not confirmed yet. Please check your e-mails and use the confirmation link.');
return $response->withRedirect($this->c->router->pathFor('auth.show'));
}
$user->login($userdata['username']);
# if user is allowed to view content-area
if($this->c->acl->hasRole($userdata['userrole']) && $this->c->acl->isAllowed($userdata['userrole'], 'content', 'view'))
{
$settings = $this->c->get('settings');
$editor = (isset($settings['editor']) && $settings['editor'] == 'visual') ? 'visual' : 'raw';
return $response->withRedirect($this->c->router->pathFor('content.' . $editor));
}
return $response->withRedirect($this->c->router->pathFor('user.account'));
}
}
if(isset($this->settings['securitylog']) && $this->settings['securitylog'])
{
\Typemill\Models\Helpers::addLogEntry('wrong login');
}
$this->c->flash->addMessage('error', 'Ups, wrong password or username, please try again.');
return $response->withRedirect($this->c->router->pathFor('auth.show'));
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Typemill\Events;
#use Symfony\Component\EventDispatcher\GenericEvent;
use Symfony\Contracts\EventDispatcher\Event;
class BaseEvent extends Event
#class BaseEvent extends GenericEvent
{
protected $data;
public function __construct($data)
{
$this->data = $data;
}
public function getData()
{
return $this->data;
}
public function setData($data)
{
$this->data = $data;
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for breadcrumb.
*/
class OnBreadcrumbLoaded extends BaseEvent
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for the page rendering data.
*/
class OnCacheUpdated extends BaseEvent
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for the page rendering data.
*/
class OnContentArrayLoaded extends BaseEvent
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for html page.
*/
class OnHtmlLoaded extends BaseEvent
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for item.
*/
class OnItemLoaded extends BaseEvent
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for markdown.
*/
class OnMarkdownLoaded extends BaseEvent
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for markdown.
*/
class OnMetaDefinitionsLoaded extends BaseEvent
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for markdown.
*/
class OnMetaLoaded extends BaseEvent
{
}

View File

@@ -0,0 +1,35 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
use Typemill\Extensions\ParsedownExtension;
/**
* Event for html page.
*/
class OnOriginalLoaded extends Event
{
protected $data;
public function __construct($data)
{
$this->data = $data;
}
public function getMarkdown()
{
return $this->data;
}
public function getHTML($urlrel)
{
$parsedown = new ParsedownExtension();
$contentArray = $parsedown->text($this->data);
$contentHTML = $parsedown->markup($contentArray, $urlrel);
return $contentHTML;
}
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for breadcrumb.
*/
class OnPageDeleted extends BaseEvent
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for breadcrumb.
*/
class OnPagePublished extends BaseEvent
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for html data page.
*/
class OnPageReady extends BaseEvent
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for breadcrumb.
*/
class OnPageSorted extends BaseEvent
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for breadcrumb.
*/
class OnPageUnpublished extends BaseEvent
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for the page tree.
*/
class OnPagetreeLoaded extends BaseEvent
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for the folder structure.
*/
class OnPluginsLoaded extends BaseEvent
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for acl.
*/
class OnResourcesLoaded extends BaseEvent
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for page restrictions.
*/
class OnRestrictionsLoaded extends BaseEvent
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for acl.
*/
class OnRolesPermissionsLoaded extends BaseEvent
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for breadcrumb.
*/
class OnSessionSegmentsLoaded extends BaseEvent
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for settings
*/
class OnSettingsLoaded extends BaseEvent
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for breadcrumb.
*/
class OnShortcodeFound extends BaseEvent
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for settings
*/
class OnSystemnaviLoaded extends BaseEvent
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for breadcrumb.
*/
class OnTwigLoaded extends BaseEvent
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for the page tree.
*/
class OnUserConfirmed extends BaseEvent
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for the page tree.
*/
class OnUserDeleted extends BaseEvent
{
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Typemill\Events;
use Symfony\Component\EventDispatcher\Event;
/**
* Event for the page tree.
*/
class OnUserfieldsLoaded extends BaseEvent
{
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Typemill\Extensions;
use Slim\Csrf\Guard;
class TwigCsrfExtension extends \Twig\Extension\AbstractExtension
{
protected $csrf;
public function __construct(Guard $csrf)
{
$this->csrf = $csrf;
}
public function getFunctions()
{
return [
new \Twig\TwigFunction('csrf', [$this, 'csrf'])
];
}
public function csrf()
{
$csrf = '<p>TokenNameValue: '. $this->csrf->getTokenName() .'</p><input type="hidden" name="' . $this->csrf->getTokenNameKey(). '" value="' . $this->csrf->getTokenName() . '">
<input type="hidden" name="' . $this->csrf->getTokenValueKey(). '" value="' . $this->csrf->getTokenValue(). '">';
return $csrf;
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace Typemill\Middleware;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Routing\RouteContext;
class CreateSession implements MiddlewareInterface
{
protected $sessionSegments = [];
protected $routepath = false;
public function __construct($session_segments, $routepath)
{
$this->sessionSegments = $session_segments;
$this->routepath = $routepath;
}
public function process(Request $request, RequestHandler $handler) :response
{
foreach($this->sessionSegments as $segment)
{
if(substr( $this->routepath, 0, strlen($segment) ) === ltrim($segment, '/'))
{
echo '<br>Create Session';
// configure session
ini_set('session.cookie_httponly', 1 );
ini_set('session.use_strict_mode', 1);
ini_set('session.cookie_samesite', 'lax');
/*
if($uri->getScheme() == 'https')
{
ini_set('session.cookie_secure', 1);
session_name('__Secure-typemill-session');
}
else
{
session_name('typemill-session');
}
*/
// start session
session_start();
$request = $request->withAttribute('session', $_SESSION);
}
}
return $handler->handle($request);
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Typemill\Middleware;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Psr\Http\Server\MiddlewareInterface;
use Slim\Routing\RouteContext;
use Slim\Csrf\Guard;
class CsrfProtection implements MiddlewareInterface
{
protected $container;
protected $responseFactory;
public function __construct($container, $responseFactory)
{
$this->container = $container;
$this->responseFactory = $responseFactory;
}
public function process(Request $request, RequestHandler $handler) :response
{
if(is_array($request->getAttribute('session')))
{
echo '<br> csrf protection';
$responseFactory = $this->responseFactory;
# Register Middleware On Container
$this->container->set('csrf', function () use ($responseFactory)
{
return new Guard($responseFactory);
});
}
return $handler->handle($request);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Typemill\Middleware;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Psr\Http\Server\MiddlewareInterface;
use Slim\Routing\RouteContext;
use Slim\Csrf\Guard;
class CsrfProtectionToMiddleware
{
protected $container;
public function __construct($container)
{
$this->container = $container;
}
public function __invoke(Request $request, RequestHandler $handler)
{
if(is_array($request->getAttribute('session')))
{
echo '<br> csrf protection to middleware';
return $this->container->get('csrf');
}
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Typemill\Middleware;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Psr\Http\Server\MiddlewareInterface;
use Slim\Flash\Messages;
class FlashMessages implements MiddlewareInterface
{
protected $container;
public function __construct($container)
{
$this->container = $container;
}
public function process(Request $request, RequestHandler $handler) :response
{
if(is_array($request->getAttribute('session')))
{
echo '<br> flash messages';
$this->container->set('flash', function(){
return new Messages();
});
}
return $handler->handle($request);
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Typemill\Middleware;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Psr\Http\Server\MiddlewareInterface;
class JsonBodyParser implements MiddlewareInterface
{
public function process(Request $request, RequestHandler $handler) :response
{
echo '<br> JSON Body parser';
$contentType = $request->getHeaderLine('Content-Type');
if (strstr($contentType, 'application/json'))
{
$contents = json_decode(file_get_contents('php://input'), true);
if (json_last_error() === JSON_ERROR_NONE)
{
$request = $request->withParsedBody($contents);
}
}
return $handler->handle($request);
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Typemill\Middleware;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Psr\Http\Server\MiddlewareInterface;
# use Slim\Routing\RouteContext;
use Slim\Routing\RouteParser;
class RedirectIfAuthenticated implements MiddlewareInterface
{
public function __construct(RouteParser $router, $settings)
{
$this->router = $router;
$this->settings = $settings;
}
public function process(Request $request, RequestHandler $handler) :response
{
$response = $handler->handle($request);
$editor = (isset($this->settings['editor']) && $this->settings['editor'] == 'visual') ? 'visual' : 'raw';
if(isset($_SESSION['login']))
{
return $response->withHeader('Location', $this->router->pathFor('content.' . $editor))->withStatus(302);
# $response = $response->withRedirect($this->router->pathFor('content.' . $editor));
}
return $response;
}
}

333
system/typemill/Plugin.php Normal file
View File

@@ -0,0 +1,333 @@
<?php
namespace Typemill;
use \Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Typemill\Models\Fields;
use Typemill\Models\WriteYaml;
use Typemill\Models\Validation;
use Typemill\Extensions\ParsedownExtension;
abstract class Plugin implements EventSubscriberInterface
{
protected $container;
protected $path;
protected $adminpath = false;
/**
* Constructor
*
*/
public function __construct($container)
{
$this->container = $container;
/*
$this->path = trim($this->container['request']->getUri()->getPath(),"/");
if(substr($this->path, 0, 3) === "tm/")
{
$this->adminpath = true;
}
*/
}
protected function isXhr()
{
if($this->container['request']->isXhr())
{
return true;
}
return false;
}
protected function getParams()
{
return $this->container['request']->getParams();
}
protected function returnJson($data)
{
return $this->container['response']
->withHeader("Content-Type", "application/json")
->withStatus(200)
->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
}
protected function returnJsonError($data)
{
return $this->container['response']
->withHeader("Content-Type", "application/json")
->withStatus(400)
->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
}
protected function getSettings()
{
return $this->container->get('settings');
}
protected function getPluginSettings($plugin)
{
return $this->container->get('settings')['plugins'][$plugin];
}
protected function getRoute()
{
return $this->container['request']->getUri()->withUserInfo('');
}
protected function getPath()
{
return $this->container['request']->getUri()->getPath();
}
protected function getDispatcher()
{
return $this->container['dispatcher'];
}
protected function getTwig()
{
return $this->container['view'];
}
protected function addTwigGlobal($name, $class)
{
$this->container->view->getEnvironment()->addGlobal($name, $class);
}
protected function addTwigFilter($name, $filter)
{
$filter = new \Twig_SimpleFilter($name, $filter);
$this->container->view->getEnvironment()->addFilter($filter);
}
protected function addTwigFunction($name, $function)
{
$function = new \Twig_SimpleFunction($name, $function);
$this->container->view->getEnvironment()->addFunction($function);
}
protected function addJS($JS)
{
$this->container->assets->addJS($JS);
}
protected function addEditorJS($JS)
{
$this->container->assets->addEditorJS($JS);
}
protected function addInlineJS($JS)
{
$this->container->assets->addInlineJS($JS);
}
protected function addSvgSymbol($symbol)
{
$this->container->assets->addSvgSymbol($symbol);
}
protected function addEditorInlineJS($JS)
{
$this->container->assets->addEditorInlineJS($JS);
}
protected function addCSS($CSS)
{
$this->container->assets->addCSS($CSS);
}
protected function addInlineCSS($CSS)
{
$this->container->assets->addInlineCSS($CSS);
}
protected function addEditorCSS($CSS)
{
$this->container->assets->addEditorCSS($CSS);
}
protected function getMeta()
{
return $this->container->assets->meta;
}
public function addMeta($key,$meta)
{
$this->container->assets->addMeta($key, $meta);
}
protected function activateAxios()
{
$this->container->assets->activateAxios();
}
protected function activateVue()
{
$this->container->assets->activateVue();
}
protected function activateTachyons()
{
$this->container->assets->activateTachyons();
}
protected function markdownToHtml($markdown)
{
$parsedown = new ParsedownExtension();
$contentArray = $parsedown->text($markdown);
$html = $parsedown->markup($contentArray);
return $html;
}
protected function getFormData($pluginName)
{
$flash = $this->container->flash->getMessages();
if(isset($flash['formdata']))
{
$yaml = new Models\WriteYaml();
$formdata = $yaml->getYaml('settings', 'formdata.yaml');
$yaml->updateYaml('settings', 'formdata.yaml', '');
if($flash['formdata'][0] == $pluginName && isset($formdata[$pluginName]))
{
return $formdata[$pluginName];
}
}
elseif(isset($flash['publicform']) && $flash['publicform'][0] == 'bot')
{
return 'bot';
}
return false;
}
protected function generateForm($pluginName, $routename)
{
$fieldsModel = new Fields();
$settings = $this->getSettings();
$form = false;
$pluginDefinitions = \Typemill\Settings::getObjectSettings('plugins', $pluginName);
if(isset($settings['plugins'][$pluginName]['publicformdefinitions']) && $settings['plugins'][$pluginName]['publicformdefinitions'] != '')
{
$arrayFromYaml = \Symfony\Component\Yaml\Yaml::parse($settings['plugins'][$pluginName]['publicformdefinitions']);
$pluginDefinitions['public']['fields'] = $arrayFromYaml;
}
$buttonlabel = isset($settings['plugins'][$pluginName]['button_label']) ? $settings['plugins'][$pluginName]['button_label'] : false;
$captchaoptions = isset($settings['plugins'][$pluginName]['captchaoptions']) ? $settings['plugins'][$pluginName]['captchaoptions'] : false;
$recaptcha = isset($settings['plugins'][$pluginName]['recaptcha']) ? $settings['plugins'][$pluginName]['recaptcha_webkey'] : false;
if($captchaoptions == 'disabled')
{
# in case a captcha has failed on another page like login, the captcha-session must be deleted, otherwise it will not pass the security middleware
unset($_SESSION['captcha']);
}
$fieldsModel = new Fields();
if(isset($pluginDefinitions['public']['fields']))
{
# get all the fields and prefill them with the dafault-data, the user-data or old input data
$fields = $fieldsModel->getFields($settings, 'plugins', $pluginName, $pluginDefinitions, 'public');
# get Twig Instance
$twig = $this->getTwig();
# render each field and add it to the form
$form = $twig->fetch('/partials/form.twig', [
'routename' => $routename,
'fields' => $fields,
'itemName' => $pluginName,
'object' => 'plugins',
'buttonlabel' => $buttonlabel,
'captchaoptions' => $captchaoptions,
'recaptcha_webkey' => $recaptcha,
]);
}
return $form;
}
protected function validateParams($params)
{
$pluginName = key($params);
if(isset($params[$pluginName]))
{
$userInput = $params[$pluginName];
$settings = $this->getSettings();
# get settings and start validation
$originalSettings = \Typemill\Settings::getObjectSettings('plugins', $pluginName);
if(isset($settings['plugins'][$pluginName]['publicformdefinitions']) && $settings['plugins'][$pluginName]['publicformdefinitions'] != '')
{
$arrayFromYaml = \Symfony\Component\Yaml\Yaml::parse($settings['plugins'][$pluginName]['publicformdefinitions']);
$originalSettings['public']['fields'] = $arrayFromYaml;
}
elseif(isset($originalSettings['settings']['publicformdefinitions']))
{
$arrayFromYaml = \Symfony\Component\Yaml\Yaml::parse($originalSettings['settings']['publicformdefinitions']);
$originalSettings['public']['fields'] = $arrayFromYaml;
}
$validate = new Validation();
if(isset($originalSettings['public']['fields']))
{
# flaten the multi-dimensional array with fieldsets to a one-dimensional array
$originalFields = array();
foreach($originalSettings['public']['fields'] as $fieldName => $fieldValue)
{
if(isset($fieldValue['fields']))
{
foreach($fieldValue['fields'] as $subFieldName => $subFieldValue)
{
$originalFields[$subFieldName] = $subFieldValue;
}
}
else
{
$originalFields[$fieldName] = $fieldValue;
}
}
# take the user input data and iterate over all fields and values
foreach($userInput as $fieldName => $fieldValue)
{
# get the corresponding field definition from original plugin settings
$fieldDefinition = isset($originalFields[$fieldName]) ? $originalFields[$fieldName] : false;
if($fieldDefinition)
{
# validate user input for this field
$validate->objectField($fieldName, $fieldValue, $pluginName, $fieldDefinition);
}
if(!$fieldDefinition && $fieldName != 'active')
{
$_SESSION['errors'][$pluginName][$fieldName] = array('This field is not defined!');
}
}
if(isset($_SESSION['errors']))
{
$this->container->flash->addMessage('error', 'Please correct the errors');
return false;
}
return $params[$pluginName];
}
}
$this->container->flash->addMessage('error', 'The data from the form was invalid (missing or not defined)');
return false;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Typemill\Static;
class Helpers{
public static function printTimer($timer)
{
$lastTime = NULL;
$table = '<html><body><table>';
$table .= '<tr><th>Breakpoint</th><th>Time</th><th>Duration</th></tr>';
foreach($timer as $breakpoint => $time)
{
$duration = $time - $lastTime;
$table .= '<tr>';
$table .= '<td>' . $breakpoint . '</td>';
$table .= '<td>' . $time . '</td>';
$table .= '<td>' . $duration . '</td>';
$table .= '</tr>';
$lastTime = $time;
}
$table .= '</table></body></html>';
echo $table;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Typemill\Static;
class Languages
{
public static function whichLanguage()
{
# Check which languages are available
$langs = [];
$path = __DIR__ . '/author/languages/*.yaml';
foreach (glob($path) as $filename)
{
$langs[] = basename($filename,'.yaml');
}
# Detect browser language
$accept_lang = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2) : false;
$lang = in_array($accept_lang, $langs) ? $accept_lang : 'en';
return $lang;
}
}

View File

@@ -0,0 +1,68 @@
<?php
namespace Typemill\Static;
use Laminas\Permissions\Acl\Acl;
use Laminas\Permissions\Acl\Role\GenericRole as Role;
use Laminas\Permissions\Acl\Resource\GenericResource as Resource;
class Permissions
{
public static function loadResources($defaultsettingspath)
{
$resourcesfile = $defaultsettingspath . 'resources.yaml';
if(file_exists($resourcesfile))
{
$resourcesyaml = file_get_contents($resourcesfile);
$resources = \Symfony\Component\Yaml\Yaml::parse($resourcesyaml);
return $resources;
}
return false;
}
public static function loadRolesAndPermissions($defaultsettingspath)
{
$permissionsfile = $defaultsettingspath . 'permissions.yaml';
if(file_exists($permissionsfile))
{
$permissionsyaml = file_get_contents($permissionsfile);
$permissions = \Symfony\Component\Yaml\Yaml::parse($permissionsyaml);
return $permissions;
}
return false;
}
public static function createAcl($roles, $resources)
{
$acl = new Acl();
foreach($resources as $resource)
{
$acl->addResource(new Resource($resource));
}
# add all other roles dynamically
foreach($roles as $role)
{
$acl->addRole(new Role($role['name']), $role['inherits']);
foreach($role['permissions'] as $resource => $permissions)
{
$acl->allow($role['name'], $resource, $permissions);
}
}
# add administrator role
$acl->addRole(new Role('administrator'));
$acl->allow('administrator');
return $acl;
}
}

View File

@@ -0,0 +1,112 @@
<?php
namespace Typemill\Static;
class Plugins
{
public static function loadPlugins($rootpath)
{
$pluginFolder = self::scanPluginFolder($rootpath);
$classNames = [];
# iterate over plugin folders
foreach($pluginFolder as $plugin)
{
$className = '\\Plugins\\' . $plugin . '\\' . $plugin;
# if plugin-class exists, add classname to array
if(class_exists($className))
{
$classNames[] = ['className' => $className, 'name' => $plugin];
}
}
return $classNames;
}
public static function scanPluginFolder($rootpath)
{
$pluginsDir = $rootpath . '/plugins';
# check if plugins directory exists
if(!is_dir($pluginsDir)){ return array(); }
# get all plugin folders
$plugins = array_diff(scandir($pluginsDir), array('..', '.'));
return $plugins;
}
public static function getNewRoutes($className, $routes)
{
# if route-method exists in plugin-class
if(method_exists($className, 'addNewRoutes'))
{
# add the routes
$pluginRoutes = $className::addNewRoutes();
# multi-dimensional or simple array of routes
if(isset($pluginRoutes[0]))
{
# if they are properly formatted, add them to routes array
foreach($pluginRoutes as $pluginRoute)
{
if(self::checkRouteArray($routes,$pluginRoute))
{
$pluginRoute['route'] = strtolower($pluginRoute['route']);
$routes[] = $pluginRoute;
}
}
}
elseif(is_array($routes))
{
if(self::checkRouteArray($routes,$pluginRoutes))
{
$pluginRoutes['route'] = strtolower($pluginRoutes['route']);
$routes[] = $pluginRoutes;
}
}
}
return $routes;
}
public static function getNewMiddleware($className, $middleware)
{
if(method_exists($className, 'addNewMiddleware'))
{
$pluginMiddleware = $className::addNewMiddleware();
if($pluginMiddleware)
{
$middleware[] = $pluginMiddleware;
}
}
return $middleware;
}
private static function checkRouteArray($routes,$route)
{
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']))
{
return true;
}
return false;
}
private function in_array_r($needle, $haystack, $strict = false)
{
foreach ($haystack as $item)
{
if (($strict ? $item === $needle : $item == $needle) || (is_array($item) && $this->in_array_r($needle, $item, $strict)))
{
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Typemill\Static;
class Session
{
public static function startSessionForSegments($sessionSegments, $routepath)
{
$routepath = ltrim($routepath, '/');
foreach($sessionSegments as $segment)
{
echo '<br>' . $segment;
echo '<br>' . $routepath;
if(substr( $routepath, 0, strlen($segment) ) === ltrim($segment, '/'))
{
echo '<br>Create Session';
# configure session
ini_set('session.cookie_httponly', 1 );
ini_set('session.use_strict_mode', 1);
ini_set('session.cookie_samesite', 'lax');
/*
if($uri->getScheme() == 'https')
{
ini_set('session.cookie_secure', 1);
session_name('__Secure-typemill-session');
}
else
{
session_name('typemill-session');
}
*/
# start session
session_start();
break;
}
}
}
}

View File

@@ -0,0 +1,202 @@
<?php
namespace Typemill\Static;
class Settings
{
public static function loadSettings($rootpath)
{
$defaultsettings = self::getDefaultSettings($rootpath);
$usersettings = self::getUserSettings($rootpath);
$settings = $defaultsettings;
if($usersettings)
{
$settings = array_merge($defaultsettings, $usersettings);
# make sure all image-size information are there
if(isset($usersettings['images']))
{
$images = array_merge($defaultsettings['images'], $settings['images']);
$settings['images'] = $images;
}
}
$settings = self::addThemeSettings($settings);
return $settings;
}
public static function addThemeSettings($settings)
{
# we have to check if the theme has been deleted
$themefolder = $settings['rootPath'] . DIRECTORY_SEPARATOR . $settings['themeFolder'] . DIRECTORY_SEPARATOR;
# if there is no theme in settings or theme has been deleted
if(!isset($settings['theme']) OR !file_exists($themefolder . $settings['theme']))
{
# scan theme folder and get the first theme
$themes = array_filter(scandir($themefolder), function ($item) use($themefolder)
{
return is_dir($themefolder . $item) && strpos($item, '.') !== 0;
});
$firsttheme = reset($themes);
# if there is a theme with an index.twig-file
if($firsttheme && file_exists($themefolder . $firsttheme . DIRECTORY_SEPARATOR . 'index.twig'))
{
$settings['theme'] = $firsttheme;
}
else
{
die('You need at least one theme with an index.twig-file in your theme-folder.');
}
}
# We have the theme so create the theme path
$settings['themePath'] = $settings['rootPath'] . $settings['themeFolder'] . DIRECTORY_SEPARATOR . $settings['theme'];
# if there are no theme settings yet (e.g. no setup yet) use default theme settings
if(!isset($settings['themes']))
{
$themeSettings = self::getObjectSettings('themes', $settings['theme']);
$settings['themes'][$settings['theme']] = isset($themeSettings['settings']) ? $themeSettings['settings'] : false;
}
return $settings;
}
public static function getDefaultSettings($rootpath)
{
$defaultsettingspath = $rootpath . DIRECTORY_SEPARATOR . 'system' . DIRECTORY_SEPARATOR . 'typemill' . DIRECTORY_SEPARATOR . 'settings' . DIRECTORY_SEPARATOR;
$defaultsettingsfile = $defaultsettingspath . 'defaults.yaml';
if(file_exists($defaultsettingsfile))
{
$defaultsettingsyaml = file_get_contents($defaultsettingsfile);
$defaultsettings = \Symfony\Component\Yaml\Yaml::parse($defaultsettingsyaml);
$defaultsettings['rootPath'] = $rootpath;
$defaultsettings['defaultSettingsPath'] = $defaultsettingspath;
return $defaultsettings;
}
return false;
}
public static function getUserSettings($rootpath)
{
$usersettingsfile = $rootpath . DIRECTORY_SEPARATOR . 'settings' . DIRECTORY_SEPARATOR . 'settings.yaml';
if(file_exists($usersettingsfile))
{
$usersettingsyaml = file_get_contents($usersettingsfile);
$usersettings = \Symfony\Component\Yaml\Yaml::parse($usersettingsyaml);
return $usersettings;
}
return false;
}
### refactor
public static function getObjectSettings($objectType, $objectName)
{
$yaml = new Models\WriteYaml();
$objectFolder = $objectType . DIRECTORY_SEPARATOR . $objectName;
$objectFile = $objectName . '.yaml';
$objectSettings = $yaml->getYaml($objectFolder, $objectFile);
return $objectSettings;
}
public static function createSettings()
{
$yaml = new Models\WriteYaml();
$language = self::whichLanguage();
# create initial settings file with only setup false
if($yaml->updateYaml('settings', 'settings.yaml', array('setup' => false, 'language' => $language)))
{
return true;
}
return false;
}
public static function updateSettings($settings)
{
# only allow if usersettings already exists (setup has been done)
$userSettings = self::getUserSettings();
if($userSettings)
{
# whitelist settings that can be stored in usersettings (values are not relevant here, only keys)
$allowedUserSettings = ['displayErrorDetails' => true,
'title' => true,
'copyright' => true,
'language' => true,
'langattr' => true,
'startpage' => true,
'author' => true,
'year' => true,
'access' => true,
'pageaccess' => true,
'hrdelimiter' => true,
'restrictionnotice' => true,
'wraprestrictionnotice' => true,
'headlineanchors' => true,
'theme' => true,
'editor' => true,
'formats' => true,
'setup' => true,
'welcome' => true,
'images' => true,
'live' => true,
'width' => true,
'height' => true,
'plugins' => true,
'themes' => true,
'latestVersion' => true,
'logo' => true,
'favicon' => true,
'twigcache' => true,
'proxy' => true,
'trustedproxies' => true,
'headersoff' => true,
'urlschemes' => true,
'svg' => true,
'recoverpw' => true,
'recoversubject' => true,
'recovermessage' => true,
'recoverfrom' => true,
'securitylog' => true,
'oldslug' => true,
'refreshcache' => true,
'pingsitemap' => true,
];
# cleanup the existing usersettings
$userSettings = array_intersect_key($userSettings, $allowedUserSettings);
# cleanup the new settings passed as an argument
$settings = array_intersect_key($settings, $allowedUserSettings);
# merge usersettings with new settings
$settings = array_merge($userSettings, $settings);
# write settings to yaml
$yaml = new Models\WriteYaml();
$yaml->updateYaml('settings', 'settings.yaml', $settings);
}
}
}

View File

@@ -0,0 +1 @@
404 from author

View File

@@ -0,0 +1,5 @@
<h1>Home from Author</h1>
<h2>{{ title }}</h2>
<p>{{ description}}</p>

View File

@@ -0,0 +1,56 @@
{% block title %}Login{% endblock %}
{% block content %}
<div class="setupWrapper">
<div class="setupContent">
</div>
<div class="authformWrapper">
<form method="POST" action="{{ url_for("auth.login") }}" autocomplete="off">
<fieldset class="auth">
<div class="formElement{{ errors.username ? ' errors' : '' }}">
<input type="text" name="username" value="{{ old.username }}" required>
{% if errors.signup_username %}
<span class="error">{{ errors.username | first }}</span>
{% endif %}
</div>
<div class="formElement{{ errors.password ? ' errors' : '' }}">
<input type="password" name="password" required autoomplete="off">
{% if errors.password %}
<span class="error">{{ errors.password | first }}</span>
{% endif %}
</div>
<div class="personal-mail">
<label>Personal Mail</label>
<input type="text" name="personal-honey-mail">
</div>
</fieldset>
<div class="loginarea" id="loginarea">
{{ csrf() | raw }}
</div>
</form>
</div>
{% if settings.recoverpw %}
{% else %}
<div class="setupContent">
</div>
{% endif %}
</div>
<footer></footer>
{% endblock %}

View File

@@ -0,0 +1,55 @@
<?php
/*
use Typemill\Controllers\ControllerAuthorArticleApi;
use Typemill\Controllers\ControllerAuthorBlockApi;
use Typemill\Controllers\ControllerAuthorMetaApi;
use Typemill\Controllers\ControllerAuthorMediaApi;
use Typemill\Controllers\ControllerSettings;
use Typemill\Middleware\RestrictApiAccess;
$app->get('/api/v1/themes', ControllerSettings::class . ':getThemeSettings')->setName('api.themes')->add(new RestrictApiAccess($container['router']));
$app->delete('/api/v1/clearcache', ControllerSettings::class . ':clearCache')->setName('api.clearcache')->add(new RestrictApiAccess($container['router']));
$app->get('/api/v1/users/getbynames', ControllerSettings::class . ':getUsersByNames')->setName('api.usersbynames')->add(new RestrictApiAccess($container['router']));
$app->get('/api/v1/users/getbyemail', ControllerSettings::class . ':getUsersByEmail')->setName('api.usersbyemail')->add(new RestrictApiAccess($container['router']));
$app->get('/api/v1/users/getbyrole', ControllerSettings::class . ':getUsersByRole')->setName('api.usersbyrole')->add(new RestrictApiAccess($container['router']));
$app->post('/api/v1/article/markdown', ControllerAuthorArticleApi::class . ':getArticleMarkdown')->setName('api.article.markdown')->add(new RestrictApiAccess($container['router']));
$app->post('/api/v1/article/html', ControllerAuthorArticleApi::class . ':getArticleHtml')->setName('api.article.html')->add(new RestrictApiAccess($container['router']));
$app->post('/api/v1/article/publish', ControllerAuthorArticleApi::class . ':publishArticle')->setName('api.article.publish')->add(new RestrictApiAccess($container['router']));
$app->delete('/api/v1/article/unpublish', ControllerAuthorArticleApi::class . ':unpublishArticle')->setName('api.article.unpublish')->add(new RestrictApiAccess($container['router']));
$app->delete('/api/v1/article/discard', ControllerAuthorArticleApi::class . ':discardArticleChanges')->setName('api.article.discard')->add(new RestrictApiAccess($container['router']));
$app->post('/api/v1/article/rename', ControllerAuthorArticleApi::class . ':renameArticle')->setName('api.article.rename')->add(new RestrictApiAccess($container['router']));
$app->post('/api/v1/article/sort', ControllerAuthorArticleApi::class . ':sortArticle')->setName('api.article.sort')->add(new RestrictApiAccess($container['router']));
$app->post('/api/v1/article', ControllerAuthorArticleApi::class . ':createArticle')->setName('api.article.create')->add(new RestrictApiAccess($container['router']));
$app->put('/api/v1/article', ControllerAuthorArticleApi::class . ':updateArticle')->setName('api.article.update')->add(new RestrictApiAccess($container['router']));
$app->delete('/api/v1/article', ControllerAuthorArticleApi::class . ':deleteArticle')->setName('api.article.delete')->add(new RestrictApiAccess($container['router']));
$app->post('/api/v1/baseitem', ControllerAuthorArticleApi::class . ':createBaseItem')->setName('api.baseitem.create')->add(new RestrictApiAccess($container['router']));
$app->get('/api/v1/navigation', ControllerAuthorArticleApi::class . ':getNavigation')->setName('api.navigation.get')->add(new RestrictApiAccess($container['router']));
$app->post('/api/v1/post', ControllerAuthorArticleApi::class . ':createPost')->setName('api.post.create')->add(new RestrictApiAccess($container['router']));
$app->get('/api/v1/metadefinitions', ControllerAuthorMetaApi::class . ':getMetaDefinitions')->setName('api.metadefinitions.get')->add(new RestrictApiAccess($container['router']));
$app->get('/api/v1/article/metaobject', ControllerAuthorMetaApi::class . ':getArticleMetaobject')->setName('api.articlemetaobject.get')->add(new RestrictApiAccess($container['router']));
$app->get('/api/v1/article/metadata', ControllerAuthorMetaApi::class . ':getArticleMeta')->setName('api.articlemeta.get')->add(new RestrictApiAccess($container['router']));
$app->post('/api/v1/article/metadata', ControllerAuthorMetaApi::class . ':updateArticleMeta')->setName('api.articlemeta.update')->add(new RestrictApiAccess($container['router']));
$app->post('/api/v1/block', ControllerAuthorBlockApi::class . ':addBlock')->setName('api.block.add')->add(new RestrictApiAccess($container['router']));
$app->put('/api/v1/block', ControllerAuthorBlockApi::class . ':updateBlock')->setName('api.block.update')->add(new RestrictApiAccess($container['router']));
$app->delete('/api/v1/block', ControllerAuthorBlockApi::class . ':deleteBlock')->setName('api.block.delete')->add(new RestrictApiAccess($container['router']));
$app->put('/api/v1/moveblock', ControllerAuthorBlockApi::class . ':moveBlock')->setName('api.block.move')->add(new RestrictApiAccess($container['router']));
$app->get('/api/v1/shortcodedata', ControllerAuthorBlockApi::class . ':getShortcodeData')->setName('api.shortcodedata.get')->add(new RestrictApiAccess($container['router']));
$app->post('/api/v1/video', ControllerAuthorMediaApi::class . ':saveVideoImage')->setName('api.video.save')->add(new RestrictApiAccess($container['router']));
$app->get('/api/v1/medialib/images', ControllerAuthorMediaApi::class . ':getMediaLibImages')->setName('api.medialibimg.get')->add(new RestrictApiAccess($container['router']));
$app->get('/api/v1/medialib/files', ControllerAuthorMediaApi::class . ':getMediaLibFiles')->setName('api.medialibfiles.get')->add(new RestrictApiAccess($container['router']));
$app->get('/api/v1/image', ControllerAuthorMediaApi::class . ':getImage')->setName('api.image.get')->add(new RestrictApiAccess($container['router']));
$app->post('/api/v1/image', ControllerAuthorMediaApi::class . ':createImage')->setName('api.image.create')->add(new RestrictApiAccess($container['router']));
$app->put('/api/v1/image', ControllerAuthorMediaApi::class . ':publishImage')->setName('api.image.publish')->add(new RestrictApiAccess($container['router']));
$app->delete('/api/v1/image', ControllerAuthorMediaApi::class . ':deleteImage')->setName('api.image.delete')->add(new RestrictApiAccess($container['router']));
$app->get('/api/v1/filerestrictions', ControllerAuthorMediaApi::class . ':getFileRestrictions')->setName('api.file.getrestrictions')->add(new RestrictApiAccess($container['router']));
$app->post('/api/v1/filerestrictions', ControllerAuthorMediaApi::class . ':updateFileRestrictions')->setName('api.file.updaterestrictions')->add(new RestrictApiAccess($container['router']));
$app->get('/api/v1/file', ControllerAuthorMediaApi::class . ':getFile')->setName('api.file.get')->add(new RestrictApiAccess($container['router']));
$app->post('/api/v1/file', ControllerAuthorMediaApi::class . ':uploadFile')->setName('api.file.upload')->add(new RestrictApiAccess($container['router']));
$app->put('/api/v1/file', ControllerAuthorMediaApi::class . ':publishFile')->setName('api.file.publish')->add(new RestrictApiAccess($container['router']));
$app->delete('/api/v1/file', ControllerAuthorMediaApi::class . ':deleteFile')->setName('api.file.delete')->add(new RestrictApiAccess($container['router']));
*/

View File

@@ -0,0 +1,112 @@
<?php
use Typemill\Controllers\ControllerWebFrontend;
use Typemill\Controllers\ControllerWebLogin;
use Typemill\Middleware\RedirectIfUnauthenticated;
use Typemill\Middleware\RedirectIfAuthenticated;
use Slim\Views\TwigMiddleware;
$app->get('/tm/login', ControllerWebLogin::class . ':show')->setName('auth.show')
->add(new RedirectIfAuthenticated($container->get('routeParser'), $container->get('settings')))
->add(TwigMiddleware::createFromContainer($app));
#$app->post('/tm/login', ControllerWebLogin::class . ':login')->setName('auth.login')->add(new RedirectIfAuthenticated($container['router'], $container['settings']));
$app->get('/[{params:.*}]', ControllerWebFrontend::class . ':index')->setName('home');
/*
use Typemill\Controllers\ControllerAuthorEditor;
use Typemill\Controllers\ControllerSettings;
use Typemill\Controllers\ControllerDownload;
use Typemill\Controllers\ControllerFrontendForms;
use Typemill\Controllers\ControllerFrontendAuth;
use Typemill\Controllers\ControllerFrontendSetup;
use Typemill\Middleware\RedirectIfNoAdmin;
use Typemill\Middleware\accessMiddleware;
if($settings['settings']['setup'])
{
$app->get('/setup', ControllerFrontendSetup::class . ':show')->setName('setup.show');
$app->post('/setup', ControllerFrontendSetup::class . ':create')->setName('setup.create');
}
else
{
$app->get('/setup', ControllerFrontendAuth::class . ':redirect');
}
if($settings['settings']['welcome'])
{
$app->get('/setup/welcome', ControllerFrontendSetup::class . ':welcome')->setName('setup.welcome')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
}
else
{
$app->get('/setup/welcome', ControllerFrontendAuth::class . ':redirect')->setName('setup.welcome');
}
$app->post('/tm/formpost', ControllerFrontendForms::class . ':savePublicForm')->setName('form.save');
$app->get('/tm', ControllerFrontendAuth::class . ':redirect');
$app->get('/tm/login', ControllerFrontendAuth::class . ':show')->setName('auth.show')->add(new RedirectIfAuthenticated($container['router'], $container['settings']));
$app->post('/tm/login', ControllerFrontendAuth::class . ':login')->setName('auth.login')->add(new RedirectIfAuthenticated($container['router'], $container['settings']));
$app->get('/tm/logout', ControllerFrontendAuth::class . ':logout')->setName('auth.logout')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
if(isset($settings['settings']['recoverpw']) && $settings['settings']['recoverpw'])
{
$app->get('/tm/recoverpw', ControllerFrontendAuth::class . ':showrecoverpassword')->setName('auth.recoverpwshow')->add(new RedirectIfAuthenticated($container['router'], $container['settings']));
$app->post('/tm/recoverpw', ControllerFrontendAuth::class . ':recoverpassword')->setName('auth.recoverpw')->add(new RedirectIfAuthenticated($container['router'], $container['settings']));
$app->get('/tm/recoverpwnew', ControllerFrontendAuth::class . ':showrecoverpasswordnew')->setName('auth.recoverpwshownew')->add(new RedirectIfAuthenticated($container['router'], $container['settings']));
$app->post('/tm/recoverpwnew', ControllerFrontendAuth::class . ':createrecoverpasswordnew')->setName('auth.recoverpwnew')->add(new RedirectIfAuthenticated($container['router'], $container['settings']));
}
$app->get('/tm/settings', ControllerSettings::class . ':showSettings')->setName('settings.show')->add(new accessMiddleware($container['router'], $container['acl'], 'system', 'view'));
$app->post('/tm/settings', ControllerSettings::class . ':saveSettings')->setName('settings.save')->add(new accessMiddleware($container['router'], $container['acl'], 'system', 'update'));
$app->get('/tm/themes', ControllerSettings::class . ':showThemes')->setName('themes.show')->add(new accessMiddleware($container['router'], $container['acl'], 'system', 'view'));
$app->post('/tm/themes', ControllerSettings::class . ':saveThemes')->setName('themes.save')->add(new accessMiddleware($container['router'], $container['acl'], 'system', 'update'));
$app->get('/tm/plugins', ControllerSettings::class . ':showPlugins')->setName('plugins.show')->add(new accessMiddleware($container['router'], $container['acl'], 'system', 'view'));
$app->post('/tm/plugins', ControllerSettings::class . ':savePlugins')->setName('plugins.save')->add(new accessMiddleware($container['router'], $container['acl'], 'system', 'update'));
$app->get('/tm/account', ControllerSettings::class . ':showAccount')->setName('user.account')->add(new accessMiddleware($container['router'], $container['acl'], 'user', 'view'));
$app->get('/tm/user/new', ControllerSettings::class . ':newUser')->setName('user.new')->add(new accessMiddleware($container['router'], $container['acl'], 'user', 'create'));
$app->post('/tm/user/create', ControllerSettings::class . ':createUser')->setName('user.create')->add(new accessMiddleware($container['router'], $container['acl'], 'user', 'create'));
$app->post('/tm/user/update', ControllerSettings::class . ':updateUser')->setName('user.update')->add(new accessMiddleware($container['router'], $container['acl'], 'user', 'update'));
$app->post('/tm/user/delete', ControllerSettings::class . ':deleteUser')->setName('user.delete')->add(new accessMiddleware($container['router'], $container['acl'], 'user', 'delete'));
$app->get('/tm/user/{username}', ControllerSettings::class . ':showUser')->setName('user.show')->add(new accessMiddleware($container['router'], $container['acl'], 'user', 'view'));
$app->get('/tm/users', ControllerSettings::class . ':listUser')->setName('user.list')->add(new accessMiddleware($container['router'], $container['acl'], 'userlist', 'view'));
$app->get('/tm/content/raw[/{params:.*}]', ControllerAuthorEditor::class . ':showContent')->setName('content.raw')->add(new accessMiddleware($container['router'], $container['acl'], 'content', 'view'));
$app->get('/tm/content/visual[/{params:.*}]', ControllerAuthorEditor::class . ':showBlox')->setName('content.visual')->add(new accessMiddleware($container['router'], $container['acl'], 'content', 'view'));
$app->get('/tm/content[/{params:.*}]', ControllerAuthorEditor::class . ':showEmpty')->setName('content.empty')->add(new accessMiddleware($container['router'], $container['acl'], 'content', 'view'));
$app->get('/media/files[/{params:.*}]', ControllerDownload::class . ':download')->setName('download.file');
foreach($routes 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;
if(isset($pluginRoute['name']))
{
$app->{$method}($route, $class)->setName($pluginRoute['name'])->add(new accessMiddleware($container['router'], $container['acl'], $resource, $privilege));
}
else
{
$app->{$method}($route, $class)->add(new accessMiddleware($container['router'], $container['acl'], $resource, $privilege));
}
}
if($settings['settings']['setup'])
{
$app->get('/[{params:.*}]', ControllerFrontendSetup::class . ':redirect');
}
elseif(isset($settings['settings']['access']) && $settings['settings']['access'] != '')
{
$app->get('/[{params:.*}]', ControllerFrontendWebsite::class . ':index')->setName('home')->add(new accessMiddleware($container['router'], $container['acl'], 'user', 'view'));
}
else
{
$app->get('/[{params:.*}]', ControllerFrontendWebsite::class . ':index')->setName('home');
}
*/

View File

@@ -0,0 +1,36 @@
version: '2.0.0'
setup: true
welcome: true
title: 'Typemill'
author: 'Unknown'
copyright: 'Copyright'
language: 'en'
langattr: 'en'
themeFolder: 'themes'
settingsFolder: 'settings'
contentFolder: 'content'
authorFolder: '/system/typemill/author'
adminSegment: 'tm'
editor: 'visual'
formats:
- 'markdown'
- 'headline'
- 'ulist'
- 'olist'
- 'table'
- 'quote'
- 'notice'
- 'image'
- 'video'
- 'file'
- 'toc'
- 'hr'
- 'definition'
- 'code'
- 'shortcode'
images:
live:
width: 820
thumbs:
width: 250
height: 150

View File

@@ -0,0 +1,104 @@
meta:
fields:
navtitle:
type: text
label: Navigation Title
class: large
maxlength: 60
fieldsetcontent:
type: fieldset
legend: Meta-Content
fields:
title:
type: text
label: Meta title
maxlength: 100
class: large
description:
type: textarea
label: Meta description
size: 160
class: large
description: If not filled, the description is extracted from content.
heroimage:
type: image
label: Hero Image
description: Maximum size for an image is 5 MB. Hero images are not supported by all themes.
heroimagealt:
type: text
label: Alternative Text for the hero image
fieldsetvisibility:
type: fieldset
legend: Visibility
fields:
hide:
type: checkbox
label: Hide
checkboxlabel: Hide page from navigation
class: medium
noindex:
type: checkbox
label: Noindex
checkboxlabel: Add noindex tag and exclude from sitemap
class: medium
fieldsetauthor:
type: fieldset
legend: Author
fields:
owner:
type: text
label: owner (username)
class: medium
description: Has edit rights for this article.
author:
type: text
label: author
class: medium
description: Can be used for author line in frontend.
fieldsetrights:
type: fieldset
legend: Access & Rights
fields:
allowedrole:
type: select
label: For access the user must have this minimum role
class: medium
dataset: userroles
options:
description: Select the lowest userrole. Higher roles will have access too.
alloweduser:
type: text
label: Only the following users have access
class: medium
description: Add one or more usernames separated with comma.
fieldsetpubdate:
type: fieldset
legend: Article Date
fields:
manualdate:
type: date
label: Manual date
modified:
type: date
label: Last modified live (readonly)
readonly: readonly
class: medium
description: Used as fallback when no manual date is set.
created:
type: date
label: Created at (read only)
readonly: readonly
class: medium
time:
type: text
readonly: readonly
hidden: true
class: hidden
pattern: '[0-9][0-9]-[0-9][0-9]-[0-9][0-9]'
contains:
type: radio
label: This folder contains
class: medium
options:
pages: PAGES (sort in navigation with drag & drop)
posts: POSTS (sorted by publish date, for news or blogs)

View File

@@ -0,0 +1,32 @@
member:
name: member
inherits: NULL
permissions:
user:
- 'view'
- 'update'
- 'delete'
author:
name: author
inherits: member
permissions:
mycontent:
- 'view'
- 'create'
- 'update'
content:
- 'view'
editor:
name: editor
inherits: author
permissions:
mycontent:
- 'delete'
- 'publish'
- 'unpublish'
content:
- 'create'
- 'update'
- 'delete'
- 'publish'
- 'unpublish'

View File

@@ -0,0 +1,5 @@
- 'content'
- 'mycontent'
- 'user'
- 'userlist'
- 'system'

View File

@@ -0,0 +1,12 @@
basics:
fields:
media:
fields:
writing:
fields:
access:
fields:
password:
fields:
developer:
fields:

View File

@@ -1,82 +1,428 @@
<?php
# included from /public/index.php
use DI\Container;
use Psr\Http\Message\ResponseInterface as Response;
#use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Exception\HttpNotFoundException;
use Slim\Middleware\ErrorMiddleware;
use Slim\Psr7\Response as NewResponse;
use Slim\Psr7\Response as Response;
use Slim\Factory\AppFactory;
use Slim\Views\Twig;
use Slim\Views\TwigMiddleware;
use Slim\Csrf\Guard;
use Slim\Flash\Messages;
use Nquire\Middleware\ValidationErrors;
use Nquire\Middleware\FlashMessages;
use Nquire\Middleware\JsonBodyParser;
use Typemill\Events\OnSettingsLoaded;
use Typemill\Events\OnPluginsLoaded;
use Typemill\Events\OnSessionSegmentsLoaded;
use Typemill\Events\OnRolesPermissionsLoaded;
use Typemill\Events\OnResourcesLoaded;
use Typemill\Middleware\JsonBodyParser;
use Typemill\Middleware\CreateSession;
use Typemill\Middleware\TwigView;
use Typemill\Middleware\CsrfProtection;
use Typemill\Middleware\CsrfProtectionToMiddleware;
use Typemill\Middleware\FlashMessages;
#use Typemill\Middleware\ValidationErrors;
require __DIR__ . '/../vendor/autoload.php';
$timer = [];
$timer['start'] = microtime(true);
/****************************
* HIDE ERRORS BY DEFAULT *
* HIDE ERRORS BY DEFAULT *
****************************/
ini_set('display_errors', 0);
ini_set('display_startup_errors', 0);
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
/****************************
* CONTAINER *
* LOAD SETTINGS *
****************************/
$settings = Typemill\Static\Settings::loadSettings($rootpath);
/****************************
* HANDLE DISPLAY ERRORS *
****************************/
if(isset($settings['displayErrorDetails']) && $settings['displayErrorDetails'])
{
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
}
$timer['settings'] = microtime(true);
/****************************
* CREATE CONTAINER *
****************************/
# https://www.slimframework.com/docs/v4/start/upgrade.html#changes-to-container
$container = new Container();
AppFactory::setContainer($container);
$app = AppFactory::create();
$container = $app->getContainer();
$responseFactory = $app->getResponseFactory();
$routeParser = $app->getRouteCollector()->getRouteParser();
# add route parser to conatiner to use named routes in controller
$container->set('routeParser', $routeParser);
$timer['container'] = microtime(true);
/****************************
* BASE PATH *
* BASE URL AND ROOT PATH *
****************************/
# basepath must always be set in slim 4
$basepath = preg_replace('/(.*)\/.*/', '$1', $_SERVER['SCRIPT_NAME']);
$container->set('basePath', $basepath);
$uriFactory = new \Slim\Psr7\Factory\UriFactory();
$uri = $uriFactory->createFromGlobals($_SERVER);
$uripath = $uri->getPath();
$basepath = preg_replace('/(.*)\/.*/', '$1', $_SERVER['SCRIPT_NAME']);
$routepath = str_replace($basepath, '', $uripath);
# in slim 4 you alsways have to set application basepath
$app->setBasePath($basepath);
die('hello Typemill V2');
$container->set('basePath', $basepath);
$container->set('rootPath', $rootpath);
$container->set('uriPath', $uripath);
$container->set('routePath', $routepath);
/****************************
* SETTINGS *
* CREATE EVENT DISPATCHER *
****************************/
$settings = require __DIR__ . '/settings/settings.php';
$container->set('settings', function() use ($settings)
$dispatcher = new \Symfony\Component\EventDispatcher\EventDispatcher();
/****************************
* LOAD & UPDATE PLUGINS *
****************************/
$plugins = Typemill\Static\Plugins::loadPlugins($rootpath);
$pluginSettings = $routes = $middleware = [];
# if there are less plugins in the scan than in the settings, then a plugin has been removed
if(count($plugins) < count($settings['plugins']))
{
return $settings;
});
$updateSettings = true;
}
# create a session
ini_set('session.cookie_httponly', 1 );
ini_set('session.use_strict_mode', 1);
ini_set('session.cookie_samesite', 'lax');
if(isset($_SERVER['HTTPS']))
foreach($plugins as $plugin)
{
ini_set('session.cookie_secure', 1);
session_name('__Secure-nquire-session');
$pluginName = $plugin['name'];
$className = $plugin['className'];
# if plugin is not in the settings already
if(!isset($settings['plugins'][$pluginName]))
{
# it is a new plugin. Add it and set active to false
$settings['plugins'][$pluginName] = ['active' => false];
# and set flag to refresh the settings
$updateSettings = true;
}
# if the plugin is activated, add routes/middleware and add plugin as event subscriber
if($settings['plugins'][$pluginName]['active'])
{
$routes = Typemill\Static\Plugins::getNewRoutes($className, $routes);
$middleware = Typemill\Static\Plugins::getNewMiddleware($className, $middleware);
$dispatcher->addSubscriber(new $className($container));
}
}
# if plugins have been added or removed
if(isset($updateSettings))
{
# update stored settings file
Typemill\settings::updateSettings($settings);
}
# add final settings to the container
$container->set('settings', function() use ($settings){ return $settings; });
# dispatch the event onPluginsLoaded
$dispatcher->dispatch(new OnPluginsLoaded($plugins), 'onPluginsLoaded');
# dispatch settings event and get all setting-updates from plugins
$dispatcher->dispatch(new OnSettingsLoaded($settings), 'onSettingsLoaded')->getData();
$timer['plugins'] = microtime(true);
/****************************
* LOAD ROLES & PERMISSIONS *
****************************/
# load roles and permissions
$rolesAndPermissions = Typemill\Static\Permissions::loadRolesAndPermissions($settings['defaultSettingsPath']);
# dispatch roles so plugins can enhance them
$rolesAndPermissions = $dispatcher->dispatch(new OnRolesPermissionsLoaded($rolesAndPermissions), 'onRolesPermissionsLoaded')->getData();
# load resources
$resources = Typemill\Static\Permissions::loadResources($settings['defaultSettingsPath']);
# dispatch roles so plugins can enhance them
$resources = $dispatcher->dispatch(new OnResourcesLoaded($resources), 'onResourcesLoaded')->getData();
# create acl-object
$acl = Typemill\Static\Permissions::createAcl($rolesAndPermissions, $resources);
# add acl to container
$container->set('acl', function() use ($acl){ return $acl; });
$timer['permissions'] = microtime(true);
/****************************
* SEGMENTS WITH SESSION *
****************************/
# if website is restricted to registered user
if( ( isset($settings['access']) && $settings['access'] ) || ( isset($settings['pageaccess']) && $settings['pageaccess'] ) )
{
# activate session for all routes
$session_segments = [$routepath];
}
else
{
session_name('nquire-session');
$session_segments = ['setup', 'tm/', 'api/'];
# let plugins add own segments for session, eg. to enable csrf for forms
$client_segments = $dispatcher->dispatch(new OnSessionSegmentsLoaded([]), 'onSessionSegmentsLoaded')->getData();
$session_segments = array_merge($session_segments, $client_segments);
}
session_start();
# start session
# Typemill\Static\Session::startSessionForSegments($session_segments, $routepath);
$timer['session segments'] = microtime(true);
/****************************
* OTHER CONTAINER ITEMS *
****************************/
# Register Middleware On Container
if(isset($_SESSION)){
$container->set('csrf', function () use ($responseFactory){ return new Guard($responseFactory); });
}
# dispatcher to container
$container->set('dispatcher', function() use ($dispatcher){ return $dispatcher; });
# asset function for plugins
$container->set('assets', function() use ($basepath){ return new \Typemill\Assets($basepath); });
$timer['other container'] = microtime(true);
/****************************
* MIDDLEWARE *
****************************/
# Add Validation Errors Middleware
# $app->add(new ValidationErrors($container->get('view')));
# Add Flash Messages Middleware
# $app->add(new FlashMessages($container->get('view')));
# Add Twig-View Middleware
# $app->add(TwigMiddleware::createFromContainer($app));
# if session add flash messages
$app->add(new FlashMessages($container));
/*
if(isset($_SESSION))
{
echo '<br>add csrf';
# Register Middleware To Be Executed On All Routes
$app->add('csrf');
}
*/
# $container->set('csrf', null);
# $app->add('csrf');
# $app->add(new CsrfProtectionToMiddleware($container));
$app->add(function($request, $handler) use ($container){
$response = $handler->handle($request);
$existingContent = (string) $response->getBody();
$response = new Response();
$response->getBody()->write('BEFORE' . $existingContent);
return $response;
});
# if session add csrf protection
$app->add(new CsrfProtection($container, $responseFactory));
# add session
$app->add(new CreateSession($session_segments, ltrim($routepath, '/') ));
# check if user : apikey
# if yes
# validate it as normal password
# do not create sessions
# set authentication to true somehow
# add JsonBodyParser Middleware
$app->add(new JsonBodyParser());
# routing middleware earlier than error middleware so errors are shown
$app->addRoutingMiddleware();
# error middleware
$errorMiddleware = new ErrorMiddleware(
$app->getCallableResolver(),
$app->getResponseFactory(),
true,
false,
false
);
# Set the Not Found Handler
$errorMiddleware->setErrorHandler(HttpNotFoundException::class, function ($request, $exception) use ($container) {
$response = new NewResponse();
return $container->get('view')->render($response->withStatus(404), '404.twig');
});
$app->add($errorMiddleware);
$timer['middleware'] = microtime(true);
/************************
* ADD ROUTES *
************************/
require __DIR__ . '/routes/api.php';
require __DIR__ . '/routes/web.php';
$timer['routes'] = microtime(true);
/************************
* RUN APP *
************************/
$app->run();
$timer['run'] = microtime(true);
Typemill\Static\Helpers::printTimer($timer);
die('After app run');
/********************************
* MOVE TO MIDDLEWARE NEXT TIME *
********************************/
print_r($session_segments);
$trimmedRoute = ltrim($routepath,'/');
foreach($session_segments as $segment)
{
$test = substr( $trimmedRoute, 0, strlen($segment) );
echo '<br>' . $test . ' = ' . $segment;
continue;
if(substr( $uri->getPath(), 0, strlen($segment) ) === ltrim($segment, '/'))
{
// configure session
ini_set('session.cookie_httponly', 1 );
ini_set('session.use_strict_mode', 1);
ini_set('session.cookie_samesite', 'lax');
if($uri->getScheme() == 'https')
{
ini_set('session.cookie_secure', 1);
session_name('__Secure-typemill-session');
}
else
{
session_name('typemill-session');
}
// add csrf-protection
$container['csrf'] = function ($c)
{
$guard = new \Slim\Csrf\Guard();
$guard->setPersistentTokenMode(true);
$guard->setfailurecallable(function ($request, $response, $next)
{
$request = $request->withattribute("csrf_result", false);
return $next($request, $response);
});
return $guard;
};
// add flash to container
$container['flash'] = function ()
{
return new \Slim\Flash\Messages();
};
// start session
session_start();
}
}
Typemill\Static\Helpers::printTimer($timer);
die('Typemill 2 is comming');
# add flash messsages
$container->set('flash', function(){

View File

@@ -1,3 +1,5 @@
<h1>HOME from theme</h1>
{% set home = {
"landingpageIntro" : settings.themes.cyanine.landingpageIntro,
"landingpageInfo" : settings.themes.cyanine.landingpageInfo,