Version 1.3.4: Media Library
38
.gitignore
vendored
|
@ -1,20 +1,20 @@
|
|||
cache/lastCache.txt
|
||||
cache/lastSitemap.txt
|
||||
cache/metatabs.yaml
|
||||
cache/navigation.txt
|
||||
cache/sitemap.xml
|
||||
cache/structure-draft.txt
|
||||
cache/structure-extended.yaml
|
||||
cache/structure.txt
|
||||
content/index.yaml
|
||||
content/00-Welcome/index.yaml
|
||||
content/00-Welcome/00-Setup.yaml
|
||||
content/00-Welcome/01-Write-Content.yaml
|
||||
content/00-Welcome/02-Get-Help.yaml
|
||||
content/00-Welcome/03-Markdown-Test.yaml
|
||||
settings/settings.yaml
|
||||
settings/users
|
||||
system/vendor
|
||||
plugins/demo
|
||||
zips
|
||||
cache/lastCache.txt
|
||||
cache/lastSitemap.txt
|
||||
cache/metatabs.yaml
|
||||
cache/navigation.txt
|
||||
cache/sitemap.xml
|
||||
cache/structure-draft.txt
|
||||
cache/structure-extended.yaml
|
||||
cache/structure.txt
|
||||
content/index.yaml
|
||||
content/00-welcome/index.yaml
|
||||
content/00-welcome/00-setup.yaml
|
||||
content/00-welcome/01-write-content.yaml
|
||||
content/00-welcome/02-get-help.yaml
|
||||
content/00-welcome/03-markdown-test.yaml
|
||||
settings/settings.yaml
|
||||
settings/users
|
||||
system/vendor
|
||||
plugins/demo
|
||||
zips
|
||||
build.php
|
80
.htaccess
|
@ -1,41 +1,41 @@
|
|||
RewriteEngine On
|
||||
|
||||
# If your homepage is http://yourdomain.com/yoursite
|
||||
# Set the RewriteBase to:
|
||||
# RewriteBase /yoursite
|
||||
|
||||
# In some environements, an empty RewriteBase is required:
|
||||
# RewriteBase /
|
||||
|
||||
# Protect your system files from prying eyes
|
||||
RewriteRule ^(system\/author\/) - [L]
|
||||
RewriteRule ^(system) - [F,L]
|
||||
RewriteRule ^(content) - [F,L]
|
||||
RewriteRule ^(settings) - [F,L]
|
||||
RewriteRule ^(.*)?\.yml$ - [F,L]
|
||||
Rewriterule ^(.*)?\.yaml$ - [F,L]
|
||||
RewriteRule ^(.*)?\.txt$ - [F,L]
|
||||
RewriteRule ^(.*)?\.example$ - [F,L]
|
||||
RewriteRule ^(.*/)?\.git+ - [F,L]
|
||||
|
||||
# Use this to redirect HTTP to HTTPS on apache servers
|
||||
# RewriteCond %{HTTPS} off
|
||||
# RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
|
||||
|
||||
# Use this to redirect www to non-wwww on apache servers
|
||||
# RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC]
|
||||
# RewriteRule ^(.*)$ http://%1/$1 [R=301,L]
|
||||
|
||||
# Use this to redirect slash/ to no slash urls on apache servers
|
||||
# RewriteCond %{REQUEST_FILENAME} !-d
|
||||
# RewriteRule ^(.*)/$ /$1 [R=301,L]
|
||||
|
||||
# Removes index.php
|
||||
RewriteCond %{THE_REQUEST} ^GET.*index\.php [NC]
|
||||
RewriteRule (.*?)index\.php/*(.*) /$1$2 [R=301,NE,L]
|
||||
|
||||
# Directs all web requests through the site index file
|
||||
RewriteCond %{REQUEST_URI} !^/index\.php
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteEngine On
|
||||
|
||||
# If your homepage is http://yourdomain.com/yoursite
|
||||
# Set the RewriteBase to:
|
||||
# RewriteBase /yoursite
|
||||
|
||||
# In some environements, an empty RewriteBase is required:
|
||||
# RewriteBase /
|
||||
|
||||
# Protect your system files from prying eyes
|
||||
RewriteRule ^(system\/author\/) - [L]
|
||||
RewriteRule ^(system) - [F,L]
|
||||
RewriteRule ^(content) - [F,L]
|
||||
RewriteRule ^(settings) - [F,L]
|
||||
RewriteRule ^(.*)?\.yml$ - [F,L]
|
||||
Rewriterule ^(.*)?\.yaml$ - [F,L]
|
||||
RewriteRule ^(.*)?\.txt$ - [F,L]
|
||||
RewriteRule ^(.*)?\.example$ - [F,L]
|
||||
RewriteRule ^(.*/)?\.git+ - [F,L]
|
||||
|
||||
# Use this to redirect HTTP to HTTPS on apache servers
|
||||
# RewriteCond %{HTTPS} off
|
||||
# RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
|
||||
|
||||
# Use this to redirect www to non-wwww on apache servers
|
||||
# RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC]
|
||||
# RewriteRule ^(.*)$ http://%1/$1 [R=301,L]
|
||||
|
||||
# Use this to redirect slash/ to no slash urls on apache servers
|
||||
# RewriteCond %{REQUEST_FILENAME} !-d
|
||||
# RewriteRule ^(.*)/$ /$1 [R=301,L]
|
||||
|
||||
# Removes index.php
|
||||
RewriteCond %{THE_REQUEST} ^GET.*index\.php [NC]
|
||||
RewriteRule (.*?)index\.php/*(.*) /$1$2 [R=301,NE,L]
|
||||
|
||||
# Directs all web requests through the site index file
|
||||
RewriteCond %{REQUEST_URI} !^/index\.php
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^ index.php [QSA,L]
|
Before Width: | Height: | Size: 1 KiB |
|
@ -58,8 +58,10 @@ QUOTES: Quote
|
|||
TABLE_OF_CONTENTS: Table of Contents
|
||||
TABLE: Table
|
||||
TEXT_FILE: text-file
|
||||
UPLOAD: upload
|
||||
VIDEO: Video
|
||||
|
||||
# others
|
||||
ACCOUNT: Account
|
||||
ACTIVE: Active
|
||||
ACTUAL_PASSWORD: Actual Password
|
||||
|
@ -70,7 +72,9 @@ ADD_ITEM: add item
|
|||
ALL_USERS: All users
|
||||
AUTHOR: Author
|
||||
BACK_TO_STARTPAGE: back to startpage
|
||||
BROWSE: BROWSE
|
||||
BY: by
|
||||
CHOOSE_FILE: Choose file
|
||||
CODE: code
|
||||
CONTENT: Content
|
||||
COPYRIGHT: Copyright
|
||||
|
|
|
@ -22,7 +22,7 @@ en:
|
|||
delete content-block: delete content-block
|
||||
delete row: delete row
|
||||
description: description
|
||||
drag a picture or click to select: drag a picture or click to select
|
||||
drag a picture or click to select: upload an image
|
||||
Head: Head
|
||||
Headline: Headline
|
||||
Horizontal Line: Horizontal Line
|
||||
|
|
|
@ -7,7 +7,6 @@ use Slim\Http\Response;
|
|||
use Typemill\Models\Folder;
|
||||
use Typemill\Models\Write;
|
||||
use Typemill\Models\WriteYaml;
|
||||
use Typemill\Models\ProcessImage;
|
||||
use Typemill\Extensions\ParsedownExtension;
|
||||
use Typemill\Events\OnPagePublished;
|
||||
use Typemill\Events\OnPageUnpublished;
|
||||
|
@ -16,7 +15,7 @@ use Typemill\Events\OnPageSorted;
|
|||
use \URLify;
|
||||
|
||||
|
||||
class ContentApiController extends ContentController
|
||||
class ArticleApiController extends ContentController
|
||||
{
|
||||
public function publishArticle(Request $request, Response $response, $args)
|
||||
{
|
||||
|
@ -847,619 +846,4 @@ class ContentApiController extends ContentController
|
|||
|
||||
return $response->withJson(array('data' => $content, 'errors' => false));
|
||||
}
|
||||
|
||||
public function addBlock(Request $request, Response $response, $args)
|
||||
{
|
||||
/* get params from call */
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
|
||||
/* validate input */
|
||||
if(!$this->validateBlockInput()){ return $response->withJson($this->errors,422); }
|
||||
|
||||
# set structure
|
||||
if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
|
||||
|
||||
/* set item */
|
||||
if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
|
||||
|
||||
# set the status for published and drafted
|
||||
$this->setPublishStatus();
|
||||
|
||||
# set path
|
||||
$this->setItemPath($this->item->fileType);
|
||||
|
||||
# read content from file
|
||||
if(!$this->setContent()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
|
||||
|
||||
# make it more clear which content we have
|
||||
$pageMarkdown = $this->content;
|
||||
|
||||
$blockMarkdown = $this->params['markdown'];
|
||||
|
||||
# standardize line breaks
|
||||
$blockMarkdown = str_replace(array("\r\n", "\r"), "\n", $blockMarkdown);
|
||||
|
||||
# remove surrounding line breaks
|
||||
$blockMarkdown = trim($blockMarkdown, "\n");
|
||||
|
||||
if($pageMarkdown == '')
|
||||
{
|
||||
$pageMarkdown = [];
|
||||
}
|
||||
|
||||
# initialize parsedown extension
|
||||
$parsedown = new ParsedownExtension();
|
||||
|
||||
# if content is not an array, then transform it
|
||||
if(!is_array($pageMarkdown))
|
||||
{
|
||||
# turn markdown into an array of markdown-blocks
|
||||
$pageMarkdown = $parsedown->markdownToArrayBlocks($pageMarkdown);
|
||||
}
|
||||
|
||||
# if it is a new content-block
|
||||
if($this->params['block_id'] == 99999)
|
||||
{
|
||||
# set the id of the markdown-block (it will be one more than the actual array, so count is perfect)
|
||||
$id = count($pageMarkdown);
|
||||
|
||||
# add the new markdown block to the page content
|
||||
$pageMarkdown[] = $blockMarkdown;
|
||||
}
|
||||
elseif(($this->params['block_id'] == 0) OR !isset($pageMarkdown[$this->params['block_id']]))
|
||||
{
|
||||
# if the block does not exists, return an error
|
||||
return $response->withJson(array('data' => false, 'errors' => 'The ID of the content-block is wrong.'), 404);
|
||||
}
|
||||
else
|
||||
{
|
||||
# insert new markdown block
|
||||
array_splice( $pageMarkdown, $this->params['block_id'], 0, $blockMarkdown );
|
||||
$id = $this->params['block_id'];
|
||||
}
|
||||
|
||||
# encode the content into json
|
||||
$pageJson = json_encode($pageMarkdown);
|
||||
|
||||
# set path for the file (or folder)
|
||||
$this->setItemPath('txt');
|
||||
|
||||
/* update the file */
|
||||
if($this->write->writeFile($this->settings['contentFolder'], $this->path, $pageJson))
|
||||
{
|
||||
# update the internal structure
|
||||
$this->setStructure($draft = true, $cache = false);
|
||||
$this->content = $pageMarkdown;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Could not write to file. Please check if the file is writable']], 404);
|
||||
}
|
||||
|
||||
/* set safe mode to escape javascript and html in markdown */
|
||||
$parsedown->setSafeMode(true);
|
||||
|
||||
/* parse markdown-file to content-array */
|
||||
$blockArray = $parsedown->text($blockMarkdown);
|
||||
|
||||
# we assume that toc is not relevant
|
||||
$toc = false;
|
||||
|
||||
# needed for ToC links
|
||||
$relurl = '/tm/content/' . $this->settings['editor'] . '/' . $this->item->urlRel;
|
||||
|
||||
if($blockMarkdown == '[TOC]')
|
||||
{
|
||||
# if block is table of content itself, then generate the table of content
|
||||
$tableofcontent = $this->generateToc();
|
||||
|
||||
# and only use the html-markup
|
||||
$blockHTML = $tableofcontent['html'];
|
||||
}
|
||||
else
|
||||
{
|
||||
# parse markdown-content-array to content-string
|
||||
$blockHTML = $parsedown->markup($blockArray, $relurl);
|
||||
|
||||
# if it is a headline
|
||||
if($blockMarkdown[0] == '#')
|
||||
{
|
||||
# then the TOC holds either false (if no toc used in the page) or it holds an object with the id and toc-markup
|
||||
$toc = $this->generateToc();
|
||||
}
|
||||
}
|
||||
|
||||
return $response->withJson(array('content' => [ 'id' => $id, 'html' => $blockHTML ] , 'markdown' => $blockMarkdown, 'id' => $id, 'toc' => $toc, 'errors' => false));
|
||||
}
|
||||
|
||||
protected function generateToc()
|
||||
{
|
||||
# we assume that page has no table of content
|
||||
$toc = false;
|
||||
|
||||
# make sure $this->content is updated
|
||||
$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;
|
||||
|
||||
# loop through mardkown-array and create html-blocks
|
||||
foreach($content as $key => $block)
|
||||
{
|
||||
# parse markdown-file to content-array
|
||||
$contentArray = $parsedown->text($block);
|
||||
|
||||
if($block == '[TOC]')
|
||||
{
|
||||
# toc is true and holds the key of the table of content now
|
||||
$toc = $key;
|
||||
}
|
||||
|
||||
# parse markdown-content-array to content-string
|
||||
$content[$key] = ['id' => $key, 'html' => $parsedown->markup($contentArray, $relurl)];
|
||||
}
|
||||
|
||||
# if page has a table of content
|
||||
if($toc)
|
||||
{
|
||||
# generate the toc markup
|
||||
$tocMarkup = $parsedown->buildTOC($parsedown->headlines);
|
||||
|
||||
# toc holds the id of the table of content and the html-markup now
|
||||
$toc = ['id' => $toc, 'html' => $tocMarkup];
|
||||
}
|
||||
|
||||
return $toc;
|
||||
}
|
||||
|
||||
public function updateBlock(Request $request, Response $response, $args)
|
||||
{
|
||||
/* get params from call */
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
|
||||
/* validate input */
|
||||
if(!$this->validateBlockInput()){ return $response->withJson($this->errors,422); }
|
||||
|
||||
# set structure
|
||||
if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
|
||||
|
||||
/* set item */
|
||||
if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
|
||||
|
||||
# set the status for published and drafted
|
||||
$this->setPublishStatus();
|
||||
|
||||
# set path
|
||||
$this->setItemPath($this->item->fileType);
|
||||
|
||||
# read content from file
|
||||
if(!$this->setContent()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
|
||||
|
||||
# make it more clear which content we have
|
||||
$pageMarkdown = $this->content;
|
||||
|
||||
$blockMarkdown = $this->params['markdown'];
|
||||
|
||||
# standardize line breaks
|
||||
$blockMarkdown = str_replace(array("\r\n", "\r"), "\n", $blockMarkdown);
|
||||
|
||||
# remove surrounding line breaks
|
||||
$blockMarkdown = trim($blockMarkdown, "\n");
|
||||
|
||||
if($pageMarkdown == '')
|
||||
{
|
||||
$pageMarkdown = [];
|
||||
}
|
||||
|
||||
# initialize parsedown extension
|
||||
$parsedown = new ParsedownExtension();
|
||||
$parsedown->setVisualMode();
|
||||
|
||||
# if content is not an array, then transform it
|
||||
if(!is_array($pageMarkdown))
|
||||
{
|
||||
# turn markdown into an array of markdown-blocks
|
||||
$pageMarkdown = $parsedown->markdownToArrayBlocks($pageMarkdown);
|
||||
}
|
||||
|
||||
if(!isset($pageMarkdown[$this->params['block_id']]))
|
||||
{
|
||||
# if the block does not exists, return an error
|
||||
return $response->withJson(array('data' => false, 'errors' => 'The ID of the content-block is wrong.'), 404);
|
||||
}
|
||||
elseif($this->params['block_id'] == 0)
|
||||
{
|
||||
# if it is the title, then delete the "# " if it exists
|
||||
$blockMarkdown = trim($blockMarkdown, "# ");
|
||||
|
||||
# store the markdown-headline in a separate variable
|
||||
$blockMarkdownTitle = '# ' . $blockMarkdown;
|
||||
|
||||
# add the markdown-headline to the page-markdown
|
||||
$pageMarkdown[0] = $blockMarkdownTitle;
|
||||
$id = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
# update the markdown block in the page content
|
||||
$pageMarkdown[$this->params['block_id']] = $blockMarkdown;
|
||||
$id = $this->params['block_id'];
|
||||
}
|
||||
|
||||
# encode the content into json
|
||||
$pageJson = json_encode($pageMarkdown);
|
||||
|
||||
# set path for the file (or folder)
|
||||
$this->setItemPath('txt');
|
||||
|
||||
/* update the file */
|
||||
if($this->write->writeFile($this->settings['contentFolder'], $this->path, $pageJson))
|
||||
{
|
||||
# update the internal structure
|
||||
$this->setStructure($draft = true, $cache = false);
|
||||
|
||||
# updated the content variable
|
||||
$this->content = $pageMarkdown;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Could not write to file. Please check if the file is writable']], 404);
|
||||
}
|
||||
|
||||
|
||||
/* parse markdown-file to content-array, if title parse title. */
|
||||
if($this->params['block_id'] == 0)
|
||||
{
|
||||
$blockArray = $parsedown->text($blockMarkdownTitle);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* set safe mode to escape javascript and html in markdown */
|
||||
$parsedown->setSafeMode(true);
|
||||
|
||||
$blockArray = $parsedown->text($blockMarkdown);
|
||||
}
|
||||
|
||||
# we assume that toc is not relevant
|
||||
$toc = false;
|
||||
|
||||
# needed for ToC links
|
||||
$relurl = '/tm/content/' . $this->settings['editor'] . '/' . $this->item->urlRel;
|
||||
|
||||
if($blockMarkdown == '[TOC]')
|
||||
{
|
||||
# if block is table of content itself, then generate the table of content
|
||||
$tableofcontent = $this->generateToc();
|
||||
|
||||
# and only use the html-markup
|
||||
$blockHTML = $tableofcontent['html'];
|
||||
}
|
||||
else
|
||||
{
|
||||
# parse markdown-content-array to content-string
|
||||
$blockHTML = $parsedown->markup($blockArray, $relurl);
|
||||
|
||||
# if it is a headline
|
||||
if($blockMarkdown[0] == '#')
|
||||
{
|
||||
# then the TOC holds either false (if no toc used in the page) or it holds an object with the id and toc-markup
|
||||
$toc = $this->generateToc();
|
||||
}
|
||||
}
|
||||
|
||||
return $response->withJson(array('content' => [ 'id' => $id, 'html' => $blockHTML ] , 'markdown' => $blockMarkdown, 'id' => $id, 'toc' => $toc, 'errors' => false));
|
||||
}
|
||||
|
||||
public function moveBlock(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
|
||||
# validate input
|
||||
# if(!$this->validateBlockInput()){ return $response->withJson($this->errors,422); }
|
||||
|
||||
# set structure
|
||||
if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
|
||||
|
||||
# set item
|
||||
if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
|
||||
|
||||
# set the status for published and drafted
|
||||
$this->setPublishStatus();
|
||||
|
||||
# set path
|
||||
$this->setItemPath($this->item->fileType);
|
||||
|
||||
# read content from file
|
||||
if(!$this->setContent()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
|
||||
|
||||
# make it more clear which content we have
|
||||
$pageMarkdown = $this->content;
|
||||
|
||||
if($pageMarkdown == '')
|
||||
{
|
||||
$pageMarkdown = [];
|
||||
}
|
||||
|
||||
# initialize parsedown extension
|
||||
$parsedown = new ParsedownExtension();
|
||||
|
||||
# if content is not an array, then transform it
|
||||
if(!is_array($pageMarkdown))
|
||||
{
|
||||
# turn markdown into an array of markdown-blocks
|
||||
$pageMarkdown = $parsedown->markdownToArrayBlocks($pageMarkdown);
|
||||
}
|
||||
|
||||
$oldIndex = ($this->params['old_index'] + 1);
|
||||
$newIndex = ($this->params['new_index'] + 1);
|
||||
|
||||
if(!isset($pageMarkdown[$oldIndex]))
|
||||
{
|
||||
# if the block does not exists, return an error
|
||||
return $response->withJson(array('data' => false, 'errors' => 'The ID of the content-block is wrong.'), 404);
|
||||
}
|
||||
|
||||
$extract = array_splice($pageMarkdown, $oldIndex, 1);
|
||||
array_splice($pageMarkdown, $newIndex, 0, $extract);
|
||||
|
||||
# encode the content into json
|
||||
$pageJson = json_encode($pageMarkdown);
|
||||
|
||||
# set path for the file (or folder)
|
||||
$this->setItemPath('txt');
|
||||
|
||||
/* update the file */
|
||||
if($this->write->writeFile($this->settings['contentFolder'], $this->path, $pageJson))
|
||||
{
|
||||
# update the internal structure
|
||||
$this->setStructure($draft = true, $cache = false);
|
||||
|
||||
# update this content
|
||||
$this->content = $pageMarkdown;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Could not write to file. Please check if the file is writable']], 404);
|
||||
}
|
||||
|
||||
# we assume that toc is not relevant
|
||||
$toc = false;
|
||||
|
||||
# needed for ToC links
|
||||
$relurl = '/tm/content/' . $this->settings['editor'] . '/' . $this->item->urlRel;
|
||||
|
||||
# if the moved item is a headline
|
||||
if($extract[0][0] == '#')
|
||||
{
|
||||
$toc = $this->generateToc();
|
||||
}
|
||||
|
||||
# if it is the title, then delete the "# " if it exists
|
||||
$pageMarkdown[0] = trim($pageMarkdown[0], "# ");
|
||||
|
||||
return $response->withJson(array('markdown' => $pageMarkdown, 'toc' => $toc, 'errors' => false));
|
||||
}
|
||||
|
||||
public function deleteBlock(Request $request, Response $response, $args)
|
||||
{
|
||||
/* get params from call */
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
$errors = false;
|
||||
|
||||
# set structure
|
||||
if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
|
||||
|
||||
# set item
|
||||
if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
|
||||
|
||||
# set the status for published and drafted
|
||||
$this->setPublishStatus();
|
||||
|
||||
# set path
|
||||
$this->setItemPath($this->item->fileType);
|
||||
|
||||
# read content from file
|
||||
if(!$this->setContent()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
|
||||
|
||||
# get content
|
||||
$this->content;
|
||||
|
||||
if($this->content == '')
|
||||
{
|
||||
$this->content = [];
|
||||
}
|
||||
|
||||
# initialize parsedown extension
|
||||
$parsedown = new ParsedownExtension();
|
||||
|
||||
# if content is not an array, then transform it
|
||||
if(!is_array($this->content))
|
||||
{
|
||||
# turn markdown into an array of markdown-blocks
|
||||
$this->content = $parsedown->markdownToArrayBlocks($this->content);
|
||||
}
|
||||
|
||||
# check if id exists
|
||||
if(!isset($this->content[$this->params['block_id']])){ return $response->withJson(array('data' => false, 'errors' => 'The ID of the content-block is wrong.'), 404); }
|
||||
|
||||
# check if block is image
|
||||
$contentBlock = $this->content[$this->params['block_id']];
|
||||
$contentBlockStart = substr($contentBlock, 0, 2);
|
||||
if($contentBlockStart == '[!' OR $contentBlockStart == '![')
|
||||
{
|
||||
# extract image path
|
||||
preg_match("/\((.*?)\)/",$contentBlock,$matches);
|
||||
if(isset($matches[1]))
|
||||
{
|
||||
$imageBaseName = explode('-', $matches[1]);
|
||||
$imageBaseName = str_replace('media/live/', '', $imageBaseName[0]);
|
||||
$processImage = new ProcessImage();
|
||||
if(!$processImage->deleteImage($imageBaseName))
|
||||
{
|
||||
$errors = 'Could not delete some of the images, please check manually';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# delete the block
|
||||
unset($this->content[$this->params['block_id']]);
|
||||
$this->content = array_values($this->content);
|
||||
|
||||
$pageMarkdown = $this->content;
|
||||
|
||||
# delete markdown from title
|
||||
if(isset($pageMarkdown[0]))
|
||||
{
|
||||
$pageMarkdown[0] = trim($pageMarkdown[0], "# ");
|
||||
}
|
||||
|
||||
# encode the content into json
|
||||
$pageJson = json_encode($this->content);
|
||||
|
||||
# set path for the file (or folder)
|
||||
$this->setItemPath('txt');
|
||||
|
||||
/* update the file */
|
||||
if($this->write->writeFile($this->settings['contentFolder'], $this->path, $pageJson))
|
||||
{
|
||||
# update the internal structure
|
||||
$this->setStructure($draft = true, $cache = false);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Could not write to file. Please check if the file is writable']], 404);
|
||||
}
|
||||
|
||||
$toc = false;
|
||||
|
||||
if($contentBlock[0] == '#')
|
||||
{
|
||||
$toc = $this->generateToc();
|
||||
}
|
||||
|
||||
return $response->withJson(array('markdown' => $pageMarkdown, 'toc' => $toc, 'errors' => $errors));
|
||||
}
|
||||
|
||||
public function createImage(Request $request, Response $response, $args)
|
||||
{
|
||||
/* get params from call */
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
|
||||
$imageProcessor = new ProcessImage();
|
||||
|
||||
if($imageProcessor->createImage($this->params['image'], $this->settings['images']))
|
||||
{
|
||||
return $response->withJson(array('errors' => false));
|
||||
}
|
||||
|
||||
return $response->withJson(array('errors' => 'could not store image to temporary folder'));
|
||||
}
|
||||
|
||||
public function publishImage(Request $request, Response $response, $args)
|
||||
{
|
||||
$params = $request->getParsedBody();
|
||||
|
||||
$imageProcessor = new ProcessImage();
|
||||
|
||||
$imageUrl = $imageProcessor->publishImage($this->settings['images'], $name = false);
|
||||
if($imageUrl)
|
||||
{
|
||||
$params['markdown'] = str_replace('imgplchldr', $imageUrl, $params['markdown']);
|
||||
|
||||
$request = $request->withParsedBody($params);
|
||||
|
||||
return $this->addBlock($request, $response, $args);
|
||||
}
|
||||
|
||||
return $response->withJson(array('errors' => 'could not store image to media folder'));
|
||||
}
|
||||
|
||||
public function saveVideoImage(Request $request, Response $response, $args)
|
||||
{
|
||||
/* get params from call */
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
$class = false;
|
||||
|
||||
$imageUrl = $this->params['markdown'];
|
||||
|
||||
if(strpos($imageUrl, 'https://www.youtube.com/watch?v=') !== false)
|
||||
{
|
||||
$videoID = str_replace('https://www.youtube.com/watch?v=', '', $imageUrl);
|
||||
$videoID = strpos($videoID, '&') ? substr($videoID, 0, strpos($videoID, '&')) : $videoID;
|
||||
$class = 'youtube';
|
||||
}
|
||||
if(strpos($imageUrl, 'https://youtu.be/') !== false)
|
||||
{
|
||||
$videoID = str_replace('https://youtu.be/', '', $imageUrl);
|
||||
$videoID = strpos($videoID, '?') ? substr($videoID, 0, strpos($videoID, '?')) : $videoID;
|
||||
$class = 'youtube';
|
||||
}
|
||||
|
||||
if($class == 'youtube')
|
||||
{
|
||||
$videoURLmaxres = 'https://i1.ytimg.com/vi/' . $videoID . '/maxresdefault.jpg';
|
||||
$videoURL0 = 'https://i1.ytimg.com/vi/' . $videoID . '/0.jpg';
|
||||
}
|
||||
|
||||
$ctx = stream_context_create(array(
|
||||
'https' => array(
|
||||
'timeout' => 1
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$imageData = @file_get_contents($videoURLmaxres, 0, $ctx);
|
||||
if($imageData === false)
|
||||
{
|
||||
$imageData = @file_get_contents($videoURL0, 0, $ctx);
|
||||
if($imageData === false)
|
||||
{
|
||||
return $response->withJson(array('errors' => 'could not get the video image'));
|
||||
}
|
||||
}
|
||||
|
||||
$imageData64 = 'data:image/jpeg;base64,' . base64_encode($imageData);
|
||||
$desiredSizes = ['live' => ['width' => 560, 'height' => 315]];
|
||||
$imageProcessor = new ProcessImage();
|
||||
$tmpImage = $imageProcessor->createImage($imageData64, $desiredSizes);
|
||||
|
||||
if(!$tmpImage)
|
||||
{
|
||||
return $response->withJson(array('errors' => 'could not create temporary image'));
|
||||
}
|
||||
|
||||
$imageUrl = $imageProcessor->publishImage($desiredSizes, $videoID);
|
||||
if($imageUrl)
|
||||
{
|
||||
$this->params['markdown'] = '{#' . $videoID. ' .' . $class . '}';
|
||||
|
||||
$request = $request->withParsedBody($this->params);
|
||||
|
||||
return $this->addBlock($request, $response, $args);
|
||||
}
|
||||
|
||||
return $response->withJson(array('errors' => 'could not store the preview image'));
|
||||
}
|
||||
}
|
841
system/Controllers/BlockApiController.php
Normal file
|
@ -0,0 +1,841 @@
|
|||
<?php
|
||||
|
||||
namespace Typemill\Controllers;
|
||||
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
use Typemill\Models\Folder;
|
||||
use Typemill\Models\Write;
|
||||
use Typemill\Models\WriteYaml;
|
||||
use Typemill\Models\ProcessImage;
|
||||
use Typemill\Models\ProcessFile;
|
||||
use Typemill\Extensions\ParsedownExtension;
|
||||
use \URLify;
|
||||
|
||||
class BlockApiController extends ContentController
|
||||
{
|
||||
|
||||
public function addBlock(Request $request, Response $response, $args)
|
||||
{
|
||||
/* get params from call */
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
|
||||
/* validate input */
|
||||
if(!$this->validateBlockInput()){ return $response->withJson($this->errors,422); }
|
||||
|
||||
# set structure
|
||||
if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
|
||||
|
||||
/* set item */
|
||||
if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
|
||||
|
||||
# set the status for published and drafted
|
||||
$this->setPublishStatus();
|
||||
|
||||
# set path
|
||||
$this->setItemPath($this->item->fileType);
|
||||
|
||||
# read content from file
|
||||
if(!$this->setContent()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
|
||||
|
||||
# make it more clear which content we have
|
||||
$pageMarkdown = $this->content;
|
||||
|
||||
$blockMarkdown = $this->params['markdown'];
|
||||
|
||||
# standardize line breaks
|
||||
$blockMarkdown = str_replace(array("\r\n", "\r"), "\n", $blockMarkdown);
|
||||
|
||||
# remove surrounding line breaks
|
||||
$blockMarkdown = trim($blockMarkdown, "\n");
|
||||
|
||||
if($pageMarkdown == '')
|
||||
{
|
||||
$pageMarkdown = [];
|
||||
}
|
||||
|
||||
# initialize parsedown extension
|
||||
$parsedown = new ParsedownExtension();
|
||||
|
||||
# if content is not an array, then transform it
|
||||
if(!is_array($pageMarkdown))
|
||||
{
|
||||
# turn markdown into an array of markdown-blocks
|
||||
$pageMarkdown = $parsedown->markdownToArrayBlocks($pageMarkdown);
|
||||
}
|
||||
|
||||
# if it is a new content-block
|
||||
if($this->params['block_id'] == 99999)
|
||||
{
|
||||
# set the id of the markdown-block (it will be one more than the actual array, so count is perfect)
|
||||
$id = count($pageMarkdown);
|
||||
|
||||
# add the new markdown block to the page content
|
||||
$pageMarkdown[] = $blockMarkdown;
|
||||
}
|
||||
elseif(($this->params['block_id'] == 0) OR !isset($pageMarkdown[$this->params['block_id']]))
|
||||
{
|
||||
# if the block does not exists, return an error
|
||||
return $response->withJson(array('data' => false, 'errors' => 'The ID of the content-block is wrong.'), 404);
|
||||
}
|
||||
else
|
||||
{
|
||||
# insert new markdown block
|
||||
array_splice( $pageMarkdown, $this->params['block_id'], 0, $blockMarkdown );
|
||||
$id = $this->params['block_id'];
|
||||
}
|
||||
|
||||
# encode the content into json
|
||||
$pageJson = json_encode($pageMarkdown);
|
||||
|
||||
# set path for the file (or folder)
|
||||
$this->setItemPath('txt');
|
||||
|
||||
/* update the file */
|
||||
if($this->write->writeFile($this->settings['contentFolder'], $this->path, $pageJson))
|
||||
{
|
||||
# update the internal structure
|
||||
$this->setStructure($draft = true, $cache = false);
|
||||
$this->content = $pageMarkdown;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Could not write to file. Please check if the file is writable']], 404);
|
||||
}
|
||||
|
||||
/* set safe mode to escape javascript and html in markdown */
|
||||
$parsedown->setSafeMode(true);
|
||||
|
||||
/* parse markdown-file to content-array */
|
||||
$blockArray = $parsedown->text($blockMarkdown);
|
||||
|
||||
# we assume that toc is not relevant
|
||||
$toc = false;
|
||||
|
||||
# needed for ToC links
|
||||
$relurl = '/tm/content/' . $this->settings['editor'] . '/' . $this->item->urlRel;
|
||||
|
||||
if($blockMarkdown == '[TOC]')
|
||||
{
|
||||
# if block is table of content itself, then generate the table of content
|
||||
$tableofcontent = $this->generateToc();
|
||||
|
||||
# and only use the html-markup
|
||||
$blockHTML = $tableofcontent['html'];
|
||||
}
|
||||
else
|
||||
{
|
||||
# parse markdown-content-array to content-string
|
||||
$blockHTML = $parsedown->markup($blockArray, $relurl);
|
||||
|
||||
# if it is a headline
|
||||
if($blockMarkdown[0] == '#')
|
||||
{
|
||||
# then the TOC holds either false (if no toc used in the page) or it holds an object with the id and toc-markup
|
||||
$toc = $this->generateToc();
|
||||
}
|
||||
}
|
||||
|
||||
return $response->withJson(array('content' => [ 'id' => $id, 'html' => $blockHTML ] , 'markdown' => $blockMarkdown, 'id' => $id, 'toc' => $toc, 'errors' => false));
|
||||
}
|
||||
|
||||
protected function generateToc()
|
||||
{
|
||||
# we assume that page has no table of content
|
||||
$toc = false;
|
||||
|
||||
# make sure $this->content is updated
|
||||
$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;
|
||||
|
||||
# loop through mardkown-array and create html-blocks
|
||||
foreach($content as $key => $block)
|
||||
{
|
||||
# parse markdown-file to content-array
|
||||
$contentArray = $parsedown->text($block);
|
||||
|
||||
if($block == '[TOC]')
|
||||
{
|
||||
# toc is true and holds the key of the table of content now
|
||||
$toc = $key;
|
||||
}
|
||||
|
||||
# parse markdown-content-array to content-string
|
||||
$content[$key] = ['id' => $key, 'html' => $parsedown->markup($contentArray, $relurl)];
|
||||
}
|
||||
|
||||
# if page has a table of content
|
||||
if($toc)
|
||||
{
|
||||
# generate the toc markup
|
||||
$tocMarkup = $parsedown->buildTOC($parsedown->headlines);
|
||||
|
||||
# toc holds the id of the table of content and the html-markup now
|
||||
$toc = ['id' => $toc, 'html' => $tocMarkup];
|
||||
}
|
||||
|
||||
return $toc;
|
||||
}
|
||||
|
||||
public function updateBlock(Request $request, Response $response, $args)
|
||||
{
|
||||
/* get params from call */
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
|
||||
/* validate input */
|
||||
if(!$this->validateBlockInput()){ return $response->withJson($this->errors,422); }
|
||||
|
||||
# set structure
|
||||
if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
|
||||
|
||||
/* set item */
|
||||
if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
|
||||
|
||||
# set the status for published and drafted
|
||||
$this->setPublishStatus();
|
||||
|
||||
# set path
|
||||
$this->setItemPath($this->item->fileType);
|
||||
|
||||
# read content from file
|
||||
if(!$this->setContent()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
|
||||
|
||||
# make it more clear which content we have
|
||||
$pageMarkdown = $this->content;
|
||||
|
||||
$blockMarkdown = $this->params['markdown'];
|
||||
|
||||
# standardize line breaks
|
||||
$blockMarkdown = str_replace(array("\r\n", "\r"), "\n", $blockMarkdown);
|
||||
|
||||
# remove surrounding line breaks
|
||||
$blockMarkdown = trim($blockMarkdown, "\n");
|
||||
|
||||
if($pageMarkdown == '')
|
||||
{
|
||||
$pageMarkdown = [];
|
||||
}
|
||||
|
||||
# initialize parsedown extension
|
||||
$parsedown = new ParsedownExtension();
|
||||
$parsedown->setVisualMode();
|
||||
|
||||
# if content is not an array, then transform it
|
||||
if(!is_array($pageMarkdown))
|
||||
{
|
||||
# turn markdown into an array of markdown-blocks
|
||||
$pageMarkdown = $parsedown->markdownToArrayBlocks($pageMarkdown);
|
||||
}
|
||||
|
||||
if(!isset($pageMarkdown[$this->params['block_id']]))
|
||||
{
|
||||
# if the block does not exists, return an error
|
||||
return $response->withJson(array('data' => false, 'errors' => 'The ID of the content-block is wrong.'), 404);
|
||||
}
|
||||
elseif($this->params['block_id'] == 0)
|
||||
{
|
||||
# if it is the title, then delete the "# " if it exists
|
||||
$blockMarkdown = trim($blockMarkdown, "# ");
|
||||
|
||||
# store the markdown-headline in a separate variable
|
||||
$blockMarkdownTitle = '# ' . $blockMarkdown;
|
||||
|
||||
# add the markdown-headline to the page-markdown
|
||||
$pageMarkdown[0] = $blockMarkdownTitle;
|
||||
$id = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
# update the markdown block in the page content
|
||||
$pageMarkdown[$this->params['block_id']] = $blockMarkdown;
|
||||
$id = $this->params['block_id'];
|
||||
}
|
||||
|
||||
# encode the content into json
|
||||
$pageJson = json_encode($pageMarkdown);
|
||||
|
||||
# set path for the file (or folder)
|
||||
$this->setItemPath('txt');
|
||||
|
||||
/* update the file */
|
||||
if($this->write->writeFile($this->settings['contentFolder'], $this->path, $pageJson))
|
||||
{
|
||||
# update the internal structure
|
||||
$this->setStructure($draft = true, $cache = false);
|
||||
|
||||
# updated the content variable
|
||||
$this->content = $pageMarkdown;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Could not write to file. Please check if the file is writable']], 404);
|
||||
}
|
||||
|
||||
|
||||
/* parse markdown-file to content-array, if title parse title. */
|
||||
if($this->params['block_id'] == 0)
|
||||
{
|
||||
$blockArray = $parsedown->text($blockMarkdownTitle);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* set safe mode to escape javascript and html in markdown */
|
||||
$parsedown->setSafeMode(true);
|
||||
|
||||
$blockArray = $parsedown->text($blockMarkdown);
|
||||
}
|
||||
|
||||
# we assume that toc is not relevant
|
||||
$toc = false;
|
||||
|
||||
# needed for ToC links
|
||||
$relurl = '/tm/content/' . $this->settings['editor'] . '/' . $this->item->urlRel;
|
||||
|
||||
if($blockMarkdown == '[TOC]')
|
||||
{
|
||||
# if block is table of content itself, then generate the table of content
|
||||
$tableofcontent = $this->generateToc();
|
||||
|
||||
# and only use the html-markup
|
||||
$blockHTML = $tableofcontent['html'];
|
||||
}
|
||||
else
|
||||
{
|
||||
# parse markdown-content-array to content-string
|
||||
$blockHTML = $parsedown->markup($blockArray, $relurl);
|
||||
|
||||
# if it is a headline
|
||||
if($blockMarkdown[0] == '#')
|
||||
{
|
||||
# then the TOC holds either false (if no toc used in the page) or it holds an object with the id and toc-markup
|
||||
$toc = $this->generateToc();
|
||||
}
|
||||
}
|
||||
|
||||
return $response->withJson(array('content' => [ 'id' => $id, 'html' => $blockHTML ] , 'markdown' => $blockMarkdown, 'id' => $id, 'toc' => $toc, 'errors' => false));
|
||||
}
|
||||
|
||||
public function moveBlock(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
|
||||
# validate input
|
||||
# if(!$this->validateBlockInput()){ return $response->withJson($this->errors,422); }
|
||||
|
||||
# set structure
|
||||
if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
|
||||
|
||||
# set item
|
||||
if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
|
||||
|
||||
# set the status for published and drafted
|
||||
$this->setPublishStatus();
|
||||
|
||||
# set path
|
||||
$this->setItemPath($this->item->fileType);
|
||||
|
||||
# read content from file
|
||||
if(!$this->setContent()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
|
||||
|
||||
# make it more clear which content we have
|
||||
$pageMarkdown = $this->content;
|
||||
|
||||
if($pageMarkdown == '')
|
||||
{
|
||||
$pageMarkdown = [];
|
||||
}
|
||||
|
||||
# initialize parsedown extension
|
||||
$parsedown = new ParsedownExtension();
|
||||
|
||||
# if content is not an array, then transform it
|
||||
if(!is_array($pageMarkdown))
|
||||
{
|
||||
# turn markdown into an array of markdown-blocks
|
||||
$pageMarkdown = $parsedown->markdownToArrayBlocks($pageMarkdown);
|
||||
}
|
||||
|
||||
$oldIndex = ($this->params['old_index'] + 1);
|
||||
$newIndex = ($this->params['new_index'] + 1);
|
||||
|
||||
if(!isset($pageMarkdown[$oldIndex]))
|
||||
{
|
||||
# if the block does not exists, return an error
|
||||
return $response->withJson(array('data' => false, 'errors' => 'The ID of the content-block is wrong.'), 404);
|
||||
}
|
||||
|
||||
$extract = array_splice($pageMarkdown, $oldIndex, 1);
|
||||
array_splice($pageMarkdown, $newIndex, 0, $extract);
|
||||
|
||||
# encode the content into json
|
||||
$pageJson = json_encode($pageMarkdown);
|
||||
|
||||
# set path for the file (or folder)
|
||||
$this->setItemPath('txt');
|
||||
|
||||
/* update the file */
|
||||
if($this->write->writeFile($this->settings['contentFolder'], $this->path, $pageJson))
|
||||
{
|
||||
# update the internal structure
|
||||
$this->setStructure($draft = true, $cache = false);
|
||||
|
||||
# update this content
|
||||
$this->content = $pageMarkdown;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Could not write to file. Please check if the file is writable']], 404);
|
||||
}
|
||||
|
||||
# we assume that toc is not relevant
|
||||
$toc = false;
|
||||
|
||||
# needed for ToC links
|
||||
$relurl = '/tm/content/' . $this->settings['editor'] . '/' . $this->item->urlRel;
|
||||
|
||||
# if the moved item is a headline
|
||||
if($extract[0][0] == '#')
|
||||
{
|
||||
$toc = $this->generateToc();
|
||||
}
|
||||
|
||||
# if it is the title, then delete the "# " if it exists
|
||||
$pageMarkdown[0] = trim($pageMarkdown[0], "# ");
|
||||
|
||||
return $response->withJson(array('markdown' => $pageMarkdown, 'toc' => $toc, 'errors' => false));
|
||||
}
|
||||
|
||||
public function deleteBlock(Request $request, Response $response, $args)
|
||||
{
|
||||
/* get params from call */
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
$errors = false;
|
||||
|
||||
# set structure
|
||||
if(!$this->setStructure($draft = true)){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
|
||||
|
||||
# set item
|
||||
if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
|
||||
|
||||
# set the status for published and drafted
|
||||
$this->setPublishStatus();
|
||||
|
||||
# set path
|
||||
$this->setItemPath($this->item->fileType);
|
||||
|
||||
# read content from file
|
||||
if(!$this->setContent()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
|
||||
|
||||
# get content
|
||||
$this->content;
|
||||
|
||||
if($this->content == '')
|
||||
{
|
||||
$this->content = [];
|
||||
}
|
||||
|
||||
# initialize parsedown extension
|
||||
$parsedown = new ParsedownExtension();
|
||||
|
||||
# if content is not an array, then transform it
|
||||
if(!is_array($this->content))
|
||||
{
|
||||
# turn markdown into an array of markdown-blocks
|
||||
$this->content = $parsedown->markdownToArrayBlocks($this->content);
|
||||
}
|
||||
|
||||
# check if id exists
|
||||
if(!isset($this->content[$this->params['block_id']])){ return $response->withJson(array('data' => false, 'errors' => 'The ID of the content-block is wrong.'), 404); }
|
||||
|
||||
$contentBlock = $this->content[$this->params['block_id']];
|
||||
|
||||
# delete the block
|
||||
unset($this->content[$this->params['block_id']]);
|
||||
$this->content = array_values($this->content);
|
||||
|
||||
$pageMarkdown = $this->content;
|
||||
|
||||
# delete markdown from title
|
||||
if(isset($pageMarkdown[0]))
|
||||
{
|
||||
$pageMarkdown[0] = trim($pageMarkdown[0], "# ");
|
||||
}
|
||||
|
||||
# encode the content into json
|
||||
$pageJson = json_encode($this->content);
|
||||
|
||||
# set path for the file (or folder)
|
||||
$this->setItemPath('txt');
|
||||
|
||||
/* update the file */
|
||||
if($this->write->writeFile($this->settings['contentFolder'], $this->path, $pageJson))
|
||||
{
|
||||
# update the internal structure
|
||||
$this->setStructure($draft = true, $cache = false);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Could not write to file. Please check if the file is writable']], 404);
|
||||
}
|
||||
|
||||
$toc = false;
|
||||
|
||||
if($contentBlock[0] == '#')
|
||||
{
|
||||
$toc = $this->generateToc();
|
||||
}
|
||||
|
||||
return $response->withJson(array('markdown' => $pageMarkdown, 'toc' => $toc, 'errors' => $errors));
|
||||
}
|
||||
|
||||
public function getMediaLibImages(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
if(!$imageProcessor->checkFolders('images'))
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Please check if your media-folder exists and all folders inside are writable.']], 500);
|
||||
}
|
||||
|
||||
$imagelist = $imageProcessor->scanMediaFlat();
|
||||
|
||||
return $response->withJson(array('images' => $imagelist));
|
||||
}
|
||||
|
||||
public function getMediaLibFiles(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
|
||||
$fileProcessor = new ProcessFile();
|
||||
if(!$fileProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Please check if your media-folder exists and all folders inside are writable.']], 500);
|
||||
}
|
||||
|
||||
$filelist = $fileProcessor->scanFilesFlat();
|
||||
|
||||
return $response->withJson(array('files' => $filelist));
|
||||
}
|
||||
|
||||
public function getImage(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
|
||||
$this->setStructure($draft = true, $cache = false);
|
||||
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
if(!$imageProcessor->checkFolders('images'))
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Please check if your media-folder exists and all folders inside are writable.']], 500);
|
||||
}
|
||||
|
||||
$imageDetails = $imageProcessor->getImageDetails($this->params['name'], $this->structure);
|
||||
|
||||
if($imageDetails)
|
||||
{
|
||||
return $response->withJson(array('image' => $imageDetails));
|
||||
}
|
||||
|
||||
# return $response->withJson(array('image' => false, 'errors' => 'image name invalid or not found'));
|
||||
return $response->withJson(['errors' => ['message' => 'Image name invalid or not found.']], 404);
|
||||
}
|
||||
|
||||
public function getFile(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
|
||||
$this->setStructure($draft = true, $cache = false);
|
||||
|
||||
$fileProcessor = new ProcessFile();
|
||||
if(!$fileProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Please check if your media-folder exists and all folders inside are writable.']], 500);
|
||||
}
|
||||
|
||||
$fileDetails = $fileProcessor->getFileDetails($this->params['name'], $this->structure);
|
||||
|
||||
if($fileDetails)
|
||||
{
|
||||
return $response->withJson(['file' => $fileDetails]);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => ['message' => 'file name invalid or not found']],404);
|
||||
}
|
||||
|
||||
public function createImage(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
|
||||
# do this shit in the model ...
|
||||
$imagename = explode('.', $this->params['name']);
|
||||
array_pop($imagename);
|
||||
$imagename = implode('-', $imagename);
|
||||
$name = URLify::filter(iconv(mb_detect_encoding($imagename, mb_detect_order(), true), "UTF-8", $imagename));
|
||||
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
if(!$imageProcessor->checkFolders('images'))
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Please check if your media-folder exists and all folders inside are writable.']], 500);
|
||||
}
|
||||
|
||||
if($imageProcessor->createImage($this->params['image'], $name, $this->settings['images']))
|
||||
{
|
||||
return $response->withJson(array('errors' => false));
|
||||
}
|
||||
|
||||
return $response->withJson(array('errors' => 'could not store image to temporary folder'));
|
||||
}
|
||||
|
||||
public function createFile(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
|
||||
$finfo = finfo_open( FILEINFO_MIME_TYPE );
|
||||
$mtype = finfo_file( $finfo, $this->params['file'] );
|
||||
finfo_close( $finfo );
|
||||
|
||||
$allowedMimes = $this->getAllowedMtypes();
|
||||
if(!in_array($mtype, $allowedMimes))
|
||||
{
|
||||
return $response->withJson(array('errors' => 'File-type is not allowed'));
|
||||
}
|
||||
|
||||
# sanitize file name
|
||||
$filename = basename($this->params['name']);
|
||||
$filename = explode('.', $this->params['name']);
|
||||
array_pop($filename);
|
||||
$filename = implode('-', $filename);
|
||||
$name = URLify::filter(iconv(mb_detect_encoding($filename, mb_detect_order(), true), "UTF-8", $filename));
|
||||
|
||||
$fileProcessor = new ProcessFile();
|
||||
if(!$fileProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Please check if your media-folder exists and all folders inside are writable.']], 500);
|
||||
}
|
||||
|
||||
if($fileProcessor->createFile($this->params['file'], $name))
|
||||
{
|
||||
return $response->withJson(array('errors' => false, 'name' => $name));
|
||||
}
|
||||
|
||||
return $response->withJson(array('errors' => 'could not store file to temporary folder'));
|
||||
}
|
||||
|
||||
public function publishImage(Request $request, Response $response, $args)
|
||||
{
|
||||
$params = $request->getParsedBody();
|
||||
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
if(!$imageProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Please check if your media-folder exists and all folders inside are writable.']], 500);
|
||||
}
|
||||
|
||||
$imageUrl = $imageProcessor->publishImage();
|
||||
if($imageUrl)
|
||||
{
|
||||
# replace the image placeholder in markdown with the image url
|
||||
$params['markdown'] = str_replace('imgplchldr', $imageUrl, $params['markdown']);
|
||||
|
||||
$request = $request->withParsedBody($params);
|
||||
|
||||
if($params['new'])
|
||||
{
|
||||
return $this->addBlock($request, $response, $args);
|
||||
}
|
||||
return $this->updateBlock($request, $response, $args);
|
||||
}
|
||||
|
||||
return $response->withJson(array('errors' => 'could not store image to media folder'));
|
||||
}
|
||||
|
||||
public function deleteImage(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
|
||||
if(!isset($this->params['name']))
|
||||
{
|
||||
return $response->withJson(array('errors' => 'image name is missing'));
|
||||
}
|
||||
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
if(!$imageProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Please check if your media-folder exists and all folders inside are writable.']], 500);
|
||||
}
|
||||
|
||||
$errors = $imageProcessor->deleteImage($this->params['name']);
|
||||
|
||||
return $response->withJson(array('errors' => $errors));
|
||||
}
|
||||
|
||||
public function deleteFile(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
|
||||
if(!isset($this->params['name']))
|
||||
{
|
||||
return $response->withJson(array('errors' => 'file name is missing'));
|
||||
}
|
||||
|
||||
$fileProcessor = new ProcessFile();
|
||||
|
||||
$errors = false;
|
||||
if($fileProcessor->deleteFile($this->params['name']))
|
||||
{
|
||||
return $response->withJson(array('errors' => false));
|
||||
}
|
||||
|
||||
return $response->withJson(array('errors' => 'could not delete the file'));
|
||||
}
|
||||
|
||||
public function saveVideoImage(Request $request, Response $response, $args)
|
||||
{
|
||||
/* get params from call */
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
$class = false;
|
||||
|
||||
$imageUrl = $this->params['markdown'];
|
||||
|
||||
if(strpos($imageUrl, 'https://www.youtube.com/watch?v=') !== false)
|
||||
{
|
||||
$videoID = str_replace('https://www.youtube.com/watch?v=', '', $imageUrl);
|
||||
$videoID = strpos($videoID, '&') ? substr($videoID, 0, strpos($videoID, '&')) : $videoID;
|
||||
$class = 'youtube';
|
||||
}
|
||||
if(strpos($imageUrl, 'https://youtu.be/') !== false)
|
||||
{
|
||||
$videoID = str_replace('https://youtu.be/', '', $imageUrl);
|
||||
$videoID = strpos($videoID, '?') ? substr($videoID, 0, strpos($videoID, '?')) : $videoID;
|
||||
$class = 'youtube';
|
||||
}
|
||||
|
||||
if($class == 'youtube')
|
||||
{
|
||||
$videoURLmaxres = 'https://i1.ytimg.com/vi/' . $videoID . '/maxresdefault.jpg';
|
||||
$videoURL0 = 'https://i1.ytimg.com/vi/' . $videoID . '/0.jpg';
|
||||
}
|
||||
|
||||
$ctx = stream_context_create(array(
|
||||
'https' => array(
|
||||
'timeout' => 1
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$imageData = @file_get_contents($videoURLmaxres, 0, $ctx);
|
||||
if($imageData === false)
|
||||
{
|
||||
$imageData = @file_get_contents($videoURL0, 0, $ctx);
|
||||
if($imageData === false)
|
||||
{
|
||||
return $response->withJson(array('errors' => 'could not get the video image'));
|
||||
}
|
||||
}
|
||||
|
||||
$imageData64 = 'data:image/jpeg;base64,' . base64_encode($imageData);
|
||||
$desiredSizes = ['live' => ['width' => 560, 'height' => 315]];
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
if(!$imageProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Please check if your media-folder exists and all folders inside are writable.']], 500);
|
||||
}
|
||||
|
||||
$tmpImage = $imageProcessor->createImage($imageData64, $desiredSizes);
|
||||
|
||||
if(!$tmpImage)
|
||||
{
|
||||
return $response->withJson(array('errors' => 'could not create temporary image'));
|
||||
}
|
||||
|
||||
$imageUrl = $imageProcessor->publishImage($desiredSizes, $videoID);
|
||||
if($imageUrl)
|
||||
{
|
||||
$this->params['markdown'] = '{#' . $videoID. ' .' . $class . '}';
|
||||
|
||||
$request = $request->withParsedBody($this->params);
|
||||
|
||||
if($this->params['new'])
|
||||
{
|
||||
return $this->addBlock($request, $response, $args);
|
||||
}
|
||||
return $this->updateBlock($request, $response, $args);
|
||||
}
|
||||
|
||||
return $response->withJson(array('errors' => 'could not store the preview image'));
|
||||
}
|
||||
|
||||
private function getAllowedMtypes()
|
||||
{
|
||||
return array(
|
||||
'application/zip',
|
||||
'application/gzip',
|
||||
'application/vnd.rar',
|
||||
'application/vnd.visio',
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.ms-powerpoint',
|
||||
'application/vnd.ms-word.document.macroEnabled.12',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'application/vnd.apple.keynote',
|
||||
'application/vnd.apple.mpegurl',
|
||||
'application/vnd.apple.numbers',
|
||||
'application/vnd.apple.pages',
|
||||
'application/vnd.amazon.mobi8-ebook',
|
||||
'application/epub+zip',
|
||||
'application/pdf',
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/gif',
|
||||
'image/svg+xml',
|
||||
'font/*',
|
||||
'audio/mpeg',
|
||||
'audio/mp4',
|
||||
'audio/ogg',
|
||||
'video/mpeg',
|
||||
'video/mp4',
|
||||
'video/ogg',
|
||||
);
|
||||
}
|
||||
}
|
356
system/Controllers/MediaApiController.php
Normal file
|
@ -0,0 +1,356 @@
|
|||
<?php
|
||||
|
||||
namespace Typemill\Controllers;
|
||||
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
use Typemill\Models\ProcessImage;
|
||||
use Typemill\Models\ProcessFile;
|
||||
use Typemill\Controllers\BlockApiController;
|
||||
use \URLify;
|
||||
|
||||
class MediaApiController extends ContentController
|
||||
{
|
||||
public function getMediaLibImages(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
if(!$imageProcessor->checkFolders('images'))
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
$imagelist = $imageProcessor->scanMediaFlat();
|
||||
|
||||
return $response->withJson(['images' => $imagelist]);
|
||||
}
|
||||
|
||||
public function getMediaLibFiles(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
|
||||
$fileProcessor = new ProcessFile();
|
||||
if(!$fileProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
$filelist = $fileProcessor->scanFilesFlat();
|
||||
|
||||
return $response->withJson(['files' => $filelist]);
|
||||
}
|
||||
|
||||
public function getImage(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
|
||||
$this->setStructure($draft = true, $cache = false);
|
||||
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
if(!$imageProcessor->checkFolders('images'))
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
$imageDetails = $imageProcessor->getImageDetails($this->params['name'], $this->structure);
|
||||
|
||||
if($imageDetails)
|
||||
{
|
||||
return $response->withJson(['image' => $imageDetails]);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => 'Image not found or image name not valid.'], 404);
|
||||
}
|
||||
|
||||
public function getFile(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
|
||||
$this->setStructure($draft = true, $cache = false);
|
||||
|
||||
$fileProcessor = new ProcessFile();
|
||||
if(!$fileProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
$fileDetails = $fileProcessor->getFileDetails($this->params['name'], $this->structure);
|
||||
|
||||
if($fileDetails)
|
||||
{
|
||||
return $response->withJson(['file' => $fileDetails]);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => 'file not found or file name invalid'],404);
|
||||
}
|
||||
|
||||
public function createImage(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
|
||||
if(!$imageProcessor->checkFolders('images'))
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
if($imageProcessor->createImage($this->params['image'], $this->params['name'], $this->settings['images']))
|
||||
{
|
||||
return $response->withJson(['name' => 'media/live/' . $imageProcessor->getFullName(),'errors' => false]);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => 'could not store image to temporary folder']);
|
||||
}
|
||||
|
||||
public function uploadFile(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
|
||||
# make sure only allowed filetypes are uploaded
|
||||
$finfo = finfo_open( FILEINFO_MIME_TYPE );
|
||||
$mtype = finfo_file( $finfo, $this->params['file'] );
|
||||
finfo_close( $finfo );
|
||||
$allowedMimes = $this->getAllowedMtypes();
|
||||
if(!in_array($mtype, $allowedMimes))
|
||||
{
|
||||
return $response->withJson(array('errors' => 'File-type is not allowed'));
|
||||
}
|
||||
|
||||
$fileProcessor = new ProcessFile();
|
||||
|
||||
if(!$fileProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
$fileinfo = $fileProcessor->storeFile($this->params['file'], $this->params['name']);
|
||||
if($fileinfo)
|
||||
{
|
||||
return $response->withJson(['errors' => false, 'info' => $fileinfo]);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => 'could not store file to temporary folder'],500);
|
||||
}
|
||||
|
||||
public function publishImage(Request $request, Response $response, $args)
|
||||
{
|
||||
$params = $request->getParsedBody();
|
||||
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
if(!$imageProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
if($imageProcessor->publishImage())
|
||||
{
|
||||
$request = $request->withParsedBody($params);
|
||||
|
||||
$block = new BlockApiController($this->c);
|
||||
if($params['new'])
|
||||
{
|
||||
return $block->addBlock($request, $response, $args);
|
||||
}
|
||||
return $block->updateBlock($request, $response, $args);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => 'could not store image to media folder'],500);
|
||||
}
|
||||
|
||||
public function publishFile(Request $request, Response $response, $args)
|
||||
{
|
||||
$params = $request->getParsedBody();
|
||||
|
||||
$fileProcessor = new ProcessFile();
|
||||
if(!$fileProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
if($fileProcessor->publishFile())
|
||||
{
|
||||
$request = $request->withParsedBody($params);
|
||||
|
||||
$block = new BlockApiController($this->c);
|
||||
if($params['new'])
|
||||
{
|
||||
return $block->addBlock($request, $response, $args);
|
||||
}
|
||||
return $block->updateBlock($request, $response, $args);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => 'could not store file to media folder'],500);
|
||||
}
|
||||
|
||||
public function deleteImage(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
|
||||
if(!isset($this->params['name']))
|
||||
{
|
||||
return $response->withJson(['errors' => 'image name is missing'],500);
|
||||
}
|
||||
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
if(!$imageProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
if($imageProcessor->deleteImage($this->params['name']))
|
||||
{
|
||||
return $response->withJson(['errors' => false]);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => 'Oops, looks like we could not delete all sizes of that image.'], 500);
|
||||
}
|
||||
|
||||
public function deleteFile(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
|
||||
if(!isset($this->params['name']))
|
||||
{
|
||||
return $response->withJson(['errors' => 'file name is missing'],500);
|
||||
}
|
||||
|
||||
$fileProcessor = new ProcessFile();
|
||||
|
||||
if($fileProcessor->deleteFile($this->params['name']))
|
||||
{
|
||||
return $response->withJson(['errors' => false]);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => 'could not delete the file'],500);
|
||||
}
|
||||
|
||||
public function saveVideoImage(Request $request, Response $response, $args)
|
||||
{
|
||||
/* get params from call */
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri();
|
||||
$class = false;
|
||||
|
||||
$imageUrl = $this->params['markdown'];
|
||||
|
||||
if(strpos($imageUrl, 'https://www.youtube.com/watch?v=') !== false)
|
||||
{
|
||||
$videoID = str_replace('https://www.youtube.com/watch?v=', '', $imageUrl);
|
||||
$videoID = strpos($videoID, '&') ? substr($videoID, 0, strpos($videoID, '&')) : $videoID;
|
||||
$class = 'youtube';
|
||||
}
|
||||
if(strpos($imageUrl, 'https://youtu.be/') !== false)
|
||||
{
|
||||
$videoID = str_replace('https://youtu.be/', '', $imageUrl);
|
||||
$videoID = strpos($videoID, '?') ? substr($videoID, 0, strpos($videoID, '?')) : $videoID;
|
||||
$class = 'youtube';
|
||||
}
|
||||
|
||||
if($class == 'youtube')
|
||||
{
|
||||
$videoURLmaxres = 'https://i1.ytimg.com/vi/' . $videoID . '/maxresdefault.jpg';
|
||||
$videoURL0 = 'https://i1.ytimg.com/vi/' . $videoID . '/0.jpg';
|
||||
}
|
||||
|
||||
$ctx = stream_context_create(array(
|
||||
'https' => array(
|
||||
'timeout' => 1
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$imageData = @file_get_contents($videoURLmaxres, 0, $ctx);
|
||||
if($imageData === false)
|
||||
{
|
||||
$imageData = @file_get_contents($videoURL0, 0, $ctx);
|
||||
if($imageData === false)
|
||||
{
|
||||
return $response->withJson(array('errors' => 'could not get the video image'));
|
||||
}
|
||||
}
|
||||
|
||||
$imageData64 = 'data:image/jpeg;base64,' . base64_encode($imageData);
|
||||
$desiredSizes = ['live' => ['width' => 560, 'height' => 315]];
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
if(!$imageProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Please check if your media-folder exists and all folders inside are writable.']], 500);
|
||||
}
|
||||
|
||||
$tmpImage = $imageProcessor->createImage($imageData64, $desiredSizes);
|
||||
|
||||
if(!$tmpImage)
|
||||
{
|
||||
return $response->withJson(array('errors' => 'could not create temporary image'));
|
||||
}
|
||||
|
||||
$imageUrl = $imageProcessor->publishImage($desiredSizes, $videoID);
|
||||
if($imageUrl)
|
||||
{
|
||||
$this->params['markdown'] = '{#' . $videoID. ' .' . $class . '}';
|
||||
|
||||
$request = $request->withParsedBody($this->params);
|
||||
|
||||
$block = new BlockApiController($this->c);
|
||||
if($params['new'])
|
||||
{
|
||||
return $block->addBlock($request, $response, $args);
|
||||
}
|
||||
return $block->updateBlock($request, $response, $args);
|
||||
}
|
||||
|
||||
return $response->withJson(array('errors' => 'could not store the preview image'));
|
||||
}
|
||||
|
||||
private function getAllowedMtypes()
|
||||
{
|
||||
return array(
|
||||
'application/zip',
|
||||
'application/gzip',
|
||||
'application/vnd.rar',
|
||||
'application/vnd.visio',
|
||||
'application/vnd.ms-excel',
|
||||
'application/vnd.ms-powerpoint',
|
||||
'application/vnd.ms-word.document.macroEnabled.12',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
'application/vnd.apple.keynote',
|
||||
'application/vnd.apple.mpegurl',
|
||||
'application/vnd.apple.numbers',
|
||||
'application/vnd.apple.pages',
|
||||
'application/vnd.amazon.mobi8-ebook',
|
||||
'application/epub+zip',
|
||||
'application/pdf',
|
||||
'image/png',
|
||||
'image/jpeg',
|
||||
'image/gif',
|
||||
'image/svg+xml',
|
||||
'font/*',
|
||||
'audio/mpeg',
|
||||
'audio/mp4',
|
||||
'audio/ogg',
|
||||
'video/mpeg',
|
||||
'video/mp4',
|
||||
'video/ogg',
|
||||
);
|
||||
}
|
||||
}
|
|
@ -200,9 +200,8 @@ class MetaApiController extends ContentController
|
|||
|
||||
if($tab == 'meta')
|
||||
{
|
||||
|
||||
# if manual date has been modified
|
||||
if(isset($metaInput['manualdate']) && !isset($metaPage['meta']['manualdate']) OR ($metaInput['manualdate'] != $metaPage['meta']['manualdate']))
|
||||
if( $this->hasChanged($metaInput, $metaPage['meta'], 'manualdate'))
|
||||
{
|
||||
# update the time
|
||||
$metaInput['time'] = date('H-i-s', time());
|
||||
|
@ -230,7 +229,7 @@ class MetaApiController extends ContentController
|
|||
}
|
||||
|
||||
# if folder has changed and contains pages instead of posts or posts instead of pages
|
||||
if($this->item->elementType == "folder" && ($metaPage['meta']['contains'] !== $metaInput['contains']))
|
||||
if($this->item->elementType == "folder" && isset($metaInput['contains']) && $this->hasChanged($metaInput, $metaPage['meta'], 'contains'))
|
||||
{
|
||||
$structure = true;
|
||||
|
||||
|
@ -258,9 +257,9 @@ class MetaApiController extends ContentController
|
|||
}
|
||||
elseif(
|
||||
# check if navtitle or hide-value has been changed
|
||||
($metaPage['meta']['navtitle'] != $metaInput['navtitle'])
|
||||
($this->hasChanged($metaInput, $metaPage['meta'], 'navtitle'))
|
||||
OR
|
||||
($metaPage['meta']['hide'] != $metaInput['hide'])
|
||||
($this->hasChanged($metaInput, $metaPage['meta'], 'hide'))
|
||||
)
|
||||
{
|
||||
# add new file data. Also makes sure that the value is set.
|
||||
|
@ -297,6 +296,19 @@ class MetaApiController extends ContentController
|
|||
# return with the new metadata
|
||||
return $response->withJson(array('metadata' => $metaInput, 'structure' => $structure, 'item' => $this->item, 'errors' => false));
|
||||
}
|
||||
|
||||
protected function hasChanged($input, $page, $field)
|
||||
{
|
||||
if(isset($input[$field]) && isset($page[$field]) && $input[$field] == $page[$field])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if(!isset($input[$field]) && !isset($input[$field]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
# check models -> writeYaml for getPageMeta and getPageMetaDefaults.
|
|
@ -108,13 +108,16 @@ class PageController extends Controller
|
|||
/* get breadcrumb for page */
|
||||
$breadcrumb = Folder::getBreadcrumb($structure, $item->keyPathArray);
|
||||
$breadcrumb = $this->c->dispatcher->dispatch('onBreadcrumbLoaded', new OnBreadcrumbLoaded($breadcrumb))->getData();
|
||||
|
||||
# set pages active for navigation again
|
||||
Folder::getBreadcrumb($navigation, $item->keyPathArray);
|
||||
|
||||
/* add the paging to the item */
|
||||
$item = Folder::getPagingForItem($structure, $item);
|
||||
}
|
||||
|
||||
# dispatch the item
|
||||
$item = $this->c->dispatcher->dispatch('onItemLoaded', new OnItemLoaded($item))->getData();
|
||||
$item = $this->c->dispatcher->dispatch('onItemLoaded', new OnItemLoaded($item))->getData();
|
||||
|
||||
# set the filepath
|
||||
$filePath = $pathToContent . $item->path;
|
||||
|
@ -211,6 +214,18 @@ class PageController extends Controller
|
|||
$this->c->assets->addCSS($base_url . '/cache/' . $theme . '-custom.css');
|
||||
}
|
||||
|
||||
$logo = false;
|
||||
if(isset($settings['logo']) && $settings['logo'] != '')
|
||||
{
|
||||
$logo = 'media/files/' . $settings['logo'];
|
||||
}
|
||||
|
||||
$favicon = false;
|
||||
if(isset($settings['favicon']) && $settings['favicon'] != '')
|
||||
{
|
||||
$favicon = true;
|
||||
}
|
||||
|
||||
return $this->render($response, $route, [
|
||||
'home' => $home,
|
||||
'navigation' => $navigation,
|
||||
|
@ -221,7 +236,10 @@ class PageController extends Controller
|
|||
'settings' => $settings,
|
||||
'metatabs' => $metatabs,
|
||||
'base_url' => $base_url,
|
||||
'image' => $firstImage ]);
|
||||
'image' => $firstImage,
|
||||
'logo' => $logo,
|
||||
'favicon' => $favicon
|
||||
]);
|
||||
}
|
||||
|
||||
protected function getCachedStructure($cache)
|
||||
|
|
|
@ -7,6 +7,8 @@ use Typemill\Models\Write;
|
|||
use Typemill\Models\Fields;
|
||||
use Typemill\Models\Validation;
|
||||
use Typemill\Models\User;
|
||||
use Typemill\Models\ProcessFile;
|
||||
use Typemill\Models\ProcessImage;
|
||||
|
||||
class SettingsController extends Controller
|
||||
{
|
||||
|
@ -15,7 +17,7 @@ class SettingsController extends Controller
|
|||
*********************/
|
||||
|
||||
public function showSettings($request, $response, $args)
|
||||
{
|
||||
{
|
||||
$user = new User();
|
||||
$settings = $this->c->get('settings');
|
||||
$defaultSettings = \Typemill\Settings::getDefaultSettings();
|
||||
|
@ -46,8 +48,10 @@ class SettingsController extends Controller
|
|||
$settings = \Typemill\Settings::getUserSettings();
|
||||
$defaultSettings = \Typemill\Settings::getDefaultSettings();
|
||||
$params = $request->getParams();
|
||||
$files = $request->getUploadedFiles();
|
||||
$newSettings = isset($params['settings']) ? $params['settings'] : false;
|
||||
$validate = new Validation();
|
||||
$processFiles = new ProcessFile();
|
||||
|
||||
if($newSettings)
|
||||
{
|
||||
|
@ -62,6 +66,8 @@ class SettingsController extends Controller
|
|||
'formats' => $newSettings['formats'],
|
||||
);
|
||||
|
||||
# https://www.slimframework.com/docs/v3/cookbook/uploading-files.html;
|
||||
|
||||
$copyright = $this->getCopyright();
|
||||
|
||||
$validate->settings($newSettings, $copyright, $defaultSettings['formats'], 'settings');
|
||||
|
@ -77,8 +83,80 @@ class SettingsController extends Controller
|
|||
$this->c->flash->addMessage('error', 'Please correct the errors');
|
||||
return $response->withRedirect($this->c->router->pathFor('settings.show'));
|
||||
}
|
||||
|
||||
/* store updated settings */
|
||||
|
||||
if(!$processFiles->checkFolders())
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'Please make sure that your media folder exists and is writable.');
|
||||
return $response->withRedirect($this->c->router->pathFor('settings.show'));
|
||||
}
|
||||
|
||||
# handle single input with single file upload
|
||||
$logo = $files['settings']['logo'];
|
||||
if($logo->getError() === UPLOAD_ERR_OK)
|
||||
{
|
||||
$allowed = ['jpg', 'jpeg', 'png', 'svg'];
|
||||
$extension = pathinfo($logo->getClientFilename(), PATHINFO_EXTENSION);
|
||||
if(!in_array(strtolower($extension), $allowed))
|
||||
{
|
||||
$_SESSION['errors']['settings']['logo'] = array('Only jpg, jpeg, png and svg allowed');
|
||||
$this->c->flash->addMessage('error', 'Please correct the errors');
|
||||
return $response->withRedirect($this->c->router->pathFor('settings.show'));
|
||||
}
|
||||
|
||||
$processFiles->deleteFileWithName('logo');
|
||||
$newSettings['logo'] = $processFiles->moveUploadedFile($logo, $overwrite = true, $name = 'logo');
|
||||
}
|
||||
elseif(isset($params['settings']['deletelogo']) && $params['settings']['deletelogo'] == 'delete')
|
||||
{
|
||||
$processFiles->deleteFileWithName('logo');
|
||||
$newSettings['logo'] = '';
|
||||
}
|
||||
else
|
||||
{
|
||||
$newSettings['logo'] = isset($settings['logo']) ? $settings['logo'] : '';
|
||||
}
|
||||
|
||||
# handle single input with single file upload
|
||||
$favicon = $files['settings']['favicon'];
|
||||
if ($favicon->getError() === UPLOAD_ERR_OK)
|
||||
{
|
||||
$extension = pathinfo($favicon->getClientFilename(), PATHINFO_EXTENSION);
|
||||
if(strtolower($extension) != 'png')
|
||||
{
|
||||
$_SESSION['errors']['settings']['favicon'] = array('Only .png-files allowed');
|
||||
$this->c->flash->addMessage('error', 'Please correct the errors');
|
||||
return $response->withRedirect($this->c->router->pathFor('settings.show'));
|
||||
}
|
||||
|
||||
$processImage = new ProcessImage([
|
||||
'16' => ['width' => 16, 'height' => 16],
|
||||
'32' => ['width' => 32, 'height' => 32],
|
||||
'72' => ['width' => 72, 'height' => 72],
|
||||
'114' => ['width' => 114, 'height' => 114],
|
||||
'144' => ['width' => 144, 'height' => 144],
|
||||
'180' => ['width' => 180, 'height' => 180],
|
||||
]);
|
||||
$favicons = $processImage->generateSizesFromImageFile('favicon.png', $favicon->file);
|
||||
|
||||
foreach($favicons as $key => $favicon)
|
||||
{
|
||||
imagepng( $favicon, $processFiles->fileFolder . 'favicon-' . $key . '.png' );
|
||||
# $processFiles->moveUploadedFile($favicon, $overwrite = true, $name = 'favicon-' . $key);
|
||||
}
|
||||
|
||||
$newSettings['favicon'] = 'favicon';
|
||||
}
|
||||
elseif(isset($params['settings']['deletefav']) && $params['settings']['deletefav'] == 'delete')
|
||||
{
|
||||
$processFiles->deleteFileWithName('favicon');
|
||||
$newSettings['favicon'] = '';
|
||||
}
|
||||
else
|
||||
{
|
||||
$newSettings['favicon'] = isset($settings['favicon']) ? $settings['favicon'] : '';
|
||||
}
|
||||
|
||||
# store updated settings
|
||||
\Typemill\Settings::updateSettings(array_merge($settings, $newSettings));
|
||||
|
||||
$this->c->flash->addMessage('info', 'Settings are stored');
|
||||
|
|
|
@ -9,6 +9,13 @@ use Typemill\Models\Write;
|
|||
|
||||
class SetupController extends Controller
|
||||
{
|
||||
|
||||
# redirect if visit /setup route
|
||||
public function redirect($request, $response)
|
||||
{
|
||||
return $response->withRedirect($this->c->router->pathFor('setup.show'));
|
||||
}
|
||||
|
||||
public function show($request, $response, $args)
|
||||
{
|
||||
/* make some checks befor you install */
|
||||
|
|
22
system/Extensions/TwigPagelistExtension.php
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace Typemill\Extensions;
|
||||
|
||||
use Typemill\Models\Folder;
|
||||
|
||||
class TwigPagelistExtension extends \Twig_Extension
|
||||
{
|
||||
public function getFunctions()
|
||||
{
|
||||
return [
|
||||
new \Twig_SimpleFunction('getPageList', array($this, 'getList' ))
|
||||
];
|
||||
}
|
||||
|
||||
public function getList($folderContentDetails, $url)
|
||||
{
|
||||
$pagelist = Folder::getItemForUrlFrontend($folderContentDetails, $url);
|
||||
|
||||
return $pagelist;
|
||||
}
|
||||
}
|
|
@ -30,10 +30,9 @@ class Folder
|
|||
}
|
||||
}
|
||||
}
|
||||
return $folderContent;
|
||||
return $folderContent;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* scans content of a folder recursively
|
||||
* vars: folder path as string
|
||||
|
@ -301,6 +300,25 @@ class Folder
|
|||
return $result;
|
||||
}
|
||||
|
||||
public static 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 = self::getItemForUrlFrontend($item->folderContent, $url, $result);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function getPagingForItem($content, $item)
|
||||
{
|
||||
$keyPos = count($item->keyPathArray)-1;
|
||||
|
@ -475,6 +493,7 @@ class Folder
|
|||
|
||||
while($i < count($searchArray))
|
||||
{
|
||||
if(!isset($content[$searchArray[$i]])){ return false; }
|
||||
$item = $content[$searchArray[$i]];
|
||||
|
||||
if($i == count($searchArray)-1)
|
||||
|
|
|
@ -24,4 +24,39 @@ class Helpers{
|
|||
$table .= '</table></body></html>';
|
||||
echo $table;
|
||||
}
|
||||
|
||||
public static function array_sort($array, $on, $order=SORT_ASC)
|
||||
{
|
||||
$new_array = array();
|
||||
$sortable_array = array();
|
||||
|
||||
if (count($array) > 0) {
|
||||
foreach ($array as $k => $v) {
|
||||
if (is_array($v)) {
|
||||
foreach ($v as $k2 => $v2) {
|
||||
if ($k2 == $on) {
|
||||
$sortable_array[$k] = $v2;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$sortable_array[$k] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
switch ($order) {
|
||||
case SORT_ASC:
|
||||
asort($sortable_array);
|
||||
break;
|
||||
case SORT_DESC:
|
||||
arsort($sortable_array);
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($sortable_array as $k => $v) {
|
||||
$new_array[] = $array[$k];
|
||||
}
|
||||
}
|
||||
|
||||
return $new_array;
|
||||
}
|
||||
}
|
231
system/Models/ProcessAssets.php
Normal file
|
@ -0,0 +1,231 @@
|
|||
<?php
|
||||
namespace Typemill\Models;
|
||||
|
||||
use \URLify;
|
||||
|
||||
class ProcessAssets
|
||||
{
|
||||
# holds the path to the baseFolder
|
||||
protected $baseFolder;
|
||||
|
||||
# holds the path to the mediaFolder
|
||||
protected $mediaFolder;
|
||||
|
||||
# holds the path to the temporary image folder
|
||||
protected $tmpFolder;
|
||||
|
||||
# holds the path where original images are stored
|
||||
protected $originalFolder;
|
||||
|
||||
# holds the path where images for frontend use are stored
|
||||
protected $liveFolder;
|
||||
|
||||
# holds the folder where the thumbs for the media library are stored
|
||||
protected $thumbFolder;
|
||||
|
||||
# holds the folder where the thumbs for the media library are stored
|
||||
public $fileFolder;
|
||||
|
||||
# holds the desired sizes for image resizing
|
||||
protected $desiredSizes;
|
||||
|
||||
public function __construct($desiredSizes = NULL)
|
||||
{
|
||||
$this->baseFolder = getcwd() . DIRECTORY_SEPARATOR;
|
||||
|
||||
$this->mediaFolder = $this->baseFolder . 'media' . DIRECTORY_SEPARATOR;
|
||||
|
||||
$this->tmpFolder = $this->mediaFolder . 'tmp' . DIRECTORY_SEPARATOR;
|
||||
|
||||
$this->originalFolder = $this->mediaFolder . 'original' . DIRECTORY_SEPARATOR;
|
||||
|
||||
$this->liveFolder = $this->mediaFolder . 'live' . DIRECTORY_SEPARATOR;
|
||||
|
||||
$this->thumbFolder = $this->mediaFolder . 'thumbs' . DIRECTORY_SEPARATOR;
|
||||
|
||||
$this->fileFolder = $this->mediaFolder . 'files' . DIRECTORY_SEPARATOR;
|
||||
|
||||
$this->desiredSizes = $desiredSizes;
|
||||
}
|
||||
|
||||
public function checkFolders($forassets = null)
|
||||
{
|
||||
|
||||
$folders = [$this->mediaFolder, $this->tmpFolder, $this->fileFolder];
|
||||
|
||||
if($forassets == 'images')
|
||||
{
|
||||
$folders = [$this->mediaFolder, $this->tmpFolder, $this->originalFolder, $this->liveFolder, $this->thumbFolder];
|
||||
}
|
||||
|
||||
foreach($folders as $folder)
|
||||
{
|
||||
if(!file_exists($folder))
|
||||
{
|
||||
if(!mkdir($folder, 0774, true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if($folder == $this->thumbFolder)
|
||||
{
|
||||
# cleanup old systems
|
||||
$this->cleanupLiveFolder();
|
||||
|
||||
# generate thumbnails from live folder
|
||||
$this->generateThumbs();
|
||||
}
|
||||
}
|
||||
elseif(!is_writeable($folder))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setFileName($originalname, $type, $overwrite = null)
|
||||
{
|
||||
$pathinfo = pathinfo($originalname);
|
||||
|
||||
$this->extension = strtolower($pathinfo['extension']);
|
||||
$this->filename = URLify::filter(iconv(mb_detect_encoding($pathinfo['filename'], mb_detect_order(), true), "UTF-8", $pathinfo['filename']));
|
||||
|
||||
$filename = $this->filename;
|
||||
|
||||
# check if file name is
|
||||
if(!$overwrite)
|
||||
{
|
||||
$suffix = 1;
|
||||
|
||||
$destination = $this->liveFolder;
|
||||
if($type == 'file')
|
||||
{
|
||||
$destination = $this->fileFolder;
|
||||
}
|
||||
|
||||
while(file_exists($destination . $filename . '.' . $this->extension))
|
||||
{
|
||||
$filename = $this->filename . '-' . $suffix;
|
||||
$suffix++;
|
||||
}
|
||||
}
|
||||
|
||||
$this->filename = $filename;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->filename;
|
||||
}
|
||||
|
||||
public function getExtension()
|
||||
{
|
||||
return $this->extension;
|
||||
}
|
||||
|
||||
public function getFullName()
|
||||
{
|
||||
return $this->filename . '.' . $this->extension;
|
||||
}
|
||||
|
||||
public function clearTempFolder()
|
||||
{
|
||||
$files = scandir($this->tmpFolder);
|
||||
$result = true;
|
||||
|
||||
foreach($files as $file)
|
||||
{
|
||||
if (!in_array($file, array(".","..")))
|
||||
{
|
||||
$filelink = $this->tmpFolder . $file;
|
||||
if(!unlink($filelink))
|
||||
{
|
||||
$success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function cleanupLiveFolder()
|
||||
{
|
||||
# delete all old thumbs mlibrary in live folder
|
||||
foreach(glob($this->liveFolder . '*mlibrary*') as $filename)
|
||||
{
|
||||
unlink($filename);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function findPagesWithUrl($structure, $url, $result)
|
||||
{
|
||||
foreach ($structure as $key => $item)
|
||||
{
|
||||
if($item->elementType == 'folder')
|
||||
{
|
||||
$result = $this->findPagesWithUrl($item->folderContent, $url, $result);
|
||||
}
|
||||
else
|
||||
{
|
||||
$live = getcwd() . DIRECTORY_SEPARATOR . 'content' . $item->pathWithoutType . '.md';
|
||||
$draft = getcwd() . DIRECTORY_SEPARATOR . 'content' . $item->pathWithoutType . '.txt';
|
||||
|
||||
# check live first
|
||||
if(file_exists($live))
|
||||
{
|
||||
$content = file_get_contents($live);
|
||||
|
||||
if (stripos($content, $url) !== false)
|
||||
{
|
||||
$result[] = $item->urlRelWoF;
|
||||
}
|
||||
# if not in live, check in draft
|
||||
elseif(file_exists($draft))
|
||||
{
|
||||
$content = file_get_contents($draft);
|
||||
|
||||
if (stripos($content, $url) !== false)
|
||||
{
|
||||
$result[] = $item->urlRelWoF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function formatSizeUnits($bytes)
|
||||
{
|
||||
if ($bytes >= 1073741824)
|
||||
{
|
||||
$bytes = number_format($bytes / 1073741824, 2) . ' GB';
|
||||
}
|
||||
elseif ($bytes >= 1048576)
|
||||
{
|
||||
$bytes = number_format($bytes / 1048576, 2) . ' MB';
|
||||
}
|
||||
elseif ($bytes >= 1024)
|
||||
{
|
||||
$bytes = number_format($bytes / 1024, 2) . ' KB';
|
||||
}
|
||||
elseif ($bytes > 1)
|
||||
{
|
||||
$bytes = $bytes . ' bytes';
|
||||
}
|
||||
elseif ($bytes == 1)
|
||||
{
|
||||
$bytes = $bytes . ' byte';
|
||||
}
|
||||
else
|
||||
{
|
||||
$bytes = '0 bytes';
|
||||
}
|
||||
|
||||
return $bytes;
|
||||
}
|
||||
}
|
165
system/Models/ProcessFile.php
Normal file
|
@ -0,0 +1,165 @@
|
|||
<?php
|
||||
namespace Typemill\Models;
|
||||
|
||||
use Slim\Http\UploadedFile;
|
||||
use Typemill\Models\Helpers;
|
||||
use \URLify;
|
||||
|
||||
class ProcessFile extends ProcessAssets
|
||||
{
|
||||
/**
|
||||
* Moves the uploaded file to the upload directory. Only used for settings / NON VUE.JS uploads
|
||||
*
|
||||
* @param string $directory directory to which the file is moved
|
||||
* @param UploadedFile $uploadedFile file uploaded file to move
|
||||
* @return string filename of moved file
|
||||
*/
|
||||
public function moveUploadedFile(UploadedFile $uploadedFile, $overwrite = false, $name = false)
|
||||
{
|
||||
$this->setFileName($uploadedFile->getClientFilename(), 'file');
|
||||
|
||||
if($name)
|
||||
{
|
||||
$this->setFileName($name . '.' . $this->extension, 'file', $overwrite);
|
||||
}
|
||||
|
||||
$uploadedFile->moveTo($this->fileFolder . $this->getFullName());
|
||||
|
||||
return $this->getFullName();
|
||||
}
|
||||
|
||||
public function storeFile($file, $name)
|
||||
{
|
||||
$this->setFileName($name, 'file');
|
||||
|
||||
$this->clearTempFolder();
|
||||
|
||||
$file = $this->decodeFile($file);
|
||||
|
||||
$path = $this->tmpFolder . $this->getFullName();
|
||||
|
||||
if(file_put_contents($path, $file))
|
||||
{
|
||||
$size = filesize($path);
|
||||
$size = $this->formatSizeUnits($size);
|
||||
|
||||
$title = str_replace('-', ' ', $this->filename);
|
||||
$title = $title . ' (' . strtoupper($this->extension) . ', ' . $size .')';
|
||||
|
||||
return ['title' => $title, 'name' => $this->filename, 'extension' => $this->extension, 'size' => $size, 'url' => 'media/files/' . $this->getFullName()];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function publishFile()
|
||||
{
|
||||
$files = scandir($this->tmpFolder);
|
||||
$success = true;
|
||||
|
||||
foreach($files as $file)
|
||||
{
|
||||
if (!in_array($file, array(".","..")))
|
||||
{
|
||||
$success = rename($this->tmpFolder . $file, $this->fileFolder . $file);
|
||||
}
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
public function decodeFile(string $file)
|
||||
{
|
||||
$fileParts = explode(";base64,", $file);
|
||||
$fileType = explode("/", $fileParts[0]);
|
||||
$fileData = base64_decode($fileParts[1]);
|
||||
|
||||
if ($fileData !== false)
|
||||
{
|
||||
return array("file" => $fileData, "type" => $fileType[1]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function deleteFile($name)
|
||||
{
|
||||
# validate name
|
||||
$name = basename($name);
|
||||
|
||||
if(file_exists($this->fileFolder . $name) && unlink($this->fileFolder . $name))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function deleteFileWithName($name)
|
||||
{
|
||||
# e.g. delete $name = 'logo';
|
||||
|
||||
$name = basename($name);
|
||||
|
||||
if($name != '' && !in_array($name, array(".","..")))
|
||||
{
|
||||
foreach(glob($this->fileFolder . $name . '.*') as $file)
|
||||
{
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* scans content of a folder (without recursion)
|
||||
* vars: folder path as string
|
||||
* returns: one-dimensional array with names of folders and files
|
||||
*/
|
||||
public function scanFilesFlat()
|
||||
{
|
||||
$files = scandir($this->fileFolder);
|
||||
$filelist = array();
|
||||
|
||||
foreach ($files as $key => $name)
|
||||
{
|
||||
if (!in_array($name, array(".","..")) && file_exists($this->fileFolder . $name))
|
||||
{
|
||||
$filelist[] = [
|
||||
'name' => $name,
|
||||
'timestamp' => filemtime($this->fileFolder . $name),
|
||||
'info' => pathinfo($this->fileFolder . $name),
|
||||
'url' => 'media/files/' . $name,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$filelist = Helpers::array_sort($filelist, 'timestamp', SORT_DESC);
|
||||
|
||||
return $filelist;
|
||||
}
|
||||
|
||||
|
||||
public function getFileDetails($name, $structure)
|
||||
{
|
||||
$name = basename($name);
|
||||
|
||||
if (!in_array($name, array(".","..")) && file_exists($this->fileFolder . $name))
|
||||
{
|
||||
$filedetails = [
|
||||
'name' => $name,
|
||||
'timestamp' => filemtime($this->fileFolder . $name),
|
||||
'bytes' => filesize($this->fileFolder . $name),
|
||||
'info' => pathinfo($this->fileFolder . $name),
|
||||
'url' => 'media/files/' . $name,
|
||||
'pages' => $this->findPagesWithUrl($structure, $name, $result = [])
|
||||
];
|
||||
|
||||
return $filedetails;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -1,9 +1,11 @@
|
|||
<?php
|
||||
namespace Typemill\Models;
|
||||
|
||||
class ProcessImage
|
||||
use Typemill\Models\Helpers;
|
||||
|
||||
class ProcessImage extends ProcessAssets
|
||||
{
|
||||
public function createImage(string $image, array $desiredSizes)
|
||||
public function createImage(string $image, string $name, array $desiredSizes)
|
||||
{
|
||||
# fix error from jpeg-library
|
||||
ini_set ('gd.jpeg_ignore_warning', 1);
|
||||
|
@ -11,7 +13,10 @@ class ProcessImage
|
|||
|
||||
# clear temporary folder
|
||||
$this->clearTempFolder();
|
||||
|
||||
|
||||
# set the name of the image
|
||||
$this->setFileName($name, 'image');
|
||||
|
||||
# decode the image from base64-string
|
||||
$imageDecoded = $this->decodeImage($image);
|
||||
$imageData = $imageDecoded["image"];
|
||||
|
@ -28,25 +33,24 @@ class ProcessImage
|
|||
|
||||
# resize the images
|
||||
$resizedImages = $this->imageResize($image, $imageSize, $desiredSizes, $imageType);
|
||||
|
||||
$basePath = getcwd() . DIRECTORY_SEPARATOR . 'media';
|
||||
$tmpFolder = $basePath . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR;
|
||||
|
||||
$this->saveOriginal($tmpFolder, $imageData, 'original', $imageType);
|
||||
|
||||
if($imageType == "gif" && $this->detectAnimatedGif($imageData))
|
||||
{
|
||||
$this->saveOriginal($tmpFolder, $imageData, 'live', $imageType);
|
||||
# store the original name as txt-file
|
||||
$tmpname = fopen($this->tmpFolder . $this->getName() . '.' . $imageType . ".txt", "w");
|
||||
|
||||
$this->saveOriginal($this->tmpFolder, $imageData, $name = 'original', $imageType);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
# temporary store resized images
|
||||
foreach($resizedImages as $key => $resizedImage)
|
||||
{
|
||||
$this->saveImage($tmpFolder, $resizedImage, $key, $imageType);
|
||||
$this->saveImage($this->tmpFolder, $resizedImage, $key, $imageType);
|
||||
}
|
||||
|
||||
|
||||
# if the image is an animated gif, then overwrite the resized version for live use with the original version
|
||||
if($imageType == "gif" && $this->detectAnimatedGif($imageData))
|
||||
{
|
||||
$this->saveOriginal($this->tmpFolder, $imageData, $name = 'live', $imageType);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -60,42 +64,54 @@ class ProcessImage
|
|||
return false;
|
||||
}
|
||||
|
||||
public function publishImage(array $desiredSizes, $name = false)
|
||||
public function publishImage()
|
||||
{
|
||||
/* get images from tmp folder */
|
||||
$basePath = getcwd() . DIRECTORY_SEPARATOR . 'media';
|
||||
$tmpFolder = $basePath . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR;
|
||||
$originalFolder = $basePath . DIRECTORY_SEPARATOR . 'original' . DIRECTORY_SEPARATOR;
|
||||
$liveFolder = $basePath . DIRECTORY_SEPARATOR . 'live' . DIRECTORY_SEPARATOR;
|
||||
# name is stored in temporary folder as name of the .txt-file
|
||||
foreach(glob($this->tmpFolder . '*.txt') as $imagename)
|
||||
{
|
||||
$tmpname = str_replace('.txt', '', basename($imagename));
|
||||
|
||||
if(!file_exists($originalFolder)){ mkdir($originalFolder, 0774, true); }
|
||||
if(!file_exists($liveFolder)){ mkdir($liveFolder, 0774, true); }
|
||||
# set extension and sanitize name
|
||||
$this->setFileName($tmpname, 'image');
|
||||
|
||||
$name = $name ? $name : uniqid();
|
||||
|
||||
$files = scandir($tmpFolder);
|
||||
unlink($imagename);
|
||||
}
|
||||
|
||||
$name = uniqid();
|
||||
|
||||
if($this->filename && $this->extension)
|
||||
{
|
||||
$name = $this->filename;
|
||||
}
|
||||
|
||||
$files = scandir($this->tmpFolder);
|
||||
$success = true;
|
||||
|
||||
foreach($files as $file)
|
||||
{
|
||||
if (!in_array($file, array(".","..")))
|
||||
{
|
||||
{
|
||||
$tmpfilename = explode(".", $file);
|
||||
|
||||
if($tmpfilename[0] == 'original')
|
||||
{
|
||||
$success = rename($tmpFolder . $file, $originalFolder . $name . '-' . $file);
|
||||
$success = rename($this->tmpFolder . $file, $this->originalFolder . $name . '.' . $tmpfilename[1]);
|
||||
}
|
||||
else
|
||||
if($tmpfilename[0] == 'live')
|
||||
{
|
||||
$success = rename($tmpFolder . $file, $liveFolder . $name . '-' . $file);
|
||||
$success = rename($this->tmpFolder . $file, $this->liveFolder . $name . '.' . $tmpfilename[1]);
|
||||
}
|
||||
if($tmpfilename[0] == 'thumbs')
|
||||
{
|
||||
$success = rename($this->tmpFolder . $file, $this->thumbFolder . $name . '.' . $tmpfilename[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($success)
|
||||
{
|
||||
return 'media/live/' . $name . '-live.' . $tmpfilename[1];
|
||||
return true;
|
||||
return 'media/live/' . $name . '.' . $tmpfilename[1];
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -126,6 +142,7 @@ class ProcessImage
|
|||
{
|
||||
foreach($desiredSizes as $key => $desiredSize)
|
||||
{
|
||||
# if desired size is bigger than the actual image, then drop the desired sizes and use the actual image size instead
|
||||
if($desiredSize['width'] > $imageSize['width'])
|
||||
{
|
||||
$desiredSizes[$key] = $imageSize;
|
||||
|
@ -141,129 +158,61 @@ class ProcessImage
|
|||
return $desiredSizes;
|
||||
}
|
||||
|
||||
public function imageResize($imageData, array $imageSize, array $desiredSizes, $imageType)
|
||||
public function imageResize($imageData, array $source, array $desiredSizes, $imageType)
|
||||
{
|
||||
|
||||
$copiedImages = array();
|
||||
$source_aspect_ratio = $imageSize['width'] / $imageSize['height'];
|
||||
|
||||
foreach($desiredSizes as $key => $desiredSize)
|
||||
|
||||
foreach($desiredSizes as $key => $desired)
|
||||
{
|
||||
$desired_aspect_ratio = $desiredSize['width'] / $desiredSize['height'];
|
||||
// resize
|
||||
$ratio = max($desired['width']/$source['width'], $desired['height']/$source['height']);
|
||||
$h = $desired['height'] / $ratio;
|
||||
$x = ($source['width'] - $desired['width'] / $ratio) / 2;
|
||||
$y = ($source['height'] - $desired['height'] / $ratio) / 2;
|
||||
$w = $desired['width'] / $ratio;
|
||||
|
||||
if ( $source_aspect_ratio > $desired_aspect_ratio )
|
||||
{
|
||||
# when source image is wider
|
||||
$temp_height = $desiredSize['height'];
|
||||
$temp_width = ( int ) ($desiredSize['height'] * $source_aspect_ratio);
|
||||
$temp_width = round($temp_width, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
# when source image is similar or taller
|
||||
$temp_width = $desiredSize['width'];
|
||||
$temp_height = ( int ) ($desiredSize['width'] / $source_aspect_ratio);
|
||||
$temp_height = round($temp_height, 0);
|
||||
}
|
||||
$new = imagecreatetruecolor($desired['width'], $desired['height']);
|
||||
|
||||
# Create a temporary GD image with desired size
|
||||
$temp_gdim = imagecreatetruecolor( $temp_width, $temp_height );
|
||||
// preserve transparency
|
||||
if($imageType == "gif" or $imageType == "png")
|
||||
{
|
||||
imagecolortransparent($new, imagecolorallocatealpha($new, 0, 0, 0, 127));
|
||||
imagealphablending($new, false);
|
||||
imagesavealpha($new, true);
|
||||
}
|
||||
|
||||
if ($imageType == "gif")
|
||||
{
|
||||
$transparent_index = imagecolortransparent($imageData);
|
||||
imagepalettecopy($imageData, $temp_gdim);
|
||||
imagefill($temp_gdim, 0, 0, $transparent_index);
|
||||
imagecolortransparent($temp_gdim, $transparent_index);
|
||||
imagetruecolortopalette($temp_gdim, true, 256);
|
||||
}
|
||||
elseif($imageType == "png")
|
||||
{
|
||||
imagealphablending($temp_gdim, false);
|
||||
imagesavealpha($temp_gdim, true);
|
||||
$transparent = imagecolorallocatealpha($temp_gdim, 255, 255, 255, 127);
|
||||
imagefilledrectangle($temp_gdim, 0, 0, $temp_width, $temp_height, $transparent);
|
||||
}
|
||||
|
||||
# resize image
|
||||
imagecopyresampled(
|
||||
$temp_gdim,
|
||||
$imageData,
|
||||
0, 0,
|
||||
0, 0,
|
||||
$temp_width, $temp_height,
|
||||
$imageSize['width'], $imageSize['height']
|
||||
);
|
||||
imagecopyresampled($new, $imageData, 0, 0, $x, $y, $desired['width'], $desired['height'], $w, $h);
|
||||
|
||||
$copiedImages[$key] = $temp_gdim;
|
||||
|
||||
/*
|
||||
|
||||
# Copy cropped region from temporary image into the desired GD image
|
||||
$x0 = ( $temp_width - $desiredSize['width'] ) / 2;
|
||||
$y0 = ( $temp_height - $desiredSize['height'] ) / 2;
|
||||
|
||||
$desired_gdim = imagecreatetruecolor( $desiredSize['width'], $desiredSize['height'] );
|
||||
|
||||
if ($imageType == "gif")
|
||||
{
|
||||
imagepalettecopy($temp_gdim, $desired_gdim);
|
||||
imagefill($desired_gdim, 0, 0, $transparent_index);
|
||||
imagecolortransparent($desired_gdim, $transparent_index);
|
||||
imagetruecolortopalette($desired_gdim, true, 256);
|
||||
}
|
||||
elseif($imageType == "png")
|
||||
{
|
||||
imagealphablending($desired_gdim, false);
|
||||
imagesavealpha($desired_gdim,true);
|
||||
$transparent = imagecolorallocatealpha($desired_gdim, 255, 255, 255, 127);
|
||||
imagefilledrectangle($desired_gdim, 0, 0, $desired_size['with'], $desired_size['height'], $transparent);
|
||||
}
|
||||
|
||||
imagecopyresampled(
|
||||
$desired_gdim,
|
||||
$temp_gdim,
|
||||
0, 0,
|
||||
0, 0,
|
||||
$x0, $y0,
|
||||
$desiredSize['width'], $desiredSize['height']
|
||||
);
|
||||
$copiedImages[$key] = $desired_gdim;
|
||||
|
||||
*/
|
||||
$copiedImages[$key] = $new;
|
||||
}
|
||||
|
||||
return $copiedImages;
|
||||
}
|
||||
|
||||
# save original in temporary folder
|
||||
public function saveOriginal($folder, $image, $name, $type)
|
||||
{
|
||||
if(!file_exists($folder))
|
||||
{
|
||||
mkdir($folder, 0774, true);
|
||||
}
|
||||
|
||||
{
|
||||
$path = $folder . $name . '.' . $type;
|
||||
|
||||
file_put_contents($path, $image);
|
||||
}
|
||||
|
||||
|
||||
# save resized images in temporary folder
|
||||
public function saveImage($folder, $image, $name, $type)
|
||||
{
|
||||
if(!file_exists($folder))
|
||||
{
|
||||
mkdir($folder, 0774, true);
|
||||
}
|
||||
|
||||
{
|
||||
if($type == "png")
|
||||
{
|
||||
$result = imagepng( $image, $folder . '/' . $name . '.png' );
|
||||
$result = imagepng( $image, $folder . $name . '.png' );
|
||||
}
|
||||
elseif($type == "gif")
|
||||
{
|
||||
$result = imagegif( $image, $folder . '/' . $name . '.gif' );
|
||||
$result = imagegif( $image, $folder . $name . '.gif' );
|
||||
}
|
||||
else
|
||||
{
|
||||
$result = imagejpeg( $image, $folder . '/' . $name . '.jpeg' );
|
||||
$result = imagejpeg( $image, $folder . $name . '.jpeg' );
|
||||
$type = 'jpeg';
|
||||
}
|
||||
|
||||
|
@ -277,60 +226,156 @@ class ProcessImage
|
|||
return false;
|
||||
}
|
||||
|
||||
public function clearTempFolder()
|
||||
{
|
||||
$folder = getcwd() . DIRECTORY_SEPARATOR . 'media' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR;
|
||||
|
||||
if(!file_exists($folder))
|
||||
{
|
||||
mkdir($folder, 0774, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
$files = scandir($folder);
|
||||
$result = true;
|
||||
|
||||
foreach($files as $file)
|
||||
{
|
||||
if (!in_array($file, array(".","..")))
|
||||
{
|
||||
$filelink = $folder . $file;
|
||||
if(!unlink($filelink))
|
||||
{
|
||||
$success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function deleteImage($name)
|
||||
{
|
||||
$baseFolder = getcwd() . DIRECTORY_SEPARATOR . 'media' . DIRECTORY_SEPARATOR;
|
||||
$original = $baseFolder . 'original' . DIRECTORY_SEPARATOR . $name . '*';
|
||||
$live = $baseFolder . 'live' . DIRECTORY_SEPARATOR . $name . '*';
|
||||
$success = true;
|
||||
|
||||
foreach(glob($original) as $image)
|
||||
|
||||
# validate name
|
||||
$name = basename($name);
|
||||
|
||||
$result = true;
|
||||
|
||||
if(!file_exists($this->originalFolder . $name) OR !unlink($this->originalFolder . $name))
|
||||
{
|
||||
$result = false;
|
||||
}
|
||||
|
||||
if(!file_exists($this->liveFolder . $name) OR !unlink($this->liveFolder . $name))
|
||||
{
|
||||
$result = false;
|
||||
}
|
||||
|
||||
if(!file_exists($this->thumbFolder . $name) OR !unlink($this->thumbFolder . $name))
|
||||
{
|
||||
$result = false;
|
||||
}
|
||||
|
||||
# you should not use glob but exact name with ending
|
||||
/*
|
||||
foreach(glob($this->originalFolder . $name) as $image)
|
||||
{
|
||||
if(!unlink($image))
|
||||
{
|
||||
$success = false;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
foreach(glob($live) as $image)
|
||||
{
|
||||
if(!unlink($image))
|
||||
{
|
||||
$success = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $success;
|
||||
# array_map('unlink', glob("some/dir/*.txt"));
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/*
|
||||
* scans content of a folder (without recursion)
|
||||
* vars: folder path as string
|
||||
* returns: one-dimensional array with names of folders and files
|
||||
*/
|
||||
public function scanMediaFlat()
|
||||
{
|
||||
$thumbs = array_diff(scandir($this->thumbFolder), array('..', '.'));
|
||||
$imagelist = array();
|
||||
|
||||
foreach ($thumbs as $key => $name)
|
||||
{
|
||||
if (file_exists($this->liveFolder . $name))
|
||||
{
|
||||
$imagelist[] = [
|
||||
'name' => $name,
|
||||
'timestamp' => filemtime($this->liveFolder . $name),
|
||||
'src_thumb' => 'media/thumbs/' . $name,
|
||||
'src_live' => 'media/live/' . $name,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$imagelist = Helpers::array_sort($imagelist, 'timestamp', SORT_DESC);
|
||||
|
||||
return $imagelist;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
?>
|
||||
public function getImageDetails($name, $structure)
|
||||
{
|
||||
$name = basename($name);
|
||||
|
||||
if (!in_array($name, array(".","..")) && file_exists($this->liveFolder . $name))
|
||||
{
|
||||
$imageinfo = getimagesize($this->liveFolder . $name);
|
||||
|
||||
$imagedetails = [
|
||||
'name' => $name,
|
||||
'timestamp' => filemtime($this->liveFolder . $name),
|
||||
'bytes' => filesize($this->liveFolder . $name),
|
||||
'width' => $imageinfo[0],
|
||||
'height' => $imageinfo[1],
|
||||
'type' => $imageinfo['mime'],
|
||||
'src_thumb' => 'media/thumbs/' . $name,
|
||||
'src_live' => 'media/live/' . $name,
|
||||
'pages' => $this->findPagesWithUrl($structure, $name, $result = [])
|
||||
];
|
||||
|
||||
return $imagedetails;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function generateThumbs()
|
||||
{
|
||||
# generate images from live folder to 'tmthumbs'
|
||||
$liveImages = scandir($this->liveFolder);
|
||||
|
||||
foreach ($liveImages as $key => $name)
|
||||
{
|
||||
if (!in_array($name, array(".","..")))
|
||||
{
|
||||
$this->generateThumbFromImageFile($name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function generateThumbFromImageFile($filename)
|
||||
{
|
||||
$this->setFileName($filename, 'image', $overwrite = true);
|
||||
|
||||
if($this->extension == 'jpeg') $this->extension = 'jpg';
|
||||
|
||||
switch($this->extension)
|
||||
{
|
||||
case 'gif': $image = imagecreatefromgif($this->liveFolder . $filename); break;
|
||||
case 'jpg': $image = imagecreatefromjpeg($this->liveFolder . $filename); break;
|
||||
case 'png': $image = imagecreatefrompng($this->liveFolder . $filename); break;
|
||||
default: return 'image type not supported';
|
||||
}
|
||||
|
||||
$originalSize = $this->getImageSize($image);
|
||||
|
||||
$thumbSize = $this->desiredSizes['thumbs'];
|
||||
|
||||
$thumb = $this->imageResize($image, $originalSize, ['thumbs' => $thumbSize ], $this->extension);
|
||||
|
||||
$this->saveImage($this->thumbFolder, $thumb['thumbs'], $this->filename, $this->extension);
|
||||
}
|
||||
|
||||
public function generateSizesFromImageFile($filename, $image)
|
||||
{
|
||||
$this->setFileName($filename, 'image');
|
||||
|
||||
if($this->extension == 'jpeg') $this->extension = 'jpg';
|
||||
|
||||
switch($this->extension)
|
||||
{
|
||||
case 'gif': $image = imagecreatefromgif($image); break;
|
||||
case 'jpg': $image = imagecreatefromjpeg($image); break;
|
||||
case 'png': $image = imagecreatefrompng($image); break;
|
||||
default: return 'image type not supported';
|
||||
}
|
||||
|
||||
$originalSize = $this->getImageSize($image);
|
||||
|
||||
$resizedImages = $this->imageResize($image, $originalSize, $this->desiredSizes, $this->extension);
|
||||
|
||||
return $resizedImages;
|
||||
}
|
||||
|
||||
}
|
|
@ -3,34 +3,45 @@
|
|||
use Typemill\Controllers\SettingsController;
|
||||
use Typemill\Controllers\ContentController;
|
||||
use Typemill\Controllers\ContentApiController;
|
||||
use Typemill\Controllers\ArticleApiController;
|
||||
use Typemill\Controllers\BlockApiController;
|
||||
use Typemill\Controllers\MediaApiController;
|
||||
use Typemill\Controllers\MetaApiController;
|
||||
use Typemill\Middleware\RestrictApiAccess;
|
||||
|
||||
$app->get('/api/v1/themes', SettingsController::class . ':getThemeSettings')->setName('api.themes')->add(new RestrictApiAccess($container['router']));
|
||||
|
||||
$app->post('/api/v1/article/markdown', ContentApiController::class . ':getArticleMarkdown')->setName('api.article.markdown')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/article/html', ContentApiController::class . ':getArticleHtml')->setName('api.article.html')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/article/publish', ContentApiController::class . ':publishArticle')->setName('api.article.publish')->add(new RestrictApiAccess($container['router']));
|
||||
$app->delete('/api/v1/article/unpublish', ContentApiController::class . ':unpublishArticle')->setName('api.article.unpublish')->add(new RestrictApiAccess($container['router']));
|
||||
$app->delete('/api/v1/article/discard', ContentApiController::class . ':discardArticleChanges')->setName('api.article.discard')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/post', ContentApiController::class . ':createPost')->setName('api.post.create')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/article', ContentApiController::class . ':createArticle')->setName('api.article.create')->add(new RestrictApiAccess($container['router']));
|
||||
$app->put('/api/v1/article', ContentApiController::class . ':updateArticle')->setName('api.article.update')->add(new RestrictApiAccess($container['router']));
|
||||
$app->delete('/api/v1/article', ContentApiController::class . ':deleteArticle')->setName('api.article.delete')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/article/sort', ContentApiController::class . ':sortArticle')->setName('api.article.sort')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/article/markdown', ArticleApiController::class . ':getArticleMarkdown')->setName('api.article.markdown')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/article/html', ArticleApiController::class . ':getArticleHtml')->setName('api.article.html')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/article/publish', ArticleApiController::class . ':publishArticle')->setName('api.article.publish')->add(new RestrictApiAccess($container['router']));
|
||||
$app->delete('/api/v1/article/unpublish', ArticleApiController::class . ':unpublishArticle')->setName('api.article.unpublish')->add(new RestrictApiAccess($container['router']));
|
||||
$app->delete('/api/v1/article/discard', ArticleApiController::class . ':discardArticleChanges')->setName('api.article.discard')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/article/sort', ArticleApiController::class . ':sortArticle')->setName('api.article.sort')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/article', ArticleApiController::class . ':createArticle')->setName('api.article.create')->add(new RestrictApiAccess($container['router']));
|
||||
$app->put('/api/v1/article', ArticleApiController::class . ':updateArticle')->setName('api.article.update')->add(new RestrictApiAccess($container['router']));
|
||||
$app->delete('/api/v1/article', ArticleApiController::class . ':deleteArticle')->setName('api.article.delete')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/baseitem', ArticleApiController::class . ':createBaseItem')->setName('api.baseitem.create')->add(new RestrictApiAccess($container['router']));
|
||||
$app->get('/api/v1/navigation', ArticleApiController::class . ':getNavigation')->setName('api.navigation.get')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/post', ArticleApiController::class . ':createPost')->setName('api.post.create')->add(new RestrictApiAccess($container['router']));
|
||||
|
||||
$app->get('/api/v1/metadefinitions', MetaApiController::class . ':getMetaDefinitions')->setName('api.metadefinitions.get')->add(new RestrictApiAccess($container['router']));
|
||||
$app->get('/api/v1/article/metaobject', MetaApiController::class . ':getArticleMetaobject')->setName('api.articlemetaobject.get')->add(new RestrictApiAccess($container['router']));
|
||||
$app->get('/api/v1/article/metadata', MetaApiController::class . ':getArticleMeta')->setName('api.articlemeta.get')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/article/metadata', MetaApiController::class . ':updateArticleMeta')->setName('api.articlemeta.update')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/baseitem', ContentApiController::class . ':createBaseItem')->setName('api.baseitem.create')->add(new RestrictApiAccess($container['router']));
|
||||
$app->get('/api/v1/navigation', ContentApiController::class . ':getNavigation')->setName('api.navigation.get')->add(new RestrictApiAccess($container['router']));
|
||||
$app->get('/api/v1/metadefinitions', MetaApiController::class . ':getMetaDefinitions')->setName('api.metadefinitions.get')->add(new RestrictApiAccess($container['router']));
|
||||
|
||||
$app->post('/api/v1/block', ContentApiController::class . ':addBlock')->setName('api.block.add')->add(new RestrictApiAccess($container['router']));
|
||||
$app->put('/api/v1/block', ContentApiController::class . ':updateBlock')->setName('api.block.update')->add(new RestrictApiAccess($container['router']));
|
||||
$app->delete('/api/v1/block', ContentApiController::class . ':deleteBlock')->setName('api.block.delete')->add(new RestrictApiAccess($container['router']));
|
||||
$app->put('/api/v1/moveblock', ContentApiController::class . ':moveBlock')->setName('api.block.move')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/block', BlockApiController::class . ':addBlock')->setName('api.block.add')->add(new RestrictApiAccess($container['router']));
|
||||
$app->put('/api/v1/block', BlockApiController::class . ':updateBlock')->setName('api.block.update')->add(new RestrictApiAccess($container['router']));
|
||||
$app->delete('/api/v1/block', BlockApiController::class . ':deleteBlock')->setName('api.block.delete')->add(new RestrictApiAccess($container['router']));
|
||||
$app->put('/api/v1/moveblock', BlockApiController::class . ':moveBlock')->setName('api.block.move')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/video', BlockApiController::class . ':saveVideoImage')->setName('api.video.save')->add(new RestrictApiAccess($container['router']));
|
||||
|
||||
$app->post('/api/v1/image', ContentApiController::class . ':createImage')->setName('api.image.create')->add(new RestrictApiAccess($container['router']));
|
||||
$app->put('/api/v1/image', ContentApiController::class . ':publishImage')->setName('api.image.publish')->add(new RestrictApiAccess($container['router']));
|
||||
|
||||
$app->post('/api/v1/video', ContentApiController::class . ':saveVideoImage')->setName('api.video.save')->add(new RestrictApiAccess($container['router']));
|
||||
$app->get('/api/v1/medialib/images', MediaApiController::class . ':getMediaLibImages')->setName('api.medialibimg.get')->add(new RestrictApiAccess($container['router']));
|
||||
$app->get('/api/v1/medialib/files', MediaApiController::class . ':getMediaLibFiles')->setName('api.medialibfiles.get')->add(new RestrictApiAccess($container['router']));
|
||||
$app->get('/api/v1/image', MediaApiController::class . ':getImage')->setName('api.image.get')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/image', MediaApiController::class . ':createImage')->setName('api.image.create')->add(new RestrictApiAccess($container['router']));
|
||||
$app->put('/api/v1/image', MediaApiController::class . ':publishImage')->setName('api.image.publish')->add(new RestrictApiAccess($container['router']));
|
||||
$app->delete('/api/v1/image', MediaApiController::class . ':deleteImage')->setName('api.image.delete')->add(new RestrictApiAccess($container['router']));
|
||||
$app->get('/api/v1/file', MediaApiController::class . ':getFile')->setName('api.file.get')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/file', MediaApiController::class . ':uploadFile')->setName('api.file.upload')->add(new RestrictApiAccess($container['router']));
|
||||
$app->put('/api/v1/file', MediaApiController::class . ':publishFile')->setName('api.file.publish')->add(new RestrictApiAccess($container['router']));
|
||||
$app->delete('/api/v1/file', MediaApiController::class . ':deleteFile')->setName('api.file.delete')->add(new RestrictApiAccess($container['router']));
|
|
@ -69,4 +69,11 @@ foreach($routes as $pluginRoute)
|
|||
}
|
||||
}
|
||||
|
||||
$app->get('/[{params:.*}]', PageController::class . ':index')->setName('home');
|
||||
if($settings['settings']['setup'])
|
||||
{
|
||||
$app->get('/[{params:.*}]', SetupController::class . ':redirect');
|
||||
}
|
||||
else
|
||||
{
|
||||
$app->get('/[{params:.*}]', PageController::class . ':index')->setName('home');
|
||||
}
|
|
@ -16,6 +16,25 @@ class Settings
|
|||
$settings = array_merge($defaultSettings, $userSettings);
|
||||
}
|
||||
|
||||
# if there is no theme set
|
||||
if(!isset($settings['theme']))
|
||||
{
|
||||
# scan theme folder and get the first theme
|
||||
$themefolder = $settings['rootPath'] . $settings['themeFolder'] . DIRECTORY_SEPARATOR;
|
||||
$themes = array_diff(scandir($themefolder), array('..', '.'));
|
||||
$firsttheme = reset($themes);
|
||||
|
||||
# if there is a theme with valid theme settings-file
|
||||
if($firsttheme && self::getObjectSettings('themes', $firsttheme))
|
||||
{
|
||||
$settings['theme'] = $firsttheme;
|
||||
}
|
||||
else
|
||||
{
|
||||
die('There is no theme in the theme-folder. Please add a theme from https://themes.typemill.net');
|
||||
}
|
||||
}
|
||||
|
||||
# i18n
|
||||
# load the strings of the set language
|
||||
$language = $settings['language'];
|
||||
|
@ -30,7 +49,7 @@ class Settings
|
|||
{
|
||||
$themeSettings = self::getObjectSettings('themes', $settings['theme']);
|
||||
$settings['themes'][$settings['theme']] = isset($themeSettings['settings']) ? $themeSettings['settings'] : false;
|
||||
}
|
||||
}
|
||||
|
||||
return array('settings' => $settings);
|
||||
}
|
||||
|
@ -48,7 +67,6 @@ class Settings
|
|||
'language' => 'en',
|
||||
'startpage' => true,
|
||||
'rootPath' => $rootPath,
|
||||
'theme' => 'typemill',
|
||||
'themeFolder' => 'themes',
|
||||
'themeBasePath' => $rootPath,
|
||||
'themePath' => '',
|
||||
|
@ -56,14 +74,14 @@ class Settings
|
|||
'userPath' => $rootPath . 'settings' . DIRECTORY_SEPARATOR . 'users',
|
||||
'authorPath' => __DIR__ . DIRECTORY_SEPARATOR . 'author' . DIRECTORY_SEPARATOR,
|
||||
'editor' => 'visual',
|
||||
'formats' => ['markdown', 'headline', 'ulist', 'olist', 'table', 'quote', 'image', 'video', 'toc', 'hr', 'definition', 'code'],
|
||||
'formats' => ['markdown', 'headline', 'ulist', 'olist', 'table', 'quote', 'image', 'video', 'file', 'toc', 'hr', 'definition', 'code'],
|
||||
'contentFolder' => 'content',
|
||||
'cache' => true,
|
||||
'cachePath' => $rootPath . 'cache',
|
||||
'version' => '1.3.3',
|
||||
'setup' => true,
|
||||
'welcome' => true,
|
||||
'images' => ['live' => ['width' => 820], 'mlibrary' => ['width' => 50, 'height' => 50]],
|
||||
'images' => ['live' => ['width' => 820], 'thumbs' => ['width' => 250, 'height' => 150]],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -137,23 +155,25 @@ class Settings
|
|||
|
||||
if($userSettings)
|
||||
{
|
||||
# whitelist settings that can be stored in usersettings (values are not relevant here, only keys)
|
||||
# whitelist settings that can be stored in usersettings (values are not relevant here, only keys)
|
||||
$allowedUserSettings = ['displayErrorDetails' => true,
|
||||
'title' => false,
|
||||
'copyright' => false,
|
||||
'language' => false,
|
||||
'startpage' => false,
|
||||
'author' => false,
|
||||
'year' => false,
|
||||
'theme' => false,
|
||||
'editor' => false,
|
||||
'formats' => false,
|
||||
'setup' => false,
|
||||
'welcome' => false,
|
||||
'images' => false,
|
||||
'plugins' => false,
|
||||
'themes' => false,
|
||||
'latestVersion' => false
|
||||
'title' => true,
|
||||
'copyright' => true,
|
||||
'language' => true,
|
||||
'startpage' => true,
|
||||
'author' => true,
|
||||
'year' => true,
|
||||
'theme' => true,
|
||||
'editor' => true,
|
||||
'formats' => true,
|
||||
'setup' => true,
|
||||
'welcome' => true,
|
||||
'images' => true,
|
||||
'plugins' => true,
|
||||
'themes' => true,
|
||||
'latestVersion' => true,
|
||||
'logo' => true,
|
||||
'favicon' => true,
|
||||
];
|
||||
|
||||
# cleanup the existing usersettings
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
* TRANSITION *
|
||||
**********************/
|
||||
|
||||
a, a:link, a:visited, a:focus, a:hover, a:active, button, .button, .tab-button, input, .control-group, .sidebar-menu, .sidebar-menu--content, .menu-action, .button-arrow{
|
||||
a, a:link, a:visited, a:focus, a:hover, a:active, .link, button, .button, .tab-button, input, .control-group, .sidebar-menu, .sidebar-menu--content, .menu-action, .button-arrow{
|
||||
-webkit-transition: color 0.2s ease;
|
||||
-moz-transition: color 0.2s ease;
|
||||
-o-transition: color 0.2s ease;
|
||||
|
@ -30,6 +30,130 @@ a, a:link, a:visited, a:focus, a:hover, a:active, button, .button, .tab-button,
|
|||
-ms-transition: all 0.1s ease;
|
||||
transition: all 0.1s ease;
|
||||
}
|
||||
/********************
|
||||
* TACHYONS *
|
||||
********************/
|
||||
|
||||
.tm-red{
|
||||
color:#e0474c;
|
||||
}
|
||||
.bg-tm-red{
|
||||
background:#e0474c;
|
||||
}
|
||||
.b--tm-red{
|
||||
border-color: #e0474c;
|
||||
}
|
||||
.hover-tm-red:hover{
|
||||
color:#e0474c;
|
||||
}
|
||||
.hover-bg-tm-red:hover{
|
||||
background:#e0474c;
|
||||
}
|
||||
.hover-b--tm-red:hover{
|
||||
border-color:#e0474c;
|
||||
}
|
||||
.tm-green{
|
||||
color:#66b0a3;
|
||||
}
|
||||
.bg-tm-green{
|
||||
background:#66b0a3;
|
||||
}
|
||||
.b--tm-green{
|
||||
border-color:#66b0a3;
|
||||
}
|
||||
.hover-tm-green:hover{
|
||||
color:#66b0a3;
|
||||
}
|
||||
.hover-bg-tm-green:hover{
|
||||
background:#66b0a3;
|
||||
}
|
||||
.hover-b--tm-green:hover{
|
||||
border-color:#66b0a3;
|
||||
}
|
||||
.w-100{
|
||||
width:100%!important;
|
||||
}
|
||||
.w-15{
|
||||
width: 15%;
|
||||
}
|
||||
.w-29{
|
||||
width: 29%;
|
||||
}
|
||||
.w-95{
|
||||
width:95%;
|
||||
}
|
||||
.w6{
|
||||
width: 22rem;
|
||||
}
|
||||
.h0{
|
||||
height: 0;
|
||||
}
|
||||
.h6{
|
||||
height: 22rem;
|
||||
}
|
||||
.mw6{
|
||||
max-width:22rem;
|
||||
}
|
||||
.max-h6{
|
||||
max-height: 22rem;
|
||||
}
|
||||
.shadow-tm{
|
||||
box-shadow: 0 0 2px 1px rgba(0,0,0,.1);
|
||||
}
|
||||
.bg-chess{
|
||||
background-image: linear-gradient(45deg, #808080 25%, transparent 25%), linear-gradient(-45deg, #808080 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #808080 75%), linear-gradient(-45deg, transparent 75%, #808080 75%);
|
||||
background-size: 20px 20px;
|
||||
background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
|
||||
}
|
||||
input.upload{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
opacity: 0;
|
||||
}
|
||||
.top-3{
|
||||
top: 3rem;
|
||||
}
|
||||
.list-enter-active, .list-leave-active {
|
||||
transition: all .3s;
|
||||
}
|
||||
.list-enter, .list-leave-to /* .list-leave-active below version 2.1.8 */ {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/****************************
|
||||
* download-commponent *
|
||||
****************************/
|
||||
|
||||
a.tm-download
|
||||
{
|
||||
line-height: 35px;
|
||||
margin-left: 40px;
|
||||
}
|
||||
a.tm-download::before{
|
||||
content: '\2193';
|
||||
position: absolute;
|
||||
margin-left: -40px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
font-family: "Comic Sans MS",cursive,sans-serif;
|
||||
border: 2px solid #e0474c;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
text-decoration: underline;
|
||||
}
|
||||
a.tm-download:hover::before{
|
||||
text-decoration:underline;
|
||||
color: #fff;
|
||||
background: #e0474c;
|
||||
}
|
||||
|
||||
|
||||
/********************
|
||||
* COMMONS *
|
||||
********************/
|
||||
|
@ -453,8 +577,7 @@ footer{
|
|||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #FFF;
|
||||
background: rgba(255,255,255,0.9);
|
||||
background: rgba(0,0,0,0.4);
|
||||
}
|
||||
.modal{
|
||||
display: none;
|
||||
|
@ -1526,14 +1649,14 @@ label .help, .label .help{
|
|||
}
|
||||
.blox-buttons button.edit{
|
||||
background: #70c1b3;
|
||||
color: #eee;
|
||||
color: #fff;
|
||||
}
|
||||
.blox-buttons button.edit:hover{
|
||||
background: #4D978A;
|
||||
}
|
||||
.blox-buttons button.cancel:hover{
|
||||
background: #e0474c;
|
||||
color: #eee;
|
||||
color: #fff;
|
||||
}
|
||||
.blox-buttons button.edit:disabled, .blox-buttons button.cancel:disabled{
|
||||
background: #eee;
|
||||
|
@ -1750,6 +1873,9 @@ button.post-button:hover{
|
|||
width: 0.7142857142857142em;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.icon-upload {
|
||||
width: 0.9285714285714285em;
|
||||
}
|
||||
.format-bar .hidden{
|
||||
display: none;
|
||||
}
|
||||
|
@ -1764,7 +1890,7 @@ button.post-button:hover{
|
|||
.format-bar .editactive{
|
||||
position: relative;
|
||||
margin-left: -20px;
|
||||
margin-right: 20px;
|
||||
margin-right: -20px;
|
||||
}
|
||||
.format-bar.blox{
|
||||
width: auto;
|
||||
|
@ -2121,6 +2247,25 @@ button.delDL i:hover{
|
|||
.blox blockquote p{
|
||||
margin-left: 50px;
|
||||
}
|
||||
.imageupload{
|
||||
width: 50%;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
border-right: 1px dotted grey;
|
||||
box-sizing:border-box;
|
||||
}
|
||||
.imageselect{
|
||||
width: 50%;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
box-sizing:border-box;
|
||||
border:0px;
|
||||
padding: 0 0 0 0;
|
||||
margin: 0 0 0 0;
|
||||
min-height: 70px;
|
||||
background: #f9f8f6;
|
||||
font-family: Helvetica, Calibri, Arial, sans-serif;
|
||||
}
|
||||
.dropbox{
|
||||
min-height: 70px;
|
||||
background: #f9f8f6;
|
||||
|
@ -2212,6 +2357,22 @@ button.delDL i:hover{
|
|||
border-color: transparent transparent transparent rgba(255, 255, 255, 0.75);
|
||||
content: ' ';
|
||||
}
|
||||
.medialib{
|
||||
margin: auto;
|
||||
width: 100%;
|
||||
height: 80%;
|
||||
overflow: auto;
|
||||
background: #f9f8f6;
|
||||
max-width: 1200px;
|
||||
}
|
||||
.imagecard{
|
||||
margin: 10px;
|
||||
box-shadow:0 2px 5px rgba(22,23,26,.05);
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
sup{}
|
||||
cite{}
|
||||
abbr{}
|
||||
|
|
|
@ -125,7 +125,7 @@
|
|||
/**********************************
|
||||
** START VERSION CHECK **
|
||||
**********************************/
|
||||
|
||||
|
||||
if(document.getElementById("system"))
|
||||
{
|
||||
getVersions('system', document.getElementsByClassName("fc-system-version"));
|
||||
|
@ -243,7 +243,6 @@
|
|||
}
|
||||
return segmentsA.length - segmentsB.length;
|
||||
}
|
||||
|
||||
|
||||
/*************************************
|
||||
** CARDS: ACTIVATE/OPEN CLOSE **
|
||||
|
@ -269,6 +268,38 @@
|
|||
}
|
||||
}
|
||||
|
||||
/*************************************
|
||||
** Input Type File **
|
||||
*************************************/
|
||||
|
||||
var fileinputs = document.querySelectorAll( ".fileinput" );
|
||||
|
||||
for (i = 0; i < fileinputs.length; ++i)
|
||||
{
|
||||
(function () {
|
||||
|
||||
thisfileinput = fileinputs[i];
|
||||
|
||||
var deletefilebutton = thisfileinput.getElementsByClassName("deletefilebutton")[0];
|
||||
var deletefileinput = thisfileinput.getElementsByClassName("deletefileinput")[0];
|
||||
var visiblefilename = thisfileinput.getElementsByClassName("visiblefilename")[0];
|
||||
var hiddenfile = thisfileinput.getElementsByClassName("hiddenfile")[0];
|
||||
|
||||
hiddenfile.onchange = function()
|
||||
{
|
||||
visiblefilename.value = this.files[0].name;
|
||||
}
|
||||
|
||||
deletefilebutton.onclick = function(event)
|
||||
{
|
||||
event.preventDefault();
|
||||
deletefileinput.value = 'delete';
|
||||
visiblefilename.value = '';
|
||||
}
|
||||
|
||||
}());
|
||||
}
|
||||
|
||||
/*************************************
|
||||
** COLOR PICKER **
|
||||
*************************************/
|
||||
|
|
|
@ -42,6 +42,13 @@ let determiner = {
|
|||
}
|
||||
return false;
|
||||
},
|
||||
file: function(block,lines,firstChar,secondChar,thirdChar){
|
||||
if( (firstChar == '[' && lines[0].indexOf('{.tm-download') != -1) )
|
||||
{
|
||||
return "file-component";
|
||||
}
|
||||
return false;
|
||||
},
|
||||
code: function(block,lines,firstChar,secondChar,thirdChar){
|
||||
if( firstChar == '`' && secondChar == '`' && thirdChar == '`')
|
||||
{
|
||||
|
@ -67,6 +74,7 @@ let bloxFormats = {
|
|||
quote: { label: '<svg class="icon icon-quotes-left"><use xlink:href="#icon-quotes-left"></use></svg>', title: 'Quote', component: 'quote-component' },
|
||||
image: { label: '<svg class="icon icon-image"><use xlink:href="#icon-image"></use></svg>', title: 'Image', component: 'image-component' },
|
||||
video: { label: '<svg class="icon icon-play"><use xlink:href="#icon-play"></use></svg>', title: 'Video', component: 'video-component' },
|
||||
file: { label: '<svg class="icon icon-paperclip"><use xlink:href="#icon-paperclip"></use></svg>', title: 'File', component: 'file-component' },
|
||||
toc: { label: '<svg class="icon icon-list-alt"><use xlink:href="#icon-list-alt"></use></svg>', title: 'Table of Contents', component: 'toc-component' },
|
||||
hr: { label: '<svg class="icon icon-pagebreak"><use xlink:href="#icon-pagebreak"></use></svg>', title: 'Horizontal Line', component: 'hr-component' },
|
||||
definition: { label: '<svg class="icon icon-dots-two-vertical"><use xlink:href="#icon-dots-two-vertical"></use></svg>', title: 'Definition List', component: 'definition-component' },
|
||||
|
|
|
@ -5,9 +5,9 @@ const contentComponent = Vue.component('content-block', {
|
|||
template: '<div ref="bloxcomponent" class="blox-editor" :class="newblock">' +
|
||||
'<div v-if="newblock" class="newblock-info">Choose a content-type <button class="newblock-close" @click.prevent="closeNewBlock($event)">close</button></div>' +
|
||||
'<div class="blox-wrapper" :class="{ editactive: edit }">' +
|
||||
'<div class="sideaction" v-if="body">' +
|
||||
'<button class="add" :disabled="disabled" :title="$t(\'add content-block\')" @click.prevent="addNewBlock($event)"><svg class="icon icon-plus"><use xlink:href="#icon-plus"></use></svg></button>' +
|
||||
'<button class="delete" :disabled="disabled" :title="$t(\'delete content-block\')" @click.prevent="deleteBlock($event)"><svg class="icon icon-close"><use xlink:href="#icon-close"></use></svg></button>' +
|
||||
'<div class="sideaction" slot="header" v-if="body">' +
|
||||
'<button class="add" :disabled="disabled" :title="$t(\'add content-block\')" @mousedown.prevent="disableSort()" @click.prevent="addNewBlock($event)"><svg class="icon icon-plus"><use xlink:href="#icon-plus"></use></svg></button>' +
|
||||
'<button class="delete" :disabled="disabled" :title="$t(\'delete content-block\')" @mousedown.prevent="disableSort()" @click.prevent="deleteBlock($event)"><svg class="icon icon-close"><use xlink:href="#icon-close"></use></svg></button>' +
|
||||
'</div>' +
|
||||
'<div class="background-helper" @keyup.enter="submitBlock" @click="getData">' +
|
||||
'<div class="component" ref="component">' +
|
||||
|
@ -40,6 +40,10 @@ const contentComponent = Vue.component('content-block', {
|
|||
eventBus.$on('closeComponents', this.closeComponents);
|
||||
},
|
||||
methods: {
|
||||
disableSort: function(event)
|
||||
{
|
||||
this.$root.$data.sortdisabled = true;
|
||||
},
|
||||
addNewBlock: function(event)
|
||||
{
|
||||
/* we have to get from dom because block-data might not be set when user clicked on add button before opened the component */
|
||||
|
@ -76,9 +80,27 @@ const contentComponent = Vue.component('content-block', {
|
|||
updateMarkdown: function($event)
|
||||
{
|
||||
this.compmarkdown = $event;
|
||||
this.$nextTick(function () {
|
||||
this.$refs.preview.style.minHeight = this.$refs.component.offsetHeight + 'px';
|
||||
});
|
||||
this.setComponentSize();
|
||||
},
|
||||
setComponentSize: function()
|
||||
{
|
||||
if(this.componentType == 'image-component')
|
||||
{
|
||||
myself = this;
|
||||
setTimeout(function(){
|
||||
myself.$nextTick(function ()
|
||||
{
|
||||
myself.$refs.preview.style.minHeight = myself.$refs.component.offsetHeight + 'px';
|
||||
});
|
||||
}, 200);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.$nextTick(function ()
|
||||
{
|
||||
this.$refs.preview.style.minHeight = this.$refs.component.offsetHeight + 'px';
|
||||
});
|
||||
}
|
||||
},
|
||||
switchToEditMode: function()
|
||||
{
|
||||
|
@ -91,22 +113,7 @@ const contentComponent = Vue.component('content-block', {
|
|||
this.edit = true; /* show the edit-mode */
|
||||
this.compmarkdown = self.$root.$data.blockMarkdown; /* get markdown data */
|
||||
this.componentType = self.$root.$data.blockType; /* get block-type of element */
|
||||
if(this.componentType == 'image-component')
|
||||
{
|
||||
setTimeout(function(){
|
||||
self.$nextTick(function ()
|
||||
{
|
||||
self.$refs.preview.style.minHeight = self.$refs.component.offsetHeight + 'px';
|
||||
});
|
||||
}, 200);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.$nextTick(function ()
|
||||
{
|
||||
this.$refs.preview.style.minHeight = self.$refs.component.offsetHeight + 'px';
|
||||
});
|
||||
}
|
||||
this.setComponentSize();
|
||||
},
|
||||
closeComponents: function()
|
||||
{
|
||||
|
@ -216,7 +223,8 @@ const contentComponent = Vue.component('content-block', {
|
|||
{
|
||||
var compmarkdown = this.compmarkdown.split('\n\n').join('\n');
|
||||
}
|
||||
*/ var compmarkdown = this.compmarkdown;
|
||||
*/
|
||||
var compmarkdown = this.compmarkdown;
|
||||
|
||||
var params = {
|
||||
'url': document.getElementById("path").value,
|
||||
|
@ -228,13 +236,33 @@ const contentComponent = Vue.component('content-block', {
|
|||
|
||||
if(this.componentType == 'image-component' && self.$root.$data.file)
|
||||
{
|
||||
var url = self.$root.$data.root + '/api/v1/image';
|
||||
var url = self.$root.$data.root + '/api/v1/image';
|
||||
var method = 'PUT';
|
||||
params.new = false;
|
||||
if(self.$root.$data.newblock || self.$root.$data.blockId == 99999)
|
||||
{
|
||||
params.new = true;
|
||||
}
|
||||
}
|
||||
else if(this.componentType == 'video-component')
|
||||
{
|
||||
var url = self.$root.$data.root + '/api/v1/video';
|
||||
var method = 'POST';
|
||||
var url = self.$root.$data.root + '/api/v1/video';
|
||||
var method = 'POST';
|
||||
params.new = false;
|
||||
if(self.$root.$data.newblock || self.$root.$data.blockId == 99999)
|
||||
{
|
||||
params.new = true;
|
||||
}
|
||||
}
|
||||
else if(this.componentType == 'file-component')
|
||||
{
|
||||
var url = self.$root.$data.root + '/api/v1/file';
|
||||
var method = 'PUT';
|
||||
params.new = false;
|
||||
if(self.$root.$data.newblock || self.$root.$data.blockId == 99999)
|
||||
{
|
||||
params.new = true;
|
||||
}
|
||||
}
|
||||
else if(self.$root.$data.newblock || self.$root.$data.blockId == 99999)
|
||||
{
|
||||
|
@ -246,7 +274,7 @@ const contentComponent = Vue.component('content-block', {
|
|||
var url = self.$root.$data.root + '/api/v1/block';
|
||||
var method = 'PUT';
|
||||
}
|
||||
|
||||
|
||||
sendJson(function(response, httpStatus)
|
||||
{
|
||||
if(httpStatus == 400)
|
||||
|
@ -1180,12 +1208,21 @@ const videoComponent = Vue.component('video-component', {
|
|||
},
|
||||
})
|
||||
|
||||
|
||||
const imageComponent = Vue.component('image-component', {
|
||||
props: ['compmarkdown', 'disabled'],
|
||||
template: '<div class="dropbox">' +
|
||||
'<input type="hidden" ref="markdown" :value="compmarkdown" :disabled="disabled" @input="updatemarkdown" />' +
|
||||
'<input type="file" name="image" accept="image/*" class="input-file" @change="onFileChange( $event )" /> ' +
|
||||
'<p>{{ $t(\'drag a picture or click to select\') }}</p>' +
|
||||
'<div class="imageupload">' +
|
||||
'<input type="file" name="image" accept="image/*" class="input-file" @change="onFileChange( $event )" /> ' +
|
||||
'<p><svg class="icon icon-upload baseline"><use xlink:href="#icon-upload"></use></svg> {{ $t(\'drag a picture or click to select\') }}</p>' +
|
||||
'</div>' +
|
||||
'<button class="imageselect" @click.prevent="openmedialib()"><svg class="icon icon-image baseline"><use xlink:href="#icon-image"></use></svg> select from medialib</button>' +
|
||||
'<transition name="fade-editor">' +
|
||||
'<div v-if="showmedialib" class="modalWindow">' +
|
||||
'<medialib parentcomponent="images"></medialib>' +
|
||||
'</div>' +
|
||||
'</transition>' +
|
||||
'<div class="contenttype"><svg class="icon icon-image"><use xlink:href="#icon-image"></use></svg></div>' +
|
||||
'<img class="uploadPreview" :src="imgpreview" />' +
|
||||
'<div v-if="load" class="loadwrapper"><span class="load"></span></div>' +
|
||||
|
@ -1201,6 +1238,7 @@ const imageComponent = Vue.component('image-component', {
|
|||
return {
|
||||
maxsize: 5, // megabyte
|
||||
imgpreview: false,
|
||||
showmedialib: false,
|
||||
load: false,
|
||||
imgmeta: false,
|
||||
imgalt: '',
|
||||
|
@ -1209,7 +1247,7 @@ const imageComponent = Vue.component('image-component', {
|
|||
imglink: '',
|
||||
imgclass: 'center',
|
||||
imgid: '',
|
||||
imgfile: 'imgplchldr',
|
||||
imgfile: '',
|
||||
}
|
||||
},
|
||||
mounted: function(){
|
||||
|
@ -1280,6 +1318,10 @@ const imageComponent = Vue.component('image-component', {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
openmedialib: function()
|
||||
{
|
||||
this.showmedialib = true;
|
||||
},
|
||||
isChecked: function(classname)
|
||||
{
|
||||
if(this.imgclass == classname)
|
||||
|
@ -1372,7 +1414,7 @@ const imageComponent = Vue.component('image-component', {
|
|||
errors = 'Maximum size of image caption is 140 characters';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(errors)
|
||||
{
|
||||
this.$parent.freezePage();
|
||||
|
@ -1402,17 +1444,17 @@ const imageComponent = Vue.component('image-component', {
|
|||
}
|
||||
else
|
||||
{
|
||||
self = this;
|
||||
this.$parent.freezePage();
|
||||
this.$root.$data.file = true;
|
||||
this.load = true;
|
||||
self = this;
|
||||
|
||||
self.$parent.freezePage();
|
||||
self.$root.$data.file = true;
|
||||
self.load = true;
|
||||
|
||||
let reader = new FileReader();
|
||||
reader.readAsDataURL(imageFile);
|
||||
reader.onload = function(e) {
|
||||
|
||||
self.imgpreview = e.target.result;
|
||||
self.$emit('updatedMarkdown', '');
|
||||
|
||||
|
||||
/* load image to server */
|
||||
var url = self.$root.$data.root + '/api/v1/image';
|
||||
|
@ -1420,22 +1462,23 @@ const imageComponent = Vue.component('image-component', {
|
|||
var params = {
|
||||
'url': document.getElementById("path").value,
|
||||
'image': e.target.result,
|
||||
'name': imageFile.name,
|
||||
'csrf_name': document.getElementById("csrf_name").value,
|
||||
'csrf_value': document.getElementById("csrf_value").value,
|
||||
};
|
||||
|
||||
|
||||
var method = 'POST';
|
||||
|
||||
|
||||
sendJson(function(response, httpStatus)
|
||||
{
|
||||
{
|
||||
if(httpStatus == 400)
|
||||
{
|
||||
self.$parent.activatePage();
|
||||
self.activatePage();
|
||||
}
|
||||
if(response)
|
||||
{
|
||||
self.$parent.activatePage();
|
||||
self.load = false;
|
||||
self.$parent.activatePage();
|
||||
|
||||
var result = JSON.parse(response);
|
||||
|
||||
|
@ -1446,6 +1489,200 @@ const imageComponent = Vue.component('image-component', {
|
|||
else
|
||||
{
|
||||
self.imgmeta = true;
|
||||
self.imgfile = result.name;
|
||||
self.$emit('updatedMarkdown', '');
|
||||
}
|
||||
}
|
||||
}, method, url, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const fileComponent = Vue.component('file-component', {
|
||||
props: ['compmarkdown', 'disabled'],
|
||||
template: '<div class="dropbox">' +
|
||||
'<input type="hidden" ref="markdown" :value="compmarkdown" :disabled="disabled" @input="updatemarkdown" />' +
|
||||
'<div class="imageupload">' +
|
||||
'<input type="file" accept="*" name="file" class="input-file" @change="onFileChange( $event )" /> ' +
|
||||
'<p><svg class="icon icon-upload baseline"><use xlink:href="#icon-upload"></use></svg> {{ $t(\'upload file\') }}</p>' +
|
||||
'</div>' +
|
||||
'<button class="imageselect" @click.prevent="openmedialib()"><svg class="icon icon-paperclip baseline"><use xlink:href="#icon-paperclip"></use></svg> select from medialib</button>' +
|
||||
'<transition name="fade-editor">' +
|
||||
'<div v-if="showmedialib" class="modalWindow">' +
|
||||
'<medialib parentcomponent="files"></medialib>' +
|
||||
'</div>' +
|
||||
'</transition>' +
|
||||
'<div class="contenttype"><svg class="icon icon-paperclip"><use xlink:href="#icon-paperclip"></use></svg></div>' +
|
||||
'<div v-if="load" class="loadwrapper"><span class="load"></span></div>' +
|
||||
'<div class="imgmeta relative" v-if="filemeta">' +
|
||||
'<label for="filetitle">{{ $t(\'Title\') }}: </label>' +
|
||||
'<input name="filetitle" type="text" placeholder="Add a title for the download-link" v-model="filetitle" @input="createmarkdown" max="64" />' +
|
||||
'<input title="fileid" type="hidden" placeholder="id" v-model="fileid" @input="createmarkdown" max="140" />' +
|
||||
'</div></div>',
|
||||
data: function(){
|
||||
return {
|
||||
maxsize: 5, // megabyte
|
||||
showmedialib: false,
|
||||
load: false,
|
||||
filemeta: false,
|
||||
filetitle: '',
|
||||
fileextension: '',
|
||||
fileurl: '',
|
||||
fileid: ''
|
||||
}
|
||||
},
|
||||
mounted: function(){
|
||||
|
||||
this.$refs.markdown.focus();
|
||||
|
||||
if(this.compmarkdown)
|
||||
{
|
||||
this.filemeta = true;
|
||||
|
||||
var filemarkdown = this.compmarkdown;
|
||||
|
||||
var filetitle = filemarkdown.match(/\[.*?\]/);
|
||||
if(filetitle)
|
||||
{
|
||||
filemarkdown = filemarkdown.replace(filetitle[0],'');
|
||||
this.filetitle = filetitle[0].slice(1,-1);
|
||||
}
|
||||
|
||||
var fileattr = filemarkdown.match(/\{.*?\}/);
|
||||
var fileextension = filemarkdown.match(/file-(.*)?\}/);
|
||||
if(fileattr && fileextension)
|
||||
{
|
||||
filemarkdown = filemarkdown.replace(fileattr[0],'');
|
||||
this.fileextension = fileextension[1].trim();
|
||||
}
|
||||
|
||||
var fileurl = filemarkdown.match(/\(.*?\)/g);
|
||||
if(fileurl)
|
||||
{
|
||||
filemarkdown = filemarkdown.replace(fileurl[0],'');
|
||||
this.fileurl = fileurl[0].slice(1,-1);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
openmedialib: function()
|
||||
{
|
||||
this.showmedialib = true;
|
||||
},
|
||||
isChecked: function(classname)
|
||||
{
|
||||
if(this.fileclass == classname)
|
||||
{
|
||||
return ' checked';
|
||||
}
|
||||
},
|
||||
updatemarkdown: function(event)
|
||||
{
|
||||
this.$emit('updatedMarkdown', event.target.value);
|
||||
},
|
||||
createmarkdown: function()
|
||||
{
|
||||
var errors = false;
|
||||
|
||||
if(this.filetitle.length < 101)
|
||||
{
|
||||
filemarkdown = '[' + this.filetitle + ']';
|
||||
}
|
||||
else
|
||||
{
|
||||
errors = 'Maximum size of file-text is 100 characters';
|
||||
filemarkdown = '[]';
|
||||
}
|
||||
if(this.fileurl != '')
|
||||
{
|
||||
if(this.fileurl.length < 101)
|
||||
{
|
||||
filemarkdown = '[' + this.filetitle + '](' + this.fileurl + ')';
|
||||
}
|
||||
else
|
||||
{
|
||||
errors = 'Maximum size of file link is 100 characters';
|
||||
}
|
||||
}
|
||||
if(this.fileextension != '')
|
||||
{
|
||||
filemarkdown = filemarkdown + '{.tm-download file-' + this.fileextension + '}';
|
||||
}
|
||||
if(errors)
|
||||
{
|
||||
this.$parent.freezePage();
|
||||
publishController.errors.message = errors;
|
||||
}
|
||||
else
|
||||
{
|
||||
publishController.errors.message = false;
|
||||
this.$parent.activatePage();
|
||||
this.$emit('updatedMarkdown', filemarkdown);
|
||||
}
|
||||
},
|
||||
onFileChange: function( e )
|
||||
{
|
||||
if(e.target.files.length > 0)
|
||||
{
|
||||
let uploadedFile = e.target.files[0];
|
||||
let size = uploadedFile.size / 1024 / 1024;
|
||||
|
||||
if (size > this.maxsize)
|
||||
{
|
||||
publishController.errors.message = "The maximal size of a file is " + this.maxsize + " MB";
|
||||
}
|
||||
else
|
||||
{
|
||||
self = this;
|
||||
|
||||
self.$parent.freezePage();
|
||||
self.$root.$data.file = true;
|
||||
self.load = true;
|
||||
|
||||
let reader = new FileReader();
|
||||
reader.readAsDataURL(uploadedFile);
|
||||
reader.onload = function(e) {
|
||||
|
||||
/* load file to server */
|
||||
var url = self.$root.$data.root + '/api/v1/file';
|
||||
|
||||
var params = {
|
||||
'url': document.getElementById("path").value,
|
||||
'file': e.target.result,
|
||||
'name': uploadedFile.name,
|
||||
'csrf_name': document.getElementById("csrf_name").value,
|
||||
'csrf_value': document.getElementById("csrf_value").value,
|
||||
};
|
||||
|
||||
var method = 'POST';
|
||||
|
||||
sendJson(function(response, httpStatus)
|
||||
{
|
||||
if(httpStatus == 400)
|
||||
{
|
||||
self.activatePage();
|
||||
}
|
||||
if(response)
|
||||
{
|
||||
self.load = false;
|
||||
self.$parent.activatePage();
|
||||
|
||||
var result = JSON.parse(response);
|
||||
|
||||
if(result.errors)
|
||||
{
|
||||
publishController.errors.message = result.errors;
|
||||
}
|
||||
else
|
||||
{
|
||||
self.filemeta = true;
|
||||
self.filetitle = result.info.title;
|
||||
self.fileextension = result.info.extension;
|
||||
self.fileurl = result.info.url;
|
||||
self.createmarkdown();
|
||||
}
|
||||
}
|
||||
}, method, url, params);
|
||||
|
@ -1456,6 +1693,493 @@ const imageComponent = Vue.component('image-component', {
|
|||
}
|
||||
})
|
||||
|
||||
const medialib = Vue.component('medialib', {
|
||||
props: ['parentcomponent'],
|
||||
template: '<div class="medialib">' +
|
||||
'<div class="mt3">' +
|
||||
'<div class="w-30 dib v-top ph4 pv3">' +
|
||||
'<button class="f6 link br0 ba ph3 pv2 mb2 w-100 dim white bn bg-tm-red" @click.prevent="closemedialib()">{{ $t(\'close library\') }}</button>' +
|
||||
'<div class="w-100 relative">' +
|
||||
'<div><input v-model="search" class="w-100 border-box pa2 mb3 br0 ba b--light-silver"><svg class="icon icon-search absolute top-1 right-1 pa1 gray"><use xlink:href="#icon-search"></use></svg></div>' +
|
||||
'</div>' +
|
||||
'<button @click.prevent="showImages()" class="link br0 ba ph3 pv2 mv2 mr1" :class="isImagesActive()">Images</button>' +
|
||||
'<button @click.prevent="showFiles()" class="link br0 ba ph3 pv2 mv2 ml1" :class="isFilesActive()">Files</button>' +
|
||||
'</div>' +
|
||||
'<div class="w-70 dib v-top center">' +
|
||||
'<div v-if="errors" class="w-95 mv3 white bg-tm-red tc f5 lh-copy pa3">{{errors}}</div>' +
|
||||
'<transition-group name="list">' +
|
||||
'<div class="w-29 ma3 dib v-top bg-white shadow-tm overflow-hidden" v-for="(image, index) in filteredImages" :key="image.name" v-if="showimages">' +
|
||||
'<a href="#" @click.prevent="selectImage(image)" :style="getBackgroundImage(image)" class="link mw5 dt hide-child cover bg-center">' +
|
||||
'<span class="white dtc v-mid center w-100 h-100 child bg-black-80 pa5"><svg class="icon icon-check baseline"><use xlink:href="#icon-check"></use></svg> click to select</span>' +
|
||||
'</a>' +
|
||||
'<div>' +
|
||||
'<div class="w-70 dib v-top pl3 pv3 f6 truncate"><strong>{{ image.name }}</strong></div>' +
|
||||
'<button @click.prevent="showImageDetails(image,index)" class="w-15 center dib link bn v-mid pv3 bg-white hover-bg-tm-green hover-white"><svg class="icon icon-info baseline"><use xlink:href="#icon-info"></use></svg></button>' +
|
||||
'<button @click.prevent="deleteImage(image,index)" class="w-15 center dib link bn v-mid pv3 bg-white hover-bg-tm-red hover-white"><svg class="icon icon-trash-o baseline"><use xlink:href="#icon-trash-o"></use></svg></button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</transition-group>' +
|
||||
'<div class="w-95 dib v-top bg-white mv3 relative" v-if="showimagedetails">' +
|
||||
'<div class="flex flex-wrap item-start">' +
|
||||
'<div class="w-50">' +
|
||||
'<div class="w6 h6 bg-black-40 dtc v-mid bg-chess">' +
|
||||
'<img :src="imagedetaildata.src_live" class="mw6 max-h6 dt center">' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="w-50 pa3 lh-copy f7 relative">' +
|
||||
'<div class="black-30 mt3 mb1">Name</div><div class="b">{{ imagedetaildata.name}}</div>' +
|
||||
'<div class="black-30 mt3 mb1">URL</div><div class="b">{{ imagedetaildata.src_live}}</div>' +
|
||||
'<div class="flex flex-wrap item-start">' +
|
||||
'<div class="w-50">' +
|
||||
'<div class="black-30 mt3 mb1">Size</div><div class="b">{{ getSize(imagedetaildata.bytes) }}</div>' +
|
||||
'</div>' +
|
||||
'<div class="w-50">' +
|
||||
'<div class="black-30 mt3 mb1">Dimensions</div><div class="b">{{ imagedetaildata.width }}x{{ imagedetaildata.height }} px</div>' +
|
||||
'</div>' +
|
||||
'<div class="w-50">' +
|
||||
'<div class="black-30 mt3 mb1">Type</div><div class="b">{{ imagedetaildata.type }}</div>' +
|
||||
'</div>' +
|
||||
'<div class="w-50">' +
|
||||
'<div class="black-30 mt3 mb1">Date</div><div class="b">{{ getDate(imagedetaildata.timestamp) }}</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="absolute w-90 bottom-0 flex justify-between">' +
|
||||
'<button @click.prevent="selectImage(imagedetaildata)" class="w-50 mr1 pa2 link bn bg-light-gray hover-bg-tm-green hover-white"><svg class="icon icon-check baseline"><use xlink:href="#icon-check"></use></svg> select</button>' +
|
||||
'<button @click.prevent="deleteImage(imagedetaildata, detailindex)" class="w-50 ml1 pa2 link bn bg-light-gray hover-bg-tm-red hover-white"><svg class="icon icon-trash-o baseline"><use xlink:href="#icon-trash-o"></use></svg> delete</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<button class="f7 link br0 ba ph3 pv2 dim white bn bg-tm-red absolute top-0 right-0" @click.prevent="showImages()">close details</button>' +
|
||||
'<div class="pa3">' +
|
||||
'<h4>Image used in:</h4>' +
|
||||
'<ul class="ma0 pa0" v-if="imagedetaildata.pages && imagedetaildata.pages.length > 0">' +
|
||||
'<li class="list pa1" v-for="page in imagedetaildata.pages">' +
|
||||
'<a class="link tm-red" :href="baseurl + page">{{ page }}</a>' +
|
||||
'</li>' +
|
||||
'</ul>' +
|
||||
'<div v-else>No pages found.</div>'+
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<transition-group name="list">' +
|
||||
'<div class="w-29 ma3 dib v-top bg-white shadow-tm overflow-hidden" v-for="(file, index) in filteredFiles" :key="file.name" v-if="showfiles">' +
|
||||
'<a href="#" @click.prevent="selectFile(file)" class="w-100 link cover bg-tm-green bg-center relative dt">' +
|
||||
'<div class="absolute w-100 tc white f1 top-3 h0 ttu" v-html="file.info.extension"></div>' +
|
||||
'<div class="link dt hide-child w-100">' +
|
||||
'<span class="white dtc v-top center w-100 h-100 child pt6 pb3 tc"><svg class="icon icon-check baseline"><use xlink:href="#icon-check"></use></svg> click to select</span>' +
|
||||
'</div>' +
|
||||
'</a>' +
|
||||
'<div>' +
|
||||
'<div class="w-70 dib v-top pl3 pv3 f6 truncate"><strong>{{ file.name }}</strong></div>' +
|
||||
'<button @click.prevent="showFileDetails(file,index)" class="w-15 center dib link bn v-mid pv3 bg-white hover-bg-tm-green hover-white"><svg class="icon icon-info baseline"><use xlink:href="#icon-info"></use></svg></button>' +
|
||||
'<button @click.prevent="deleteFile(file,index)" class="w-15 center dib link bn v-mid pv3 bg-white hover-bg-tm-red hover-white"><svg class="icon icon-trash-o baseline"><use xlink:href="#icon-trash-o"></use></svg></button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</transition-group>' +
|
||||
'<div class="w-95 dib v-top bg-white mv3 relative" v-if="showfiledetails">' +
|
||||
'<div class="flex flex-wrap item-start">' +
|
||||
'<div class="w-50">' +
|
||||
'<div class="w6 h6 bg-black-40 dtc v-mid bg-tm-green tc">' +
|
||||
'<div class="w-100 dt center white f1 ttu">{{ filedetaildata.info.extension }}</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="w-50 pa3 lh-copy f7 relative">' +
|
||||
'<div class="black-30 mt3 mb1">Name</div><div class="b">{{ filedetaildata.name}}</div>' +
|
||||
'<div class="black-30 mt3 mb1">URL</div><div class="b">{{ filedetaildata.url}}</div>' +
|
||||
'<div class="flex flex-wrap item-start">' +
|
||||
'<div class="w-50">' +
|
||||
'<div class="black-30 mt3 mb1">Size</div><div class="b">{{ getSize(filedetaildata.bytes) }}</div>' +
|
||||
'</div>' +
|
||||
'<div class="w-50">' +
|
||||
'<div class="black-30 mt3 mb1">Type</div><div class="b">{{ filedetaildata.info.extension }}</div>' +
|
||||
'</div>' +
|
||||
'<div class="w-50">' +
|
||||
'<div class="black-30 mt3 mb1">Date</div><div class="b">{{ getDate(filedetaildata.timestamp) }}</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="absolute w-90 bottom-0 flex justify-between">' +
|
||||
'<button @click.prevent="selectFile(filedetaildata)" class="w-50 mr1 pa2 link bn bg-light-gray hover-bg-tm-green hover-white"><svg class="icon icon-check baseline"><use xlink:href="#icon-check"></use></svg> select</button>' +
|
||||
'<button @click.prevent="deleteFile(filedetaildata, detailindex)" class="w-50 ml1 pa2 link bn bg-light-gray hover-bg-tm-red hover-white"><svg class="icon icon-trash-o baseline"><use xlink:href="#icon-trash-o"></use></svg> delete</button>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<button class="f7 link br0 ba ph3 pv2 dim white bn bg-tm-red absolute top-0 right-0" @click.prevent="showFiles()">close details</button>' +
|
||||
'<div class="pa3">' +
|
||||
'<h4>File used in:</h4>' +
|
||||
'<ul class="ma0 pa0" v-if="filedetaildata.pages && filedetaildata.pages.length > 0">' +
|
||||
'<li class="list pa1" v-for="page in filedetaildata.pages">' +
|
||||
'<a class="link tm-red" :href="baseurl + page">{{ page }}</a>' +
|
||||
'</li>' +
|
||||
'</ul>' +
|
||||
'<div v-else>No pages found.</div>'+
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>',
|
||||
data: function(){
|
||||
return {
|
||||
imagedata: false,
|
||||
showimages: true,
|
||||
imagedetaildata: false,
|
||||
showimagedetails: false,
|
||||
filedata: false,
|
||||
showfiles: false,
|
||||
filedetaildata: false,
|
||||
showfiledetails: false,
|
||||
detailindex: false,
|
||||
load: false,
|
||||
baseurl: false,
|
||||
search: '',
|
||||
errors: false,
|
||||
}
|
||||
},
|
||||
mounted: function(){
|
||||
|
||||
if(this.parentcomponent == 'files')
|
||||
{
|
||||
this.showFiles();
|
||||
}
|
||||
|
||||
this.errors = false;
|
||||
var self = this;
|
||||
|
||||
myaxios.get('/api/v1/medialib/images',{
|
||||
params: {
|
||||
'url': document.getElementById("path").value,
|
||||
'csrf_name': document.getElementById("csrf_name").value,
|
||||
'csrf_value': document.getElementById("csrf_value").value,
|
||||
}
|
||||
})
|
||||
.then(function (response)
|
||||
{
|
||||
self.imagedata = response.data.images;
|
||||
})
|
||||
.catch(function (error)
|
||||
{
|
||||
if(error.response)
|
||||
{
|
||||
self.errors = error.response.data.errors;
|
||||
}
|
||||
});
|
||||
},
|
||||
computed: {
|
||||
filteredImages() {
|
||||
|
||||
var searchimages = this.search;
|
||||
var filteredImages = {};
|
||||
var images = this.imagedata;
|
||||
Object.keys(images).forEach(function(key) {
|
||||
var searchindex = key + ' ' + images[key].name;
|
||||
if(searchindex.toLowerCase().indexOf(searchimages.toLowerCase()) !== -1)
|
||||
{
|
||||
filteredImages[key] = images[key];
|
||||
}
|
||||
});
|
||||
return filteredImages;
|
||||
},
|
||||
filteredFiles() {
|
||||
|
||||
var searchfiles = this.search;
|
||||
var filteredFiles = {};
|
||||
var files = this.filedata;
|
||||
Object.keys(files).forEach(function(key) {
|
||||
var searchindex = key + ' ' + files[key].name;
|
||||
if(searchindex.toLowerCase().indexOf(searchfiles.toLowerCase()) !== -1)
|
||||
{
|
||||
filteredFiles[key] = files[key];
|
||||
}
|
||||
});
|
||||
return filteredFiles;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isImagesActive: function()
|
||||
{
|
||||
if(this.showimages)
|
||||
{
|
||||
return 'bg-tm-green white';
|
||||
}
|
||||
return 'bg-light-gray black';
|
||||
},
|
||||
isFilesActive: function()
|
||||
{
|
||||
if(this.showfiles)
|
||||
{
|
||||
return 'bg-tm-green white';
|
||||
}
|
||||
return 'bg-light-gray black';
|
||||
},
|
||||
closemedialib: function()
|
||||
{
|
||||
this.$parent.showmedialib = false;
|
||||
},
|
||||
getBackgroundImage: function(image)
|
||||
{
|
||||
return 'background-image: url(' + image.src_thumb + ');width:250px';
|
||||
},
|
||||
showImages: function()
|
||||
{
|
||||
this.errors = false;
|
||||
this.showimages = true;
|
||||
this.showfiles = false;
|
||||
this.showimagedetails = false;
|
||||
this.showfiledetails = false;
|
||||
this.imagedetaildata = false;
|
||||
this.detailindex = false;
|
||||
},
|
||||
showFiles: function()
|
||||
{
|
||||
this.showimages = false;
|
||||
this.showfiles = true;
|
||||
this.showimagedetails = false;
|
||||
this.showfiledetails = false;
|
||||
this.imagedetaildata = false;
|
||||
this.filedetaildata = false;
|
||||
this.detailindex = false;
|
||||
|
||||
if(!this.files)
|
||||
{
|
||||
this.errors = false;
|
||||
var filesself = this;
|
||||
|
||||
myaxios.get('/api/v1/medialib/files',{
|
||||
params: {
|
||||
'url': document.getElementById("path").value,
|
||||
'csrf_name': document.getElementById("csrf_name").value,
|
||||
'csrf_value': document.getElementById("csrf_value").value,
|
||||
}
|
||||
})
|
||||
.then(function (response)
|
||||
{
|
||||
filesself.filedata = response.data.files;
|
||||
})
|
||||
.catch(function (error)
|
||||
{
|
||||
if(error.response)
|
||||
{
|
||||
filesself.errors = error.response.data.errors;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
showImageDetails: function(image,index)
|
||||
{
|
||||
this.errors = false;
|
||||
this.showimages = false;
|
||||
this.showfiles = false;
|
||||
this.showimagedetails = true;
|
||||
this.detailindex = index;
|
||||
this.baseurl = myaxios.defaults.baseURL + '/tm/content/visual';
|
||||
|
||||
var imageself = this;
|
||||
|
||||
myaxios.get('/api/v1/image',{
|
||||
params: {
|
||||
'url': document.getElementById("path").value,
|
||||
'name': image.name,
|
||||
'csrf_name': document.getElementById("csrf_name").value,
|
||||
'csrf_value': document.getElementById("csrf_value").value,
|
||||
}
|
||||
})
|
||||
.then(function (response)
|
||||
{
|
||||
imageself.imagedetaildata = response.data.image;
|
||||
})
|
||||
.catch(function (error)
|
||||
{
|
||||
if(error.response)
|
||||
{
|
||||
imageself.errors = error.response.data.errors;
|
||||
}
|
||||
});
|
||||
},
|
||||
showFileDetails: function(file,index)
|
||||
{
|
||||
this.errors = false;
|
||||
this.showimages = false;
|
||||
this.showfiles = false;
|
||||
this.showimagedetails = false;
|
||||
this.showfiledetails = true;
|
||||
this.detailindex = index;
|
||||
|
||||
this.baseurl = myaxios.defaults.baseURL + '/tm/content/visual';
|
||||
|
||||
var fileself = this;
|
||||
|
||||
myaxios.get('/api/v1/file',{
|
||||
params: {
|
||||
'url': document.getElementById("path").value,
|
||||
'name': file.name,
|
||||
'csrf_name': document.getElementById("csrf_name").value,
|
||||
'csrf_value': document.getElementById("csrf_value").value,
|
||||
}
|
||||
})
|
||||
.then(function (response)
|
||||
{
|
||||
fileself.filedetaildata = response.data.file;
|
||||
})
|
||||
.catch(function (error)
|
||||
{
|
||||
if(error.response)
|
||||
{
|
||||
fileself.errors = error.response.data.errors;
|
||||
}
|
||||
});
|
||||
},
|
||||
selectImage: function(image)
|
||||
{
|
||||
this.showImages();
|
||||
|
||||
if(this.parentcomponent == 'images')
|
||||
{
|
||||
var imgmarkdown = {target: {value: '' }};
|
||||
|
||||
this.$parent.imgfile = image.src_live;
|
||||
this.$parent.imgpreview = image.src_live;
|
||||
this.$parent.imgmeta = true;
|
||||
|
||||
this.$parent.showmedialib = false;
|
||||
|
||||
this.$parent.updatemarkdown(imgmarkdown);
|
||||
}
|
||||
if(this.parentcomponent == 'files')
|
||||
{
|
||||
var filemarkdown = {target: {value: '[' + image.name + '](' + image.src_live +'){.tm-download}' }};
|
||||
|
||||
this.$parent.filemeta = true;
|
||||
this.$parent.filetitle = image.name;
|
||||
|
||||
this.$parent.showmedialib = false;
|
||||
|
||||
this.$parent.updatemarkdown(filemarkdown);
|
||||
}
|
||||
},
|
||||
selectFile: function(file)
|
||||
{
|
||||
/* if image component is open */
|
||||
if(this.parentcomponent == 'images')
|
||||
{
|
||||
var imgextensions = ['png','jpg', 'jpeg', 'gif', 'svg'];
|
||||
if(imgextensions.indexOf(file.info.extension) == -1)
|
||||
{
|
||||
this.errors = "you cannot insert a file into an image component";
|
||||
return;
|
||||
}
|
||||
var imgmarkdown = {target: {value: '' }};
|
||||
|
||||
this.$parent.imgfile = file.url;
|
||||
this.$parent.imgpreview = file.url;
|
||||
this.$parent.imgmeta = true;
|
||||
|
||||
this.$parent.showmedialib = false;
|
||||
|
||||
this.$parent.updatemarkdown(imgmarkdown);
|
||||
}
|
||||
if(this.parentcomponent == 'files')
|
||||
{
|
||||
var filemarkdown = {target: {value: '['+ file.name +']('+ file.url +'){.tm-download file-' + file.info.extension + '}' }};
|
||||
|
||||
this.$parent.showmedialib = false;
|
||||
|
||||
this.$parent.filemeta = true;
|
||||
this.$parent.filetitle = file.info.filename + ' (' + file.info.extension.toUpperCase() + ')';
|
||||
|
||||
this.$parent.updatemarkdown(filemarkdown);
|
||||
}
|
||||
this.showFiles();
|
||||
},
|
||||
removeImage: function(index)
|
||||
{
|
||||
this.imagedata.splice(index,1);
|
||||
},
|
||||
removeFile: function(index)
|
||||
{
|
||||
this.filedata.splice(index,1);
|
||||
},
|
||||
deleteImage: function(image, index)
|
||||
{
|
||||
imageself = this;
|
||||
|
||||
myaxios.delete('/api/v1/image',{
|
||||
data: {
|
||||
'url': document.getElementById("path").value,
|
||||
'name': image.name,
|
||||
'index': index,
|
||||
'csrf_name': document.getElementById("csrf_name").value,
|
||||
'csrf_value': document.getElementById("csrf_value").value,
|
||||
}
|
||||
})
|
||||
.then(function (response)
|
||||
{
|
||||
imageself.showImages();
|
||||
imageself.removeImage(index);
|
||||
})
|
||||
.catch(function (error)
|
||||
{
|
||||
if(error.response)
|
||||
{
|
||||
imageself.errors = error.response.data.errors;
|
||||
}
|
||||
});
|
||||
},
|
||||
deleteFile: function(file, index)
|
||||
{
|
||||
fileself = this;
|
||||
|
||||
myaxios.delete('/api/v1/file',{
|
||||
data: {
|
||||
'url': document.getElementById("path").value,
|
||||
'name': file.name,
|
||||
'index': index,
|
||||
'csrf_name': document.getElementById("csrf_name").value,
|
||||
'csrf_value': document.getElementById("csrf_value").value,
|
||||
}
|
||||
})
|
||||
.then(function (response)
|
||||
{
|
||||
fileself.showFiles();
|
||||
fileself.removeFile(index);
|
||||
})
|
||||
.catch(function (error)
|
||||
{
|
||||
if(error.response)
|
||||
{
|
||||
fileself.errors = error.response.data.errors;
|
||||
}
|
||||
});
|
||||
},
|
||||
getDate(timestamp)
|
||||
{
|
||||
date = new Date(timestamp * 1000);
|
||||
|
||||
datevalues = {
|
||||
'year': date.getFullYear(),
|
||||
'month': date.getMonth()+1,
|
||||
'day': date.getDate(),
|
||||
'hour': date.getHours(),
|
||||
'minute': date.getMinutes(),
|
||||
'second': date.getSeconds(),
|
||||
};
|
||||
return datevalues.year + '-' + datevalues.month + '-' + datevalues.day;
|
||||
},
|
||||
getSize(bytes)
|
||||
{
|
||||
var i = Math.floor(Math.log(bytes) / Math.log(1024)),
|
||||
sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
|
||||
return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i];
|
||||
},
|
||||
isChecked: function(classname)
|
||||
{
|
||||
if(this.imgclass == classname)
|
||||
{
|
||||
return ' checked';
|
||||
}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
let activeFormats = [];
|
||||
|
||||
for(var i = 0; i < formatConfig.length; i++)
|
||||
|
@ -1576,11 +2300,13 @@ let editor = new Vue({
|
|||
}, method, url, params);
|
||||
},
|
||||
methods: {
|
||||
onStart: function(evt)
|
||||
onStart: function()
|
||||
{
|
||||
|
||||
},
|
||||
moveBlock: function(evt)
|
||||
{
|
||||
|
||||
var params = {
|
||||
'url': document.getElementById("path").value,
|
||||
'old_index': evt.oldIndex,
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="{{ base_url }}/system/author/img/apple-touch-icon-152x152.png" />
|
||||
|
||||
<link rel="stylesheet" href="{{ base_url }}/system/author/css/normalize.css" />
|
||||
<link rel="stylesheet" href="{{ base_url }}/system/author/css/tachyons.min.css?20200226" />
|
||||
<link rel="stylesheet" href="{{ base_url }}/system/author/css/style.css?20200226" />
|
||||
<link rel="stylesheet" href="{{ base_url }}/system/author/css/color-picker.min.css" />
|
||||
</head>
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="{{ base_url }}/system/author/img/apple-touch-icon-152x152.png" />
|
||||
|
||||
<link rel="stylesheet" href="{{ base_url }}/system/author/css/normalize.css" />
|
||||
<link rel="stylesheet" href="{{ base_url }}/system/author/css/tachyons.min.css" />
|
||||
<link rel="stylesheet" href="{{ base_url }}/system/author/css/style.css?20200226" />
|
||||
<link rel="stylesheet" href="{{ base_url }}/system/author/css/color-picker.min.css" />
|
||||
|
||||
|
@ -68,14 +69,20 @@
|
|||
<title>{{ __('FOLDER') }}</title>
|
||||
<path d="M24 20.5v-11c0-0.828-0.672-1.5-1.5-1.5h-11c-0.828 0-1.5-0.672-1.5-1.5v-1c0-0.828-0.672-1.5-1.5-1.5h-5c-0.828 0-1.5 0.672-1.5 1.5v15c0 0.828 0.672 1.5 1.5 1.5h19c0.828 0 1.5-0.672 1.5-1.5zM26 9.5v11c0 1.922-1.578 3.5-3.5 3.5h-19c-1.922 0-3.5-1.578-3.5-3.5v-15c0-1.922 1.578-3.5 3.5-3.5h5c1.922 0 3.5 1.578 3.5 3.5v0.5h10.5c1.922 0 3.5 1.578 3.5 3.5z"></path>
|
||||
</symbol>
|
||||
|
||||
|
||||
<symbol id="icon-upload" viewBox="0 0 26 28">
|
||||
<title>{{ __('UPLOAD') }}</title>
|
||||
<path d="M20 23c0-0.547-0.453-1-1-1s-1 0.453-1 1 0.453 1 1 1 1-0.453 1-1zM24 23c0-0.547-0.453-1-1-1s-1 0.453-1 1 0.453 1 1 1 1-0.453 1-1zM26 19.5v5c0 0.828-0.672 1.5-1.5 1.5h-23c-0.828 0-1.5-0.672-1.5-1.5v-5c0-0.828 0.672-1.5 1.5-1.5h6.672c0.422 1.156 1.531 2 2.828 2h4c1.297 0 2.406-0.844 2.828-2h6.672c0.828 0 1.5 0.672 1.5 1.5zM20.922 9.375c-0.156 0.375-0.516 0.625-0.922 0.625h-4v7c0 0.547-0.453 1-1 1h-4c-0.547 0-1-0.453-1-1v-7h-4c-0.406 0-0.766-0.25-0.922-0.625-0.156-0.359-0.078-0.797 0.219-1.078l7-7c0.187-0.203 0.453-0.297 0.703-0.297s0.516 0.094 0.703 0.297l7 7c0.297 0.281 0.375 0.719 0.219 1.078z"></path>
|
||||
</symbol>
|
||||
<symbol id="icon-image" viewBox="0 0 32 32">
|
||||
<title>{{ __('IMAGE') }}</title>
|
||||
<path d="M29.996 4c0.001 0.001 0.003 0.002 0.004 0.004v23.993c-0.001 0.001-0.002 0.003-0.004 0.004h-27.993c-0.001-0.001-0.003-0.002-0.004-0.004v-23.993c0.001-0.001 0.002-0.003 0.004-0.004h27.993zM30 2h-28c-1.1 0-2 0.9-2 2v24c0 1.1 0.9 2 2 2h28c1.1 0 2-0.9 2-2v-24c0-1.1-0.9-2-2-2v0z"></path>
|
||||
<path d="M26 9c0 1.657-1.343 3-3 3s-3-1.343-3-3 1.343-3 3-3 3 1.343 3 3z"></path>
|
||||
<path d="M28 26h-24v-4l7-12 8 10h2l7-6z"></path>
|
||||
</symbol>
|
||||
<symbol id="icon-paperclip" viewBox="0 0 22 28">
|
||||
<title>{{ __('PAPERCLIP') }}</title>
|
||||
<path d="M21.938 21.641c0 2.438-1.859 4.297-4.297 4.297-1.375 0-2.703-0.594-3.672-1.563l-12.141-12.125c-1.109-1.125-1.766-2.656-1.766-4.234 0-3.313 2.609-5.953 5.922-5.953 1.594 0 3.125 0.641 4.266 1.766l9.453 9.469c0.094 0.094 0.156 0.219 0.156 0.344 0 0.328-0.875 1.203-1.203 1.203-0.141 0-0.266-0.063-0.359-0.156l-9.469-9.484c-0.75-0.734-1.766-1.203-2.828-1.203-2.219 0-3.938 1.797-3.938 4 0 1.062 0.438 2.078 1.188 2.828l12.125 12.141c0.594 0.594 1.422 0.984 2.266 0.984 1.328 0 2.312-0.984 2.312-2.312 0-0.859-0.391-1.672-0.984-2.266l-9.078-9.078c-0.25-0.234-0.594-0.375-0.938-0.375-0.594 0-1.047 0.438-1.047 1.047 0 0.344 0.156 0.672 0.391 0.922l6.406 6.406c0.094 0.094 0.156 0.219 0.156 0.344 0 0.328-0.891 1.219-1.219 1.219-0.125 0-0.25-0.063-0.344-0.156l-6.406-6.406c-0.625-0.609-0.984-1.469-0.984-2.328 0-1.719 1.344-3.062 3.063-3.062 0.875 0 1.719 0.359 2.328 0.984l9.078 9.078c0.984 0.969 1.563 2.297 1.563 3.672z"></path>
|
||||
</symbol>
|
||||
<symbol id="icon-play" viewBox="0 0 32 32">
|
||||
<title>{{ __('VIDEO') }}</title>
|
||||
<path d="M30.662 5.003c-4.488-0.645-9.448-1.003-14.662-1.003s-10.174 0.358-14.662 1.003c-0.86 3.366-1.338 7.086-1.338 10.997s0.477 7.63 1.338 10.997c4.489 0.645 9.448 1.003 14.662 1.003s10.174-0.358 14.662-1.003c0.86-3.366 1.338-7.086 1.338-10.997s-0.477-7.63-1.338-10.997zM12 22v-12l10 6-10 6z"></path>
|
||||
|
@ -142,12 +149,31 @@
|
|||
<title>{{ __('CROSS') }}</title>
|
||||
<path d="M14.348 14.849c-0.469 0.469-1.229 0.469-1.697 0l-2.651-3.030-2.651 3.029c-0.469 0.469-1.229 0.469-1.697 0-0.469-0.469-0.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-0.469-0.469-0.469-1.228 0-1.697s1.228-0.469 1.697 0l2.652 3.031 2.651-3.031c0.469-0.469 1.228-0.469 1.697 0s0.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c0.469 0.469 0.469 1.229 0 1.698z"></path>
|
||||
</symbol>
|
||||
<symbol id="icon-trash-o" viewBox="0 0 22 28">
|
||||
<title>{{ __('TRASH') }}</title>
|
||||
<path d="M8 11.5v9c0 0.281-0.219 0.5-0.5 0.5h-1c-0.281 0-0.5-0.219-0.5-0.5v-9c0-0.281 0.219-0.5 0.5-0.5h1c0.281 0 0.5 0.219 0.5 0.5zM12 11.5v9c0 0.281-0.219 0.5-0.5 0.5h-1c-0.281 0-0.5-0.219-0.5-0.5v-9c0-0.281 0.219-0.5 0.5-0.5h1c0.281 0 0.5 0.219 0.5 0.5zM16 11.5v9c0 0.281-0.219 0.5-0.5 0.5h-1c-0.281 0-0.5-0.219-0.5-0.5v-9c0-0.281 0.219-0.5 0.5-0.5h1c0.281 0 0.5 0.219 0.5 0.5zM18 22.813v-14.812h-14v14.812c0 0.75 0.422 1.188 0.5 1.188h13c0.078 0 0.5-0.438 0.5-1.188zM7.5 6h7l-0.75-1.828c-0.047-0.063-0.187-0.156-0.266-0.172h-4.953c-0.094 0.016-0.219 0.109-0.266 0.172zM22 6.5v1c0 0.281-0.219 0.5-0.5 0.5h-1.5v14.812c0 1.719-1.125 3.187-2.5 3.187h-13c-1.375 0-2.5-1.406-2.5-3.125v-14.875h-1.5c-0.281 0-0.5-0.219-0.5-0.5v-1c0-0.281 0.219-0.5 0.5-0.5h4.828l1.094-2.609c0.313-0.766 1.25-1.391 2.078-1.391h5c0.828 0 1.766 0.625 2.078 1.391l1.094 2.609h4.828c0.281 0 0.5 0.219 0.5 0.5z"></path>
|
||||
</symbol>
|
||||
<symbol id="icon-info" viewBox="0 0 32 32">
|
||||
<title>{{ __('INFO') }}</title>
|
||||
<path d="M14 9.5c0-0.825 0.675-1.5 1.5-1.5h1c0.825 0 1.5 0.675 1.5 1.5v1c0 0.825-0.675 1.5-1.5 1.5h-1c-0.825 0-1.5-0.675-1.5-1.5v-1z"></path>
|
||||
<path d="M20 24h-8v-2h2v-6h-2v-2h6v8h2z"></path>
|
||||
<path d="M16 0c-8.837 0-16 7.163-16 16s7.163 16 16 16 16-7.163 16-16-7.163-16-16-16zM16 29c-7.18 0-13-5.82-13-13s5.82-13 13-13 13 5.82 13 13-5.82 13-13 13z"></path>
|
||||
</symbol>
|
||||
<symbol id="icon-eye-blocked" viewBox="0 0 32 32">
|
||||
<title>eye-blocked</title>
|
||||
<title>{{ __('EYE_BLOCKED') }}</title>
|
||||
<path d="M29.561 0.439c-0.586-0.586-1.535-0.586-2.121 0l-6.318 6.318c-1.623-0.492-3.342-0.757-5.122-0.757-6.979 0-13.028 4.064-16 10 1.285 2.566 3.145 4.782 5.407 6.472l-4.968 4.968c-0.586 0.586-0.586 1.535 0 2.121 0.293 0.293 0.677 0.439 1.061 0.439s0.768-0.146 1.061-0.439l27-27c0.586-0.586 0.586-1.536 0-2.121zM13 10c1.32 0 2.44 0.853 2.841 2.037l-3.804 3.804c-1.184-0.401-2.037-1.521-2.037-2.841 0-1.657 1.343-3 3-3zM3.441 16c1.197-1.891 2.79-3.498 4.67-4.697 0.122-0.078 0.246-0.154 0.371-0.228-0.311 0.854-0.482 1.776-0.482 2.737 0 1.715 0.54 3.304 1.459 4.607l-1.904 1.904c-1.639-1.151-3.038-2.621-4.114-4.323z"></path>
|
||||
<path d="M24 13.813c0-0.849-0.133-1.667-0.378-2.434l-10.056 10.056c0.768 0.245 1.586 0.378 2.435 0.378 4.418 0 8-3.582 8-8z"></path>
|
||||
<path d="M25.938 9.062l-2.168 2.168c0.040 0.025 0.079 0.049 0.118 0.074 1.88 1.199 3.473 2.805 4.67 4.697-1.197 1.891-2.79 3.498-4.67 4.697-2.362 1.507-5.090 2.303-7.889 2.303-1.208 0-2.403-0.149-3.561-0.439l-2.403 2.403c1.866 0.671 3.873 1.036 5.964 1.036 6.978 0 13.027-4.064 16-10-1.407-2.81-3.504-5.2-6.062-6.938z"></path>
|
||||
</symbol>
|
||||
</symbol>
|
||||
<symbol id="icon-search" viewBox="0 0 26 28">
|
||||
<title>{{ __('SEARCH') }}</title>
|
||||
<path d="M18 13c0-3.859-3.141-7-7-7s-7 3.141-7 7 3.141 7 7 7 7-3.141 7-7zM26 26c0 1.094-0.906 2-2 2-0.531 0-1.047-0.219-1.406-0.594l-5.359-5.344c-1.828 1.266-4.016 1.937-6.234 1.937-6.078 0-11-4.922-11-11s4.922-11 11-11 11 4.922 11 11c0 2.219-0.672 4.406-1.937 6.234l5.359 5.359c0.359 0.359 0.578 0.875 0.578 1.406z"></path>
|
||||
</symbol>
|
||||
<symbol id="icon-cancel-circle" viewBox="0 0 32 32">
|
||||
<title>{{ __('CANCEL') }}</title>
|
||||
<path d="M16 0c-8.837 0-16 7.163-16 16s7.163 16 16 16 16-7.163 16-16-7.163-16-16-16zM16 29c-7.18 0-13-5.82-13-13s5.82-13 13-13 13 5.82 13 13-5.82 13-13 13z"></path>
|
||||
<path d="M21 8l-5 5-5-5-3 3 5 5-5 5 3 3 5-5 5 5 3-3-5-5 5-5z"></path>
|
||||
</symbol>
|
||||
{{ assets.renderSvg() }}
|
||||
</defs>
|
||||
</svg>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
<div class="formWrapper">
|
||||
|
||||
<form method="POST" action="{{ path_for('settings.save') }}">
|
||||
<form method="POST" action="{{ path_for('settings.save') }}" enctype="multipart/form-data">
|
||||
|
||||
<section id="system" class="settings">
|
||||
|
||||
|
@ -63,6 +63,34 @@
|
|||
</div><div class="medium">
|
||||
<label for="settings[sitemap]">Google Sitemap <small>({{ __('Readonly') }})</small></label>
|
||||
<input type="text" name="settings[sitemap]" id="sitemap" readonly value="{{ base_url }}/cache/sitemap.xml" />
|
||||
</div><div class="medium{{ errors.settings.logo ? ' error' : '' }}">
|
||||
<label for="settings[logo]">Logo <small>(jpg,jpeg,png,svg)</small></label>
|
||||
<div class="flex fileinput">
|
||||
<button class="deletefilebutton w-10 bg-tm-gray bn hover-bg-tm-red hover-white">x</button>
|
||||
<input class="deletefileinput" name="settings[deletelogo]" type="hidden" value="NULL" />
|
||||
<input class="visiblefilename w-60" type="text" disabled="disabled" value="{{ old.settings.logo ? old.settings.logo : settings.logo }}" placeholder="{{ __('CHOOSE_FILE') }}" />
|
||||
<div class="relative w-30 pv3 tc bg-tm-green white dim pointer">
|
||||
<span class="f7">{{ __('BROWSE') }}</span>
|
||||
<input class="upload hiddenfile" type="file" name="settings[logo]" value="{{ old.settings.logo ? old.settings.logo : settings.logo }}" />
|
||||
</div>
|
||||
</div>
|
||||
{% if errors.settings.logo %}
|
||||
<span class="error">{{ errors.settings.logo | first }}</span>
|
||||
{% endif %}
|
||||
</div><div class="medium{{ errors.settings.favicon ? ' error' : '' }}">
|
||||
<label for="settings[favicon]">Favicon <small>(png)</small></label>
|
||||
<div class="flex fileinput">
|
||||
<button class="deletefilebutton w-10 bg-tm-gray bn hover-bg-tm-red hover-white">x</button>
|
||||
<input class="deletefileinput" name="settings[deletefav]" type="hidden" value="NULL" />
|
||||
<input class="visiblefilename w-60" type="text" disabled="disabled" value="{{ old.settings.favicon ? old.settings.favicon : settings.favicon }}" placeholder="{{ __('CHOOSE_FILE') }}" />
|
||||
<div class="relative w-30 pv3 tc bg-tm-green white dim pointer">
|
||||
<span class="f7">{{ __('BROWSE') }}</span>
|
||||
<input class="upload hiddenfile" type="file" name="settings[favicon]" value="{{ old.settings.favicon ? old.settings.favicon : settings.favicon }}" />
|
||||
</div>
|
||||
</div>
|
||||
{% if errors.settings.favicon %}
|
||||
<span class="error">{{ errors.settings.favicon | first }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<hr>
|
||||
<header class="headline">
|
||||
|
@ -102,5 +130,4 @@
|
|||
</form>
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -199,8 +199,9 @@ $container['view'] = function ($container)
|
|||
$view->addExtension(new Slim\Views\TwigExtension($container['router'], $basePath));
|
||||
$view->addExtension(new Twig_Extension_Debug());
|
||||
$view->addExtension(new Typemill\Extensions\TwigUserExtension());
|
||||
$view->addExtension(new Typemill\Extensions\TwigMarkdownExtension());
|
||||
$view->addExtension(new Typemill\Extensions\TwigMarkdownExtension());
|
||||
$view->addExtension(new Typemill\Extensions\TwigMetaExtension());
|
||||
$view->addExtension(new Typemill\Extensions\TwigPagelistExtension());
|
||||
|
||||
// i18n
|
||||
$view->addExtension(new Typemill\Extensions\TwigLanguageExtension( $container->get('settings')['labels'] ));
|
||||
|
|
|
@ -2,7 +2,15 @@
|
|||
|
||||
{% block content %}
|
||||
|
||||
<h1>{{ title }}</h1>
|
||||
{% if logo and settings.themes.typemill.coverlogo %}
|
||||
|
||||
<img src="{{ logo }}" alt="logo {{title}}" class="coverlogo"/>
|
||||
|
||||
{% else %}
|
||||
|
||||
<h1 class="coverheadline">{{ title }}</h1>
|
||||
|
||||
{% endif %}
|
||||
|
||||
<div class="lead">
|
||||
|
||||
|
|
|
@ -578,6 +578,41 @@ cite{}
|
|||
abbr{}
|
||||
hr{}
|
||||
|
||||
img.logo{
|
||||
width: 100%;
|
||||
}
|
||||
img.coverlogo{
|
||||
margin-top: 4em;
|
||||
}
|
||||
/****************************
|
||||
* download-commponent *
|
||||
****************************/
|
||||
|
||||
a.tm-download
|
||||
{
|
||||
line-height: 35px;
|
||||
margin-left: 40px;
|
||||
}
|
||||
a.tm-download::before{
|
||||
content: '\2193';
|
||||
position: absolute;
|
||||
margin-left: -40px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
font-family: "Comic Sans MS",cursive,sans-serif;
|
||||
border: 2px solid #e0474c;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
text-decoration: underline;
|
||||
}
|
||||
a.tm-download:hover::before{
|
||||
text-decoration:underline;
|
||||
color: #fff;
|
||||
background: #e0474c;
|
||||
}
|
||||
|
||||
|
||||
/************************
|
||||
* TABLE OF CONTENTS *
|
||||
************************/
|
||||
|
|
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 500 B |
Before Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 11 KiB |
|
@ -10,14 +10,15 @@
|
|||
<meta name="description" content="{{ metatabs.meta.description }}" />
|
||||
<meta name="author" content="{{ settings.author }}" />
|
||||
<meta name="generator" content="TYPEMILL" />
|
||||
<meta name="msapplication-TileColor" content="#F9F8F6" />
|
||||
<meta name="msapplication-TileImage" content="{{ base_url }}/themes/typemill/img/mstile-144x144.png" />
|
||||
|
||||
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="{{ base_url }}/themes/typemill/img/apple-touch-icon-144x144.png" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="{{ base_url }}/themes/typemill/img/apple-touch-icon-152x152.png" />
|
||||
<link rel="icon" type="image/png" href="{{ base_url }}/themes/typemill/img/favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="{{ base_url }}/themes/typemill/img/favicon-16x16.png" sizes="16x16" />
|
||||
|
||||
{% if favicon %}
|
||||
<meta name="msapplication-TileColor" content="#F9F8F6" />
|
||||
<meta name="msapplication-TileImage" content="{{ base_url }}/media/files/favicon-144.png" />
|
||||
<link rel="icon" type="image/png" href="{{ base_url }}/media/files/favicon-16.png" sizes="16x16" />
|
||||
<link rel="icon" type="image/png" href="{{ base_url }}/media/files/favicon-32.png" sizes="32x32" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ base_url }}/media/files/favicon-180.png" />
|
||||
{% endif %}
|
||||
|
||||
<link rel="canonical" href="{{ item.urlAbs }}" />
|
||||
|
||||
<meta property="og:site_name" content="{{ settings.title }}">
|
||||
|
@ -41,7 +42,13 @@
|
|||
<body>
|
||||
<div class="main">
|
||||
<header>
|
||||
<p><a href="{{ base_url }}">{{ settings.title }}</a></p>
|
||||
<p><a href="{{ base_url }}">
|
||||
{% if logo %}
|
||||
<img src="{{ logo }}" class="logo"/>
|
||||
{% else %}
|
||||
{{ settings.title }}
|
||||
{% endif %}
|
||||
</a></p>
|
||||
</header>
|
||||
|
||||
<article class="{{ item.elementType == 'file' ? 'page' : 'chapter' }}">
|
||||
|
|
|
@ -10,13 +10,14 @@
|
|||
<meta name="description" content="{{ metatabs.meta.description }}" />
|
||||
<meta name="author" content="{{ settings.author }}" />
|
||||
<meta name="generator" content="TYPEMILL" />
|
||||
<meta name="msapplication-TileColor" content="#F9F8F6" />
|
||||
<meta name="msapplication-TileImage" content="{{ base_url }}/themes/typemill/img/mstile-144x144.png" />
|
||||
|
||||
<link rel="apple-touch-icon-precomposed" sizes="144x144" href="{{ base_url }}/themes/typemill/img/apple-touch-icon-144x144.png" />
|
||||
<link rel="apple-touch-icon-precomposed" sizes="152x152" href="{{ base_url }}/themes/typemill/img/apple-touch-icon-152x152.png" />
|
||||
<link rel="icon" type="image/png" href="{{ base_url }}/themes/typemill/img/favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="{{ base_url }}/themes/typemill/img/favicon-16x16.png" sizes="16x16" />
|
||||
{% if favicon %}
|
||||
<meta name="msapplication-TileColor" content="#F9F8F6" />
|
||||
<meta name="msapplication-TileImage" content="{{ base_url }}/media/files/favicon-144.png" />
|
||||
<link rel="icon" type="image/png" href="{{ base_url }}/media/files/favicon-16.png" sizes="16x16" />
|
||||
<link rel="icon" type="image/png" href="{{ base_url }}/media/files/favicon-32.png" sizes="32x32" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ base_url }}/media/files/favicon-180.png" />
|
||||
{% endif %}
|
||||
|
||||
<link rel="canonical" href="{{ item.urlAbs }}" />
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
name: Typemill Theme
|
||||
version: 1.2.2
|
||||
version: 1.2.3
|
||||
description: The standard theme for Typemill. Responsive, minimal and without any dependencies. It uses the system fonts Calibri and Helvetica. No JavaScript is used.
|
||||
author: Sebastian Schürmanns
|
||||
homepage: https://typemill.net
|
||||
|
@ -27,6 +27,11 @@ forms:
|
|||
label: Different Design for Startpage
|
||||
checkboxlabel: Activate Special Startpage-Design
|
||||
|
||||
coverlogo:
|
||||
type: checkbox
|
||||
label: Logo on startpage
|
||||
checkboxlabel: Show logo instead of title on startpage
|
||||
|
||||
start:
|
||||
type: text
|
||||
label: Label for Start Button
|
||||
|
|