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

Version 1.2.16

This commit is contained in:
trendschau
2019-10-20 12:09:45 +02:00
parent c03a27c10a
commit 62c7650f55
165 changed files with 19328 additions and 17815 deletions

View File

@@ -1,179 +1,179 @@
<?php
namespace Typemill\Controllers;
use Slim\Views\Twig;
use Slim\Http\Request;
use Slim\Http\Response;
use Typemill\Models\Validation;
use Typemill\Models\User;
use Typemill\Models\WriteYaml;
class AuthController extends Controller
{
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)
{
$data = array();
/* check previous login attemps */
$yaml = new WriteYaml();
$logins = $yaml->getYaml('settings/users', '.logins');
$userIP = $this->getUserIP();
$userLogins = isset($logins[$userIP]) ? count($logins[$userIP]) : false;
if($userLogins)
{
/* get the latest */
$lastLogin = intval($logins[$userIP][$userLogins-1]);
/* if last login is longer than 60 seconds ago, clear it. */
if(time() - $lastLogin > 60)
{
unset($logins[$userIP]);
$yaml->updateYaml('settings/users', '.logins', $logins);
}
/* Did the user made three login attemps that failed? */
elseif($userLogins >= 3)
{
$timeleft = 60 - (time() - $lastLogin);
$data['messages'] = array('time' => $timeleft, 'error' => array( 'Too many bad logins. Please wait.'));
}
}
return $this->render($response, '/auth/login.twig', $data);
}
/**
* 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)
{
/* log user attemps to authenticate */
$yaml = new WriteYaml();
$logins = $yaml->getYaml('settings/users', '.logins');
$userIP = $this->getUserIP();
$userLogins = isset($logins[$userIP]) ? count($logins[$userIP]) : false;
/* if there have been user logins before. You have to do this again, because user does not always refresh the login page and old login attemps are stored. */
if($userLogins)
{
/* get the latest */
$lastLogin = intval($logins[$userIP][$userLogins-1]);
/* if last login is longer than 60 seconds ago, clear it and add this attempt */
if(time() - $lastLogin > 60)
{
unset($logins[$userIP]);
$yaml->updateYaml('settings/users', '.logins', $logins);
}
/* Did the user made three login attemps that failed? */
elseif($userLogins >= 2)
{
$logins[$userIP][] = time();
$yaml->updateYaml('settings/users', '.logins', $logins);
return $response->withRedirect($this->c->router->pathFor('auth.show'));
}
}
/* authentication */
$params = $request->getParams();
$validation = new Validation();
if($validation->signin($params))
{
$user = new User();
$userdata = $user->getUser($params['username']);
if($userdata && password_verify($params['password'], $userdata['password']))
{
$user->login($userdata['username']);
/* clear the user login attemps */
if($userLogins)
{
unset($logins[$userIP]);
$yaml->updateYaml('settings/users', '.logins', $logins);
}
return $response->withRedirect($this->c->router->pathFor('content.raw'));
}
}
/* if authentication failed, add attempt to log file */
$logins[$userIP][] = time();
$yaml->updateYaml('settings/users', '.logins', $logins);
$this->c->flash->addMessage('error', 'Ups, wrong password or username, please try again.');
return $response->withRedirect($this->c->router->pathFor('auth.show'));
}
/**
* log out a user
*
* @param obj $request the slim request object
* @param obj $response the slim response object
* @return obje $response with redirect to route
*/
public function logout(Request $request, Response $response)
{
if(isset($_SESSION))
{
session_destroy();
}
return $response->withRedirect($this->c->router->pathFor('auth.show'));
}
private function getUserIP()
{
$client = @$_SERVER['HTTP_CLIENT_IP'];
$forward = @$_SERVER['HTTP_X_FORWARDED_FOR'];
$remote = $_SERVER['REMOTE_ADDR'];
if(filter_var($client, FILTER_VALIDATE_IP))
{
$ip = $client;
}
elseif(filter_var($forward, FILTER_VALIDATE_IP))
{
$ip = $forward;
}
else
{
$ip = $remote;
}
return $ip;
}
<?php
namespace Typemill\Controllers;
use Slim\Views\Twig;
use Slim\Http\Request;
use Slim\Http\Response;
use Typemill\Models\Validation;
use Typemill\Models\User;
use Typemill\Models\WriteYaml;
class AuthController extends Controller
{
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)
{
$data = array();
/* check previous login attemps */
$yaml = new WriteYaml();
$logins = $yaml->getYaml('settings/users', '.logins');
$userIP = $this->getUserIP();
$userLogins = isset($logins[$userIP]) ? count($logins[$userIP]) : false;
if($userLogins)
{
/* get the latest */
$lastLogin = intval($logins[$userIP][$userLogins-1]);
/* if last login is longer than 60 seconds ago, clear it. */
if(time() - $lastLogin > 60)
{
unset($logins[$userIP]);
$yaml->updateYaml('settings/users', '.logins', $logins);
}
/* Did the user made three login attemps that failed? */
elseif($userLogins >= 3)
{
$timeleft = 60 - (time() - $lastLogin);
$data['messages'] = array('time' => $timeleft, 'error' => array( 'Too many bad logins. Please wait.'));
}
}
return $this->render($response, '/auth/login.twig', $data);
}
/**
* 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)
{
/* log user attemps to authenticate */
$yaml = new WriteYaml();
$logins = $yaml->getYaml('settings/users', '.logins');
$userIP = $this->getUserIP();
$userLogins = isset($logins[$userIP]) ? count($logins[$userIP]) : false;
/* if there have been user logins before. You have to do this again, because user does not always refresh the login page and old login attemps are stored. */
if($userLogins)
{
/* get the latest */
$lastLogin = intval($logins[$userIP][$userLogins-1]);
/* if last login is longer than 60 seconds ago, clear it and add this attempt */
if(time() - $lastLogin > 60)
{
unset($logins[$userIP]);
$yaml->updateYaml('settings/users', '.logins', $logins);
}
/* Did the user made three login attemps that failed? */
elseif($userLogins >= 2)
{
$logins[$userIP][] = time();
$yaml->updateYaml('settings/users', '.logins', $logins);
return $response->withRedirect($this->c->router->pathFor('auth.show'));
}
}
/* authentication */
$params = $request->getParams();
$validation = new Validation();
if($validation->signin($params))
{
$user = new User();
$userdata = $user->getUser($params['username']);
if($userdata && password_verify($params['password'], $userdata['password']))
{
$user->login($userdata['username']);
/* clear the user login attemps */
if($userLogins)
{
unset($logins[$userIP]);
$yaml->updateYaml('settings/users', '.logins', $logins);
}
return $response->withRedirect($this->c->router->pathFor('content.raw'));
}
}
/* if authentication failed, add attempt to log file */
$logins[$userIP][] = time();
$yaml->updateYaml('settings/users', '.logins', $logins);
$this->c->flash->addMessage('error', 'Ups, wrong password or username, please try again.');
return $response->withRedirect($this->c->router->pathFor('auth.show'));
}
/**
* log out a user
*
* @param obj $request the slim request object
* @param obj $response the slim response object
* @return obje $response with redirect to route
*/
public function logout(Request $request, Response $response)
{
if(isset($_SESSION))
{
session_destroy();
}
return $response->withRedirect($this->c->router->pathFor('auth.show'));
}
private function getUserIP()
{
$client = @$_SERVER['HTTP_CLIENT_IP'];
$forward = @$_SERVER['HTTP_X_FORWARDED_FOR'];
$remote = $_SERVER['REMOTE_ADDR'];
if(filter_var($client, FILTER_VALIDATE_IP))
{
$ip = $client;
}
elseif(filter_var($forward, FILTER_VALIDATE_IP))
{
$ip = $forward;
}
else
{
$ip = $remote;
}
return $ip;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,160 +1,163 @@
<?php
namespace Typemill\Controllers;
use Slim\Http\Request;
use Slim\Http\Response;
use Slim\Views\Twig;
use Typemill\Models\Folder;
use Typemill\Extensions\ParsedownExtension;
class ContentBackendController extends ContentController
{
/**
* Show Content for raw editor
*
* @param obj $request the slim request object
* @param obj $response the slim response object
* @return obje $response with redirect to route
*/
public function showContent(Request $request, Response $response, $args)
{
# get params from call
$this->uri = $request->getUri();
$this->params = isset($args['params']) ? ['url' => $this->uri->getBasePath() . '/' . $args['params']] : ['url' => $this->uri->getBasePath()];
# set structure
if(!$this->setStructure($draft = true)){ return $this->renderIntern404($response, array( 'navigation' => true, 'content' => $this->errors )); }
# set information for homepage
$this->setHomepage();
# set item
if(!$this->setItem()){ return $this->renderIntern404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); }
# get the breadcrumb (here we need it only to mark the actual item active in navigation)
$breadcrumb = isset($this->item->keyPathArray) ? Folder::getBreadcrumb($this->structure, $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();
$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->render($response, 'editor/editor-raw.twig', array('navigation' => $this->structure, 'homepage' => $this->homepage, 'title' => $title, 'content' => $content, 'item' => $this->item, 'settings' => $this->settings ));
}
/**
* Show Content for blox editor
*
* @param obj $request the slim request object
* @param obj $response the slim response object
* @return obje $response with redirect to route
*/
public function showBlox(Request $request, Response $response, $args)
{
# get params from call
$this->uri = $request->getUri();
$this->params = isset($args['params']) ? ['url' => $this->uri->getBasePath() . '/' . $args['params']] : ['url' => $this->uri->getBasePath()];
# set structure
if(!$this->setStructure($draft = true)){ return $this->renderIntern404($response, array( 'navigation' => true, 'content' => $this->errors )); }
# set information for homepage
$this->setHomepage();
# set item
if(!$this->setItem()){ return $this->renderIntern404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); }
# 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();
# 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, $relurl);
}
# 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->render($response, 'editor/editor-blox.twig', array('navigation' => $this->structure, 'homepage' => $this->homepage, 'title' => $title, 'content' => $content, 'item' => $this->item, 'settings' => $this->settings ));
}
public function showEmpty(Request $request, Response $response, $args)
{
return $this->renderIntern404($response, array( 'settings' => $this->settings ));
}
<?php
namespace Typemill\Controllers;
use Slim\Http\Request;
use Slim\Http\Response;
use Slim\Views\Twig;
use Typemill\Models\Folder;
use Typemill\Extensions\ParsedownExtension;
class ContentBackendController extends ContentController
{
/**
* Show Content for raw editor
*
* @param obj $request the slim request object
* @param obj $response the slim response object
* @return obje $response with redirect to route
*/
public function showContent(Request $request, Response $response, $args)
{
# get params from call
$this->uri = $request->getUri();
$this->params = isset($args['params']) ? ['url' => $this->uri->getBasePath() . '/' . $args['params']] : ['url' => $this->uri->getBasePath()];
# set structure
if(!$this->setStructure($draft = true)){ return $this->renderIntern404($response, array( 'navigation' => true, 'content' => $this->errors )); }
# set information for homepage
$this->setHomepage();
# set item
if(!$this->setItem()){ return $this->renderIntern404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); }
# get the breadcrumb (here we need it only to mark the actual item active in navigation)
$breadcrumb = isset($this->item->keyPathArray) ? Folder::getBreadcrumb($this->structure, $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();
$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->render($response, 'editor/editor-raw.twig', array('navigation' => $this->structure, 'homepage' => $this->homepage, 'title' => $title, 'content' => $content, 'item' => $this->item, 'settings' => $this->settings ));
}
/**
* Show Content for blox editor
*
* @param obj $request the slim request object
* @param obj $response the slim response object
* @return obje $response with redirect to route
*/
public function showBlox(Request $request, Response $response, $args)
{
# get params from call
$this->uri = $request->getUri();
$this->params = isset($args['params']) ? ['url' => $this->uri->getBasePath() . '/' . $args['params']] : ['url' => $this->uri->getBasePath()];
# set structure
if(!$this->setStructure($draft = true)){ return $this->renderIntern404($response, array( 'navigation' => true, 'content' => $this->errors )); }
# set information for homepage
$this->setHomepage();
# set item
if(!$this->setItem()){ return $this->renderIntern404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); }
# 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();
# 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, $relurl);
}
# 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->render($response, 'editor/editor-blox.twig', array('navigation' => $this->structure, 'homepage' => $this->homepage, 'title' => $title, 'content' => $content, 'item' => $this->item, 'settings' => $this->settings ));
}
public function showEmpty(Request $request, Response $response, $args)
{
return $this->renderIntern404($response, array( 'settings' => $this->settings ));
}
}

View File

@@ -1,370 +1,370 @@
<?php
namespace Typemill\Controllers;
use Slim\Http\Request;
use Slim\Http\Response;
use Interop\Container\ContainerInterface;
use Typemill\Models\Validation;
use Typemill\Models\Folder;
use Typemill\Models\Write;
use Typemill\Models\WriteCache;
abstract class ContentController
{
# holds the pimple container
protected $c;
# holds the params from request
protected $params;
# holds the slim-uri-object
protected $uri;
# holds the errors to output in frontend
protected $errors = false;
# holds a write object to write files
protected $write;
# holds the structure of content folder as a serialized array of objects
protected $structure;
# holds the name of the structure-file with drafts for author environment
protected $structureDraftName;
# holds the name of the structure-file without drafts for live site
protected $structureLiveName;
# holds informations about the homepage
protected $homepage;
# hold the page-item as an object
protected $item;
# hold the breadcrumb as an object
protected $breadcrumb;
# holds the path to the requested file
protected $path = false;
# holds the content of the page
protected $content;
public function __construct(ContainerInterface $c)
{
$this->c = $c;
$this->settings = $this->c->get('settings');
$this->structureLiveName = 'structure.txt';
$this->structureDraftName = 'structure-draft.txt';
}
protected function render($response, $route, $data)
{
if(isset($_SESSION['old']))
{
unset($_SESSION['old']);
}
$response = $response->withoutHeader('Server');
$response = $response->withoutHeader('X-Powered-By');
if($this->c->request->getUri()->getScheme() == 'https')
{
$response = $response->withAddedHeader('Strict-Transport-Security', 'max-age=63072000');
}
$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');
return $this->c->view->render($response, $route, $data);
}
protected function render404($response, $data = NULL)
{
return $this->c->view->render($response->withStatus(404), '/404.twig', $data);
}
protected function renderIntern404($response, $data = NULL)
{
return $this->c->view->render($response->withStatus(404), '/intern404.twig', $data);
}
protected function validateEditorInput()
{
$validate = new Validation();
$vResult = $validate->editorInput($this->params);
if(is_array($vResult))
{
$message = reset($vResult);
$this->errors = ['errors' => $vResult];
if(isset($message[0])){ $this->errors['errors']['message'] = $message[0]; }
return false;
}
return true;
}
protected function validateBlockInput()
{
$validate = new Validation();
$vResult = $validate->blockInput($this->params);
if(is_array($vResult))
{
$message = reset($vResult);
$this->errors = ['errors' => $vResult];
if(isset($message[0])){ $this->errors['errors']['message'] = $message[0]; }
return false;
}
return true;
}
protected function validateNavigationSort()
{
$validate = new Validation();
$vResult = $validate->navigationSort($this->params);
if(is_array($vResult))
{
$message = reset($vResult);
$this->errors = ['errors' => $vResult];
if(isset($message[0])){ $this->errors['errors']['message'] = $message[0]; }
return false;
}
return true;
}
protected function validateNaviItem()
{
$validate = new Validation();
$vResult = $validate->navigationItem($this->params);
if(is_array($vResult))
{
$message = reset($vResult);
$this->errors = ['errors' => $vResult];
if(isset($message[0])){ $this->errors['errors']['message'] = $message[0]; }
return false;
}
return true;
}
protected function setStructure($draft = false, $cache = true)
{
# set initial structure to false
$structure = false;
# name of structure-file for draft or live
$filename = $draft ? $this->structureDraftName : $this->structureLiveName;
# set variables and objects
$this->write = new writeCache();
# check, if cached structure is still valid
if($cache && $this->write->validate('cache', 'lastCache.txt', 600))
{
# get the cached structure
$structure = $this->write->getCache('cache', $filename);
}
# if no structure was found or cache is deactivated
if(!$structure)
{
# scan the content of the folder
$structure = Folder::scanFolder($this->settings['rootPath'] . $this->settings['contentFolder'], $draft);
# if there is content, then get the content details
if(count($structure) > 0)
{
# create an array of object with the whole content of the folder
$structure = Folder::getFolderContentDetails($structure, $this->uri->getBaseUrl(), $this->uri->getBasePath());
}
# cache navigation
$this->write->updateCache('cache', $filename, 'lastCache.txt', $structure);
}
$this->structure = $structure;
return true;
}
protected function setHomepage()
{
$contentFolder = Folder::scanFolderFlat($this->settings['rootPath'] . $this->settings['contentFolder']);
if(in_array('index.md', $contentFolder))
{
$md = true;
$status = 'published';
}
if(in_array('index.txt', $contentFolder))
{
$txt = true;
$status = 'unpublished';
}
if(isset($txt) && isset($md))
{
$status = 'modified';
}
$active = false;
if($this->params['url'] == '/' || $this->params['url'] == $this->uri->getBasePath() )
{
$active = 'active';
}
$this->homepage = ['status' => $status, 'active' => $active];
}
protected function setItem()
{
# if it is the homepage
if($this->params['url'] == $this->uri->getBasePath() OR $this->params['url'] == '/')
{
$item = new \stdClass;
$item->elementType = 'folder';
$item->path = '';
$item->urlRel = '/';
}
else
{
# search for the url in the structure
$item = Folder::getItemForUrl($this->structure, $this->params['url']);
}
if($item)
{
if($item->elementType == 'file')
{
$pathParts = explode('.', $item->path);
$fileType = array_pop($pathParts);
$pathWithoutType = implode('.', $pathParts);
$item->pathWithoutType = $pathWithoutType;
}
elseif($item->elementType == 'folder')
{
$item->pathWithoutItem = $item->path;
$item->path = $item->path . DIRECTORY_SEPARATOR . 'index';
$item->pathWithoutType = $item->path;
}
$this->item = $item;
return true;
}
$this->errors = ['errors' => ['message' => 'requested page-url not found']];
return false;
}
# determine if you want to write to published file (md) or to draft (txt)
protected function setItemPath($fileType)
{
$this->path = $this->item->pathWithoutType . '.' . $fileType;
}
protected function setPublishStatus()
{
$this->item->published = false;
$this->item->drafted = false;
if(file_exists($this->settings['rootPath'] . $this->settings['contentFolder'] . $this->item->pathWithoutType . '.md'))
{
$this->item->published = true;
# add file-type in case it is a folder
$this->item->fileType = "md";
}
if(file_exists($this->settings['rootPath'] . $this->settings['contentFolder'] . $this->item->pathWithoutType . '.txt'))
{
$this->item->drafted = true;
# add file-type in case it is a folder
$this->item->fileType = "txt";
}
if(!$this->item->drafted && !$this->item->published && $this->item->elementType == "folder")
{
# set txt as default for a folder, so that we can create an index.txt for a folder.
$this->item->fileType = "txt";
}
}
protected function deleteContentFiles($fileTypes, $folder = false)
{
$basePath = $this->settings['rootPath'] . $this->settings['contentFolder'];
foreach($fileTypes as $fileType)
{
if(file_exists($basePath . $this->item->pathWithoutType . '.' . $fileType) && !unlink($basePath . $this->item->pathWithoutType . '.' . $fileType) )
{
$this->errors = ['message' => 'We could not delete the file, please check, if the file is writable.'];
}
}
if($this->errors)
{
return false;
}
return true;
}
protected function deleteContentFolder()
{
$basePath = $this->settings['rootPath'] . $this->settings['contentFolder'];
$path = $basePath . $this->item->pathWithoutItem;
if(file_exists($path))
{
$files = array_diff(scandir($path), array('.', '..'));
# check if there are folders first, then stop the operation
foreach ($files as $file)
{
if(is_dir(realpath($path) . DIRECTORY_SEPARATOR . $file))
{
$this->errors = ['message' => 'Please delete the sub-folder first.'];
}
}
if(!$this->errors)
{
foreach ($files as $file)
{
unlink(realpath($path) . DIRECTORY_SEPARATOR . $file);
}
return rmdir($path);
}
}
return false;
}
protected function setContent()
{
# if the file exists
if($this->item->published OR $this->item->drafted)
{
$content = $this->write->getFile($this->settings['contentFolder'], $this->path);
if($this->item->fileType == 'txt')
{
# decode the json-draft to an array
$content = json_decode($content);
}
}
elseif($this->item->elementType == "folder")
{
$content = '';
}
else
{
$this->errors = ['errors' => ['message' => 'requested file not found']];
return false;
}
$this->content = $content;
return true;
}
<?php
namespace Typemill\Controllers;
use Slim\Http\Request;
use Slim\Http\Response;
use Interop\Container\ContainerInterface;
use Typemill\Models\Validation;
use Typemill\Models\Folder;
use Typemill\Models\Write;
use Typemill\Models\WriteCache;
abstract class ContentController
{
# holds the pimple container
protected $c;
# holds the params from request
protected $params;
# holds the slim-uri-object
protected $uri;
# holds the errors to output in frontend
protected $errors = false;
# holds a write object to write files
protected $write;
# holds the structure of content folder as a serialized array of objects
protected $structure;
# holds the name of the structure-file with drafts for author environment
protected $structureDraftName;
# holds the name of the structure-file without drafts for live site
protected $structureLiveName;
# holds informations about the homepage
protected $homepage;
# hold the page-item as an object
protected $item;
# hold the breadcrumb as an object
protected $breadcrumb;
# holds the path to the requested file
protected $path = false;
# holds the content of the page
protected $content;
public function __construct(ContainerInterface $c)
{
$this->c = $c;
$this->settings = $this->c->get('settings');
$this->structureLiveName = 'structure.txt';
$this->structureDraftName = 'structure-draft.txt';
}
protected function render($response, $route, $data)
{
if(isset($_SESSION['old']))
{
unset($_SESSION['old']);
}
$response = $response->withoutHeader('Server');
$response = $response->withoutHeader('X-Powered-By');
if($this->c->request->getUri()->getScheme() == 'https')
{
$response = $response->withAddedHeader('Strict-Transport-Security', 'max-age=63072000');
}
$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');
return $this->c->view->render($response, $route, $data);
}
protected function render404($response, $data = NULL)
{
return $this->c->view->render($response->withStatus(404), '/404.twig', $data);
}
protected function renderIntern404($response, $data = NULL)
{
return $this->c->view->render($response->withStatus(404), '/intern404.twig', $data);
}
protected function validateEditorInput()
{
$validate = new Validation();
$vResult = $validate->editorInput($this->params);
if(is_array($vResult))
{
$message = reset($vResult);
$this->errors = ['errors' => $vResult];
if(isset($message[0])){ $this->errors['errors']['message'] = $message[0]; }
return false;
}
return true;
}
protected function validateBlockInput()
{
$validate = new Validation();
$vResult = $validate->blockInput($this->params);
if(is_array($vResult))
{
$message = reset($vResult);
$this->errors = ['errors' => $vResult];
if(isset($message[0])){ $this->errors['errors']['message'] = $message[0]; }
return false;
}
return true;
}
protected function validateNavigationSort()
{
$validate = new Validation();
$vResult = $validate->navigationSort($this->params);
if(is_array($vResult))
{
$message = reset($vResult);
$this->errors = ['errors' => $vResult];
if(isset($message[0])){ $this->errors['errors']['message'] = $message[0]; }
return false;
}
return true;
}
protected function validateNaviItem()
{
$validate = new Validation();
$vResult = $validate->navigationItem($this->params);
if(is_array($vResult))
{
$message = reset($vResult);
$this->errors = ['errors' => $vResult];
if(isset($message[0])){ $this->errors['errors']['message'] = $message[0]; }
return false;
}
return true;
}
protected function setStructure($draft = false, $cache = true)
{
# set initial structure to false
$structure = false;
# name of structure-file for draft or live
$filename = $draft ? $this->structureDraftName : $this->structureLiveName;
# set variables and objects
$this->write = new writeCache();
# check, if cached structure is still valid
if($cache && $this->write->validate('cache', 'lastCache.txt', 600))
{
# get the cached structure
$structure = $this->write->getCache('cache', $filename);
}
# if no structure was found or cache is deactivated
if(!$structure)
{
# scan the content of the folder
$structure = Folder::scanFolder($this->settings['rootPath'] . $this->settings['contentFolder'], $draft);
# if there is content, then get the content details
if(count($structure) > 0)
{
# create an array of object with the whole content of the folder
$structure = Folder::getFolderContentDetails($structure, $this->uri->getBaseUrl(), $this->uri->getBasePath());
}
# cache navigation
$this->write->updateCache('cache', $filename, 'lastCache.txt', $structure);
}
$this->structure = $structure;
return true;
}
protected function setHomepage()
{
$contentFolder = Folder::scanFolderFlat($this->settings['rootPath'] . $this->settings['contentFolder']);
if(in_array('index.md', $contentFolder))
{
$md = true;
$status = 'published';
}
if(in_array('index.txt', $contentFolder))
{
$txt = true;
$status = 'unpublished';
}
if(isset($txt) && isset($md))
{
$status = 'modified';
}
$active = false;
if($this->params['url'] == '/' || $this->params['url'] == $this->uri->getBasePath() )
{
$active = 'active';
}
$this->homepage = ['status' => $status, 'active' => $active];
}
protected function setItem()
{
# if it is the homepage
if($this->params['url'] == $this->uri->getBasePath() OR $this->params['url'] == '/')
{
$item = new \stdClass;
$item->elementType = 'folder';
$item->path = '';
$item->urlRel = '/';
}
else
{
# search for the url in the structure
$item = Folder::getItemForUrl($this->structure, $this->params['url']);
}
if($item)
{
if($item->elementType == 'file')
{
$pathParts = explode('.', $item->path);
$fileType = array_pop($pathParts);
$pathWithoutType = implode('.', $pathParts);
$item->pathWithoutType = $pathWithoutType;
}
elseif($item->elementType == 'folder')
{
$item->pathWithoutItem = $item->path;
$item->path = $item->path . DIRECTORY_SEPARATOR . 'index';
$item->pathWithoutType = $item->path;
}
$this->item = $item;
return true;
}
$this->errors = ['errors' => ['message' => 'requested page-url not found']];
return false;
}
# determine if you want to write to published file (md) or to draft (txt)
protected function setItemPath($fileType)
{
$this->path = $this->item->pathWithoutType . '.' . $fileType;
}
protected function setPublishStatus()
{
$this->item->published = false;
$this->item->drafted = false;
if(file_exists($this->settings['rootPath'] . $this->settings['contentFolder'] . $this->item->pathWithoutType . '.md'))
{
$this->item->published = true;
# add file-type in case it is a folder
$this->item->fileType = "md";
}
if(file_exists($this->settings['rootPath'] . $this->settings['contentFolder'] . $this->item->pathWithoutType . '.txt'))
{
$this->item->drafted = true;
# add file-type in case it is a folder
$this->item->fileType = "txt";
}
if(!$this->item->drafted && !$this->item->published && $this->item->elementType == "folder")
{
# set txt as default for a folder, so that we can create an index.txt for a folder.
$this->item->fileType = "txt";
}
}
protected function deleteContentFiles($fileTypes, $folder = false)
{
$basePath = $this->settings['rootPath'] . $this->settings['contentFolder'];
foreach($fileTypes as $fileType)
{
if(file_exists($basePath . $this->item->pathWithoutType . '.' . $fileType) && !unlink($basePath . $this->item->pathWithoutType . '.' . $fileType) )
{
$this->errors = ['message' => 'We could not delete the file, please check, if the file is writable.'];
}
}
if($this->errors)
{
return false;
}
return true;
}
protected function deleteContentFolder()
{
$basePath = $this->settings['rootPath'] . $this->settings['contentFolder'];
$path = $basePath . $this->item->pathWithoutItem;
if(file_exists($path))
{
$files = array_diff(scandir($path), array('.', '..'));
# check if there are folders first, then stop the operation
foreach ($files as $file)
{
if(is_dir(realpath($path) . DIRECTORY_SEPARATOR . $file))
{
$this->errors = ['message' => 'Please delete the sub-folder first.'];
}
}
if(!$this->errors)
{
foreach ($files as $file)
{
unlink(realpath($path) . DIRECTORY_SEPARATOR . $file);
}
return rmdir($path);
}
}
return false;
}
protected function setContent()
{
# if the file exists
if($this->item->published OR $this->item->drafted)
{
$content = $this->write->getFile($this->settings['contentFolder'], $this->path);
if($this->item->fileType == 'txt')
{
# decode the json-draft to an array
$content = json_decode($content);
}
}
elseif($this->item->elementType == "folder")
{
$content = '';
}
else
{
$this->errors = ['errors' => ['message' => 'requested file not found']];
return false;
}
$this->content = $content;
return true;
}
}

View File

@@ -1,50 +1,51 @@
<?php
namespace Typemill\Controllers;
/* Use the slim-container */
use Slim\Http\Request;
use Slim\Http\Response;
use Slim\Views\Twig;
use Interop\Container\ContainerInterface;
use Typemill\Events\OnPageReady;
abstract class Controller
{
protected $c;
public function __construct(ContainerInterface $c)
{
$this->c = $c;
}
protected function render($response, $route, $data)
{
// $data = $this->c->dispatcher->dispatch('onPageReady', new OnPageReady($data))->getData();
if(isset($_SESSION['old']))
{
unset($_SESSION['old']);
}
$response = $response->withoutHeader('Server');
$response = $response->withoutHeader('X-Powered-By');
if($this->c->request->getUri()->getScheme() == 'https')
{
$response = $response->withAddedHeader('Strict-Transport-Security', 'max-age=63072000');
}
$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');
return $this->c->view->render($response, $route, $data);
}
protected function render404($response, $data = NULL)
{
return $this->c->view->render($response->withStatus(404), '/404.twig', $data);
}
<?php
namespace Typemill\Controllers;
/* Use the slim-container */
use Slim\Http\Request;
use Slim\Http\Response;
use Slim\Views\Twig;
use Interop\Container\ContainerInterface;
use Typemill\Events\OnPageReady;
abstract class Controller
{
protected $c;
public function __construct(ContainerInterface $c)
{
$this->c = $c;
}
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->withoutHeader('X-Powered-By');
if($this->c->request->getUri()->getScheme() == 'https')
{
$response = $response->withAddedHeader('Strict-Transport-Security', 'max-age=63072000');
}
$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');
return $this->c->view->render($response, $route, $data);
}
protected function render404($response, $data = NULL)
{
return $this->c->view->render($response->withStatus(404), '/404.twig', $data);
}
}

View File

@@ -1,135 +1,135 @@
<?php
namespace Typemill\Controllers;
use Typemill\Models\Validation;
use Typemill\Models\WriteYaml;
class FormController extends Controller
{
/*************************************
** SAVE THEME- AND PLUGIN-SETTINGS **
*************************************/
public function savePublicForm($request, $response, $args)
{
if($request->isPost())
{
$params = $request->getParams();
reset($params);
$pluginName = key($params);
$referer = $request->getHeader('HTTP_REFERER');
# simple bot check with honeypot
if(isset($params[$pluginName]['personal-mail']))
{
if($params[$pluginName]['personal-mail'] != '')
{
$this->c->flash->addMessage('publicform', 'bot');
return $response->withRedirect($referer[0]);
}
unset($params[$pluginName]['personal-mail']);
}
#recaptcha check
if(isset($params['g-recaptcha-response']))
{
$recaptchaApi = 'https://www.google.com/recaptcha/api/siteverify';
$settings = $this->c->get('settings');
$secret = isset($settings['plugins'][$pluginName]['recaptcha_secretkey']) ? $settings['plugins'][$pluginName]['recaptcha_secretkey'] : false;
$recaptchaRequest = ['secret' => $secret, 'response' => $params['g-recaptcha-response']];
# use key 'http' even if you send the request to https://...
$options = array(
'http' => array(
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
'method' => 'POST',
'content' => http_build_query($recaptchaRequest),
'timeout' => 5
)
);
$context = stream_context_create($options);
$result = file_get_contents($recaptchaApi, false, $context);
$result = json_decode($result);
if ($result === FALSE || $result->success === FALSE)
{
$this->c->flash->addMessage('publicform', 'bot');
return $response->withRedirect($referer[0]);
}
}
if(isset($params[$pluginName]))
{
# validate the user-input
$this->validateInput('plugins', $pluginName, $params[$pluginName]);
}
# check for errors and redirect to path, if errors found
if(isset($_SESSION['errors']))
{
$this->c->flash->addMessage('error', 'Please correct the errors');
return $response->withRedirect($referer[0]);
}
# clean up and make sure that only validated data are stored
$data = [ $pluginName => $params[$pluginName]];
# create write object
$writeYaml = new WriteYaml();
# write the form data into yaml file
$writeYaml->updateYaml('settings', 'formdata.yaml', $data);
# add message and return to original site
$this->c->flash->addMessage('formdata', $pluginName);
return $response->withRedirect($referer[0]);
}
}
private function validateInput($objectType, $objectName, $userInput)
{
# get settings and start validation
$originalSettings = \Typemill\Settings::getObjectSettings($objectType, $objectName);
$userSettings = \Typemill\Settings::getUserSettings();
$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, $objectName, $fieldDefinition);
}
if(!$fieldDefinition && $fieldName != 'active')
{
$_SESSION['errors'][$objectName][$fieldName] = array('This field is not defined!');
}
}
}
}
<?php
namespace Typemill\Controllers;
use Typemill\Models\Validation;
use Typemill\Models\WriteYaml;
class FormController extends Controller
{
/*************************************
** SAVE THEME- AND PLUGIN-SETTINGS **
*************************************/
public function savePublicForm($request, $response, $args)
{
if($request->isPost())
{
$params = $request->getParams();
reset($params);
$pluginName = key($params);
$referer = $request->getHeader('HTTP_REFERER');
# simple bot check with honeypot
if(isset($params[$pluginName]['personal-mail']))
{
if($params[$pluginName]['personal-mail'] != '')
{
$this->c->flash->addMessage('publicform', 'bot');
return $response->withRedirect($referer[0]);
}
unset($params[$pluginName]['personal-mail']);
}
#recaptcha check
if(isset($params['g-recaptcha-response']))
{
$recaptchaApi = 'https://www.google.com/recaptcha/api/siteverify';
$settings = $this->c->get('settings');
$secret = isset($settings['plugins'][$pluginName]['recaptcha_secretkey']) ? $settings['plugins'][$pluginName]['recaptcha_secretkey'] : false;
$recaptchaRequest = ['secret' => $secret, 'response' => $params['g-recaptcha-response']];
# use key 'http' even if you send the request to https://...
$options = array(
'http' => array(
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
'method' => 'POST',
'content' => http_build_query($recaptchaRequest),
'timeout' => 5
)
);
$context = stream_context_create($options);
$result = file_get_contents($recaptchaApi, false, $context);
$result = json_decode($result);
if ($result === FALSE || $result->success === FALSE)
{
$this->c->flash->addMessage('publicform', 'bot');
return $response->withRedirect($referer[0]);
}
}
if(isset($params[$pluginName]))
{
# validate the user-input
$this->validateInput('plugins', $pluginName, $params[$pluginName]);
}
# check for errors and redirect to path, if errors found
if(isset($_SESSION['errors']))
{
$this->c->flash->addMessage('error', 'Please correct the errors');
return $response->withRedirect($referer[0]);
}
# clean up and make sure that only validated data are stored
$data = [ $pluginName => $params[$pluginName]];
# create write object
$writeYaml = new WriteYaml();
# write the form data into yaml file
$writeYaml->updateYaml('settings', 'formdata.yaml', $data);
# add message and return to original site
$this->c->flash->addMessage('formdata', $pluginName);
return $response->withRedirect($referer[0]);
}
}
private function validateInput($objectType, $objectName, $userInput)
{
# get settings and start validation
$originalSettings = \Typemill\Settings::getObjectSettings($objectType, $objectName);
$userSettings = \Typemill\Settings::getUserSettings();
$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, $objectName, $fieldDefinition);
}
if(!$fieldDefinition && $fieldName != 'active')
{
$_SESSION['errors'][$objectName][$fieldName] = array('This field is not defined!');
}
}
}
}
}

View File

@@ -1,236 +1,236 @@
<?php
namespace Typemill\Controllers;
use Typemill\Models\Folder;
use Typemill\Models\WriteCache;
use Typemill\Models\WriteSitemap;
use Typemill\Models\WriteYaml;
use \Symfony\Component\Yaml\Yaml;
use Typemill\Models\VersionCheck;
use Typemill\Models\Helpers;
use Typemill\Models\Markdown;
use Typemill\Events\OnPagetreeLoaded;
use Typemill\Events\OnBreadcrumbLoaded;
use Typemill\Events\OnItemLoaded;
use Typemill\Events\OnOriginalLoaded;
use Typemill\Events\OnMarkdownLoaded;
use Typemill\Events\OnContentArrayLoaded;
use Typemill\Events\OnHtmlLoaded;
use Typemill\Extensions\ParsedownExtension;
class PageController extends Controller
{
public function index($request, $response, $args)
{
/* Initiate Variables */
$structure = false;
$contentHTML = false;
$item = false;
$breadcrumb = false;
$description = '';
$settings = $this->c->get('settings');
$pathToContent = $settings['rootPath'] . $settings['contentFolder'];
$cache = new WriteCache();
$uri = $request->getUri();
$base_url = $uri->getBaseUrl();
try
{
/* if the cached structure is still valid, use it */
if($cache->validate('cache', 'lastCache.txt',600))
{
$structure = $this->getCachedStructure($cache);
}
if(!isset($structure) OR !$structure)
{
/* if not, get a fresh structure of the content folder */
$structure = $this->getFreshStructure($pathToContent, $cache, $uri);
/* if there is no structure at all, the content folder is probably empty */
if(!$structure)
{
$content = '<h1>No Content</h1><p>Your content folder is empty.</p>';
return $this->render($response, '/index.twig', array( 'content' => $content ));
}
elseif(!$cache->validate('cache', 'lastSitemap.txt', 86400))
{
/* update sitemap */
$sitemap = new WriteSitemap();
$sitemap->updateSitemap('cache', 'sitemap.xml', 'lastSitemap.txt', $structure, $uri->getBaseUrl());
/* check and update the typemill-version in the user settings */
$this->updateVersion($uri->getBaseUrl());
}
}
/* dispatch event and let others manipulate the structure */
$structure = $this->c->dispatcher->dispatch('onPagetreeLoaded', new OnPagetreeLoaded($structure))->getData();
}
catch (Exception $e)
{
echo $e->getMessage();
exit(1);
}
/* if the user is on startpage */
if(empty($args))
{
/* check, if there is an index-file in the root of the content folder */
$contentMD = file_exists($pathToContent . DIRECTORY_SEPARATOR . 'index.md') ? file_get_contents($pathToContent . DIRECTORY_SEPARATOR . 'index.md') : NULL;
}
else
{
/* get the request url */
$urlRel = $uri->getBasePath() . '/' . $args['params'];
/* find the url in the content-item-tree and return the item-object for the file */
$item = Folder::getItemForUrl($structure, $urlRel);
/* if there is still no item, return a 404-page */
if(!$item)
{
return $this->render404($response, array( 'navigation' => $structure, 'settings' => $settings, 'base_url' => $base_url ));
}
/* get breadcrumb for page */
$breadcrumb = Folder::getBreadcrumb($structure, $item->keyPathArray);
$breadcrumb = $this->c->dispatcher->dispatch('onBreadcrumbLoaded', new OnBreadcrumbLoaded($breadcrumb))->getData();
/* add the paging to the item */
$item = Folder::getPagingForItem($structure, $item);
$item = $this->c->dispatcher->dispatch('onItemLoaded', new OnItemLoaded($item))->getData();
/* check if url is a folder. If so, check if there is an index-file in that folder */
if($item->elementType == 'folder')
{
$filePath = $pathToContent . $item->path . DIRECTORY_SEPARATOR . 'index.md';
}
elseif($item->elementType == 'file')
{
$filePath = $pathToContent . $item->path;
}
/* add the modified date for the file */
$item->modified = isset($filePath) ? filemtime($filePath) : false;
/* read the content of the file */
$contentMD = isset($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));
$contentMD = $this->c->dispatcher->dispatch('onMarkdownLoaded', new OnMarkdownLoaded($contentMD))->getData();
/* initialize parsedown */
$parsedown = new ParsedownExtension();
/* set safe mode to escape javascript and html in markdown */
$parsedown->setSafeMode(true);
/* parse markdown-file to content-array */
$contentArray = $parsedown->text($contentMD);
$contentArray = $this->c->dispatcher->dispatch('onContentArrayLoaded', new OnContentArrayLoaded($contentArray))->getData();
/* get the first image from content array */
$firstImage = $this->getFirstImage($contentArray);
$itemUrl = isset($item->urlRel) ? $item->urlRel : false;
/* parse markdown-content-array to content-string */
$contentHTML = $parsedown->markup($contentArray, $itemUrl);
$contentHTML = $this->c->dispatcher->dispatch('onHtmlLoaded', new OnHtmlLoaded($contentHTML))->getData();
/* extract the h1 headline*/
$contentParts = explode("</h1>", $contentHTML);
$title = isset($contentParts[0]) ? strip_tags($contentParts[0]) : $settings['title'];
$contentHTML = isset($contentParts[1]) ? $contentParts[1] : $contentHTML;
/* create excerpt from content */
$excerpt = substr($contentHTML,0,500);
/* create description from excerpt */
$description = isset($excerpt) ? strip_tags($excerpt) : false;
if($description)
{
$description = trim(preg_replace('/\s+/', ' ', $description));
$description = substr($description, 0, 300);
$lastSpace = strrpos($description, ' ');
$description = substr($description, 0, $lastSpace);
}
/* get url and alt-tag for first image, if exists */
if($firstImage)
{
preg_match('#\((.*?)\)#', $firstImage, $img_url);
if($img_url[1])
{
preg_match('#\[(.*?)\]#', $firstImage, $img_alt);
$firstImage = array('img_url' => $base_url . '/' . $img_url[1], 'img_alt' => $img_alt[1]);
}
}
$route = empty($args) && $settings['startpage'] ? '/cover.twig' : '/index.twig';
return $this->render($response, $route, array('navigation' => $structure, 'content' => $contentHTML, 'item' => $item, 'breadcrumb' => $breadcrumb, 'settings' => $settings, 'title' => $title, 'description' => $description, 'base_url' => $base_url, 'image' => $firstImage ));
}
protected function getCachedStructure($cache)
{
return $cache->getCache('cache', 'structure.txt');
}
protected function getFreshStructure($pathToContent, $cache, $uri)
{
/* scan the content of the folder */
$structure = Folder::scanFolder($pathToContent);
/* if there is no content, render an empty page */
if(count($structure) == 0)
{
return false;
}
/* create an array of object with the whole content of the folder */
$structure = Folder::getFolderContentDetails($structure, $uri->getBaseUrl(), $uri->getBasePath());
/* cache navigation */
$cache->updateCache('cache', 'structure.txt', 'lastCache.txt', $structure);
return $structure;
}
protected function updateVersion($baseUrl)
{
/* check the latest public typemill version */
$version = new VersionCheck();
$latestVersion = $version->checkVersion($baseUrl);
if($latestVersion)
{
/* store latest version */
\Typemill\Settings::updateSettings(array('latestVersion' => $latestVersion));
}
}
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;
}
<?php
namespace Typemill\Controllers;
use Typemill\Models\Folder;
use Typemill\Models\WriteCache;
use Typemill\Models\WriteSitemap;
use Typemill\Models\WriteYaml;
use \Symfony\Component\Yaml\Yaml;
use Typemill\Models\VersionCheck;
use Typemill\Models\Helpers;
use Typemill\Models\Markdown;
use Typemill\Events\OnPagetreeLoaded;
use Typemill\Events\OnBreadcrumbLoaded;
use Typemill\Events\OnItemLoaded;
use Typemill\Events\OnOriginalLoaded;
use Typemill\Events\OnMarkdownLoaded;
use Typemill\Events\OnContentArrayLoaded;
use Typemill\Events\OnHtmlLoaded;
use Typemill\Extensions\ParsedownExtension;
class PageController extends Controller
{
public function index($request, $response, $args)
{
/* Initiate Variables */
$structure = false;
$contentHTML = false;
$item = false;
$breadcrumb = false;
$description = '';
$settings = $this->c->get('settings');
$pathToContent = $settings['rootPath'] . $settings['contentFolder'];
$cache = new WriteCache();
$uri = $request->getUri();
$base_url = $uri->getBaseUrl();
try
{
/* if the cached structure is still valid, use it */
if($cache->validate('cache', 'lastCache.txt',600))
{
$structure = $this->getCachedStructure($cache);
}
if(!isset($structure) OR !$structure)
{
/* if not, get a fresh structure of the content folder */
$structure = $this->getFreshStructure($pathToContent, $cache, $uri);
/* if there is no structure at all, the content folder is probably empty */
if(!$structure)
{
$content = '<h1>No Content</h1><p>Your content folder is empty.</p>';
return $this->render($response, '/index.twig', array( 'content' => $content ));
}
elseif(!$cache->validate('cache', 'lastSitemap.txt', 86400))
{
/* update sitemap */
$sitemap = new WriteSitemap();
$sitemap->updateSitemap('cache', 'sitemap.xml', 'lastSitemap.txt', $structure, $uri->getBaseUrl());
/* check and update the typemill-version in the user settings */
$this->updateVersion($uri->getBaseUrl());
}
}
/* dispatch event and let others manipulate the structure */
$structure = $this->c->dispatcher->dispatch('onPagetreeLoaded', new OnPagetreeLoaded($structure))->getData();
}
catch (Exception $e)
{
echo $e->getMessage();
exit(1);
}
/* if the user is on startpage */
if(empty($args))
{
/* check, if there is an index-file in the root of the content folder */
$contentMD = file_exists($pathToContent . DIRECTORY_SEPARATOR . 'index.md') ? file_get_contents($pathToContent . DIRECTORY_SEPARATOR . 'index.md') : NULL;
}
else
{
/* get the request url */
$urlRel = $uri->getBasePath() . '/' . $args['params'];
/* find the url in the content-item-tree and return the item-object for the file */
$item = Folder::getItemForUrl($structure, $urlRel);
/* if there is still no item, return a 404-page */
if(!$item)
{
return $this->render404($response, array( 'navigation' => $structure, 'settings' => $settings, 'base_url' => $base_url ));
}
/* get breadcrumb for page */
$breadcrumb = Folder::getBreadcrumb($structure, $item->keyPathArray);
$breadcrumb = $this->c->dispatcher->dispatch('onBreadcrumbLoaded', new OnBreadcrumbLoaded($breadcrumb))->getData();
/* add the paging to the item */
$item = Folder::getPagingForItem($structure, $item);
$item = $this->c->dispatcher->dispatch('onItemLoaded', new OnItemLoaded($item))->getData();
/* check if url is a folder. If so, check if there is an index-file in that folder */
if($item->elementType == 'folder')
{
$filePath = $pathToContent . $item->path . DIRECTORY_SEPARATOR . 'index.md';
}
elseif($item->elementType == 'file')
{
$filePath = $pathToContent . $item->path;
}
/* add the modified date for the file */
$item->modified = isset($filePath) ? filemtime($filePath) : false;
/* read the content of the file */
$contentMD = isset($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));
$contentMD = $this->c->dispatcher->dispatch('onMarkdownLoaded', new OnMarkdownLoaded($contentMD))->getData();
/* initialize parsedown */
$parsedown = new ParsedownExtension();
/* set safe mode to escape javascript and html in markdown */
$parsedown->setSafeMode(true);
/* parse markdown-file to content-array */
$contentArray = $parsedown->text($contentMD);
$contentArray = $this->c->dispatcher->dispatch('onContentArrayLoaded', new OnContentArrayLoaded($contentArray))->getData();
/* get the first image from content array */
$firstImage = $this->getFirstImage($contentArray);
$itemUrl = isset($item->urlRel) ? $item->urlRel : false;
/* parse markdown-content-array to content-string */
$contentHTML = $parsedown->markup($contentArray, $itemUrl);
$contentHTML = $this->c->dispatcher->dispatch('onHtmlLoaded', new OnHtmlLoaded($contentHTML))->getData();
/* extract the h1 headline*/
$contentParts = explode("</h1>", $contentHTML);
$title = isset($contentParts[0]) ? strip_tags($contentParts[0]) : $settings['title'];
$contentHTML = isset($contentParts[1]) ? $contentParts[1] : $contentHTML;
/* create excerpt from content */
$excerpt = substr($contentHTML,0,500);
/* create description from excerpt */
$description = isset($excerpt) ? strip_tags($excerpt) : false;
if($description)
{
$description = trim(preg_replace('/\s+/', ' ', $description));
$description = substr($description, 0, 300);
$lastSpace = strrpos($description, ' ');
$description = substr($description, 0, $lastSpace);
}
/* get url and alt-tag for first image, if exists */
if($firstImage)
{
preg_match('#\((.*?)\)#', $firstImage, $img_url);
if($img_url[1])
{
preg_match('#\[(.*?)\]#', $firstImage, $img_alt);
$firstImage = array('img_url' => $base_url . '/' . $img_url[1], 'img_alt' => $img_alt[1]);
}
}
$route = empty($args) && $settings['startpage'] ? '/cover.twig' : '/index.twig';
return $this->render($response, $route, array('navigation' => $structure, 'content' => $contentHTML, 'item' => $item, 'breadcrumb' => $breadcrumb, 'settings' => $settings, 'title' => $title, 'description' => $description, 'base_url' => $base_url, 'image' => $firstImage ));
}
protected function getCachedStructure($cache)
{
return $cache->getCache('cache', 'structure.txt');
}
protected function getFreshStructure($pathToContent, $cache, $uri)
{
/* scan the content of the folder */
$structure = Folder::scanFolder($pathToContent);
/* if there is no content, render an empty page */
if(count($structure) == 0)
{
return false;
}
/* create an array of object with the whole content of the folder */
$structure = Folder::getFolderContentDetails($structure, $uri->getBaseUrl(), $uri->getBasePath());
/* cache navigation */
$cache->updateCache('cache', 'structure.txt', 'lastCache.txt', $structure);
return $structure;
}
protected function updateVersion($baseUrl)
{
/* check the latest public typemill version */
$version = new VersionCheck();
$latestVersion = $version->checkVersion($baseUrl);
if($latestVersion)
{
/* store latest version */
\Typemill\Settings::updateSettings(array('latestVersion' => $latestVersion));
}
}
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;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,76 +1,76 @@
<?php
namespace Typemill\Controllers;
use \Symfony\Component\Yaml\Yaml;
use Typemill\Models\Validation;
use Typemill\Models\User;
use Typemill\Models\Write;
class SetupController extends Controller
{
public function show($request, $response, $args)
{
/* make some checks befor you install */
$checkFolder = new Write();
$systemcheck = array();
try{ $checkFolder->checkPath('settings'); }catch(\Exception $e){ $systemcheck['error'][] = $e->getMessage(); }
try{ $checkFolder->checkPath('settings/users'); }catch(\Exception $e){ $systemcheck['error'][] = $e->getMessage(); }
try{ $checkFolder->checkPath('content'); }catch(\Exception $e){ $systemcheck['error'][] = $e->getMessage(); }
try{ $checkFolder->checkPath('cache'); }catch(\Exception $e){ $systemcheck['error'][] = $e->getMessage(); }
try{ $checkFolder->checkPath('media'); }catch(\Exception $e){ $systemcheck['error'][] = $e->getMessage(); }
$systemcheck = empty($systemcheck) ? false : $systemcheck;
return $this->render($response, 'auth/setup.twig', array( 'messages' => $systemcheck ));
}
public function create($request, $response, $args)
{
if($request->isPost())
{
$params = $request->getParams();
$validate = new Validation();
$user = new User();
/* set user as admin */
$params['userrole'] = 'administrator';
/* get userroles for validation */
$userroles = $user->getUserroles();
/* validate user */
if($validate->newUser($params, $userroles))
{
$userdata = array('username' => $params['username'], 'email' => $params['email'], 'userrole' => $params['userrole'], 'password' => $params['password']);
/* create initial user */
$username = $user->createUser($userdata);
if($username)
{
/* login user */
$user->login($username);
# create initial settings file
\Typemill\Settings::createSettings();
return $response->withRedirect($this->c->router->pathFor('setup.welcome'));
}
}
$this->c->flash->addMessage('error', 'Please check your input and try again');
return $response->withRedirect($this->c->router->pathFor('setup.show'));
}
}
public function welcome($request, $response, $args)
{
/* store updated settings */
\Typemill\Settings::updateSettings(array('welcome' => false));
return $this->render($response, 'auth/welcome.twig', array());
}
<?php
namespace Typemill\Controllers;
use \Symfony\Component\Yaml\Yaml;
use Typemill\Models\Validation;
use Typemill\Models\User;
use Typemill\Models\Write;
class SetupController extends Controller
{
public function show($request, $response, $args)
{
/* make some checks befor you install */
$checkFolder = new Write();
$systemcheck = array();
try{ $checkFolder->checkPath('settings'); }catch(\Exception $e){ $systemcheck['error'][] = $e->getMessage(); }
try{ $checkFolder->checkPath('settings/users'); }catch(\Exception $e){ $systemcheck['error'][] = $e->getMessage(); }
try{ $checkFolder->checkPath('content'); }catch(\Exception $e){ $systemcheck['error'][] = $e->getMessage(); }
try{ $checkFolder->checkPath('cache'); }catch(\Exception $e){ $systemcheck['error'][] = $e->getMessage(); }
try{ $checkFolder->checkPath('media'); }catch(\Exception $e){ $systemcheck['error'][] = $e->getMessage(); }
$systemcheck = empty($systemcheck) ? false : $systemcheck;
return $this->render($response, 'auth/setup.twig', array( 'messages' => $systemcheck ));
}
public function create($request, $response, $args)
{
if($request->isPost())
{
$params = $request->getParams();
$validate = new Validation();
$user = new User();
/* set user as admin */
$params['userrole'] = 'administrator';
/* get userroles for validation */
$userroles = $user->getUserroles();
/* validate user */
if($validate->newUser($params, $userroles))
{
$userdata = array('username' => $params['username'], 'email' => $params['email'], 'userrole' => $params['userrole'], 'password' => $params['password']);
/* create initial user */
$username = $user->createUser($userdata);
if($username)
{
/* login user */
$user->login($username);
# create initial settings file
\Typemill\Settings::createSettings();
return $response->withRedirect($this->c->router->pathFor('setup.welcome'));
}
}
$this->c->flash->addMessage('error', 'Please check your input and try again');
return $response->withRedirect($this->c->router->pathFor('setup.show'));
}
}
public function welcome($request, $response, $args)
{
/* store updated settings */
\Typemill\Settings::updateSettings(array('welcome' => false));
return $this->render($response, 'auth/welcome.twig', array());
}
}