mirror of
https://github.com/typemill/typemill.git
synced 2025-08-06 22:26:32 +02:00
refactored navigation model
This commit is contained in:
@@ -90,7 +90,6 @@ class ControllerData extends Controller
|
||||
return $allowedsystemnavi;
|
||||
}
|
||||
|
||||
|
||||
protected function getThemeDetails()
|
||||
{
|
||||
$themes = $this->getThemes();
|
||||
@@ -239,4 +238,157 @@ class ControllerData extends Controller
|
||||
|
||||
return $userfields;
|
||||
}
|
||||
|
||||
|
||||
|
||||
##########################################################################################
|
||||
# GET STUFF FOR EDITOR AREA
|
||||
##########################################################################################
|
||||
|
||||
# reads the cached structure with published and non-published pages for the author
|
||||
# setStructureDraft
|
||||
protected function getStructureForAuthors($userrole, $username)
|
||||
{
|
||||
# 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
|
||||
# setFreshStrutureDraft
|
||||
protected function createNewStructureForAuthors()
|
||||
{
|
||||
# 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
|
||||
# setStrutureLive
|
||||
protected function getStructureForReaders()
|
||||
{
|
||||
# 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;
|
||||
}
|
||||
}
|
210
system/typemill/Controllers/ControllerWebAuthor.php
Normal file
210
system/typemill/Controllers/ControllerWebAuthor.php
Normal file
@@ -0,0 +1,210 @@
|
||||
<?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\Extensions\ParsedownExtension;
|
||||
# use Typemill\Models\StorageWrapper;
|
||||
# use Typemill\Models\User;
|
||||
# use Typemill\Models\License;
|
||||
|
||||
use Typemill\Models\Navigation;
|
||||
|
||||
class ControllerWebAuthor extends Controller
|
||||
{
|
||||
public function showBlox(Request $request, Response $response, $args)
|
||||
{
|
||||
# get url for requested page
|
||||
$url = isset($args['route']) ? '/' . $args['route'] : '/';
|
||||
$urlinfo = $this->c->get('urlinfo');
|
||||
$fullUrl = $urlinfo['baseurl'] . $url;
|
||||
$langattr = $this->settings['langattr'];
|
||||
|
||||
$navigation = new Navigation();
|
||||
|
||||
$extendedNavigation = $navigation->getExtendedNavigation($urlinfo, $langattr);
|
||||
$pageinfo = $extendedNavigation[$url] ?? false;
|
||||
|
||||
if(!$pageinfo)
|
||||
{
|
||||
return $this->c->get('view')->render($response->withStatus(404), '404.twig', [
|
||||
'title' => 'Typemill Author Area',
|
||||
'description' => 'Typemill Version 2 wird noch besser als Version 1.'
|
||||
]);
|
||||
}
|
||||
|
||||
# extend : $request->getAttribute('c_userrole')
|
||||
$draftNavigation = $navigation->getDraftNavigation($urlinfo, $langattr);
|
||||
|
||||
$item = $navigation->getItemWithKeyPath($draftNavigation, explode(".", $pageinfo['keyPath']));
|
||||
|
||||
$mainNavigation = $navigation->getMainNavigation($request->getAttribute('c_userrole'), $this->c->get('acl'), $urlinfo, $this->settings['editor']);
|
||||
|
||||
return $this->c->get('view')->render($response, 'content/blox-editor.twig', [
|
||||
'settings' => $this->settings,
|
||||
'mainnavi' => $mainNavigation,
|
||||
'jsdata' => [
|
||||
'settings' => $this->settings,
|
||||
'navigation' => $draftNavigation,
|
||||
'item' => $item,
|
||||
'urlinfo' => $urlinfo
|
||||
]
|
||||
]);
|
||||
|
||||
echo '<pre>';
|
||||
print_r($draftNavigation);
|
||||
die();
|
||||
|
||||
|
||||
|
||||
# set structure
|
||||
if(!$this->setStructureDraft()){ return $this->renderIntern404($response, array( 'navigation' => true, 'content' => $this->errors )); }
|
||||
|
||||
# set information for homepage
|
||||
$this->setHomepage($args);
|
||||
|
||||
# set item
|
||||
if(!$this->setItem()){ return $this->renderIntern404($response, array( 'navigation' => $this->structureDraft, 'settings' => $this->settings, 'content' => $this->errors )); }
|
||||
|
||||
# we have to check ownership here to use it for permission-check in templates
|
||||
$this->checkContentOwnership();
|
||||
|
||||
# set the status for published and drafted
|
||||
$this->setPublishStatus();
|
||||
|
||||
# set path
|
||||
$this->setItemPath($this->item->fileType);
|
||||
|
||||
# add the modified date for the file
|
||||
$this->item->modified = ($this->item->published OR $this->item->drafted) ? filemtime($this->settings['contentFolder'] . $this->path) : false;
|
||||
|
||||
# read content from file
|
||||
if(!$this->setContent()){ return $this->renderIntern404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); }
|
||||
|
||||
$content = $this->content;
|
||||
|
||||
if($content == '')
|
||||
{
|
||||
$content = [];
|
||||
}
|
||||
|
||||
# initialize parsedown extension
|
||||
$parsedown = new ParsedownExtension($this->uri->getBaseUrl());
|
||||
|
||||
# to fix footnote-logic in parsedown, set visual mode to true
|
||||
$parsedown->setVisualMode();
|
||||
|
||||
# if content is not an array, then transform it
|
||||
if(!is_array($content))
|
||||
{
|
||||
# turn markdown into an array of markdown-blocks
|
||||
$content = $parsedown->markdownToArrayBlocks($content);
|
||||
}
|
||||
|
||||
# needed for ToC links
|
||||
$relurl = '/tm/content/' . $this->settings['editor'] . '/' . $this->item->urlRel;
|
||||
|
||||
foreach($content as $key => $block)
|
||||
{
|
||||
/* parse markdown-file to content-array */
|
||||
$contentArray = $parsedown->text($block);
|
||||
|
||||
/* parse markdown-content-array to content-string */
|
||||
$content[$key] = $parsedown->markup($contentArray);
|
||||
}
|
||||
|
||||
# extract title and delete from content array, array will start at 1 after that.
|
||||
$title = '# add title';
|
||||
if(isset($content[0]))
|
||||
{
|
||||
$title = $content[0];
|
||||
unset($content[0]);
|
||||
}
|
||||
|
||||
return $this->renderIntern($response, 'editor/editor-blox.twig', array(
|
||||
'acl' => $this->c->acl,
|
||||
'mycontent' => $this->mycontent,
|
||||
'navigation' => $this->structureDraft,
|
||||
'homepage' => $this->homepage,
|
||||
'title' => $title,
|
||||
'content' => $content,
|
||||
'item' => $this->item,
|
||||
'settings' => $this->settings
|
||||
));
|
||||
}
|
||||
|
||||
public function showContent(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
# $this->uri = $request->getUri()->withUserInfo('');
|
||||
# $this->params = isset($args['params']) ? ['url' => $this->uri->getBasePath() . '/' . $args['params']] : ['url' => $this->uri->getBasePath()];
|
||||
|
||||
# set structure
|
||||
if(!$this->setStructureDraft()){ return $this->renderIntern404($response, array( 'navigation' => true, 'content' => $this->errors )); }
|
||||
|
||||
# set information for homepage
|
||||
$this->setHomepage($args);
|
||||
|
||||
# set item
|
||||
if(!$this->setItem()){ return $this->renderIntern404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); }
|
||||
|
||||
# we have to check ownership here to use it for permission-check in tempates
|
||||
$this->checkContentOwnership();
|
||||
|
||||
# get the breadcrumb (here we need it only to mark the actual item active in navigation)
|
||||
$breadcrumb = isset($this->item->keyPathArray) ? Folder::getBreadcrumb($this->structureDraft, $this->item->keyPathArray) : false;
|
||||
|
||||
# set the status for published and drafted
|
||||
$this->setPublishStatus();
|
||||
|
||||
# set path
|
||||
$this->setItemPath($this->item->fileType);
|
||||
|
||||
# add the modified date for the file
|
||||
$this->item->modified = ($this->item->published OR $this->item->drafted) ? filemtime($this->settings['contentFolder'] . $this->path) : false;
|
||||
|
||||
# read content from file
|
||||
if(!$this->setContent()){ return $this->renderIntern404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); }
|
||||
|
||||
$content = $this->content;
|
||||
$title = false;
|
||||
|
||||
# if content is an array, then it is a draft
|
||||
if(is_array($content))
|
||||
{
|
||||
# transform array to markdown
|
||||
$parsedown = new ParsedownExtension($this->uri->getBaseUrl());
|
||||
$content = $parsedown->arrayBlocksToMarkdown($content);
|
||||
}
|
||||
|
||||
# if there is content
|
||||
if($content != '')
|
||||
{
|
||||
# normalize linebreaks
|
||||
$content = str_replace(array("\r\n", "\r"), "\n", $content);
|
||||
$content = trim($content, "\n");
|
||||
|
||||
# and strip out title
|
||||
if($content[0] == '#')
|
||||
{
|
||||
$contentParts = explode("\n", $content, 2);
|
||||
$title = trim($contentParts[0], "# \t\n\r\0\x0B");
|
||||
$content = trim($contentParts[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->renderIntern($response, 'editor/editor-raw.twig', array(
|
||||
'acl' => $this->c->acl,
|
||||
'mycontent' => $this->mycontent,
|
||||
'navigation' => $this->structureDraft,
|
||||
'homepage' => $this->homepage,
|
||||
'title' => $title,
|
||||
'content' => $content,
|
||||
'item' => $this->item,
|
||||
'settings' => $this->settings
|
||||
));
|
||||
}
|
||||
}
|
652
system/typemill/Models/Folder.php
Normal file
652
system/typemill/Models/Folder.php
Normal file
@@ -0,0 +1,652 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Models;
|
||||
|
||||
use \URLify;
|
||||
|
||||
class Folder
|
||||
{
|
||||
|
||||
/*
|
||||
* scans content of a folder (without recursion)
|
||||
* vars: folder path as string
|
||||
* returns: one-dimensional array with names of folders and files
|
||||
*/
|
||||
public function scanFolderFlat($folderPath)
|
||||
{
|
||||
$folderItems = scandir($folderPath);
|
||||
$folderContent = array();
|
||||
|
||||
foreach ($folderItems as $key => $item)
|
||||
{
|
||||
if (!in_array($item, array(".","..")))
|
||||
{
|
||||
$nameParts = $this->getStringParts($item);
|
||||
$fileType = array_pop($nameParts);
|
||||
|
||||
if($fileType == 'md' OR $fileType == 'txt' )
|
||||
{
|
||||
$folderContent[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $folderContent;
|
||||
}
|
||||
|
||||
/*
|
||||
* scans content of a folder recursively
|
||||
* vars: folder path as string
|
||||
* returns: multi-dimensional array with names of folders and files
|
||||
*/
|
||||
public function scanFolder($folderPath, $draft = false)
|
||||
{
|
||||
$folderItems = scandir($folderPath);
|
||||
$folderContent = array();
|
||||
|
||||
# if it is the live version and if it is a folder that is not published, then do not show the folder and its content.
|
||||
if(!$draft && !in_array('index.md', $folderItems)){ return false; }
|
||||
|
||||
foreach ($folderItems as $key => $item)
|
||||
{
|
||||
if (!in_array($item, array(".","..")) && substr($item, 0, 1) != '.')
|
||||
{
|
||||
if (is_dir($folderPath . DIRECTORY_SEPARATOR . $item))
|
||||
{
|
||||
|
||||
$subFolder = $item;
|
||||
$folderPublished = file_exists($folderPath . DIRECTORY_SEPARATOR . $item . DIRECTORY_SEPARATOR . 'index.md');
|
||||
|
||||
# scan that folder only if it is a draft or if the folder is published (contains index.md)
|
||||
if($draft OR $folderPublished)
|
||||
{
|
||||
$folderContent[$subFolder] = $this->scanFolder($folderPath . DIRECTORY_SEPARATOR . $subFolder, $draft);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$nameParts = $this->getStringParts($item);
|
||||
$fileType = array_pop($nameParts);
|
||||
|
||||
if($fileType == 'md')
|
||||
{
|
||||
$folderContent[] = $item;
|
||||
}
|
||||
|
||||
if($draft === true && $fileType == 'txt')
|
||||
{
|
||||
if(isset($last) && ($last == implode($nameParts)) )
|
||||
{
|
||||
array_pop($folderContent);
|
||||
$item = $item . 'md';
|
||||
}
|
||||
$folderContent[] = $item;
|
||||
}
|
||||
|
||||
/* store the name of the last file */
|
||||
$last = implode($nameParts);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $folderContent;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Transforms array of folder item into an array of item-objects with additional information for each item
|
||||
* vars: multidimensional array with folder- and file-names
|
||||
* returns: array of objects. Each object contains information about an item (file or folder).
|
||||
*/
|
||||
|
||||
public function getFolderContentDetails(array $folderContent, $language, $baseUrl, $fullSlugWithFolder = NULL, $fullSlugWithoutFolder = NULL, $fullPath = NULL, $keyPath = NULL, $chapter = NULL)
|
||||
{
|
||||
$contentDetails = [];
|
||||
$iteration = 0;
|
||||
$chapternr = 1;
|
||||
|
||||
foreach($folderContent as $key => $name)
|
||||
{
|
||||
$item = new \stdClass();
|
||||
|
||||
if(is_array($name))
|
||||
{
|
||||
$nameParts = $this->getStringParts($key);
|
||||
|
||||
$fileType = '';
|
||||
if(in_array('index.md', $name))
|
||||
{
|
||||
$fileType = 'md';
|
||||
$status = 'published';
|
||||
}
|
||||
if(in_array('index.txt', $name))
|
||||
{
|
||||
$fileType = 'txt';
|
||||
$status = 'unpublished';
|
||||
}
|
||||
if(in_array('index.txtmd', $name))
|
||||
{
|
||||
$fileType = 'txt';
|
||||
$status = 'modified';
|
||||
}
|
||||
|
||||
$item->originalName = $key;
|
||||
$item->elementType = 'folder';
|
||||
$item->contains = $this->getFolderContentType($name, $fullPath . DIRECTORY_SEPARATOR . $key . DIRECTORY_SEPARATOR . 'index.yaml');
|
||||
$item->status = $status;
|
||||
$item->fileType = $fileType;
|
||||
$item->order = count($nameParts) > 1 ? array_shift($nameParts) : NULL;
|
||||
$item->name = implode(" ",$nameParts);
|
||||
$item->name = iconv(mb_detect_encoding($item->name, mb_detect_order(), true), "UTF-8", $item->name);
|
||||
$item->slug = implode("-",$nameParts);
|
||||
$item->slug = $this->createSlug($item->slug, $language);
|
||||
$item->path = $fullPath . DIRECTORY_SEPARATOR . $key;
|
||||
$item->pathWithoutType = $fullPath . DIRECTORY_SEPARATOR . $key . DIRECTORY_SEPARATOR . 'index';
|
||||
$item->urlRelWoF = $fullSlugWithoutFolder . '/' . $item->slug;
|
||||
$item->urlRel = $fullSlugWithFolder . '/' . $item->slug;
|
||||
$item->urlAbs = $baseUrl . $fullSlugWithoutFolder . '/' . $item->slug;
|
||||
$item->key = $iteration;
|
||||
$item->keyPath = isset($keyPath) ? $keyPath . '.' . $iteration : $iteration;
|
||||
$item->keyPathArray = explode('.', $item->keyPath);
|
||||
$item->chapter = $chapter ? $chapter . '.' . $chapternr : $chapternr;
|
||||
$item->active = false;
|
||||
$item->activeParent = false;
|
||||
$item->hide = false;
|
||||
|
||||
# sort posts in descending order
|
||||
if($item->contains == "posts")
|
||||
{
|
||||
rsort($name);
|
||||
}
|
||||
|
||||
$item->folderContent = $this->getFolderContentDetails($name, $language, $baseUrl, $item->urlRel, $item->urlRelWoF, $item->path, $item->keyPath, $item->chapter);
|
||||
}
|
||||
elseif($name)
|
||||
{
|
||||
# do not use files in base folder (only folders are allowed)
|
||||
# if(!isset($keyPath)) continue;
|
||||
|
||||
# do not use index files
|
||||
if($name == 'index.md' || $name == 'index.txt' || $name == 'index.txtmd' ) continue;
|
||||
|
||||
$nameParts = $this->getStringParts($name);
|
||||
$fileType = array_pop($nameParts);
|
||||
$nameWithoutType = $this->getNameWithoutType($name);
|
||||
|
||||
if($fileType == 'md')
|
||||
{
|
||||
$status = 'published';
|
||||
}
|
||||
elseif($fileType == 'txt')
|
||||
{
|
||||
$status = 'unpublished';
|
||||
}
|
||||
else
|
||||
{
|
||||
$fileType = 'txt';
|
||||
$status = 'modified';
|
||||
}
|
||||
|
||||
$item->originalName = $name;
|
||||
$item->elementType = 'file';
|
||||
$item->status = $status;
|
||||
$item->fileType = $fileType;
|
||||
$item->order = count($nameParts) > 1 ? array_shift($nameParts) : NULL;
|
||||
$item->name = implode(" ",$nameParts);
|
||||
$item->name = iconv(mb_detect_encoding($item->name, mb_detect_order(), true), "UTF-8", $item->name);
|
||||
$item->slug = implode("-",$nameParts);
|
||||
$item->slug = $this->createSlug($item->slug, $language);
|
||||
$item->path = $fullPath . DIRECTORY_SEPARATOR . $name;
|
||||
$item->pathWithoutType = $fullPath . DIRECTORY_SEPARATOR . $nameWithoutType;
|
||||
$item->key = $iteration;
|
||||
$item->keyPath = isset($keyPath) ? $keyPath . '.' . $iteration : $iteration;
|
||||
$item->keyPathArray = explode('.',$item->keyPath);
|
||||
$item->chapter = $chapter . '.' . $chapternr;
|
||||
$item->urlRelWoF = $fullSlugWithoutFolder . '/' . $item->slug;
|
||||
$item->urlRel = $fullSlugWithFolder . '/' . $item->slug;
|
||||
$item->urlAbs = $baseUrl . $fullSlugWithoutFolder . '/' . $item->slug;
|
||||
$item->active = false;
|
||||
$item->activeParent = false;
|
||||
$item->hide = false;
|
||||
}
|
||||
|
||||
$iteration++;
|
||||
$chapternr++;
|
||||
$contentDetails[] = $item;
|
||||
}
|
||||
return $contentDetails;
|
||||
}
|
||||
|
||||
public function getFolderContentType($folder, $yamlpath)
|
||||
{
|
||||
# check if folder is empty or has only index.yaml-file. This is a rare case so make it quick and dirty
|
||||
if(count($folder) == 1)
|
||||
{
|
||||
# check if in folder yaml file contains "posts", then return posts
|
||||
$folderyamlpath = getcwd() . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $yamlpath;
|
||||
|
||||
$fileContent = false;
|
||||
if(file_exists($folderyamlpath))
|
||||
{
|
||||
$fileContent = file_get_contents($folderyamlpath);
|
||||
}
|
||||
|
||||
if($fileContent && strpos($fileContent, 'contains: posts') !== false)
|
||||
{
|
||||
return 'posts';
|
||||
}
|
||||
return 'pages';
|
||||
}
|
||||
else
|
||||
{
|
||||
$file = $folder[0];
|
||||
$nameParts = $this->getStringParts($file);
|
||||
$order = count($nameParts) > 1 ? array_shift($nameParts) : NULL;
|
||||
$order = substr($order, 0, 7);
|
||||
|
||||
if(\DateTime::createFromFormat('Ymd', $order) !== FALSE)
|
||||
{
|
||||
return "posts";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "pages";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getHomepageItem($baseUrl)
|
||||
{
|
||||
# return a standard item-object
|
||||
$item = new \stdClass;
|
||||
|
||||
$item->status = 'published';
|
||||
$item->originalName = 'home';
|
||||
$item->elementType = 'folder';
|
||||
$item->fileType = 'md';
|
||||
$item->order = false;
|
||||
$item->name = 'home';
|
||||
$item->slug = '';
|
||||
$item->path = '';
|
||||
$item->pathWithoutType = DIRECTORY_SEPARATOR . 'index';
|
||||
$item->key = false;
|
||||
$item->keyPath = false;
|
||||
$item->keyPathArray = false;
|
||||
$item->chapter = false;
|
||||
$item->urlRel = '/';
|
||||
$item->urlRelWoF = '/';
|
||||
$item->urlAbs = $baseUrl;
|
||||
$item->name = 'home';
|
||||
$item->active = false;
|
||||
$item->activeParent = false;
|
||||
$item->hide = false;
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
public function getItemForUrl($folderContentDetails, $url, $baseUrl, $result = NULL, $home = NULL )
|
||||
{
|
||||
# if we are on the homepage
|
||||
if($home)
|
||||
{
|
||||
return $this->getHomepageItem($baseUrl);
|
||||
}
|
||||
|
||||
foreach($folderContentDetails as $key => $item)
|
||||
{
|
||||
# set item active, needed to move item in navigation
|
||||
if($item->urlRel === $url)
|
||||
{
|
||||
$item->active = true;
|
||||
$result = $item;
|
||||
}
|
||||
elseif($item->elementType === "folder")
|
||||
{
|
||||
$result = $this->getItemForUrl($item->folderContent, $url, $baseUrl, $result);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getItemForUrlFrontend($folderContentDetails, $url, $result = NULL)
|
||||
{
|
||||
foreach($folderContentDetails as $key => $item)
|
||||
{
|
||||
# set item active, needed to move item in navigation
|
||||
if($item->urlRelWoF === $url)
|
||||
{
|
||||
$item->active = true;
|
||||
$result = $item;
|
||||
}
|
||||
elseif($item->elementType === "folder")
|
||||
{
|
||||
$result = $this->getItemForUrlFrontend($item->folderContent, $url, $result);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getPagingForItem($content, $item)
|
||||
{
|
||||
# if page is home
|
||||
if(trim($item->pathWithoutType, DIRECTORY_SEPARATOR) == 'index')
|
||||
{
|
||||
return $item;
|
||||
}
|
||||
|
||||
$keyPos = count($item->keyPathArray)-1;
|
||||
$thisChapArray = $item->keyPathArray;
|
||||
$nextItemArray = $item->keyPathArray;
|
||||
$prevItemArray = $item->keyPathArray;
|
||||
|
||||
$item->thisChapter = false;
|
||||
$item->prevItem = false;
|
||||
$item->nextItem = false;
|
||||
|
||||
|
||||
/************************
|
||||
* ADD THIS CHAPTER *
|
||||
************************/
|
||||
|
||||
if($keyPos > 0)
|
||||
{
|
||||
array_pop($thisChapArray);
|
||||
$item->thisChapter = $this->getItemWithKeyPath($content, $thisChapArray);
|
||||
}
|
||||
|
||||
/************************
|
||||
* ADD NEXT ITEM *
|
||||
************************/
|
||||
|
||||
if($item->elementType == 'folder')
|
||||
{
|
||||
# get the first element in the folder
|
||||
$item->nextItem = isset($item->folderContent[0]) ? clone($item->folderContent[0]) : false;
|
||||
}
|
||||
|
||||
# the item is a file or an empty folder
|
||||
if(!$item->nextItem)
|
||||
{
|
||||
# walk to the next file in the same hierarchy
|
||||
$nextItemArray[$keyPos]++;
|
||||
|
||||
# get the key of the last element in this hierarchy level
|
||||
# if there is no chapter, then it is probably an empty first-level-folder. Count content to get the number of first level items
|
||||
$lastKey = $item->thisChapter ? array_key_last($item->thisChapter->folderContent) : count($content);
|
||||
|
||||
# as long as the nextItemArray is smaller than the last key in this hierarchy level, search for the next item
|
||||
# this ensures that it does not stop if key is missing (e.g. if the next page is hidden)
|
||||
while( ($nextItemArray[$keyPos] <= $lastKey) && !$item->nextItem = $this->getItemWithKeyPath($content, $nextItemArray) )
|
||||
{
|
||||
$nextItemArray[$keyPos]++;
|
||||
}
|
||||
}
|
||||
|
||||
# there is no next file or folder in this level, so walk up the hierarchy to the next folder or file
|
||||
while(!$item->nextItem)
|
||||
{
|
||||
# delete the array level with the current item, so you are in the parent folder
|
||||
array_pop($nextItemArray);
|
||||
|
||||
# if the array is empty now, then you where in the base level already, so break
|
||||
if(empty($nextItemArray)) break;
|
||||
|
||||
# define the key position where you are right now
|
||||
$newKeyPos = count($nextItemArray)-1;
|
||||
|
||||
# go to the next position
|
||||
$nextItemArray[$newKeyPos]++;
|
||||
|
||||
# search for 5 items in case there are some hidden elements
|
||||
$maxlength = $nextItemArray[$newKeyPos]+5;
|
||||
while( ($nextItemArray[$newKeyPos] <= $maxlength) && !$item->nextItem = $this->getItemWithKeyPath($content, $nextItemArray) )
|
||||
{
|
||||
$nextItemArray[$newKeyPos]++;
|
||||
}
|
||||
}
|
||||
|
||||
/************************
|
||||
* ADD PREVIOUS ITEM *
|
||||
************************/
|
||||
|
||||
# check if element is the first in the array
|
||||
$first = ($prevItemArray[$keyPos] == 0) ? true : false;
|
||||
|
||||
if(!$first)
|
||||
{
|
||||
$prevItemArray[$keyPos]--;
|
||||
|
||||
while($prevItemArray[$keyPos] >= 0 && !$item->prevItem = $this->getItemWithKeyPath($content, $prevItemArray))
|
||||
{
|
||||
$prevItemArray[$keyPos]--;
|
||||
}
|
||||
|
||||
# if no item is found, then all previous items are hidden, so set first item to true and it will walk up the array later
|
||||
if(!$item->prevItem)
|
||||
{
|
||||
$first = true;
|
||||
}
|
||||
elseif($item->prevItem && $item->prevItem->elementType == 'folder' && !empty($item->prevItem->folderContent))
|
||||
{
|
||||
# if the previous item is a folder, the get the last item of that folder
|
||||
$item->prevItem = $this->getLastItemOfFolder($item->prevItem);
|
||||
}
|
||||
}
|
||||
|
||||
# if it is the first item in the folder (or all other files are hidden)
|
||||
if($first)
|
||||
{
|
||||
# then the previous item is the containing chapter
|
||||
$item->prevItem = $item->thisChapter;
|
||||
}
|
||||
|
||||
if($item->prevItem && $item->prevItem->elementType == 'folder'){ unset($item->prevItem->folderContent); }
|
||||
if($item->nextItem && $item->nextItem->elementType == 'folder'){ unset($item->nextItem->folderContent); }
|
||||
if($item->thisChapter){unset($item->thisChapter->folderContent); }
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets a copy of an item with a key
|
||||
* @param array $content with the full structure of the content as multidimensional array
|
||||
* @param array $searchArray with the key as a one-dimensional array like array(0,3,4)
|
||||
* @return array $item
|
||||
*/
|
||||
|
||||
|
||||
# copy this to navigation
|
||||
# add keypath to the extended index
|
||||
|
||||
public function getItemWithKeyPath($content, array $searchArray)
|
||||
{
|
||||
$item = false;
|
||||
|
||||
foreach($searchArray as $key => $itemKey)
|
||||
{
|
||||
$item = isset($content[$itemKey]) ? clone($content[$itemKey]) : false;
|
||||
|
||||
unset($searchArray[$key]);
|
||||
if(!empty($searchArray) && $item)
|
||||
{
|
||||
return $this->getItemWithKeyPath($item->folderContent, $searchArray);
|
||||
}
|
||||
}
|
||||
return $item;
|
||||
}
|
||||
|
||||
# https://www.quora.com/Learning-PHP-Is-there-a-way-to-get-the-value-of-multi-dimensional-array-by-specifying-the-key-with-a-variable
|
||||
# NOT IN USE
|
||||
public function getItemWithKeyPathNew($array, array $keys)
|
||||
{
|
||||
$item = $array;
|
||||
|
||||
foreach ($keys as $key)
|
||||
{
|
||||
$item = isset($item[$key]->folderContent) ? $item[$key]->folderContent : $item[$key];
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/*
|
||||
* Extracts an item with a key https://stackoverflow.com/questions/52097092/php-delete-value-of-array-with-dynamic-key
|
||||
* @param array $content with the full structure of the content as multidimensional array
|
||||
* @param array $searchArray with the key as a one-dimensional array like array(0,3,4)
|
||||
* @return array $item
|
||||
* NOT IN USE ??
|
||||
*/
|
||||
|
||||
public function extractItemWithKeyPath($structure, array $keys)
|
||||
{
|
||||
$result = &$structure;
|
||||
$last = array_pop($keys);
|
||||
|
||||
foreach ($keys as $key) {
|
||||
if(isset($result[$key]->folderContent))
|
||||
{
|
||||
$result = &$result[$key]->folderContent;
|
||||
}
|
||||
else
|
||||
{
|
||||
$result = &$result[$key];
|
||||
}
|
||||
}
|
||||
|
||||
$item = $result[$last];
|
||||
unset($result[$last]);
|
||||
|
||||
return array('structure' => $structure, 'item' => $item);
|
||||
}
|
||||
|
||||
# NOT IN USE
|
||||
public function deleteItemWithKeyPathNOTINUSE($structure, array $keys)
|
||||
{
|
||||
$result = &$structure;
|
||||
$last = array_pop($keys);
|
||||
|
||||
foreach ($keys as $key)
|
||||
{
|
||||
if(isset($result[$key]->folderContent))
|
||||
{
|
||||
$result = &$result[$key]->folderContent;
|
||||
}
|
||||
else
|
||||
{
|
||||
$result = &$result[$key];
|
||||
}
|
||||
}
|
||||
|
||||
$item = $result[$last];
|
||||
unset($result[$last]);
|
||||
|
||||
return $structure;
|
||||
}
|
||||
|
||||
# get breadcrumb as copied array,
|
||||
# set elements active in original
|
||||
# mark parent element in original
|
||||
public function getBreadcrumb($content, $searchArray, $i = NULL, $breadcrumb = NULL)
|
||||
{
|
||||
# if it is the first round, create an empty array
|
||||
if(!$i){ $i = 0; $breadcrumb = array();}
|
||||
|
||||
if(!$searchArray){ return $breadcrumb;}
|
||||
|
||||
while($i < count($searchArray))
|
||||
{
|
||||
if(!isset($content[$searchArray[$i]])){ return false; }
|
||||
$item = $content[$searchArray[$i]];
|
||||
|
||||
if($i == count($searchArray)-1)
|
||||
{
|
||||
$item->active = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
$item->activeParent = true;
|
||||
}
|
||||
|
||||
/*
|
||||
$item->active = true;
|
||||
if($i == count($searchArray)-2)
|
||||
{
|
||||
$item->activeParent = true;
|
||||
}
|
||||
*/
|
||||
|
||||
$copy = clone($item);
|
||||
if($copy->elementType == 'folder')
|
||||
{
|
||||
unset($copy->folderContent);
|
||||
$content = $item->folderContent;
|
||||
}
|
||||
$breadcrumb[] = $copy;
|
||||
|
||||
$i++;
|
||||
return $this->getBreadcrumb($content, $searchArray, $i++, $breadcrumb);
|
||||
}
|
||||
return $breadcrumb;
|
||||
}
|
||||
|
||||
public function getParentItem($content, $searchArray, $iteration = NULL)
|
||||
{
|
||||
if(!$iteration){ $iteration = 0; }
|
||||
while($iteration < count($searchArray)-2)
|
||||
{
|
||||
$content = $content[$searchArray[$iteration]]->folderContent;
|
||||
$iteration++;
|
||||
return $this->getParentItem($content, $searchArray, $iteration);
|
||||
}
|
||||
return $content[$searchArray[$iteration]];
|
||||
}
|
||||
|
||||
private function getLastItemOfFolder($folder)
|
||||
{
|
||||
$lastItem = end($folder->folderContent);
|
||||
if(is_object($lastItem) && $lastItem->elementType == 'folder' && !empty($lastItem->folderContent))
|
||||
{
|
||||
return $this->getLastItemOfFolder($lastItem);
|
||||
}
|
||||
return $lastItem;
|
||||
}
|
||||
|
||||
public function getStringParts($name)
|
||||
{
|
||||
return preg_split('/[\-\.\_\=\+\?\!\*\#\(\)\/ ]/',$name);
|
||||
}
|
||||
|
||||
public function getFileType($fileName)
|
||||
{
|
||||
$parts = preg_split('/\./',$fileName);
|
||||
return end($parts);
|
||||
}
|
||||
|
||||
public function splitFileName($fileName)
|
||||
{
|
||||
$parts = preg_split('/\./',$fileName);
|
||||
return $parts;
|
||||
}
|
||||
|
||||
public function getNameWithoutType($fileName)
|
||||
{
|
||||
$parts = preg_split('/\./',$fileName);
|
||||
return $parts[0];
|
||||
}
|
||||
|
||||
public function createSlug($name, $language = null)
|
||||
{
|
||||
$name = iconv(mb_detect_encoding($name, mb_detect_order(), true), "UTF-8", $name);
|
||||
$language = $language ? $language : "";
|
||||
|
||||
return URLify::filter(
|
||||
$name,
|
||||
$length = 60,
|
||||
$language,
|
||||
$file_name = false,
|
||||
$use_remove_list = false,
|
||||
$lower_case = true,
|
||||
$treat_underscore_as_space = true
|
||||
);
|
||||
}
|
||||
}
|
341
system/typemill/Models/Navigation.php
Normal file
341
system/typemill/Models/Navigation.php
Normal file
@@ -0,0 +1,341 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Models;
|
||||
|
||||
use Typemill\Models\StorageWrapper;
|
||||
use Typemill\Models\Folder;
|
||||
|
||||
class Navigation
|
||||
{
|
||||
private $storage;
|
||||
|
||||
private $naviFolder;
|
||||
|
||||
private $liveNaviName;
|
||||
|
||||
private $draftNaviName;
|
||||
|
||||
private $extendedNaviName;
|
||||
|
||||
private $extendedNavigation = false;
|
||||
|
||||
private $draftNavigation = false;
|
||||
|
||||
private $basicDraftNavigation = false;
|
||||
|
||||
private $liveNavigation = false;
|
||||
|
||||
private $basicLiveNavigation = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->storage = new StorageWrapper('\Typemill\Models\Storage');
|
||||
|
||||
$this->naviFolder = 'data' . DIRECTORY_SEPARATOR . 'navigation';
|
||||
|
||||
$this->liveNaviName = 'navi-live.txt';
|
||||
|
||||
$this->draftNaviName = 'navi-draft.txt';
|
||||
|
||||
$this->extendedNaviName = 'navi-extended.txt';
|
||||
}
|
||||
|
||||
public function getMainNavigation($userrole, $acl, $urlinfo, $editor)
|
||||
{
|
||||
$mainnavi = $this->storage->getYaml('system/typemill/settings', 'mainnavi.yaml');
|
||||
|
||||
$allowedmainnavi = [];
|
||||
|
||||
foreach($mainnavi as $name => $naviitem)
|
||||
{
|
||||
if($acl->isAllowed($userrole, $naviitem['aclresource'], $naviitem['aclprivilege']))
|
||||
{
|
||||
# set the navi of current route active
|
||||
$thisRoute = '/tm/' . $name;
|
||||
if(strpos($urlinfo['route'], $thisRoute) !== false)
|
||||
{
|
||||
$naviitem['active'] = true;
|
||||
}
|
||||
|
||||
$allowedmainnavi[$name] = $naviitem;
|
||||
}
|
||||
}
|
||||
|
||||
# if system is there, then we do not need the account item
|
||||
if(isset($allowedmainnavi['system']))
|
||||
{
|
||||
unset($allowedmainnavi['account']);
|
||||
}
|
||||
|
||||
# set correct editor mode according to user settings
|
||||
if(isset($allowedmainnavi['content']) && $editor == 'raw')
|
||||
{
|
||||
$allowedmainnavi['content']['routename'] = "content.raw";
|
||||
}
|
||||
|
||||
return $allowedmainnavi;
|
||||
}
|
||||
|
||||
|
||||
# get the navigation with draft files for author environment
|
||||
public function getDraftNavigation($urlinfo, $language, $userrole = null, $username = null)
|
||||
{
|
||||
# todo: filter for userrole or username
|
||||
|
||||
$this->draftNavigation = $this->storage->getFile($this->naviFolder, $this->draftNaviName, 'unserialize');
|
||||
|
||||
if($this->draftNavigation)
|
||||
{
|
||||
return $this->draftNavigation;
|
||||
}
|
||||
|
||||
# if there is no cached navi, create a basic new draft navi
|
||||
$basicDraftNavigation = $this->getBasicDraftNavigation($urlinfo, $language);
|
||||
|
||||
# get the extended navigation with additional infos from the meta-files like title or hidden pages
|
||||
$extendedNavigation = $this->getExtendedNavigation($urlinfo, $language);
|
||||
|
||||
# merge the basic draft navi with the extended infos from meta-files
|
||||
$draftNavigation = $this->mergeNavigationWithExtended($basicDraftNavigation, $extendedNavigation);
|
||||
|
||||
# cache it
|
||||
$this->storage->writeFile($this->naviFolder, $this->draftNaviName, $draftNavigation, 'serialize');
|
||||
|
||||
return $draftNavigation;
|
||||
}
|
||||
|
||||
public function getBasicDraftNavigation($urlinfo, $language)
|
||||
{
|
||||
if(!$this->basicDraftNavigation)
|
||||
{
|
||||
$this->basicDraftNavigation = $this->createBasicDraftNavigation($urlinfo, $language);
|
||||
}
|
||||
return $this->basicDraftNavigation;
|
||||
}
|
||||
|
||||
# creates a fresh structure with published and non-published pages for the author
|
||||
public function createBasicDraftNavigation($urlinfo, $language)
|
||||
{
|
||||
$folder = new Folder();
|
||||
|
||||
# scan the content of the folder
|
||||
$draftContentTree = $folder->scanFolder($this->storage->getStorageInfo('contentFolder'), $draft = true);
|
||||
|
||||
# if there is content, then get the content details
|
||||
if(count($draftContentTree) > 0)
|
||||
{
|
||||
$draftNavigation = $folder->getFolderContentDetails($draftContentTree, $language, $urlinfo['baseurl'], $urlinfo['basepath']);
|
||||
|
||||
return $draftNavigation;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
# get the extended navigation with additional infos from the meta-files like title or hidden pages
|
||||
public function getExtendedNavigation($urlinfo, $language)
|
||||
{
|
||||
if(!$this->extendedNavigation)
|
||||
{
|
||||
# read the extended navi file
|
||||
$this->extendedNavigation = $this->storage->getYaml($this->naviFolder, $this->extendedNaviName);
|
||||
}
|
||||
|
||||
if(!$this->extendedNavigation)
|
||||
{
|
||||
$basicDraftNavigation = $this->getBasicDraftNavigation($urlinfo, $language);
|
||||
|
||||
$this->extendedNavigation = $this->createExtendedNavigation($basicDraftNavigation, $extended = NULL);
|
||||
|
||||
# cache it
|
||||
$this->storage->updateYaml($this->naviFolder, $this->extendedNaviName, $this->extendedNavigation);
|
||||
}
|
||||
|
||||
return $this->extendedNavigation;
|
||||
}
|
||||
|
||||
# reads all meta-files and creates an array with url => ['hide' => bool, 'navtitle' => 'bla']
|
||||
public function createExtendedNavigation($navigation, $extended = NULL)
|
||||
{
|
||||
if(!$extended)
|
||||
{
|
||||
$extended = [];
|
||||
}
|
||||
|
||||
$contentFolder = $this->storage->getStorageInfo('contentFolder');
|
||||
|
||||
foreach ($navigation as $key => $item)
|
||||
{
|
||||
# $filename = ($item->elementType == 'folder') ? DIRECTORY_SEPARATOR . 'index.yaml' : $item->pathWithoutType . '.yaml';
|
||||
$filename = $item->pathWithoutType . '.yaml';
|
||||
|
||||
if(file_exists($contentFolder . $filename))
|
||||
{
|
||||
# read file
|
||||
$meta = $this->storage->getYaml($contentFolder, $filename);
|
||||
|
||||
$extended[$item->urlRelWoF]['navtitle'] = isset($meta['meta']['navtitle']) ? $meta['meta']['navtitle'] : '';
|
||||
$extended[$item->urlRelWoF]['hide'] = isset($meta['meta']['hide']) ? $meta['meta']['hide'] : false;
|
||||
$extended[$item->urlRelWoF]['noindex'] = isset($meta['meta']['noindex']) ? $meta['meta']['noindex'] : false;
|
||||
$extended[$item->urlRelWoF]['path'] = $item->path;
|
||||
$extended[$item->urlRelWoF]['keyPath'] = $item->keyPath;
|
||||
}
|
||||
|
||||
if ($item->elementType == 'folder')
|
||||
{
|
||||
$extended = $this->createExtendedNavigation($item->folderContent, $extended);
|
||||
}
|
||||
}
|
||||
|
||||
return $extended;
|
||||
}
|
||||
|
||||
# merge a basic navigation (live or draft) with extended information from meta
|
||||
public function mergeNavigationWithExtended($navigation, $extended)
|
||||
{
|
||||
$mergedNavigation = [];
|
||||
|
||||
foreach($navigation as $key => $item)
|
||||
{
|
||||
if($extended && isset($extended[$item->urlRelWoF]))
|
||||
{
|
||||
$item->name = ($extended[$item->urlRelWoF]['navtitle'] != '') ? $extended[$item->urlRelWoF]['navtitle'] : $item->name;
|
||||
$item->hide = ($extended[$item->urlRelWoF]['hide'] === true) ? true : false;
|
||||
$item->noindex = (isset($extended[$item->urlRelWoF]['noindex']) && $extended[$item->urlRelWoF]['noindex'] === true) ? true : false;
|
||||
}
|
||||
|
||||
if($item->elementType == 'folder')
|
||||
{
|
||||
$item->folderContent = $this->mergeNavigationWithExtended($item->folderContent, $extended);
|
||||
}
|
||||
|
||||
$mergedNavigation[] = $item;
|
||||
}
|
||||
|
||||
return $mergedNavigation;
|
||||
}
|
||||
|
||||
public function getItemWithKeyPath($navigation, array $searchArray)
|
||||
{
|
||||
$item = false;
|
||||
|
||||
foreach($searchArray as $key => $itemKey)
|
||||
{
|
||||
$item = isset($navigation[$itemKey]) ? clone($navigation[$itemKey]) : false;
|
||||
|
||||
unset($searchArray[$key]);
|
||||
if(!empty($searchArray) && $item)
|
||||
{
|
||||
return $this->getItemWithKeyPath($item->folderContent, $searchArray);
|
||||
}
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
# reads the cached structure with published pages
|
||||
public function getLiveNavigation()
|
||||
{
|
||||
# get the cached navi
|
||||
$liveNavi = $this->storage->getFile($this->naviFolder, $this->liveNaviName, 'unserialize');
|
||||
|
||||
# if there is no cached structure
|
||||
if(!$liveNavi)
|
||||
{
|
||||
return $this->createNewLiveNavigation();
|
||||
}
|
||||
|
||||
return $liveNavi;
|
||||
}
|
||||
|
||||
# creates a fresh structure with published pages
|
||||
private function createNewLiveNavigation($urlinfo, $language)
|
||||
{
|
||||
$folder = new Folder();
|
||||
|
||||
# scan the content of the folder
|
||||
$draftNavi = $folder->scanFolder($this->storage->contentFolder, $draft = false);
|
||||
|
||||
# if there is content, then get the content details
|
||||
if($draftNavi && count($draftNavi) > 0)
|
||||
{
|
||||
# get the extended structure files with changes like navigation title or hidden pages
|
||||
$extended = $this->getExtendedNavi();
|
||||
|
||||
# create an array of object with the whole content of the folder and changes from extended file
|
||||
$liveNavi = $folder->getFolderContentDetails($liveNavi, $extended, $this->settings, $this->uri->getBaseUrl(), $this->uri->getBasePath());
|
||||
|
||||
# cache structure live
|
||||
$this->storage->writeFile($this->naviFolder, $this->liveNaviName, $liveNavi, 'serialize');
|
||||
|
||||
return $liveNavi;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,21 +4,23 @@ namespace Typemill\Models;
|
||||
|
||||
class Storage
|
||||
{
|
||||
public $error = false;
|
||||
public $error = false;
|
||||
|
||||
protected $basepath = false;
|
||||
protected $basepath = false;
|
||||
|
||||
protected $tmpFolder = false;
|
||||
protected $tmpFolder = false;
|
||||
|
||||
protected $originalFolder = false;
|
||||
protected $originalFolder = false;
|
||||
|
||||
protected $liveFolder = false;
|
||||
protected $liveFolder = false;
|
||||
|
||||
protected $thumbsFolder = false;
|
||||
protected $thumbsFolder = false;
|
||||
|
||||
protected $customFolder = false;
|
||||
protected $customFolder = false;
|
||||
|
||||
protected $fileFolder = false;
|
||||
protected $fileFolder = false;
|
||||
|
||||
protected $contentFolder = false;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -35,6 +37,8 @@ class Storage
|
||||
$this->customFolder = $this->basepath . 'media' . DIRECTORY_SEPARATOR . 'custom' . DIRECTORY_SEPARATOR;
|
||||
|
||||
$this->fileFolder = $this->basepath . 'media' . DIRECTORY_SEPARATOR . 'files' . DIRECTORY_SEPARATOR;
|
||||
|
||||
$this->contentFolder = $this->basepath . 'content';
|
||||
}
|
||||
|
||||
public function getError()
|
||||
@@ -42,6 +46,15 @@ class Storage
|
||||
return $this->error;
|
||||
}
|
||||
|
||||
public function getStorageInfo($item)
|
||||
{
|
||||
if(isset($this->$item))
|
||||
{
|
||||
return $this->$item;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function checkFolder($folder)
|
||||
{
|
||||
$folderpath = $this->basepath . $folder;
|
||||
@@ -87,7 +100,7 @@ class Storage
|
||||
return true;
|
||||
}
|
||||
|
||||
public function writeFile($folder, $filename, $data)
|
||||
public function writeFile($folder, $filename, $data, $method = NULL)
|
||||
{
|
||||
if(!$this->checkFolder($folder))
|
||||
{
|
||||
@@ -107,6 +120,12 @@ class Storage
|
||||
return false;
|
||||
}
|
||||
|
||||
# serialize, json_decode
|
||||
if($method && is_callable($method))
|
||||
{
|
||||
$data = $method($data);
|
||||
}
|
||||
|
||||
$writefile = fwrite($openfile, $data);
|
||||
if(!$writefile)
|
||||
{
|
||||
@@ -120,13 +139,19 @@ class Storage
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getFile($folder, $filename)
|
||||
public function getFile($folder, $filename, $method = NULL)
|
||||
{
|
||||
if($this->checkFile($folder, $filename))
|
||||
{
|
||||
# ??? should be with basepath???
|
||||
$fileContent = file_get_contents($folder . DIRECTORY_SEPARATOR . $filename);
|
||||
|
||||
# use unserialise or json_decode
|
||||
if($method && is_callable($method))
|
||||
{
|
||||
$fileContent = $method($fileContent);
|
||||
}
|
||||
|
||||
return $fileContent;
|
||||
}
|
||||
|
||||
@@ -207,6 +232,8 @@ class Storage
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public function createUniqueImageName($filename, $extension)
|
||||
{
|
||||
$defaultfilename = $filename;
|
||||
|
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Models;
|
||||
|
||||
class Yaml extends StorageWrapper
|
||||
{
|
||||
/**
|
||||
* Get the a yaml file.
|
||||
* @param string $fileName is the name of the Yaml Folder.
|
||||
* @param string $yamlFileName is the name of the Yaml File.
|
||||
*/
|
||||
public function getYaml($folderName, $yamlFileName)
|
||||
{
|
||||
die('Yaml class outdated. Use storage instead.');
|
||||
$yaml = $this->getFile($folderName, $yamlFileName);
|
||||
|
||||
if($yaml)
|
||||
{
|
||||
return \Symfony\Component\Yaml\Yaml::parse($yaml);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a yaml file.
|
||||
* @param string $fileName is the name of the Yaml Folder.
|
||||
* @param string $yamlFileName is the name of the Yaml File.
|
||||
* @param array $contentArray is the content as an array.
|
||||
*/
|
||||
public function updateYaml($folderName, $yamlFileName, $contentArray)
|
||||
{
|
||||
die('Yaml class outdated. Use storage instead.');
|
||||
$yaml = \Symfony\Component\Yaml\Yaml::dump($contentArray,6);
|
||||
if($this->writeFile($folderName, $yamlFileName, $yaml))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
22
system/typemill/author/content/blox-editor.twig
Normal file
22
system/typemill/author/content/blox-editor.twig
Normal file
@@ -0,0 +1,22 @@
|
||||
{% extends 'layouts/layoutContent.twig' %}
|
||||
{% block title %}{{ translate('Visual Editor') }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h1 class="text-3xl font-bold mb-4">{{ translate('Visual Editor') }} </h1>
|
||||
|
||||
<div id="veditor" v-cloak></div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/highlight.min.js?v={{ settings.version }}"></script>
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-system.js?v={{ settings.version }}"></script>
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-translate.js?v={{ settings.version }}"></script>
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-shared.js?v={{ settings.version }}"></script>
|
||||
<script>
|
||||
app.mount('#veditor');
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
68
system/typemill/author/layouts/layoutContent.twig
Normal file
68
system/typemill/author/layouts/layoutContent.twig
Normal file
@@ -0,0 +1,68 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{% block title %}{% endblock %}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
|
||||
<meta name="description" content="Edit your content with typemill"/>
|
||||
|
||||
<meta name="msapplication-TileColor" content="#F9F8F6" />
|
||||
<meta name="msapplication-TileImage" content="{{ base_url() }}/system/author/img/favicon-144.png" />
|
||||
<link rel="icon" type="image/png" href="{{ base_url() }}/system/author/img/favicon-16.png" sizes="16x16" />
|
||||
<link rel="icon" type="image/png" href="{{ base_url() }}/system/author/img/favicon-32.png" sizes="32x32" />
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="{{ base_url() }}/system/author/img/favicon-72.png" />
|
||||
<link rel="apple-touch-icon" sizes="114x114" href="{{ base_url() }}/system/author/img/favicon-114.png" />
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="{{ base_url() }}/system/author/img/favicon-144.png" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ base_url() }}/system/author/img/favicon-180.png" />
|
||||
|
||||
<link rel="stylesheet" href="{{ base_url() }}/system/typemill/author/css/output.css?v={{ settings.version }}" />
|
||||
<link rel="stylesheet" href="{{ base_url() }}/system/typemill/author/css/custom.css?v={{ settings.version }}" />
|
||||
|
||||
{% block stylesheet %}{% endblock %}
|
||||
|
||||
{{ assets.renderCSS() }}
|
||||
|
||||
</head>
|
||||
<body class="bg-stone-100">
|
||||
|
||||
{% include 'partials/symbols.twig' %}
|
||||
|
||||
<header class="border-b-2 border-stone-200">
|
||||
{% include 'partials/mainNavi.twig' %}
|
||||
</header>
|
||||
|
||||
{% include 'partials/flash.twig' %}
|
||||
|
||||
<div class="max-w-6xl m-auto mt-7 flex justify-between" id="main" data-url="{{ base_url() }}">
|
||||
<aside class="w-1/4">
|
||||
{% include 'partials/contentNavi.twig' %}
|
||||
</aside>
|
||||
<article class="w-3/4 bg-stone-50 shadow-md p-8">
|
||||
{% block content %}{% endblock %}
|
||||
</article>
|
||||
</div>
|
||||
|
||||
{{ csrf() | raw }}
|
||||
|
||||
<script>
|
||||
|
||||
const data = {{ jsdata | json_encode() | raw }};
|
||||
const labels = {{ translations|json_encode() }};
|
||||
|
||||
</script>
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/axios.min.js?v={{ settings.version }}"></script>
|
||||
<script>
|
||||
const tmaxios = axios.create();
|
||||
tmaxios.defaults.baseURL = "{{ base_url() }}";
|
||||
tmaxios.defaults.headers.common['X-Session-Auth'] = "true";
|
||||
</script>
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue.js?v={{ settings.version }}"></script>
|
||||
<script src="{{ base_url() }}/system/typemill/author/js/vue-eventbus.js?v={{ settings.version }}"></script>
|
||||
|
||||
{% block javascript %}{% endblock %}
|
||||
|
||||
{{ assets.renderJS() }}
|
||||
|
||||
</body>
|
||||
</html>
|
13
system/typemill/author/partials/contentNavi.twig
Normal file
13
system/typemill/author/partials/contentNavi.twig
Normal file
@@ -0,0 +1,13 @@
|
||||
<nav class="max-w-6xl m-auto flex justify-between">
|
||||
<ul class="flex border-l-2 border-stone-200">
|
||||
<div id="contentNavigation" v-cloak></div>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
{% block javascript %}
|
||||
|
||||
<script>
|
||||
app.mount('#contentNavigation');
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
114
system/typemill/author/partials/editorNavi.twig
Normal file
114
system/typemill/author/partials/editorNavi.twig
Normal file
@@ -0,0 +1,114 @@
|
||||
<nav id="sidebar-menu" class="sidebar-menu--content">
|
||||
<div id="data-navi" data-homepage='{{ homepage|json_encode() }}' data-editormode="{{settings.editor}}"></div>
|
||||
<div id="mobile-menu" class="menu-action">{{ __('Menu') }} <span class="button-arrow"></span></div>
|
||||
<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>
|
||||
</div>
|
||||
<draggable class="navi-list" tag="ul"
|
||||
@start="onStart"
|
||||
@end="onEnd"
|
||||
:list="items"
|
||||
:move="checkMove"
|
||||
group="file"
|
||||
animation="150"
|
||||
:disabled="freeze">
|
||||
<navigation
|
||||
v-for="item in items"
|
||||
ref="draggit"
|
||||
:freeze="freeze"
|
||||
:name="item.name"
|
||||
:hide="item.hide"
|
||||
:active="item.active"
|
||||
:parent="item.activeParent"
|
||||
:level="item.keyPath"
|
||||
:root="root"
|
||||
:url="item.urlRelWoF"
|
||||
:id="item.keyPath"
|
||||
:key="item.keyPath"
|
||||
:elementtype="item.elementType"
|
||||
:contains="item.contains"
|
||||
:filetype="item.fileType"
|
||||
:status="item.status"
|
||||
:folder="item.folderContent"
|
||||
:collapse="collapse"
|
||||
></navigation>
|
||||
</draggable>
|
||||
<ul class="navi-list addBaseItem">
|
||||
<li class="navi-item file">
|
||||
<span class="iconwrapper">
|
||||
<svg class="icon icon-plus"><use xlink:href="#icon-plus"></use></svg>
|
||||
</span>
|
||||
<span class="addNaviItem">
|
||||
<a class="addNaviLink" href="#" @click.prevent="toggleForm">{{ __('add item') }}</a>
|
||||
</span>
|
||||
<transition name="fade">
|
||||
<div v-if="showForm" class="addNaviForm">
|
||||
<input v-model="newItem">
|
||||
<button class="b-left" @click="addFile('file')">{{ __('add file') }}</button><button class="b-right" @click="addFile('folder')">{{ __('add folder') }}</button>
|
||||
</div>
|
||||
</transition>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{% verbatim %}
|
||||
<template id="navigation-template">
|
||||
<li class="navi-item" :class="elementtype">
|
||||
<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"
|
||||
@start="onStart"
|
||||
@end="onEnd"
|
||||
:list="folder"
|
||||
:move="checkMove"
|
||||
group="file"
|
||||
animation="150"
|
||||
:disabled="freeze">
|
||||
<navigation
|
||||
v-for="item in folder"
|
||||
v-if="contains == 'pages'"
|
||||
ref="draggit"
|
||||
:freeze="freeze"
|
||||
:name="item.name"
|
||||
:hide="item.hide"
|
||||
:active="item.active"
|
||||
:parent="item.activeParent"
|
||||
:level="item.keyPath"
|
||||
:url="item.urlRelWoF"
|
||||
:root="root"
|
||||
:id="item.keyPath"
|
||||
:key="item.keyPath"
|
||||
:filetype="item.fileType"
|
||||
:status="item.status"
|
||||
:elementtype="item.elementType"
|
||||
:contains="item.contains"
|
||||
:folder="item.folderContent"
|
||||
:collapse="collapse"
|
||||
></navigation>
|
||||
</draggable>
|
||||
<ul v-if="folder && contains == 'pages'" class="navi-list">
|
||||
<li class="navi-item file">
|
||||
<span class="iconwrapper">
|
||||
<svg class="icon icon-plus"><use xlink:href="#icon-plus"></use></svg>
|
||||
</span>
|
||||
<span :class="getLevel(level + '.0')" class="addNaviItem">
|
||||
<a class="addNaviLink" href="#" @click.prevent="toggleForm">{% endverbatim %}{{ __('add item') }}{% verbatim %}</a>
|
||||
</span>
|
||||
<transition name="fade">
|
||||
<div v-if="showForm" class="addNaviForm">
|
||||
<input v-model="newItem">
|
||||
<button class="b-left" @click="addFile('file')">{% endverbatim %}{{ __('add file') }}{% verbatim %}</button><button class="b-right" @click="addFile('folder')">{% endverbatim %}{{ __('add folder') }}{% verbatim %}</button>
|
||||
</div>
|
||||
</transition>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</template>
|
||||
{% endverbatim %}
|
@@ -6,6 +6,7 @@ use Typemill\Middleware\WebRedirectIfUnauthenticated;
|
||||
use Typemill\Middleware\WebAuthorization;
|
||||
use Typemill\Controllers\ControllerWebAuth;
|
||||
use Typemill\Controllers\ControllerWebSystem;
|
||||
use Typemill\Controllers\ControllerWebAuthor;
|
||||
use Typemill\Controllers\ControllerWebFrontend;
|
||||
#use Slim\Views\TwigMiddleware;
|
||||
|
||||
@@ -20,6 +21,7 @@ $app->group('/tm', function (RouteCollectorProxy $group) {
|
||||
# author and editor area, requires authentication
|
||||
$app->group('/tm', function (RouteCollectorProxy $group) use ($routeParser,$acl) {
|
||||
|
||||
# Admin Area
|
||||
$group->get('/logout', ControllerWebAuth::class . ':logout')->setName('auth.logout');
|
||||
$group->get('/system', ControllerWebSystem::class . ':showSettings')->setName('settings.show')->add(new WebAuthorization($routeParser, $acl, 'system', 'show')); # admin;
|
||||
$group->get('/license', ControllerWebSystem::class . ':showLicense')->setName('license.show')->add(new WebAuthorization($routeParser, $acl, 'system', 'show')); # admin;
|
||||
@@ -30,8 +32,8 @@ $app->group('/tm', function (RouteCollectorProxy $group) use ($routeParser,$acl)
|
||||
$group->get('/user/new', ControllerWebSystem::class . ':newUser')->setName('user.new')->add(new WebAuthorization($routeParser, $acl, 'user', 'create')); # admin;
|
||||
$group->get('/user/{username}', ControllerWebSystem::class . ':showUser')->setName('user.show')->add(new WebAuthorization($routeParser, $acl, 'user', 'show')); # admin;;
|
||||
|
||||
# REFACTOR
|
||||
$group->get('/content/visual[/{params:.*}]', ControllerAuthorEditor::class . ':showBlox')->setName('content.visual');
|
||||
# Author Area
|
||||
$group->get('/content/visual[/{route:.*}]', ControllerWebAuthor::class . ':showBlox')->setName('content.visual')->add(new WebAuthorization($routeParser, $acl, 'mycontent', 'view'));
|
||||
|
||||
})->add(new WebRedirectIfUnauthenticated($routeParser));
|
||||
|
||||
|
Reference in New Issue
Block a user