1
0
mirror of https://github.com/typemill/typemill.git synced 2025-01-16 13:00:26 +01:00

Version 1.2.16

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

2
.github/FUNDING.yml vendored
View File

@ -1 +1 @@
custom: https://www.paypal.me/typemill
custom: https://www.paypal.me/typemill

41
.gitignore vendored
View File

@ -1,21 +1,22 @@
cache
plugins/admin
plugins/contactform
plugins/demo
plugins/disqus
plugins/download
plugins/finalwords
plugins/joblistings
plugins/landingpage
plugins/mail
plugins/newsletter
plugins/textadds
plugins/version
settings/settings.yaml
settings/formdata.yaml
settings/users
system/vendor
tests
themes/monograf
zips
cache
plugins/admin
plugins/contactform
plugins/demo
plugins/disqus
plugins/download
plugins/finalwords
plubins/hyer
plugins/joblistings
plugins/landingpage
plugins/mail
plugins/newsletter
plugins/textadds
plugins/version
settings/settings.yaml
settings/formdata.yaml
settings/users
system/vendor
tests
themes/monograf
zips
build.php

View File

@ -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]

View File

@ -1,32 +1,32 @@
{
"name": "trendschau/typemill",
"type": "project",
"description": "A crazy simple tool to create web-documentations and online manuals with markdown files.",
"keywords": ["documentations","manuals","flat-file","Markdown","php"],
"homepage": "http://typemill.net",
"license": "MIT",
"config": {
"vendor-dir": "system/vendor"
},
"require": {
"slim/slim": "~3.7",
"twig/twig": "~1.18",
"slim/twig-view": "~2.3",
"slim/flash": "~0.4",
"Slim/csrf": "~0.8",
"symfony/yaml": "~2.8",
"symfony/event-dispatcher": "~3.3",
"erusev/parsedown": "~1.4",
"erusev/parsedown-extra": "dev-master",
"jbroadway/urlify": "~1.1",
"vlucas/valitron": "dev-master"
},
"autoload": {
"psr-4": {
"Typemill\\": "system/",
"Plugins\\": "plugins/"
}
},
"minimum-stability": "dev",
"prefer-stable": true
{
"name": "trendschau/typemill",
"type": "project",
"description": "A crazy simple tool to create web-documentations and online manuals with markdown files.",
"keywords": ["documentations","manuals","flat-file","Markdown","php"],
"homepage": "http://typemill.net",
"license": "MIT",
"config": {
"vendor-dir": "system/vendor"
},
"require": {
"slim/slim": "~3.7",
"twig/twig": "~1.18",
"slim/twig-view": "~2.3",
"slim/flash": "~0.4",
"Slim/csrf": "~0.8",
"symfony/yaml": "~2.8",
"symfony/event-dispatcher": "~3.3",
"erusev/parsedown": "~1.4",
"erusev/parsedown-extra": "dev-master",
"jbroadway/urlify": "~1.1",
"vlucas/valitron": "dev-master"
},
"autoload": {
"psr-4": {
"Typemill\\": "system/",
"Plugins\\": "plugins/"
}
},
"minimum-stability": "dev",
"prefer-stable": true
}

View File

@ -1,7 +1,7 @@
<?php
require __DIR__ . '/system/vendor/autoload.php';
require __DIR__ . '/system/system.php';
<?php
require __DIR__ . '/system/vendor/autoload.php';
require __DIR__ . '/system/system.php';
$app->run();

View File

@ -1,7 +1,7 @@
Copyright (c) 2017 Sebastian Schürmanns
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
Copyright (c) 2017 Sebastian Schürmanns
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,47 +1,47 @@
<?php
namespace Plugins\Analytics;
use \Typemill\Plugin;
class Analytics extends Plugin
{
protected $settings;
public static function getSubscribedEvents()
{
return array(
'onSettingsLoaded' => 'onSettingsLoaded',
'onTwigLoaded' => 'onTwigLoaded'
);
}
public function onSettingsLoaded($settings)
{
$this->settings = $settings->getData();
}
public function onTwigLoaded()
{
/* get Twig Instance and add the cookieconsent template-folder to the path */
$twig = $this->getTwig();
$loader = $twig->getLoader();
$loader->addPath(__DIR__ . '/templates');
$analyticSettings = $this->settings['settings']['plugins']['analytics'];
if(isset($analyticSettings['tool']))
{
/* fetch the template, render it with twig and add javascript with settings */
if($analyticSettings['tool'] == 'piwik')
{
$this->addInlineJS($twig->fetch('/piwikanalytics.twig', $this->settings));
}
elseif($analyticSettings['tool'] == 'google')
{
$this->addJS('https://www.googletagmanager.com/gtag/js?id=' . $analyticSettings['google_id']);
$this->addInlineJS($twig->fetch('/googleanalytics.twig', $analyticSettings));
}
}
}
<?php
namespace Plugins\Analytics;
use \Typemill\Plugin;
class Analytics extends Plugin
{
protected $settings;
public static function getSubscribedEvents()
{
return array(
'onSettingsLoaded' => 'onSettingsLoaded',
'onTwigLoaded' => 'onTwigLoaded'
);
}
public function onSettingsLoaded($settings)
{
$this->settings = $settings->getData();
}
public function onTwigLoaded()
{
/* get Twig Instance and add the cookieconsent template-folder to the path */
$twig = $this->getTwig();
$loader = $twig->getLoader();
$loader->addPath(__DIR__ . '/templates');
$analyticSettings = $this->settings['settings']['plugins']['analytics'];
if(isset($analyticSettings['tool']))
{
/* fetch the template, render it with twig and add javascript with settings */
if($analyticSettings['tool'] == 'piwik')
{
$this->addInlineJS($twig->fetch('/piwikanalytics.twig', $this->settings));
}
elseif($analyticSettings['tool'] == 'google')
{
$this->addJS('https://www.googletagmanager.com/gtag/js?id=' . $analyticSettings['google_id']);
$this->addInlineJS($twig->fetch('/googleanalytics.twig', $analyticSettings));
}
}
}
}

View File

@ -1,38 +1,38 @@
name: Analytics
version: 1.0.0
description: Integrate Piwik or Google Analytics Script
author: Sebastian Schürmanns
homepage: http://typemill.net
licence: MIT
settings:
tool: none
forms:
fields:
tool:
type: radio
label: Choose Your Tool
options:
none: None
piwik: Piwik
google: Google Analytics
piwik_url:
type: text
label: Piwik URL
help: Add the URL to your piwik installation without protocol like this: my-site.com.
placeholder: 'url like my-piwik-installation.com'
piwik_id:
type: number
label: Piwik Site-ID
help: You can find the id in Piwik under configuration and tracking code.
placeholder: 'simple number like 8'
google_id:
type: text
label: Google Tracking ID
help: You can find the tracking id in google under property. It starts with UA-
name: Analytics
version: 1.0.0
description: Integrate Piwik or Google Analytics Script
author: Sebastian Schürmanns
homepage: http://typemill.net
licence: MIT
settings:
tool: none
forms:
fields:
tool:
type: radio
label: Choose Your Tool
options:
none: None
piwik: Piwik
google: Google Analytics
piwik_url:
type: text
label: Piwik URL
help: Add the URL to your piwik installation without protocol like this: my-site.com.
placeholder: 'url like my-piwik-installation.com'
piwik_id:
type: number
label: Piwik Site-ID
help: You can find the id in Piwik under configuration and tracking code.
placeholder: 'simple number like 8'
google_id:
type: text
label: Google Tracking ID
help: You can find the tracking id in google under property. It starts with UA-
placeholder: 'UA-12345-6'

View File

@ -1,5 +1,5 @@
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '{{ google_id }}');

View File

@ -1,11 +1,11 @@
var _paq = _paq || [];
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function(){
var u="//{{ settings.plugins.analytics.piwik_url }}/";
_paq.push(['setTrackerUrl', u+'piwik.php']);
_paq.push(['setSiteId', '{{ settings.plugins.analytics.piwik_id }}']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
var _paq = _paq || [];
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function(){
var u="//{{ settings.plugins.analytics.piwik_url }}/";
_paq.push(['setTrackerUrl', u+'piwik.php']);
_paq.push(['setSiteId', '{{ settings.plugins.analytics.piwik_id }}']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
})();

View File

@ -1,38 +1,38 @@
<?php
namespace Plugins\CookieConsent;
use \Typemill\Plugin;
class CookieConsent extends Plugin
{
protected $settings;
public static function getSubscribedEvents()
{
return array(
'onSettingsLoaded' => 'onSettingsLoaded',
'onTwigLoaded' => 'onTwigLoaded'
);
}
public function onSettingsLoaded($settings)
{
$this->settings = $settings->getData();
}
public function onTwigLoaded()
{
/* add external CSS and JavaScript */
$this->addCSS('/cookieconsent/public/cookieconsent.min.css');
$this->addJS('/cookieconsent/public/cookieconsent.min.js');
/* get Twig Instance and add the cookieconsent template-folder to the path */
$twig = $this->getTwig();
$loader = $twig->getLoader();
$loader->addPath(__DIR__ . '/templates');
/* fetch the template, render it with twig and add it as inline-javascript */
$this->addInlineJS($twig->fetch('/cookieconsent.twig', $this->settings));
}
<?php
namespace Plugins\CookieConsent;
use \Typemill\Plugin;
class CookieConsent extends Plugin
{
protected $settings;
public static function getSubscribedEvents()
{
return array(
'onSettingsLoaded' => 'onSettingsLoaded',
'onTwigLoaded' => 'onTwigLoaded'
);
}
public function onSettingsLoaded($settings)
{
$this->settings = $settings->getData();
}
public function onTwigLoaded()
{
/* add external CSS and JavaScript */
$this->addCSS('/cookieconsent/public/cookieconsent.min.css');
$this->addJS('/cookieconsent/public/cookieconsent.min.js');
/* get Twig Instance and add the cookieconsent template-folder to the path */
$twig = $this->getTwig();
$loader = $twig->getLoader();
$loader->addPath(__DIR__ . '/templates');
/* fetch the template, render it with twig and add it as inline-javascript */
$this->addInlineJS($twig->fetch('/cookieconsent.twig', $this->settings));
}
}

View File

@ -1,88 +1,88 @@
name: Cookie Consent
version: 1.0.1
description: Enables a cookie consent for websites
author: Sebastian Schürmanns
homepage: https://cookieconsent.insites.com/
licence: MIT
settings:
popup_background: '#70c1b3'
popup_text: '#ffffff'
button_background: '#66b0a3'
button_text: '#ffffff'
theme: 'edgeless'
position: 'bottom'
message: 'This website uses cookies to ensure you get the best experience on our website.'
link: 'Learn More'
href: 'https://cookiesandyou.com/'
dismiss: 'Got It'
forms:
fields:
theme:
type: select
label: Theme
placeholder: 'Add name of theme'
required: true
options:
edgeless: Edgeless
block: Block
classic: Classic
position:
type: select
label: Position of Cookie Banner
options:
bottom: Bottom
top: Top
bottom-left: Bottom left
bottom-right: Bottom right
message:
type: textarea
label: Message
placeholder: 'Message for cookie-popup'
required: true
href:
type: url
label: Link to more informations
placeholder: 'https://cookiesandyou.com/'
required: true
link:
type: text
label: Label for Link
placeholder: 'Link-Lable like More infos'
required: true
dismiss:
type: text
label: Label for Button
placeholder: 'Got it'
required: true
popup_background:
type: color
label: Background Color of Popup
placeholder: 'Add hex color value like #ffffff'
required: true
popup_text:
type: color
label: Text Color of Popup
placeholder: 'Add hex color value like #ffffff'
required: true
button_background:
type: color
label: Background Color of Button
placeholder: 'Add hex color value like #ffffff'
required: true
button_text:
type: color
label: Text Color of Button
placeholder: 'Add hex color value like #ffffff'
name: Cookie Consent
version: 1.0.1
description: Enables a cookie consent for websites
author: Sebastian Schürmanns
homepage: https://cookieconsent.insites.com/
licence: MIT
settings:
popup_background: '#70c1b3'
popup_text: '#ffffff'
button_background: '#66b0a3'
button_text: '#ffffff'
theme: 'edgeless'
position: 'bottom'
message: 'This website uses cookies to ensure you get the best experience on our website.'
link: 'Learn More'
href: 'https://cookiesandyou.com/'
dismiss: 'Got It'
forms:
fields:
theme:
type: select
label: Theme
placeholder: 'Add name of theme'
required: true
options:
edgeless: Edgeless
block: Block
classic: Classic
position:
type: select
label: Position of Cookie Banner
options:
bottom: Bottom
top: Top
bottom-left: Bottom left
bottom-right: Bottom right
message:
type: textarea
label: Message
placeholder: 'Message for cookie-popup'
required: true
href:
type: url
label: Link to more informations
placeholder: 'https://cookiesandyou.com/'
required: true
link:
type: text
label: Label for Link
placeholder: 'Link-Lable like More infos'
required: true
dismiss:
type: text
label: Label for Button
placeholder: 'Got it'
required: true
popup_background:
type: color
label: Background Color of Popup
placeholder: 'Add hex color value like #ffffff'
required: true
popup_text:
type: color
label: Text Color of Popup
placeholder: 'Add hex color value like #ffffff'
required: true
button_background:
type: color
label: Background Color of Button
placeholder: 'Add hex color value like #ffffff'
required: true
button_text:
type: color
label: Text Color of Button
placeholder: 'Add hex color value like #ffffff'
required: true

View File

@ -1,6 +1,6 @@
.cc-window{opacity:1;transition:opacity 1s ease}.cc-window.cc-invisible{opacity:0}.cc-animate.cc-revoke{transition:transform 1s ease}.cc-animate.cc-revoke.cc-top{transform:translateY(-2em)}.cc-animate.cc-revoke.cc-bottom{transform:translateY(2em)}.cc-animate.cc-revoke.cc-active.cc-bottom,.cc-animate.cc-revoke.cc-active.cc-top,.cc-revoke:hover{transform:translateY(0)}.cc-grower{max-height:0;overflow:hidden;transition:max-height 1s}
.cc-link,.cc-revoke:hover{text-decoration:underline}.cc-revoke,.cc-window{position:fixed;overflow:hidden;box-sizing:border-box;font-family:Helvetica,Calibri,Arial,sans-serif;font-size:16px;line-height:1.5em;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;z-index:9999}.cc-window.cc-static{position:static}.cc-window.cc-floating{padding:2em;max-width:24em;-ms-flex-direction:column;flex-direction:column}.cc-window.cc-banner{padding:1em 1.8em;width:100%;-ms-flex-direction:row;flex-direction:row}.cc-revoke{padding:.5em}.cc-header{font-size:18px;font-weight:700}.cc-btn,.cc-close,.cc-link,.cc-revoke{cursor:pointer}.cc-link{opacity:.8;display:inline-block;padding:.2em}.cc-link:hover{opacity:1}.cc-link:active,.cc-link:visited{color:initial}.cc-btn{display:block;padding:.4em .8em;font-size:.9em;font-weight:700;border-width:2px;border-style:solid;text-align:center;white-space:nowrap}.cc-highlight .cc-btn:first-child{background-color:transparent;border-color:transparent}.cc-highlight .cc-btn:first-child:focus,.cc-highlight .cc-btn:first-child:hover{background-color:transparent;text-decoration:underline}.cc-close{display:block;position:absolute;top:.5em;right:.5em;font-size:1.6em;opacity:.9;line-height:.75}.cc-close:focus,.cc-close:hover{opacity:1}
.cc-revoke.cc-top{top:0;left:3em;border-bottom-left-radius:.5em;border-bottom-right-radius:.5em}.cc-revoke.cc-bottom{bottom:0;left:3em;border-top-left-radius:.5em;border-top-right-radius:.5em}.cc-revoke.cc-left{left:3em;right:unset}.cc-revoke.cc-right{right:3em;left:unset}.cc-top{top:1em}.cc-left{left:1em}.cc-right{right:1em}.cc-bottom{bottom:1em}.cc-floating>.cc-link{margin-bottom:1em}.cc-floating .cc-message{display:block;margin-bottom:1em}.cc-window.cc-floating .cc-compliance{-ms-flex:1 0 auto;flex:1 0 auto}.cc-window.cc-banner{-ms-flex-align:center;align-items:center}.cc-banner.cc-top{left:0;right:0;top:0}.cc-banner.cc-bottom{left:0;right:0;bottom:0}.cc-banner .cc-message{display:block;-ms-flex:1 1 auto;flex:1 1 auto;max-width:100%;margin-right:1em}.cc-compliance{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:justify;align-content:space-between}.cc-floating .cc-compliance>.cc-btn{-ms-flex:1;flex:1}.cc-btn+.cc-btn{margin-left:.5em}
@media print{.cc-revoke,.cc-window{display:none}}@media screen and (max-width:900px){.cc-btn{white-space:normal}}@media screen and (max-width:414px) and (orientation:portrait),screen and (max-width:736px) and (orientation:landscape){.cc-window.cc-top{top:0}.cc-window.cc-bottom{bottom:0}.cc-window.cc-banner,.cc-window.cc-floating,.cc-window.cc-left,.cc-window.cc-right{left:0;right:0}.cc-window.cc-banner{-ms-flex-direction:column;flex-direction:column}.cc-window.cc-banner .cc-compliance{-ms-flex:1 1 auto;flex:1 1 auto}.cc-window.cc-floating{max-width:none}.cc-window .cc-message{margin-bottom:1em}.cc-window.cc-banner{-ms-flex-align:unset;align-items:unset}.cc-window.cc-banner .cc-message{margin-right:0}}
.cc-floating.cc-theme-classic{padding:1.2em;border-radius:5px}.cc-floating.cc-type-info.cc-theme-classic .cc-compliance{text-align:center;display:inline;-ms-flex:none;flex:none}.cc-theme-classic .cc-btn{border-radius:5px}.cc-theme-classic .cc-btn:last-child{min-width:140px}.cc-floating.cc-type-info.cc-theme-classic .cc-btn{display:inline-block}
.cc-window{opacity:1;transition:opacity 1s ease}.cc-window.cc-invisible{opacity:0}.cc-animate.cc-revoke{transition:transform 1s ease}.cc-animate.cc-revoke.cc-top{transform:translateY(-2em)}.cc-animate.cc-revoke.cc-bottom{transform:translateY(2em)}.cc-animate.cc-revoke.cc-active.cc-bottom,.cc-animate.cc-revoke.cc-active.cc-top,.cc-revoke:hover{transform:translateY(0)}.cc-grower{max-height:0;overflow:hidden;transition:max-height 1s}
.cc-link,.cc-revoke:hover{text-decoration:underline}.cc-revoke,.cc-window{position:fixed;overflow:hidden;box-sizing:border-box;font-family:Helvetica,Calibri,Arial,sans-serif;font-size:16px;line-height:1.5em;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap;z-index:9999}.cc-window.cc-static{position:static}.cc-window.cc-floating{padding:2em;max-width:24em;-ms-flex-direction:column;flex-direction:column}.cc-window.cc-banner{padding:1em 1.8em;width:100%;-ms-flex-direction:row;flex-direction:row}.cc-revoke{padding:.5em}.cc-header{font-size:18px;font-weight:700}.cc-btn,.cc-close,.cc-link,.cc-revoke{cursor:pointer}.cc-link{opacity:.8;display:inline-block;padding:.2em}.cc-link:hover{opacity:1}.cc-link:active,.cc-link:visited{color:initial}.cc-btn{display:block;padding:.4em .8em;font-size:.9em;font-weight:700;border-width:2px;border-style:solid;text-align:center;white-space:nowrap}.cc-highlight .cc-btn:first-child{background-color:transparent;border-color:transparent}.cc-highlight .cc-btn:first-child:focus,.cc-highlight .cc-btn:first-child:hover{background-color:transparent;text-decoration:underline}.cc-close{display:block;position:absolute;top:.5em;right:.5em;font-size:1.6em;opacity:.9;line-height:.75}.cc-close:focus,.cc-close:hover{opacity:1}
.cc-revoke.cc-top{top:0;left:3em;border-bottom-left-radius:.5em;border-bottom-right-radius:.5em}.cc-revoke.cc-bottom{bottom:0;left:3em;border-top-left-radius:.5em;border-top-right-radius:.5em}.cc-revoke.cc-left{left:3em;right:unset}.cc-revoke.cc-right{right:3em;left:unset}.cc-top{top:1em}.cc-left{left:1em}.cc-right{right:1em}.cc-bottom{bottom:1em}.cc-floating>.cc-link{margin-bottom:1em}.cc-floating .cc-message{display:block;margin-bottom:1em}.cc-window.cc-floating .cc-compliance{-ms-flex:1 0 auto;flex:1 0 auto}.cc-window.cc-banner{-ms-flex-align:center;align-items:center}.cc-banner.cc-top{left:0;right:0;top:0}.cc-banner.cc-bottom{left:0;right:0;bottom:0}.cc-banner .cc-message{display:block;-ms-flex:1 1 auto;flex:1 1 auto;max-width:100%;margin-right:1em}.cc-compliance{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-line-pack:justify;align-content:space-between}.cc-floating .cc-compliance>.cc-btn{-ms-flex:1;flex:1}.cc-btn+.cc-btn{margin-left:.5em}
@media print{.cc-revoke,.cc-window{display:none}}@media screen and (max-width:900px){.cc-btn{white-space:normal}}@media screen and (max-width:414px) and (orientation:portrait),screen and (max-width:736px) and (orientation:landscape){.cc-window.cc-top{top:0}.cc-window.cc-bottom{bottom:0}.cc-window.cc-banner,.cc-window.cc-floating,.cc-window.cc-left,.cc-window.cc-right{left:0;right:0}.cc-window.cc-banner{-ms-flex-direction:column;flex-direction:column}.cc-window.cc-banner .cc-compliance{-ms-flex:1 1 auto;flex:1 1 auto}.cc-window.cc-floating{max-width:none}.cc-window .cc-message{margin-bottom:1em}.cc-window.cc-banner{-ms-flex-align:unset;align-items:unset}.cc-window.cc-banner .cc-message{margin-right:0}}
.cc-floating.cc-theme-classic{padding:1.2em;border-radius:5px}.cc-floating.cc-type-info.cc-theme-classic .cc-compliance{text-align:center;display:inline;-ms-flex:none;flex:none}.cc-theme-classic .cc-btn{border-radius:5px}.cc-theme-classic .cc-btn:last-child{min-width:140px}.cc-floating.cc-type-info.cc-theme-classic .cc-btn{display:inline-block}
.cc-theme-edgeless.cc-window{padding:0}.cc-floating.cc-theme-edgeless .cc-message{margin:2em 2em 1.5em}.cc-banner.cc-theme-edgeless .cc-btn{margin:0;padding:.8em 1.8em;height:100%}.cc-banner.cc-theme-edgeless .cc-message{margin-left:1em}.cc-floating.cc-theme-edgeless .cc-btn+.cc-btn{margin-left:0}

View File

@ -1,22 +1,22 @@
window.addEventListener("load", function(){
window.cookieconsent.initialise({
"palette": {
"popup": {
"background": "{{ settings.plugins.cookieconsent.popup_background }}",
"text": "{{ settings.plugins.cookieconsent.popup_text }}"
},
"button": {
"background": "{{ settings.plugins.cookieconsent.button_background }}",
"text": "{{ settings.plugins.cookieconsent.button_text }}"
}
},
"theme": "{{ settings.plugins.cookieconsent.theme }}",
"position": "{{ settings.plugins.cookieconsent.position }}",
"content": {
"message": "{{ settings.plugins.cookieconsent.message }}",
"dismiss": "{{ settings.plugins.cookieconsent.dismiss }}",
"link": "{{ settings.plugins.cookieconsent.link }}",
"href": "{{ settings.plugins.cookieconsent.href }}"
}
})
});
window.addEventListener("load", function(){
window.cookieconsent.initialise({
"palette": {
"popup": {
"background": "{{ settings.plugins.cookieconsent.popup_background }}",
"text": "{{ settings.plugins.cookieconsent.popup_text }}"
},
"button": {
"background": "{{ settings.plugins.cookieconsent.button_background }}",
"text": "{{ settings.plugins.cookieconsent.button_text }}"
}
},
"theme": "{{ settings.plugins.cookieconsent.theme }}",
"position": "{{ settings.plugins.cookieconsent.position }}",
"content": {
"message": "{{ settings.plugins.cookieconsent.message }}",
"dismiss": "{{ settings.plugins.cookieconsent.dismiss }}",
"link": "{{ settings.plugins.cookieconsent.link }}",
"href": "{{ settings.plugins.cookieconsent.href }}"
}
})
});

View File

@ -1,28 +1,28 @@
<?php
namespace Plugins\Highlight;
use \Typemill\Plugin;
class Highlight extends Plugin
{
protected $settings;
public static function getSubscribedEvents()
{
return array(
'onTwigLoaded' => 'onTwigLoaded'
);
}
public function onTwigLoaded()
{
/* add external CSS and JavaScript */
$this->addCSS('/highlight/public/default.css');
$this->addJS('/highlight/public/highlight.pack.js');
/* initialize the script */
$this->addInlineJS('hljs.initHighlightingOnLoad();');
}
<?php
namespace Plugins\Highlight;
use \Typemill\Plugin;
class Highlight extends Plugin
{
protected $settings;
public static function getSubscribedEvents()
{
return array(
'onTwigLoaded' => 'onTwigLoaded'
);
}
public function onTwigLoaded()
{
/* add external CSS and JavaScript */
$this->addCSS('/highlight/public/default.css');
$this->addJS('/highlight/public/highlight.pack.js');
/* initialize the script */
$this->addInlineJS('hljs.initHighlightingOnLoad();');
}
}

View File

@ -1,6 +1,6 @@
name: Highlight
version: 1.0.0
description: Adds the famous javascript syntax highlighter.
author: Sebastian Schürmanns
homepage: https://highlightjs.org/
name: Highlight
version: 1.0.0
description: Adds the famous javascript syntax highlighter.
author: Sebastian Schürmanns
homepage: https://highlightjs.org/
licence: BSD

View File

@ -1,24 +1,24 @@
Copyright (c) 2006, Ivan Sagalaev
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of highlight.js nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Copyright (c) 2006, Ivan Sagalaev
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of highlight.js nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,99 +1,99 @@
/*
Original highlight.js style (c) Ivan Sagalaev <maniac@softwaremaniacs.org>
*/
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
background: #F0F0F0;
}
/* Base color: saturation 0; */
.hljs,
.hljs-subst {
color: #444;
}
.hljs-comment {
color: #888888;
}
.hljs-keyword,
.hljs-attribute,
.hljs-selector-tag,
.hljs-meta-keyword,
.hljs-doctag,
.hljs-name {
font-weight: bold;
}
/* User color: hue: 0 */
.hljs-type,
.hljs-string,
.hljs-number,
.hljs-selector-id,
.hljs-selector-class,
.hljs-quote,
.hljs-template-tag,
.hljs-deletion {
color: #880000;
}
.hljs-title,
.hljs-section {
color: #880000;
font-weight: bold;
}
.hljs-regexp,
.hljs-symbol,
.hljs-variable,
.hljs-template-variable,
.hljs-link,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #BC6060;
}
/* Language color: hue: 90; */
.hljs-literal {
color: #78A960;
}
.hljs-built_in,
.hljs-bullet,
.hljs-code,
.hljs-addition {
color: #397300;
}
/* Meta color: hue: 200 */
.hljs-meta {
color: #1f7199;
}
.hljs-meta-string {
color: #4d99bf;
}
/* Misc effects */
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}
/*
Original highlight.js style (c) Ivan Sagalaev <maniac@softwaremaniacs.org>
*/
.hljs {
display: block;
overflow-x: auto;
padding: 0.5em;
background: #F0F0F0;
}
/* Base color: saturation 0; */
.hljs,
.hljs-subst {
color: #444;
}
.hljs-comment {
color: #888888;
}
.hljs-keyword,
.hljs-attribute,
.hljs-selector-tag,
.hljs-meta-keyword,
.hljs-doctag,
.hljs-name {
font-weight: bold;
}
/* User color: hue: 0 */
.hljs-type,
.hljs-string,
.hljs-number,
.hljs-selector-id,
.hljs-selector-class,
.hljs-quote,
.hljs-template-tag,
.hljs-deletion {
color: #880000;
}
.hljs-title,
.hljs-section {
color: #880000;
font-weight: bold;
}
.hljs-regexp,
.hljs-symbol,
.hljs-variable,
.hljs-template-variable,
.hljs-link,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #BC6060;
}
/* Language color: hue: 90; */
.hljs-literal {
color: #78A960;
}
.hljs-built_in,
.hljs-bullet,
.hljs-code,
.hljs-addition {
color: #397300;
}
/* Meta color: hue: 200 */
.hljs-meta {
color: #1f7199;
}
.hljs-meta-string {
color: #4d99bf;
}
/* Misc effects */
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}

File diff suppressed because one or more lines are too long

204
plugins/hyer/getads.php Normal file
View File

@ -0,0 +1,204 @@
<?php
namespace Plugins\hyer;
use \Typemill\Plugin;
class getads extends Plugin
{
public static function getSubscribedEvents(){}
# get only pro ads for product
public function pro()
{
# get referrer url
if(!isset($_SERVER['HTTP_REFERER']))
{
return $this->returnJsonError(['message' => 'Referrer is missing']);
}
# get the url parameter
$input = $this->getParams();
# create secret
$secret = time();
$secret = substr($secret,0,-1);
$oldsecret = $secret -1;
$secret = md5($secret . $this->getSalt());
$oldsecret = md5($oldsecret . $this->getSalt());
if(!isset($input['access']) OR ($input['access'] != $secret AND $input['access'] != $oldsecret))
{
return $this->returnJsonError(['message' => 'Secret is incorrect']);
}
# get the last segment of url
$referrer = $_SERVER['HTTP_REFERER'];
$segment = basename($referrer);
# get the settings
$settings = \Typemill\Settings::loadSettings();
# get url-map to find the correct cms for requesting page
$urlmap = json_decode($settings['settings']['plugins']['hyer']['urlmap'],true);
# map the request with the map in settings
if(!isset($urlmap[$segment]))
{
return $this->returnJsonError(['message' => 'We could not map that url']);
}
# make call
# $hyerApi = 'http://localhost/typemillService/public/publicapi/proads';
$hyerApi = 'https://service.cmsstash.de/publicapi/proads';
$hyerRequest = ['product' => $urlmap[$segment]];
$result = $this->makeApiCall($hyerRequest, $hyerApi);
return $this->returnJson($result);
}
# get 10 latest pro ads
public function latest()
{
# get the url parameter
$input = $this->getParams();
# create secret
$secret = time();
$secret = substr($secret,0,-1);
$oldsecret = $secret -1;
$secret = md5($secret . $this->getSalt());
$oldsecret = md5($oldsecret . $this->getSalt());
if(!isset($input['access']) OR ($input['access'] != $secret AND $input['access'] != $oldsecret))
{
return $this->returnJsonError(['message' => 'Secret is incorrect']);
}
# $hyerApi = 'http://localhost/typemillService/public/publicapi/latestproads';
$hyerApi = 'https://service.cmsstash.de/publicapi/latestproads';
$hyerRequest = [];
$result = $this->makeApiCall($hyerRequest, $hyerApi);
return $this->returnJson($result);
}
# search for ads from directory
public function search()
{
session_start();
# get the url parameter
$input = $this->getParams();
# validate input here
# read session data for simple csrf check
$token = isset($_SESSION['hyer']) ? $_SESSION['hyer'] : false;
$time = isset($_SESSION['hyer-expire']) ? $_SESSION['hyer-expire'] : false;
# perform simple security or csrf check
if(!isset($input['token']) OR !$token OR !$time OR ($input['token'] != $token ) OR (time() >= $time))
{
return $this->returnJsonError(['message' => 'Die Sitzung ist abgelaufen, bitte laden Sie die Seite neu.']);
}
# $hyerApi = 'http://localhost/typemillService/public/publicapi/searchads';
$hyerApi = 'https://service.cmsstash.de/publicapi/searchads';
$hyerRequest = [];
if(isset($input['product']) && $input['product'] != '')
{
if(!$this->validateLengthBetween($input['product'], 2, 50) OR !$this->validateHtml($input['product']))
{
return $this->returnJsonError(['message' => 'Product is invalid']);
}
$hyerRequest['product'] = $input['product'];
}
if(isset($input['region']) && $input['region'] != '')
{
if(!$this->validateLengthBetween($input['region'], 2, 50) OR !$this->validateHtml($input['region']))
{
return $this->returnJsonError(['message' => 'Product is invalid']);
}
$hyerRequest['region'] = $input['region'];
}
$result = $this->makeApiCall($hyerRequest, $hyerApi);
if($result)
{
return $this->returnJson($result);
}
return $this->returnJsonError(['message' => 'Kein Ergebnis']);
}
private function makeApiCall($hyerRequest, $hyerApi)
{
# get the settings
$settings = \Typemill\Settings::loadSettings();
# get api key from settings
$apikey = $settings['settings']['plugins']['hyer']['apikey'];
$siteid = $settings['settings']['plugins']['hyer']['siteid'];
# use key 'http' even if you send the request to https://...
$options = array(
'http' => array(
'header' => "Content-type: application/x-www-form-urlencoded\r\n" . "Authorization: Basic " . base64_encode($siteid . ":". $apikey),
'method' => 'GET',
'content' => http_build_query($hyerRequest),
'timeout' => 5
)
);
ini_set('allow_url_fopen', '1');
$context = stream_context_create($options);
$result = file_get_contents($hyerApi, false, $context);
$result = json_decode($result);
ini_set('allow_url_fopen', '0');
return $result;
}
private function validateHtml($value)
{
if($value == strip_tags($value))
{
return true;
}
return false;
}
private function validateLengthBetween($value, $min, $max)
{
$length = $this->stringLength($value);
return ($length !== false) && $length >= $min && $length <= $max;
}
private function stringLength($value)
{
if (!is_string($value)) {
return false;
} elseif (function_exists('mb_strlen')) {
return mb_strlen($value);
}
return strlen($value);
}
private function getSalt()
{
return "asPx9Derf2";
}
}

221
plugins/hyer/hyer.php Normal file
View File

@ -0,0 +1,221 @@
<?php
namespace Plugins\hyer;
use \Typemill\Plugin;
class Hyer extends Plugin
{
protected $item;
public static function getSubscribedEvents()
{
return array(
'onSettingsLoaded' => 'onsettingsLoaded',
'onItemLoaded' => 'onItemLoaded',
'onContentArrayLoaded' => 'onContentArrayLoaded',
);
}
public static function addNewRoutes()
{
# the route for the api calls
return array(
array(
'httpMethod' => 'get',
'route' => '/latestadsfe30s8edw4wdkp',
'class' => 'Plugins\hyer\getads:latest'
),
array(
'httpMethod' => 'get',
'route' => '/proadsfe30s8edw4wdkp',
'class' => 'Plugins\hyer\getads:pro'
),
array(
'httpMethod' => 'get',
'route' => '/searchadsfe30s8edw4wdkp',
'class' => 'Plugins\hyer\getads:search'
)
);
}
public function onSettingsLoaded($settings)
{
$this->settings = $settings->getData();
}
public function onItemLoaded($item)
{
$this->item = $item->getData();
}
public function onContentArrayLoaded($contentArray)
{
# get content array
$content = $contentArray->getData();
$settings = $this->getPluginSettings('hyer');
$path = $this->getPath();
$segment = basename($path); # get the last url segment
$urlmap = json_decode($settings['urlmap'],true); # to find the correct page to include app
$directory = trim($settings['directory'],"/"); # the path for the directory
$salt = "asPx9Derf2";
# if we are on the directory page
if(trim($path,"/") == trim($settings['directory'],"/"))
{
# activate axios and vue in frontend
$this->activateAxios();
$this->activateVue();
$this->activateTachyons();
# add the css and vue application
$this->addCSS('/hyer/public/hyer.css');
$this->addJS('/hyer/public/hyer.js');
$twig = $this->getTwig(); // get the twig-object
$loader = $twig->getLoader(); // get the twig-template-loader
$loader->addPath(__DIR__ . '/templates');
$svg = $twig->fetch('/hyer.twig');
# simple security for first request
$secret = time();
$secret = substr($secret,0,-1);
$secret = md5($secret . $salt);
# simple csrf protection with a session for long following requests
session_start();
$length = 32;
$token = substr(base_convert(sha1(uniqid(mt_rand())), 16, 36), 0, $length);
$_SESSION['hyer'] = $token;
$_SESSION['hyer-expire'] = time() + 1300; # 60 seconds * 30 minutes
# use this to secure the api long term
# $token = time();
# $token = substr($token,0,-4);
# $token = md5($token . $salt);
$products = '';
foreach($urlmap as $product)
{
$products .= $product . ',';
}
$products = trim($products, ",");
# create div for vue app
$textHyer = '<div data-access="' . $secret . '" data-token="' . $token . '" data-products="' . $products . '" id="hyerapp"><directory></directory></div>';
# create content type
$textHyer = Array
(
'rawHtml' => $svg . $textHyer,
'allowRawHtmlInSafeMode' => true,
'autobreak' => 1
);
$content[] = $textHyer;
}
# map the url with the map in settings
elseif(isset($urlmap[$segment]))
{
# activate axios and vue in frontend
$this->activateAxios();
$this->activateVue();
$this->activateTachyons();
# add the css and vue application
$this->addCSS('/hyer/public/hyer.css');
$this->addJS('/hyer/public/hyer.js');
$twig = $this->getTwig(); // get the twig-object
$loader = $twig->getLoader(); // get the twig-template-loader
$loader->addPath(__DIR__ . '/templates');
$svg = $twig->fetch('/hyer.twig');
# use this to secure the api a bit
$secret = time();
$secret = substr($secret,0,-1);
$secret = md5($secret . $salt);
# create div for vue app
$textHyer = '<div data-access="' . $secret . '" id="hyerapp"><premiumads></premiumads></div>';
$textHeadline = (isset($settings['headline']) && !empty($settings['headline'])) ? $settings['headline'] : false;
$textTeaser = (isset($settings['teaser']) && !empty($settings['teaser'])) ? $settings['teaser'] : false;
# create content type
$textHyer = Array
(
'rawHtml' => $svg . $textHyer,
'allowRawHtmlInSafeMode' => true,
'autobreak' => 1
);
if($textHeadline)
{
$textHeadline = array(
'name' => 'h2',
'text' => $textHeadline,
'handler' => 'line',
'attributes' => Array
(
'id' => $textHeadline
)
);
}
if($textTeaser)
{
$textTeaser = array(
'name' => 'p',
'handler' => Array
(
'function' => 'lineElements',
'argument' => $textTeaser,
'destination' => 'elements'
)
);
}
$length = count($content);
$thisElement = 0;
$pos = false;
$i = 0;
$position = isset($settings['position']) ? $settings['position'] : 3;
while($i < $length)
{
if($content[$i]['name'] == 'h2')
{
$thisElement = $thisElement + 1;
}
# place hyer app before the 3rd headline h2-level
if($thisElement == $position)
{
$pos = $i;
break;
}
$i++;
}
if($pos)
{
$start = array_slice($content, 0, $pos);
$end = array_slice($content, $pos);
if($textHeadline){ $start[] = $textHeadline; }
if($textTeaser){ $start[] = $textTeaser; }
$content = array_merge( $start, array($textHyer), $end );
}
else
{
if($textHeadline){ $content[] = $textHeadline; }
if($textTeaser){ $content[] = $textTeaser; }
$content[] = $textHyer;
}
}
$contentArray->setData($content);
}
}

44
plugins/hyer/hyer.yaml Normal file
View File

@ -0,0 +1,44 @@
name: HYER SPACE
version: 1.0.0
description: Integrate HYER Text-Adds into typemill.
author: Sebastian Schürmanns
homepage: https://typemill.net
licence: MIT
forms:
fields:
apikey:
type: text
label: API KEY
placeholder:
required: true
siteid:
type: text
label: Site ID
placeholder:
required: true
urlmap:
type: textarea
label: URL Map
placeholder: '180 characters'
required: true
directory:
type: text
label: Path to directory
placeholder: 'path/to/directory'
headline:
type: text
label: Headline before ad-block on review page
teaser:
type: textarea
label: Teaser before ad-block on review page
position:
type: number
label: Position for the ad-block (before xyz h2 headlines)

View File

@ -0,0 +1,64 @@
/**********************
** ICONS **
**********************/
.icon {
display: inline-block;
width: 1em;
height: 1em;
stroke-width: 0;
stroke: currentColor;
fill: currentColor;
}
.icon-users {
width: 1.125em;
}
.bg-customized{
background: #f9f8f6;
}
.bg-customized-2{
background: #ff4136;
}
.b--customized-2{
border-color: #ff4136;
}
.customized-2{
color: #ff4136;
}
.custombutton{
line-height: 1.55rem;
}
.link, .link:active, .link:focus, .link:hover, .link:link, .link:visited {
transition: color .15s ease-in;
color: #000;
text-decoration: none;
}
.link-white, .link-white.active, .link-white:focus, .link-white:hover, .link-white:link, .link-white:visited{
color: #fff;
}
select{
background-color: #f9f8f6;
}
select{
/* reset */
-webkit-appearance: none;
-moz-appearance: none;
/* style */
background-image:
linear-gradient(45deg, transparent 50%, #444 50%),
linear-gradient(135deg, #444 50%, transparent 50%),
linear-gradient(to right, transparent, transparent);
background-position:
calc(100% - 20px) calc(1em + 6px),
calc(100% - 15px) calc(1em + 6px),
100% 0;
background-size:
5px 5px,
5px 5px,
2.8em 2.8em;
background-repeat: no-repeat;
cursor: pointer;
}
.shadow-hover:hover{
box-shadow: 0px 0px 2px 0px rgba(158,158,158,1);
}

290
plugins/hyer/public/hyer.js Normal file
View File

@ -0,0 +1,290 @@
const myaxios = axios.create({
baseURL: document.getElementsByTagName("base")[0].href
});
Vue.component('directory', {
data: function () {
return {
ads: [],
products: [],
selectedProduct: "",
selectedRegion: "",
token: "",
message: false,
messageType: '',
}
},
template: '<div>' +
'<div>' +
'<form action="#">' +
'<fieldset class="bn ma0 pa0">' +
'<div class="flex-ns items-end justify-around">' +
'<div class="w-100 mr3-ns">' +
'<label for="country" class="f6 b db mb2 mt4 tl">CMS</label>' +
'<select v-model="selectedProduct" class="input-reset outline-focus ba b--black-20 ph2 pv3 mb2 db w-100" name="location[index][country]" v-model="location.country" required>' +
'<option value="">-</option>' +
'<option v-for="product in products">{{ product }}</option>' +
'</select>' +
'</div>' +
'<div class="w-100 mr3-ns">' +
'<label for="region" class="f6 b db mb2 mt4 tl">Region</label>' +
'<select v-model="selectedRegion" class="input-reset outline-focus ba b--black-20 ph2 pv3 mb2 db w-100" name="location[index][region]" v-model="location.region" required>' +
'<option value="">-</option>' +
'<option>Baden-Würtemberg</option>' +
'<option>Bayern</option>' +
'<option>Berlin</option>' +
'<option>Brandenburg</option>' +
'<option>Bremen</option>' +
'<option>Hamburg</option>' +
'<option>Hessen</option>' +
'<option>Mecklenburg-Vorpommern</option>' +
'<option>Niedersachsen</option>' +
'<option>Nordrhein-Westfalen</option>' +
'<option>Rheinland-Pfalz</option>' +
'<option>Saarland</option>' +
'<option>Sachsen</option>' +
'<option>Sachsen-Anhalt</option>' +
'<option>Schleswig-Holstein</option>' +
'<option>Thüringen</option>' +
'</select>' +
'</div>' +
'<button @click.prevent="searchProducts" class="w-100 w-30-ns link bg-customized-2 custombutton link-white dim bn mb2 pa3 mt3 mt0-ns pointer">Suchen</button>' +
'</div>' +
'</fieldset>' +
'</form>' +
'</div>' +
'<div v-if="message" class="w-100 pa3 tc mv3 customized-2 ba b--customized-2">{{ message }}</div>' +
'<div v-for="ad in ads">' +
'<div class="w-100 bg-customized dark-gray flex dib tl pa0 mv3 f5 relative">' +
'<div class="w-100 ba b--light-gray pa0 shadow-hover">' +
'<a :href="getLink(ad)" rel="ugc sponsored" target="_blank" class="link dark-gray pa0 mt3 mb0">' +
'<div v-if="ad.pro" class="absolute-ns tc top-0 right-0 ba b--dark-gray pa1 f6 ma3">Premium</div>' +
'<h3 class="mt4 mb1 pv0 ph3 underline-hover">{{ getTitle(ad) }}</h3>' +
'<small class="pv0 ph3 ma0 underline-hover">{{ getLink(ad) }}</small>' +
'<p class="mt1 mb3 pv0 ph3 underline-hover">{{ getTeaser(ad) }}</p>' +
'</a>' +
'<div class="f5 pv2 ph3 bt dark-gray b--moon-gray w-100 flex">' +
'<div class="tl w-50">' +
'<svg class="icon icon-location"><use xlink:href="#icon-location"></use></svg> {{ ad.city }} ' +
'<span class="pl2"><svg class="icon icon-location"><use xlink:href="#icon-users"></use></svg> {{ ad.size}}</span>' +
'</div>' +
'<div class="tr w-50">Web:' +
'<a class="link dark-gray ph2" rel="ugc sponsored" :href="ad.company_link" target="_blank"><svg class="icon icon-location"><use xlink:href="#icon-home"></use></svg></a>' +
'<a class="link dark-gray ph2" rel="ugc sponsored" v-if="ad.sm_link_1" :href="ad.sm_link_1" target="_blank"><svg :class="getSocialIcon(ad.sm_link_1)" class="icon"><use :xlink:href="getSocialIcon(ad.sm_link_1, true)"></use></svg></a>' +
'<a class="link dark-gray ph2" rel="ugc sponsored" v-if="ad.sm_link_2" :href="ad.sm_link_2" target="_blank"><svg :class="getSocialIcon(ad.sm_link_2)" class="icon"><use :xlink:href="getSocialIcon(ad.sm_link_2, true)"></use></svg></a>' +
'</div>' +
'</div>' +
'</div>' +
'</div>' +
'</div>' +
'<div class="w-100 bg-white dark-gray dib tl pa0 mv3 f5">' +
'<div class="w-100 ba b--light-gray pa0 tc">' +
'<h3 class="mt4 mb0 pv0 ph3">Web-Agentur oder Freelancer?</h3>' +
'<p class="mt3 mb3 pv0 ph3">Dann ist genau hier Ihre Expertise gefragt!<br/> Mit einem Dienstleister-Eintrag auf cmsstash.de</p>' +
'<a href="https://service.cmsstash.de" class="link bg-customized-2 link-white dim ba1 b--red mt0 mb4 ph3 pv2 dib br1">Eintrag erstellen</a>' +
'</div>' +
'</div>' +
'</div>',
mounted: function(){
var products = document.getElementById("hyerapp").dataset.products;
this.products = products.split(',');
this.token = document.getElementById("hyerapp").dataset.token;
self = this;
var access = document.getElementById("hyerapp").dataset.access;
myaxios.get('/latestadsfe30s8edw4wdkp?access='+access)
.then(function (response) {
self.ads = response.data.ads;
/* if matomo is on, check dom for new links */
if(_paq)
{
Vue.nextTick(function () {
_paq.push(['enableLinkTracking']);
})
}
})
.catch(function (error) {});
},
methods: {
searchProducts: function()
{
self = this;
myaxios.get('/searchadsfe30s8edw4wdkp',{
params: {
token: this.token,
product: this.selectedProduct,
region: this.selectedRegion
}
})
.then(function (response) {
self.ads = response.data.ads;
/* if matomo is on, check dom for new links */
if(_paq)
{
Vue.nextTick(function () {
_paq.push(['enableLinkTracking']);
})
}
})
.catch(function (error)
{
if(error.response)
{
self.message = error.response.data.message;
self.messageType = 'error';
}
});
},
getTitle: function(product)
{
if(product.ad_title)
{
return product.ad_title;
}
return product.company_name;
},
getTeaser: function(product)
{
if(product.ad_teaser)
{
return product.ad_teaser;
}
return product.company_description;
},
getLink: function(product)
{
if(product.ad_link)
{
return product.ad_link;
}
return product.company_link;
},
getSocialIcon: function(icon,id)
{
var prefix = id ? '#' : '';
if(icon.indexOf("twitter") > -1){ return prefix+'icon-twitter' }
else if(icon.indexOf("facebook") > -1){ return prefix+'icon-facebook' }
else if(icon.indexOf("xing") > -1){ return prefix+'icon-xing2' }
else if(icon.indexOf("linkedin") > -1){ return prefix+'icon-linkedin2' }
return prefix+'icon-link';
},
}
});
Vue.component('premiumads', {
data: function () {
return {
ads: [],
fill: [],
}
},
template: '<div>' +
'<div v-for="ad in ads">' +
'<div class="w-100 bg-customized dark-gray flex dib tl pa0 mv3 f5 relative">' +
'<div class="w-100 ba b--light-gray pa0 shadow-hover">' +
'<a rel="ugc sponsored" :href="getLink(ad)" target="_blank" class="link dark-gray pa0 mt3 mb0">' +
'<div v-if="ad.pro" class="absolute-ns tc top-0 right-0 ba b--dark-gray pa1 f6 ma3">Premium</div>' +
'<h3 class="mt4 mb1 pv0 ph3 underline-hover">{{ getTitle(ad) }}</h3>' +
'<small class="pv0 ph3 ma0 underline-hover">{{ getLink(ad) }}</small>' +
'<p class="mt1 mb3 pv0 ph3 underline-hover">{{ getTeaser(ad) }}</p>' +
'</a>' +
'<div class="f5 pv2 ph3 bt dark-gray b--moon-gray w-100 flex">' +
'<div class="tl w-50">' +
'<svg class="icon icon-location"><use xlink:href="#icon-location"></use></svg> {{ ad.city }} ' +
'<span class="pl2"><svg class="icon icon-location"><use xlink:href="#icon-users"></use></svg> {{ ad.size}}</span>' +
'</div>' +
'<div class="tr w-50">Web:' +
'<a class="link dark-gray ph2" rel="ugc sponsored" :href="ad.company_link" target="_blank"><svg class="icon icon-location"><use xlink:href="#icon-home"></use></svg></a>' +
'<a class="link dark-gray ph2" rel="ugc sponsored" v-if="ad.sm_link_1" :href="ad.sm_link_1" target="_blank"><svg :class="getSocialIcon(ad.sm_link_1)" class="icon"><use :xlink:href="getSocialIcon(ad.sm_link_1, true)"></use></svg></a>' +
'<a class="link dark-gray ph2" rel="ugc sponsored" v-if="ad.sm_link_2" :href="ad.sm_link_2" target="_blank"><svg :class="getSocialIcon(ad.sm_link_2)" class="icon"><use :xlink:href="getSocialIcon(ad.sm_link_2, true)"></use></svg></a>' +
'</div>' +
'</div>' +
'</div>' +
'</div>' +
'</div>' +
'<div v-if="ads.length < 3">' +
'<div class="w-100 bg-white dark-gray dib tl pa0 mv3 f5">' +
'<div class="w-100 ba b--light-gray pa0 tc">' +
'<h3 class="mt4 mb0 pv0 ph3">Web-Agentur oder Freelancer?</h3>' +
'<p class="mt3 mb3 pv0 ph3">Dann ist genau hier Ihre Expertise gefragt!<br/> Mit einem Dienstleister-Eintrag auf cmsstash.de</p>' +
'<a href="https://service.cmsstash.de" class="link bg-customized-2 link-white dim ba1 b--red mt0 mb4 ph3 pv2 dib br1">Eintrag erstellen</a>' +
'</div>' +
'</div>' +
'</div>' +
'</div>',
mounted: function(){
self = this;
var access = document.getElementById("hyerapp").dataset.access;
myaxios.get('/proadsfe30s8edw4wdkp?access='+access)
.then(function (response) {
self.ads = response.data.ads;
var restads = 3 - self.ads.length;
for(var i = 0; i < restads; i++)
{
self.fill.push(1);
}
/* if matomo is on, check dom for new links */
if(_paq)
{
Vue.nextTick(function () {
_paq.push(['enableLinkTracking']);
})
}
})
.catch(function (error) {
});
},
methods: {
getTitle: function(product)
{
if(product.ad_title)
{
return product.ad_title;
}
return product.company_name;
},
getTeaser: function(product)
{
if(product.ad_teaser)
{
return product.ad_teaser;
}
return product.company_description;
},
getLink: function(product)
{
if(product.ad_link)
{
return product.ad_link;
}
return product.company_link;
},
getSocialIcon: function(icon,id)
{
var prefix = id ? '#' : '';
if(icon.indexOf("twitter") > -1){ return prefix+'icon-twitter' }
else if(icon.indexOf("facebook") > -1){ return prefix+'icon-facebook' }
else if(icon.indexOf("xing") > -1){ return prefix+'icon-xing2' }
else if(icon.indexOf("linkedin") > -1){ return prefix+'icon-linkedin2' }
return prefix+'icon-link';
},
}
});
var app = new Vue({
el: "#hyerapp",
data: {
disabled: false,
message: '',
messageType: '',
},
});

View File

@ -0,0 +1,51 @@
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" focusable="false" style="display:none">
<symbol id="icon-facebook" viewBox="0 0 16 16">
<title>facebook</title>
<path d="M9.5 3h2.5v-3h-2.5c-1.93 0-3.5 1.57-3.5 3.5v1.5h-2v3h2v8h3v-8h2.5l0.5-3h-3v-1.5c0-0.271 0.229-0.5 0.5-0.5z"></path>
</symbol>
<symbol id="icon-home" viewBox="0 0 16 16">
<title>home</title>
<path d="M16 9.226l-8-6.21-8 6.21v-2.532l8-6.21 8 6.21zM14 9v6h-4v-4h-4v4h-4v-6l6-4.5z"></path>
</symbol>
<symbol id="icon-location" viewBox="0 0 16 16">
<title>location</title>
<path d="M8 0c-2.761 0-5 2.239-5 5 0 5 5 11 5 11s5-6 5-11c0-2.761-2.239-5-5-5zM8 8c-1.657 0-3-1.343-3-3s1.343-3 3-3 3 1.343 3 3-1.343 3-3 3z"></path>
</symbol>
<symbol id="icon-users" viewBox="0 0 18 16">
<title>users</title>
<path d="M12 12.041v-0.825c1.102-0.621 2-2.168 2-3.716 0-2.485 0-4.5-3-4.5s-3 2.015-3 4.5c0 1.548 0.898 3.095 2 3.716v0.825c-3.392 0.277-6 1.944-6 3.959h14c0-2.015-2.608-3.682-6-3.959z"></path>
<path d="M5.112 12.427c0.864-0.565 1.939-0.994 3.122-1.256-0.235-0.278-0.449-0.588-0.633-0.922-0.475-0.863-0.726-1.813-0.726-2.748 0-1.344 0-2.614 0.478-3.653 0.464-1.008 1.299-1.633 2.488-1.867-0.264-1.195-0.968-1.98-2.841-1.98-3 0-3 2.015-3 4.5 0 1.548 0.898 3.095 2 3.716v0.825c-3.392 0.277-6 1.944-6 3.959h4.359c0.227-0.202 0.478-0.393 0.753-0.573z"></path>
</symbol>
<symbol id="icon-twitter" viewBox="0 0 16 16">
<title>twitter</title>
<path d="M16 3.538c-0.588 0.263-1.222 0.438-1.884 0.516 0.678-0.406 1.197-1.050 1.444-1.816-0.634 0.375-1.338 0.65-2.084 0.797-0.6-0.638-1.453-1.034-2.397-1.034-1.813 0-3.281 1.469-3.281 3.281 0 0.256 0.028 0.506 0.084 0.747-2.728-0.138-5.147-1.444-6.766-3.431-0.281 0.484-0.444 1.050-0.444 1.65 0 1.138 0.578 2.144 1.459 2.731-0.538-0.016-1.044-0.166-1.488-0.409 0 0.013 0 0.028 0 0.041 0 1.591 1.131 2.919 2.634 3.219-0.275 0.075-0.566 0.116-0.866 0.116-0.212 0-0.416-0.022-0.619-0.059 0.419 1.303 1.631 2.253 3.066 2.281-1.125 0.881-2.538 1.406-4.078 1.406-0.266 0-0.525-0.016-0.784-0.047 1.456 0.934 3.181 1.475 5.034 1.475 6.037 0 9.341-5.003 9.341-9.341 0-0.144-0.003-0.284-0.009-0.425 0.641-0.459 1.197-1.038 1.637-1.697z"></path>
</symbol>
<symbol id="icon-linkedin2" viewBox="0 0 16 16">
<title>linkedin2</title>
<path d="M6 6h2.767v1.418h0.040c0.385-0.691 1.327-1.418 2.732-1.418 2.921 0 3.461 1.818 3.461 4.183v4.817h-2.885v-4.27c0-1.018-0.021-2.329-1.5-2.329-1.502 0-1.732 1.109-1.732 2.255v4.344h-2.883v-9z"></path>
<path d="M1 6h3v9h-3v-9z"></path>
<path d="M4 3.5c0 0.828-0.672 1.5-1.5 1.5s-1.5-0.672-1.5-1.5c0-0.828 0.672-1.5 1.5-1.5s1.5 0.672 1.5 1.5z"></path>
</symbol>
<symbol id="icon-xing2" viewBox="0 0 16 16">
<title>xing2</title>
<path d="M2.431 3.159c-0.138 0-0.256 0.050-0.316 0.144-0.059 0.1-0.050 0.225 0.013 0.353l1.559 2.7c0.003 0.006 0.003 0.009 0 0.013l-2.45 4.331c-0.063 0.128-0.059 0.256 0 0.353 0.059 0.094 0.163 0.156 0.3 0.156h2.306c0.344 0 0.513-0.234 0.628-0.447 0 0 2.397-4.241 2.491-4.406-0.009-0.016-1.588-2.766-1.588-2.766-0.116-0.203-0.287-0.431-0.644-0.431h-2.3z"></path>
<path d="M12.125 0c-0.344 0-0.494 0.216-0.619 0.441 0 0-4.972 8.816-5.134 9.106 0.009 0.016 3.278 6.016 3.278 6.016 0.116 0.203 0.291 0.441 0.644 0.441h2.306c0.137 0 0.247-0.053 0.306-0.147 0.063-0.1 0.059-0.228-0.006-0.356l-3.25-5.947c-0.003-0.006-0.003-0.009 0-0.016l5.109-9.034c0.063-0.128 0.066-0.256 0.006-0.356-0.059-0.094-0.169-0.147-0.306-0.147h-2.334z"></path>
</symbol>
<symbol id="icon-cart" viewBox="0 0 16 16">
<title>cart</title>
<path d="M6 14.5c0 0.828-0.672 1.5-1.5 1.5s-1.5-0.672-1.5-1.5c0-0.828 0.672-1.5 1.5-1.5s1.5 0.672 1.5 1.5z"></path>
<path d="M16 14.5c0 0.828-0.672 1.5-1.5 1.5s-1.5-0.672-1.5-1.5c0-0.828 0.672-1.5 1.5-1.5s1.5 0.672 1.5 1.5z"></path>
<path d="M16 8v-6h-12c0-0.552-0.448-1-1-1h-3v1h2l0.751 6.438c-0.458 0.367-0.751 0.93-0.751 1.562 0 1.105 0.895 2 2 2h12v-1h-12c-0.552 0-1-0.448-1-1 0-0.003 0-0.007 0-0.010l13-1.99z"></path>
</symbol>
<symbol id="icon-warning" viewBox="0 0 16 16">
<title>warning</title>
<path d="M8 1.45l6.705 13.363h-13.409l6.705-13.363zM8 0c-0.345 0-0.69 0.233-0.951 0.698l-6.829 13.611c-0.523 0.93-0.078 1.691 0.989 1.691h13.583c1.067 0 1.512-0.761 0.989-1.691h0l-6.829-13.611c-0.262-0.465-0.606-0.698-0.951-0.698v0z"></path>
<path d="M9 13c0 0.552-0.448 1-1 1s-1-0.448-1-1c0-0.552 0.448-1 1-1s1 0.448 1 1z"></path>
<path d="M8 11c-0.552 0-1-0.448-1-1v-3c0-0.552 0.448-1 1-1s1 0.448 1 1v3c0 0.552-0.448 1-1 1z"></path>
</symbol>
<symbol id="icon-coin-euro" viewBox="0 0 16 16">
<title>coin-euro</title>
<path d="M7.5 1c-4.142 0-7.5 3.358-7.5 7.5s3.358 7.5 7.5 7.5c4.142 0 7.5-3.358 7.5-7.5s-3.358-7.5-7.5-7.5zM7.5 14.5c-3.314 0-6-2.686-6-6s2.686-6 6-6c3.314 0 6 2.686 6 6s-2.686 6-6 6z"></path>
<path d="M10.482 10.068c-0.239-0.139-0.545-0.058-0.684 0.181-0.27 0.463-0.767 0.751-1.298 0.751h-2c-0.652 0-1.208-0.418-1.414-1h2.414c0.276 0 0.5-0.224 0.5-0.5s-0.224-0.5-0.5-0.5h-2.5v-1h2.5c0.276 0 0.5-0.224 0.5-0.5s-0.224-0.5-0.5-0.5h-2.414c0.206-0.582 0.762-1 1.414-1h2c0.531 0 1.028 0.288 1.298 0.751 0.139 0.239 0.445 0.32 0.684 0.181s0.32-0.445 0.181-0.684c-0.448-0.77-1.277-1.249-2.162-1.249h-2c-1.207 0-2.217 0.86-2.45 2h-0.55c-0.276 0-0.5 0.224-0.5 0.5s0.224 0.5 0.5 0.5h0.5v1h-0.5c-0.276 0-0.5 0.224-0.5 0.5s0.224 0.5 0.5 0.5h0.55c0.232 1.14 1.242 2 2.45 2h2c0.886 0 1.714-0.478 2.162-1.249 0.139-0.239 0.058-0.545-0.181-0.684z"></path>
</symbol>
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -1,47 +1,47 @@
<?php
namespace Plugins\Math;
use \Typemill\Plugin;
class Math extends Plugin
{
protected $settings;
public static function getSubscribedEvents()
{
return array(
'onSettingsLoaded' => 'onSettingsLoaded',
'onTwigLoaded' => 'onTwigLoaded'
);
}
public function onSettingsLoaded($settings)
{
$this->settings = $settings->getData();
}
public function onTwigLoaded()
{
$mathSettings = $this->settings['settings']['plugins']['math'];
if($mathSettings['tool'] == 'mathjax')
{
/* add external CSS and JavaScript */
$this->addJS('https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.4/latest.js?config=TeX-MML-AM_CHTML');
}
if($mathSettings['tool'] == 'katex')
{
$this->addJS('/math/public/katex.min.js');
$this->addJS('/math/public/auto-render.min.js');
$this->addCSS('/math/public/katex.min.css');
/* initialize autorendering of page only in frontend */
if (strpos($this->getPath(), 'tm/content') === false)
{
$this->addInlineJs('renderMathInElement(document.body);');
}
}
}
<?php
namespace Plugins\Math;
use \Typemill\Plugin;
class Math extends Plugin
{
protected $settings;
public static function getSubscribedEvents()
{
return array(
'onSettingsLoaded' => 'onSettingsLoaded',
'onTwigLoaded' => 'onTwigLoaded'
);
}
public function onSettingsLoaded($settings)
{
$this->settings = $settings->getData();
}
public function onTwigLoaded()
{
$mathSettings = $this->settings['settings']['plugins']['math'];
if($mathSettings['tool'] == 'mathjax')
{
/* add external CSS and JavaScript */
$this->addJS('https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.4/latest.js?config=TeX-MML-AM_CHTML');
}
if($mathSettings['tool'] == 'katex')
{
$this->addJS('/math/public/katex.min.js');
$this->addJS('/math/public/auto-render.min.js');
$this->addCSS('/math/public/katex.min.css');
/* initialize autorendering of page only in frontend */
if (strpos($this->getPath(), 'tm/content') === false)
{
$this->addInlineJs('renderMathInElement(document.body);');
}
}
}
}

View File

@ -1,20 +1,20 @@
name: Math
version: 1.0.2
description: Adds support for katex and mathjax.
author: Sebastian Schürmanns
homepage: https://mathjax.org/
licence: Apache 2.0 / MIT
settings:
tool: none
forms:
fields:
tool:
type: radio
label: Choose Your Tool
options:
none: None
katex: KaTex
name: Math
version: 1.0.2
description: Adds support for katex and mathjax.
author: Sebastian Schürmanns
homepage: https://mathjax.org/
licence: Apache 2.0 / MIT
settings:
tool: none
forms:
fields:
tool:
type: radio
label: Choose Your Tool
options:
none: None
katex: KaTex
mathjax: MathJax

View File

@ -1,140 +1,140 @@
# [<img src="https://khan.github.io/KaTeX/katex-logo.svg" width="130" alt="KaTeX">](https://khan.github.io/KaTeX/)
[![Build Status](https://travis-ci.org/Khan/KaTeX.svg?branch=master)](https://travis-ci.org/Khan/KaTeX)
[![codecov](https://codecov.io/gh/Khan/KaTeX/branch/master/graph/badge.svg)](https://codecov.io/gh/Khan/KaTeX)
[![Join the chat at https://gitter.im/Khan/KaTeX](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Khan/KaTeX?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
KaTeX is a fast, easy-to-use JavaScript library for TeX math rendering on the web.
* **Fast:** KaTeX renders its math synchronously and doesn't need to reflow the page. See how it compares to a competitor in [this speed test](http://www.intmath.com/cg5/katex-mathjax-comparison.php).
* **Print quality:** KaTeXs layout is based on Donald Knuths TeX, the gold standard for math typesetting.
* **Self contained:** KaTeX has no dependencies and can easily be bundled with your website resources.
* **Server side rendering:** KaTeX produces the same output regardless of browser or environment, so you can pre-render expressions using Node.js and send them as plain HTML.
KaTeX supports all major browsers, including Chrome, Safari, Firefox, Opera, Edge, and IE 9 - IE 11. More information can be found on the [list of supported commands](https://khan.github.io/KaTeX/function-support.html) and on the [wiki](https://github.com/khan/katex/wiki).
## Usage
You can [download KaTeX](https://github.com/khan/katex/releases) and host it on your server or include the `katex.min.js` and `katex.min.css` files on your page directly from a CDN:
```html
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.9.0-beta1/katex.min.css" integrity="sha384-VEnyslhHLHiYPca9KFkBB3CMeslnM9CzwjxsEbZTeA21JBm7tdLwKoZmCt3cZTYD" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.9.0-beta1/katex.min.js" integrity="sha384-O4hpKqcplNCe+jLuBVEXC10Rn1QEqAmX98lKAIFBEDxZI0a+6Z2w2n8AEtQbR4CD" crossorigin="anonymous"></script>
```
#### In-browser rendering
Call `katex.render` with a TeX expression and a DOM element to render into:
```js
katex.render("c = \\pm\\sqrt{a^2 + b^2}", element);
```
If KaTeX can't parse the expression, it throws a `katex.ParseError` error.
#### Server side rendering or rendering to a string
To generate HTML on the server or to generate an HTML string of the rendered math, you can use `katex.renderToString`:
```js
var html = katex.renderToString("c = \\pm\\sqrt{a^2 + b^2}");
// '<span class="katex">...</span>'
```
Make sure to include the CSS and font files, but there is no need to include the JavaScript. Like `render`, `renderToString` throws if it can't parse the expression.
#### Security
Any HTML generated by KaTeX *should* be safe from `<script>` or other code
injection attacks.
(See `maxSize` below for preventing large width/height visual affronts.)
Of course, it is always a good idea to sanitize the HTML, though you will need
a rather generous whitelist (including some of SVG and MathML) to support
all of KaTeX.
#### Handling errors
If KaTeX encounters an error (invalid or unsupported LaTeX), then it will
throw an exception of type `katex.ParseError`. The message in this error
includes some of the LaTeX source code, so needs to be escaped if you want
to render it to HTML. In particular, you should convert `&`, `<`, `>`
characters to `&amp;`, `&lt;`, `&gt;` (e.g., using `_.escape`)
before including either LaTeX source code or exception messages in your
HTML/DOM. (Failure to escape in this way makes a `<script>` injection
attack possible if your LaTeX source is untrusted.)
#### Rendering options
You can provide an object of options as the last argument to `katex.render` and `katex.renderToString`. Available options are:
- `displayMode`: `boolean`. If `true` the math will be rendered in display mode, which will put the math in display style (so `\int` and `\sum` are large, for example), and will center the math on the page on its own line. If `false` the math will be rendered in inline mode. (default: `false`)
- `throwOnError`: `boolean`. If `true`, KaTeX will throw a `ParseError` when it encounters an unsupported command. If `false`, KaTeX will render the unsupported command as text in the color given by `errorColor`. (default: `true`)
- `errorColor`: `string`. A color string given in the format `"#XXX"` or `"#XXXXXX"`. This option determines the color which unsupported commands are rendered in. (default: `#cc0000`)
- `macros`: `object`. A collection of custom macros. Each macro is a property with a name like `\name` (written `"\\name"` in JavaScript) which maps to a string that describes the expansion of the macro. Single-character keys can also be included in which case the character will be redefined as the given macro (similar to TeX active characters).
- `colorIsTextColor`: `boolean`. If `true`, `\color` will work like LaTeX's `\textcolor`, and take two arguments (e.g., `\color{blue}{hello}`), which restores the old behavior of KaTeX (pre-0.8.0). If `false` (the default), `\color` will work like LaTeX's `\color`, and take one argument (e.g., `\color{blue}hello`). In both cases, `\textcolor` works as in LaTeX (e.g., `\textcolor{blue}{hello}`).
- `maxSize`: `number`. If non-zero, all user-specified sizes, e.g. in `\rule{500em}{500em}`, will be capped to `maxSize` ems. Otherwise, users can make elements and spaces arbitrarily large (the default behavior).
For example:
```js
katex.render("c = \\pm\\sqrt{a^2 + b^2}\\in\\RR", element, {
displayMode: true,
macros: {
"\\RR": "\\mathbb{R}"
}
});
```
#### Automatic rendering of math on a page
Math on the page can be automatically rendered using the auto-render extension. See [the Auto-render README](contrib/auto-render/README.md) for more information.
#### Font size and lengths
By default, KaTeX math is rendered in a 1.21× larger font than the surrounding
context, which makes super- and subscripts easier to read. You can control
this using CSS, for example:
```css
.katex { font-size: 1.1em; }
```
KaTeX supports all TeX units, including absolute units like `cm` and `in`.
Absolute units are currently scaled relative to the default TeX font size of
10pt, so that `\kern1cm` produces the same results as `\kern2.845275em`.
As a result, relative and absolute units are both uniformly scaled relative
to LaTeX with a 10pt font; for example, the rectangle `\rule{1cm}{1em}` has
the same aspect ratio in KaTeX as in LaTeX. However, because most browsers
default to a larger font size, this typically means that a 1cm kern in KaTeX
will appear larger than 1cm in browser units.
### Common Issues
- Many Markdown preprocessors, such as the one that Jekyll and GitHub Pages use,
have a "smart quotes" feature. This changes `'` to `` which is an issue for
math containing primes, e.g. `f'`. This can be worked around by defining a
single character macro which changes them back, e.g. `{"", "'"}`.
- KaTeX follows LaTeX's rendering of `aligned` and `matrix` environments unlike
MathJax. When displaying fractions one above another in these vertical
layouts there may not be enough space between rows for people who are used to
MathJax's rendering. The distance between rows can be adjusted by using
`\\[0.1em]` instead of the standard line separator distance.
- KaTeX does not support the `align` environment because LaTeX doesn't support
`align` in math mode. The `aligned` environment offers the same functionality
but in math mode, so use that instead or define a macro that maps `align` to
`aligned`.
## Libraries
### Angular2+
- [ng-katex](https://github.com/garciparedes/ng-katex) Angular module to write beautiful math expressions with TeX syntax boosted by KaTeX library
### Ruby
- [katex-ruby](https://github.com/glebm/katex-ruby) Provides server-side rendering and integration with popular Ruby web frameworks (Rails, Hanami, and anything that uses Sprockets).
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md)
## License
KaTeX is licensed under the [MIT License](http://opensource.org/licenses/MIT).
# [<img src="https://khan.github.io/KaTeX/katex-logo.svg" width="130" alt="KaTeX">](https://khan.github.io/KaTeX/)
[![Build Status](https://travis-ci.org/Khan/KaTeX.svg?branch=master)](https://travis-ci.org/Khan/KaTeX)
[![codecov](https://codecov.io/gh/Khan/KaTeX/branch/master/graph/badge.svg)](https://codecov.io/gh/Khan/KaTeX)
[![Join the chat at https://gitter.im/Khan/KaTeX](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Khan/KaTeX?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
KaTeX is a fast, easy-to-use JavaScript library for TeX math rendering on the web.
* **Fast:** KaTeX renders its math synchronously and doesn't need to reflow the page. See how it compares to a competitor in [this speed test](http://www.intmath.com/cg5/katex-mathjax-comparison.php).
* **Print quality:** KaTeXs layout is based on Donald Knuths TeX, the gold standard for math typesetting.
* **Self contained:** KaTeX has no dependencies and can easily be bundled with your website resources.
* **Server side rendering:** KaTeX produces the same output regardless of browser or environment, so you can pre-render expressions using Node.js and send them as plain HTML.
KaTeX supports all major browsers, including Chrome, Safari, Firefox, Opera, Edge, and IE 9 - IE 11. More information can be found on the [list of supported commands](https://khan.github.io/KaTeX/function-support.html) and on the [wiki](https://github.com/khan/katex/wiki).
## Usage
You can [download KaTeX](https://github.com/khan/katex/releases) and host it on your server or include the `katex.min.js` and `katex.min.css` files on your page directly from a CDN:
```html
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.9.0-beta1/katex.min.css" integrity="sha384-VEnyslhHLHiYPca9KFkBB3CMeslnM9CzwjxsEbZTeA21JBm7tdLwKoZmCt3cZTYD" crossorigin="anonymous">
<script src="https://cdnjs.cloudflare.com/ajax/libs/KaTeX/0.9.0-beta1/katex.min.js" integrity="sha384-O4hpKqcplNCe+jLuBVEXC10Rn1QEqAmX98lKAIFBEDxZI0a+6Z2w2n8AEtQbR4CD" crossorigin="anonymous"></script>
```
#### In-browser rendering
Call `katex.render` with a TeX expression and a DOM element to render into:
```js
katex.render("c = \\pm\\sqrt{a^2 + b^2}", element);
```
If KaTeX can't parse the expression, it throws a `katex.ParseError` error.
#### Server side rendering or rendering to a string
To generate HTML on the server or to generate an HTML string of the rendered math, you can use `katex.renderToString`:
```js
var html = katex.renderToString("c = \\pm\\sqrt{a^2 + b^2}");
// '<span class="katex">...</span>'
```
Make sure to include the CSS and font files, but there is no need to include the JavaScript. Like `render`, `renderToString` throws if it can't parse the expression.
#### Security
Any HTML generated by KaTeX *should* be safe from `<script>` or other code
injection attacks.
(See `maxSize` below for preventing large width/height visual affronts.)
Of course, it is always a good idea to sanitize the HTML, though you will need
a rather generous whitelist (including some of SVG and MathML) to support
all of KaTeX.
#### Handling errors
If KaTeX encounters an error (invalid or unsupported LaTeX), then it will
throw an exception of type `katex.ParseError`. The message in this error
includes some of the LaTeX source code, so needs to be escaped if you want
to render it to HTML. In particular, you should convert `&`, `<`, `>`
characters to `&amp;`, `&lt;`, `&gt;` (e.g., using `_.escape`)
before including either LaTeX source code or exception messages in your
HTML/DOM. (Failure to escape in this way makes a `<script>` injection
attack possible if your LaTeX source is untrusted.)
#### Rendering options
You can provide an object of options as the last argument to `katex.render` and `katex.renderToString`. Available options are:
- `displayMode`: `boolean`. If `true` the math will be rendered in display mode, which will put the math in display style (so `\int` and `\sum` are large, for example), and will center the math on the page on its own line. If `false` the math will be rendered in inline mode. (default: `false`)
- `throwOnError`: `boolean`. If `true`, KaTeX will throw a `ParseError` when it encounters an unsupported command. If `false`, KaTeX will render the unsupported command as text in the color given by `errorColor`. (default: `true`)
- `errorColor`: `string`. A color string given in the format `"#XXX"` or `"#XXXXXX"`. This option determines the color which unsupported commands are rendered in. (default: `#cc0000`)
- `macros`: `object`. A collection of custom macros. Each macro is a property with a name like `\name` (written `"\\name"` in JavaScript) which maps to a string that describes the expansion of the macro. Single-character keys can also be included in which case the character will be redefined as the given macro (similar to TeX active characters).
- `colorIsTextColor`: `boolean`. If `true`, `\color` will work like LaTeX's `\textcolor`, and take two arguments (e.g., `\color{blue}{hello}`), which restores the old behavior of KaTeX (pre-0.8.0). If `false` (the default), `\color` will work like LaTeX's `\color`, and take one argument (e.g., `\color{blue}hello`). In both cases, `\textcolor` works as in LaTeX (e.g., `\textcolor{blue}{hello}`).
- `maxSize`: `number`. If non-zero, all user-specified sizes, e.g. in `\rule{500em}{500em}`, will be capped to `maxSize` ems. Otherwise, users can make elements and spaces arbitrarily large (the default behavior).
For example:
```js
katex.render("c = \\pm\\sqrt{a^2 + b^2}\\in\\RR", element, {
displayMode: true,
macros: {
"\\RR": "\\mathbb{R}"
}
});
```
#### Automatic rendering of math on a page
Math on the page can be automatically rendered using the auto-render extension. See [the Auto-render README](contrib/auto-render/README.md) for more information.
#### Font size and lengths
By default, KaTeX math is rendered in a 1.21× larger font than the surrounding
context, which makes super- and subscripts easier to read. You can control
this using CSS, for example:
```css
.katex { font-size: 1.1em; }
```
KaTeX supports all TeX units, including absolute units like `cm` and `in`.
Absolute units are currently scaled relative to the default TeX font size of
10pt, so that `\kern1cm` produces the same results as `\kern2.845275em`.
As a result, relative and absolute units are both uniformly scaled relative
to LaTeX with a 10pt font; for example, the rectangle `\rule{1cm}{1em}` has
the same aspect ratio in KaTeX as in LaTeX. However, because most browsers
default to a larger font size, this typically means that a 1cm kern in KaTeX
will appear larger than 1cm in browser units.
### Common Issues
- Many Markdown preprocessors, such as the one that Jekyll and GitHub Pages use,
have a "smart quotes" feature. This changes `'` to `` which is an issue for
math containing primes, e.g. `f'`. This can be worked around by defining a
single character macro which changes them back, e.g. `{"", "'"}`.
- KaTeX follows LaTeX's rendering of `aligned` and `matrix` environments unlike
MathJax. When displaying fractions one above another in these vertical
layouts there may not be enough space between rows for people who are used to
MathJax's rendering. The distance between rows can be adjusted by using
`\\[0.1em]` instead of the standard line separator distance.
- KaTeX does not support the `align` environment because LaTeX doesn't support
`align` in math mode. The `aligned` environment offers the same functionality
but in math mode, so use that instead or define a macro that maps `align` to
`aligned`.
## Libraries
### Angular2+
- [ng-katex](https://github.com/garciparedes/ng-katex) Angular module to write beautiful math expressions with TeX syntax boosted by KaTeX library
### Ruby
- [katex-ruby](https://github.com/glebm/katex-ruby) Provides server-side rendering and integration with popular Ruby web frameworks (Rails, Hanami, and anything that uses Sprockets).
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md)
## License
KaTeX is licensed under the [MIT License](http://opensource.org/licenses/MIT).

87
plugins/search/index.php Normal file
View File

@ -0,0 +1,87 @@
<?php
namespace Plugins\search;
use \Typemill\Plugin;
use \Typemill\Models\Write;
use \Typemill\Models\WriteCache;
class Index extends Plugin
{
public static function getSubscribedEvents(){}
public function index()
{
$write = new Write();
$index = $write->getFile('cache', 'index.json');
if(!$index)
{
$this->createIndex();
$index = $write->getFile('cache', 'index.json');
}
return $this->returnJson($index);
}
private function createIndex()
{
$write = new WriteCache();
# get content structure
$structure = $write->getCache('cache', 'structure.txt');
# get data for search-index
$index = $this->getAllContent($structure, $write);
# store the index file here
$write->writeFile('cache', 'index.json', json_encode($index, JSON_UNESCAPED_SLASHES));
}
private function getAllContent($structure, $write, $index = NULL)
{
foreach($structure as $item)
{
if($item->elementType == "folder")
{
if($item->fileType == 'md')
{
$page = $write->getFileWithPath('content' . $item->path . DIRECTORY_SEPARATOR . 'index.md');
$pageArray = $this->getPageContentArray($page, $item->urlAbs);
$index[$pageArray['url']] = $pageArray;
}
$index = $this->getAllContent($item->folderContent, $write, $index);
}
else
{
$page = $write->getFileWithPath('content' . $item->path);
$pageArray = $this->getPageContentArray($page, $item->urlAbs);
$index[$pageArray['url']] = $pageArray;
}
}
return $index;
}
private function getPageContentArray($page, $url)
{
$parts = explode("\n", $page, 2);
# get the title / headline
$title = trim($parts[0], '# ');
$title = str_replace(["\r\n", "\n", "\r"],' ', $title);
# get and cleanup the content
$content = $parts[1];
$content = strip_tags($content);
$content = str_replace(["\r\n", "\n", "\r"],' ', $content);
$pageContent = [
'title' => $title,
'content' => $content,
'url' => $url
];
return $pageContent;
}
}

View File

@ -0,0 +1,19 @@
Copyright (C) 2013 by Oliver Nightingale
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

6
plugins/search/public/lunr.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,64 @@
.searchContainer{
overflow: hidden;
width: 100%;
vertical-align: middle;
white-space: nowrap;
}
.searchContainer input{
width: 100%;
height: 50px;
border: 1px solid #ddd;
font-size: 1rem;
float: left;
padding-left: 15px;
border-radius: 2px;
box-sizing:border-box;
}
.searchContainer button{
border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
border: none;
background: #232833;
height: 50px;
width: 50px;
color: #fff;
font-size: 10pt;
margin-left: -50px;
}
.searchContainer button:hover,.searchContainer button:focus, .searchContainer button:active{
cursor: pointer;
}
#searchresult{
}
.resultwrapper{
}
button#closeSearchResult{
position: absolute;
right: 0px;
top: 0px;
margin: 10px;
border: none;
border-radius: 2px;
font-size: 1rem;
color: #fff;
background: #000;
padding: 15px;
}
button#closeSearchResult:hover,#closeSearchResult:focus{
cursor: pointer;
}
.resultlist{
margin: 0px;
padding: 0px;
list-style:none;
}
.resultitem{
}
.resultheader{
}
.resultsnippet{
}

View File

@ -0,0 +1,115 @@
var searchField = document.getElementById("searchField");
var searchButton = document.getElementById("searchButton");
if(searchField && searchButton)
{
var searchIndex = false;
var documents = false;
var holdcontent = false;
var contentwrapper = false;
searchField.addEventListener("focus", function(event){
if(!searchIndex)
{
myaxios.get('/indexrs51gfe2o2')
.then(function (response) {
documents = JSON.parse(response.data);
searchIndex = lunr(function() {
this.ref("id");
this.field("title", { boost: 10 });
this.field("content");
for (var key in documents){
this.add({
"id": documents[key].url,
"title": documents[key].title,
"content": documents[key].content
});
}
});
})
.catch(function (error) {});
}
});
searchButton.addEventListener("click", function(event){
event.preventDefault();
var term = document.getElementById('searchField').value;
var results = searchIndex.search(term);
var resultPages = results.map(function (match) {
return documents[match.ref];
});
resultsString = "<div class='resultwrapper'><h1>Result for " + term + "</h1>";
resultsString += "<button id='closeSearchResult'>close</button>";
resultsString += "<ul class='resultlist'>";
resultPages.forEach(function (r) {
resultsString += "<li class='resultitem'>";
resultsString += "<a class='resultheader' href='" + r.url + "?q=" + term + "'><h3>" + r.title + "</h3></a>";
resultsString += "<div class='resultsnippet'>" + r.content.substring(0, 200) + " ...</div>";
resultsString += "</li>"
});
resultsString += "</ul></div>";
if(!holdcontent)
{
contentwrapper = document.getElementById("searchresult").parentNode;
holdcontent = contentwrapper.innerHTML;
}
contentwrapper.innerHTML = resultsString;
document.getElementById("closeSearchResult").addEventListener("click", function(event){
contentwrapper.innerHTML = holdcontent;
});
}, false);
}
/*
var searchIndex = lunr(function() {
this.ref("id");
this.field("title", { boost: 10 });
this.field("content");
for (var key in window.pages) {
this.add({
"id": key,
"title": pages[key].title,
"content": pages[key].content
});
}
});
function getQueryVariable(variable) {
var query = window.location.search.substring(1);
var vars = query.split("&");
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split("=");
if (pair[0] === variable) {
return decodeURIComponent(pair[1].replace(/\+/g, "%20"));
}
}
}
var searchTerm = getQueryVariable("q");
// creation of searchIndex from earlier example
var results = searchIndex.search(searchTerm);
var resultPages = results.map(function (match) {
return pages[match.ref];
});
// resultPages from previous example
resultsString = "";
resultPages.forEach(function (r) {
resultsString += "<li>";
resultsString += "<a class='result' href='" + r.url + "?q=" + searchTerm + "'><h3>" + r.title + "</h3></a>";
resultsString += "<div class='snippet'>" + r.content.substring(0, 200) + "</div>";
resultsString += "</li>"
});
document.querySelector("#search-results").innerHTML = resultsString;
*/

128
plugins/search/search.php Normal file
View File

@ -0,0 +1,128 @@
<?php
namespace Plugins\search;
use \Typemill\Plugin;
use \Typemill\Models\Write;
class Search extends index
{
protected $item;
public static function getSubscribedEvents()
{
return array(
'onSettingsLoaded' => 'onsettingsLoaded',
'onContentArrayLoaded' => 'onContentArrayLoaded',
'onPageReady' => 'onPageReady',
'onPagePublished' => 'onPagePublished',
'onPageUnpublished' => 'onPageUnpublished',
'onPageSorted' => 'onPageSorted',
'onPageDeleted' => 'onPageDeleted',
);
}
# get search.json with route
# update search.json on publish
public static function addNewRoutes()
{
# the route for the api calls
return array(
array(
'httpMethod' => 'get',
'route' => '/indexrs51gfe2o2',
'class' => 'Plugins\search\index:index'
),
);
}
public function onSettingsLoaded($settings)
{
$this->settings = $settings->getData();
}
# at any of theses events, delete the old search index
public function onPagePublished($item)
{
$this->deleteSearchIndex();
}
public function onPageUnpublished($item)
{
$this->deleteSearchIndex();
}
public function onPageSorted($inputParams)
{
$this->deleteSearchIndex();
}
public function onPageDeleted($item)
{
$this->deleteSearchIndex();
}
private function deleteSearchIndex()
{
$write = new Write();
# store the index file here
$write->deleteFileWithPath('cache' . DIRECTORY_SEPARATOR . 'index.json');
}
public function onContentArrayLoaded($contentArray)
{
# get content array
$content = $contentArray->getData();
$settings = $this->getPluginSettings('search');
$salt = "asPx9Derf2";
# activate axios and vue in frontend
$this->activateAxios();
$this->activateVue();
# add the css and vue application
$this->addCSS('/search/public/search.css');
$this->addJS('/search/public/lunr.min.js');
$this->addJS('/search/public/search.js');
# simple security for first request
$secret = time();
$secret = substr($secret,0,-1);
$secret = md5($secret . $salt);
# simple csrf protection with a session for long following requests
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
$length = 32;
$token = substr(base_convert(sha1(uniqid(mt_rand())), 16, 36), 0, $length);
$_SESSION['search'] = $token;
$_SESSION['search-expire'] = time() + 1300; # 60 seconds * 30 minutes
# create div for vue app
$search = '<div data-access="' . $secret . '" data-token="' . $token . '" id="searchresult"></div>';
# create content type
$search = Array
(
'rawHtml' => $search,
'allowRawHtmlInSafeMode' => true,
'autobreak' => 1
);
$content[] = $search;
$contentArray->setData($content);
}
public function onPageReady($page)
{
$pageData = $page->getData($page);
$pageData['widgets']['search'] = '<div class="searchContainer" id="searchForm">'.
'<input id="searchField" type="text" placeholder="search ..." />'.
'<button id="searchButton" type="button">GO</button>'.
'</div>';
$page->setData($pageData);
}
}

View File

@ -0,0 +1,6 @@
name: Search
version: 1.0.0
description: Adds a search to typemill with lunr.js.
author: Sebastian Schürmanns
homepage: https://typemill.net
licence: MIT

206
readme.md
View File

@ -1,104 +1,104 @@
# About TYPEMILL
TYPEMILL is a small flat file cms created for editors and writers. It provides a author-friendly dashboard and a visual-block-editor for markdown based on vue.js. Use TYPEMILL for manuals, documentations, web-books and similar publications. The website http://typemill.net itself is an example for TYPEMILL.
![TYPEMILL Screenshot](https://typemill.net/media/tm-toc.gif)
## Features
* Creates a website based on markdown files.
* Provides an author-friendly visual markdown editor (work in progress, based on VUE.js).
* Provides a pure markdown editing mode.
* Markdown supports table of contents (TOC), tables, footnotes, abbreviations and definition lists.
* Create and sort pages with drag & drop in the navigation.
* Configure the system, the themes and the plugins in the dashboard.
* Create and manage users.
* Develop configurable plugins with the Symfony Event Dispatcher.
* Develop configurable themes with TWIG.
* Allows super easy backend and frontend forms with simple YAML-files.
* Ships with a fully responsive standard theme
* Ships with plugins for
* MathJax and KaTeX.
* Code highlighting.
* Matomo/Piwik and Google Analytics.
* Cookie Consent.
## Requirements
* PHP 7+
* Apache server
* mod_rewrite and htaccess
If you run a linux system, then please double check that mod_rewrite and htaccess are active!!!
## Installation
Download TYPEMILL from the [TYPEMILL website](http://typemill.net), unzip the files and you are done.
If you are a developer, you can also clone this repository. To do so, open your command line, go to your project folder (e.g. htdocs) and type:
git clone git://github.com/trendschau/typemill.git
The GitHub-version has no vendor-folder, so you have to update and include all libraries and dependencies with composer. To do so, open your command line, go to your TYPEMILL folder and type:
composer update
If you did not use composer before, please go to the [composer website](http://getcomposer.org) and start to learn.
To run TYPEMILL on a **live** system, simply upload the files to your server
## Make Folders Writable.
Make sure that the following folders and all their files are writable (permission 774 recursively):
* cache
* content
* media
* settings
You can use your ftp-software for that.
## Setup
Please go to `your-typemill-website.com/setup`, create an initial user and then setup your system in the author panel.
## Login
You can find your login screen under `/tm/login` or simply go to `/setup` and you will be redirected to the login-page.
## Documentation
You can read the full documentation for writers, for theme developers and for plugin developers on the [TYPEMILL website](http://typemill.net).
## Support
This is an open source project. I love it and I spend about 20 hours a week on it (starting in 2017). There is no business model right now, but you can hire me for implementation or simply support this project if you like.
Donate: https://www.paypal.me/typemill
## Contribute
Typemill is still in an early stage and contributions are highly welcome. Here are some ideas for non-coder:
* Find bugs and errors (open a new issue on github for it).
* Improve the documentation.
* Describe some missing features and explain, why they are important for other users.
* Write a blog post about typemill.
Some ideas for devs (please fork this repository make your changes and create a pull request):
* Fix a bug.
* Create a nice theme.
* Create a new plugin.
* Improve the CSS-code with BEM or utility-css (e.g. Tailwind) and make it modular.
* Rebuild the theme with the new css-grid feature.
* Improve the accessibility of html and css.
* Help to establish autotests with selenium or cypress.
* Write unit-tests.
* Write an auto-update functionality.
For hints, questions, problems and support, please open up a new issue on GitHub.
## Licence
# About TYPEMILL
TYPEMILL is a small flat file cms created for editors and writers. It provides a author-friendly dashboard and a visual-block-editor for markdown based on vue.js. Use TYPEMILL for manuals, documentations, web-books and similar publications. The website http://typemill.net itself is an example for TYPEMILL.
![TYPEMILL Screenshot](https://typemill.net/media/tm-toc.gif)
## Features
* Creates a website based on markdown files.
* Provides an author-friendly visual markdown editor (work in progress, based on VUE.js).
* Provides a pure markdown editing mode.
* Markdown supports table of contents (TOC), tables, footnotes, abbreviations and definition lists.
* Create and sort pages with drag & drop in the navigation.
* Configure the system, the themes and the plugins in the dashboard.
* Create and manage users.
* Develop configurable plugins with the Symfony Event Dispatcher.
* Develop configurable themes with TWIG.
* Allows super easy backend and frontend forms with simple YAML-files.
* Ships with a fully responsive standard theme
* Ships with plugins for
* MathJax and KaTeX.
* Code highlighting.
* Matomo/Piwik and Google Analytics.
* Cookie Consent.
## Requirements
* PHP 7+
* Apache server
* mod_rewrite and htaccess
If you run a linux system, then please double check that mod_rewrite and htaccess are active!!!
## Installation
Download TYPEMILL from the [TYPEMILL website](http://typemill.net), unzip the files and you are done.
If you are a developer, you can also clone this repository. To do so, open your command line, go to your project folder (e.g. htdocs) and type:
git clone git://github.com/trendschau/typemill.git
The GitHub-version has no vendor-folder, so you have to update and include all libraries and dependencies with composer. To do so, open your command line, go to your TYPEMILL folder and type:
composer update
If you did not use composer before, please go to the [composer website](http://getcomposer.org) and start to learn.
To run TYPEMILL on a **live** system, simply upload the files to your server
## Make Folders Writable.
Make sure that the following folders and all their files are writable (permission 774 recursively):
* cache
* content
* media
* settings
You can use your ftp-software for that.
## Setup
Please go to `your-typemill-website.com/setup`, create an initial user and then setup your system in the author panel.
## Login
You can find your login screen under `/tm/login` or simply go to `/setup` and you will be redirected to the login-page.
## Documentation
You can read the full documentation for writers, for theme developers and for plugin developers on the [TYPEMILL website](http://typemill.net).
## Support
This is an open source project. I love it and I spend about 20 hours a week on it (starting in 2017). There is no business model right now, but you can hire me for implementation or simply support this project if you like.
Donate: https://www.paypal.me/typemill
## Contribute
Typemill is still in an early stage and contributions are highly welcome. Here are some ideas for non-coder:
* Find bugs and errors (open a new issue on github for it).
* Improve the documentation.
* Describe some missing features and explain, why they are important for other users.
* Write a blog post about typemill.
Some ideas for devs (please fork this repository make your changes and create a pull request):
* Fix a bug.
* Create a nice theme.
* Create a new plugin.
* Improve the CSS-code with BEM or utility-css (e.g. Tailwind) and make it modular.
* Rebuild the theme with the new css-grid feature.
* Improve the accessibility of html and css.
* Help to establish autotests with selenium or cypress.
* Write unit-tests.
* Write an auto-update functionality.
For hints, questions, problems and support, please open up a new issue on GitHub.
## Licence
TYPEMILL is published under MIT licence. Please check the licence of the included libraries, too.

View File

@ -1,81 +1,111 @@
<?php
namespace Typemill;
class Assets
{
protected $baseUrl;
public function __construct($baseUrl)
{
$this->baseUrl = $baseUrl;
$this->JS = array();
$this->CSS = array();
$this->inlineJS = array();
$this->inlineCSS = array();
}
public function addCSS($CSS)
{
$CSSfile = $this->getFileUrl($CSS);
if($CSSfile)
{
$this->CSS[] = '<link rel="stylesheet" href="' . $CSSfile . '" />';
}
}
public function addInlineCSS($CSS)
{
$this->inlineCSS[] = '<style>' . $CSS . '</style>';
}
public function addJS($JS)
{
$JSfile = $this->getFileUrl($JS);
if($JSfile)
{
$this->JS[] = '<script src="' . $JSfile . '"></script>';
}
}
public function addInlineJS($JS)
{
$this->inlineJS[] = '<script>' . $JS . '</script>';
}
public function renderCSS()
{
return implode('', $this->CSS) . implode('', $this->inlineCSS);
}
public function renderJS()
{
return implode('', $this->JS) . implode('', $this->inlineJS);
}
/**
* Checks, if a string is a valid internal or external ressource like js-file or css-file
* @params $path string
* @return string or false
*/
public function getFileUrl($path)
{
$internalFile = __DIR__ . '/../plugins' . $path;
if(file_exists($internalFile))
{
return $this->baseUrl . '/plugins' . $path;
}
return $path;
if(fopen($path, "r"))
{
return $path;
}
return false;
}
<?php
namespace Typemill;
class Assets
{
protected $baseUrl;
public function __construct($baseUrl)
{
$this->baseUrl = $baseUrl;
$this->JS = array();
$this->CSS = array();
$this->inlineJS = array();
$this->inlineCSS = array();
}
public function addCSS($CSS)
{
$CSSfile = $this->getFileUrl($CSS);
if($CSSfile)
{
$this->CSS[] = '<link rel="stylesheet" href="' . $CSSfile . '" />';
}
}
public function addInlineCSS($CSS)
{
$this->inlineCSS[] = '<style>' . $CSS . '</style>';
}
public function addJS($JS)
{
$JSfile = $this->getFileUrl($JS);
if($JSfile)
{
$this->JS[] = '<script src="' . $JSfile . '"></script>';
}
}
public function addInlineJS($JS)
{
$this->inlineJS[] = '<script>' . $JS . '</script>';
}
public function activateVue()
{
$vueUrl = '<script src="' . $this->baseUrl . '/system/author/js/vue.min.js"></script>';
if(!in_array($vueUrl, $this->JS))
{
$this->JS[] = $vueUrl;
}
}
public function activateAxios()
{
$axiosUrl = '<script src="' . $this->baseUrl . '/system/author/js/axios.min.js"></script>';
if(!in_array($axiosUrl, $this->JS))
{
$this->JS[] = $axiosUrl;
$axios = '<script>const myaxios = axios.create({ baseURL: \'' . $this->baseUrl . '\' });</script>';
$this->JS[] = $axios;
}
}
public function activateTachyons()
{
$tachyonsUrl = '<link rel="stylesheet" href="' . $this->baseUrl . '/system/author/css/tachyons.min.css" />';
if(!in_array($tachyonsUrl, $this->CSS))
{
$this->CSS[] = $tachyonsUrl;
}
}
public function renderCSS()
{
return implode('', $this->CSS) . implode('', $this->inlineCSS);
}
public function renderJS()
{
return implode('', $this->JS) . implode('', $this->inlineJS);
}
/**
* Checks, if a string is a valid internal or external ressource like js-file or css-file
* @params $path string
* @return string or false
*/
public function getFileUrl($path)
{
$internalFile = __DIR__ . '/../plugins' . $path;
if(file_exists($internalFile))
{
return $this->baseUrl . '/plugins' . $path;
}
return $path;
if(fopen($path, "r"))
{
return $path;
}
return false;
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,160 +1,163 @@
<?php
namespace Typemill\Controllers;
use Slim\Http\Request;
use Slim\Http\Response;
use Slim\Views\Twig;
use Typemill\Models\Folder;
use Typemill\Extensions\ParsedownExtension;
class ContentBackendController extends ContentController
{
/**
* Show Content for raw editor
*
* @param obj $request the slim request object
* @param obj $response the slim response object
* @return obje $response with redirect to route
*/
public function showContent(Request $request, Response $response, $args)
{
# get params from call
$this->uri = $request->getUri();
$this->params = isset($args['params']) ? ['url' => $this->uri->getBasePath() . '/' . $args['params']] : ['url' => $this->uri->getBasePath()];
# set structure
if(!$this->setStructure($draft = true)){ return $this->renderIntern404($response, array( 'navigation' => true, 'content' => $this->errors )); }
# set information for homepage
$this->setHomepage();
# set item
if(!$this->setItem()){ return $this->renderIntern404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); }
# get the breadcrumb (here we need it only to mark the actual item active in navigation)
$breadcrumb = isset($this->item->keyPathArray) ? Folder::getBreadcrumb($this->structure, $this->item->keyPathArray) : false;
# set the status for published and drafted
$this->setPublishStatus();
# set path
$this->setItemPath($this->item->fileType);
# add the modified date for the file
$this->item->modified = ($this->item->published OR $this->item->drafted) ? filemtime($this->settings['contentFolder'] . $this->path) : false;
# read content from file
if(!$this->setContent()){ return $this->renderIntern404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); }
$content = $this->content;
$title = false;
# if content is an array, then it is a draft
if(is_array($content))
{
# transform array to markdown
$parsedown = new ParsedownExtension();
$content = $parsedown->arrayBlocksToMarkdown($content);
}
# if there is content
if($content != '')
{
# normalize linebreaks
$content = str_replace(array("\r\n", "\r"), "\n", $content);
$content = trim($content, "\n");
# and strip out title
if($content[0] == '#')
{
$contentParts = explode("\n", $content, 2);
$title = trim($contentParts[0], "# \t\n\r\0\x0B");
$content = trim($contentParts[1]);
}
}
return $this->render($response, 'editor/editor-raw.twig', array('navigation' => $this->structure, 'homepage' => $this->homepage, 'title' => $title, 'content' => $content, 'item' => $this->item, 'settings' => $this->settings ));
}
/**
* Show Content for blox editor
*
* @param obj $request the slim request object
* @param obj $response the slim response object
* @return obje $response with redirect to route
*/
public function showBlox(Request $request, Response $response, $args)
{
# get params from call
$this->uri = $request->getUri();
$this->params = isset($args['params']) ? ['url' => $this->uri->getBasePath() . '/' . $args['params']] : ['url' => $this->uri->getBasePath()];
# set structure
if(!$this->setStructure($draft = true)){ return $this->renderIntern404($response, array( 'navigation' => true, 'content' => $this->errors )); }
# set information for homepage
$this->setHomepage();
# set item
if(!$this->setItem()){ return $this->renderIntern404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); }
# set the status for published and drafted
$this->setPublishStatus();
# set path
$this->setItemPath($this->item->fileType);
# add the modified date for the file
$this->item->modified = ($this->item->published OR $this->item->drafted) ? filemtime($this->settings['contentFolder'] . $this->path) : false;
# read content from file
if(!$this->setContent()){ return $this->renderIntern404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); }
$content = $this->content;
if($content == '')
{
$content = [];
}
# initialize parsedown extension
$parsedown = new ParsedownExtension();
# if content is not an array, then transform it
if(!is_array($content))
{
# turn markdown into an array of markdown-blocks
$content = $parsedown->markdownToArrayBlocks($content);
}
# needed for ToC links
$relurl = '/tm/content/' . $this->settings['editor'] . '/' . $this->item->urlRel;
foreach($content as $key => $block)
{
/* parse markdown-file to content-array */
$contentArray = $parsedown->text($block);
/* parse markdown-content-array to content-string */
$content[$key] = $parsedown->markup($contentArray, $relurl);
}
# extract title and delete from content array, array will start at 1 after that.
$title = '# add title';
if(isset($content[0]))
{
$title = $content[0];
unset($content[0]);
}
return $this->render($response, 'editor/editor-blox.twig', array('navigation' => $this->structure, 'homepage' => $this->homepage, 'title' => $title, 'content' => $content, 'item' => $this->item, 'settings' => $this->settings ));
}
public function showEmpty(Request $request, Response $response, $args)
{
return $this->renderIntern404($response, array( 'settings' => $this->settings ));
}
<?php
namespace Typemill\Controllers;
use Slim\Http\Request;
use Slim\Http\Response;
use Slim\Views\Twig;
use Typemill\Models\Folder;
use Typemill\Extensions\ParsedownExtension;
class ContentBackendController extends ContentController
{
/**
* Show Content for raw editor
*
* @param obj $request the slim request object
* @param obj $response the slim response object
* @return obje $response with redirect to route
*/
public function showContent(Request $request, Response $response, $args)
{
# get params from call
$this->uri = $request->getUri();
$this->params = isset($args['params']) ? ['url' => $this->uri->getBasePath() . '/' . $args['params']] : ['url' => $this->uri->getBasePath()];
# set structure
if(!$this->setStructure($draft = true)){ return $this->renderIntern404($response, array( 'navigation' => true, 'content' => $this->errors )); }
# set information for homepage
$this->setHomepage();
# set item
if(!$this->setItem()){ return $this->renderIntern404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); }
# get the breadcrumb (here we need it only to mark the actual item active in navigation)
$breadcrumb = isset($this->item->keyPathArray) ? Folder::getBreadcrumb($this->structure, $this->item->keyPathArray) : false;
# set the status for published and drafted
$this->setPublishStatus();
# set path
$this->setItemPath($this->item->fileType);
# add the modified date for the file
$this->item->modified = ($this->item->published OR $this->item->drafted) ? filemtime($this->settings['contentFolder'] . $this->path) : false;
# read content from file
if(!$this->setContent()){ return $this->renderIntern404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); }
$content = $this->content;
$title = false;
# if content is an array, then it is a draft
if(is_array($content))
{
# transform array to markdown
$parsedown = new ParsedownExtension();
$content = $parsedown->arrayBlocksToMarkdown($content);
}
# if there is content
if($content != '')
{
# normalize linebreaks
$content = str_replace(array("\r\n", "\r"), "\n", $content);
$content = trim($content, "\n");
# and strip out title
if($content[0] == '#')
{
$contentParts = explode("\n", $content, 2);
$title = trim($contentParts[0], "# \t\n\r\0\x0B");
$content = trim($contentParts[1]);
}
}
return $this->render($response, 'editor/editor-raw.twig', array('navigation' => $this->structure, 'homepage' => $this->homepage, 'title' => $title, 'content' => $content, 'item' => $this->item, 'settings' => $this->settings ));
}
/**
* Show Content for blox editor
*
* @param obj $request the slim request object
* @param obj $response the slim response object
* @return obje $response with redirect to route
*/
public function showBlox(Request $request, Response $response, $args)
{
# get params from call
$this->uri = $request->getUri();
$this->params = isset($args['params']) ? ['url' => $this->uri->getBasePath() . '/' . $args['params']] : ['url' => $this->uri->getBasePath()];
# set structure
if(!$this->setStructure($draft = true)){ return $this->renderIntern404($response, array( 'navigation' => true, 'content' => $this->errors )); }
# set information for homepage
$this->setHomepage();
# set item
if(!$this->setItem()){ return $this->renderIntern404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); }
# set the status for published and drafted
$this->setPublishStatus();
# set path
$this->setItemPath($this->item->fileType);
# add the modified date for the file
$this->item->modified = ($this->item->published OR $this->item->drafted) ? filemtime($this->settings['contentFolder'] . $this->path) : false;
# read content from file
if(!$this->setContent()){ return $this->renderIntern404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); }
$content = $this->content;
if($content == '')
{
$content = [];
}
# initialize parsedown extension
$parsedown = new ParsedownExtension();
# to fix footnote-logic in parsedown, set visual mode to true
$parsedown->setVisualMode();
# if content is not an array, then transform it
if(!is_array($content))
{
# turn markdown into an array of markdown-blocks
$content = $parsedown->markdownToArrayBlocks($content);
}
# needed for ToC links
$relurl = '/tm/content/' . $this->settings['editor'] . '/' . $this->item->urlRel;
foreach($content as $key => $block)
{
/* parse markdown-file to content-array */
$contentArray = $parsedown->text($block);
/* parse markdown-content-array to content-string */
$content[$key] = $parsedown->markup($contentArray, $relurl);
}
# extract title and delete from content array, array will start at 1 after that.
$title = '# add title';
if(isset($content[0]))
{
$title = $content[0];
unset($content[0]);
}
return $this->render($response, 'editor/editor-blox.twig', array('navigation' => $this->structure, 'homepage' => $this->homepage, 'title' => $title, 'content' => $content, 'item' => $this->item, 'settings' => $this->settings ));
}
public function showEmpty(Request $request, Response $response, $args)
{
return $this->renderIntern404($response, array( 'settings' => $this->settings ));
}
}

View File

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

View File

@ -1,50 +1,51 @@
<?php
namespace Typemill\Controllers;
/* Use the slim-container */
use Slim\Http\Request;
use Slim\Http\Response;
use Slim\Views\Twig;
use Interop\Container\ContainerInterface;
use Typemill\Events\OnPageReady;
abstract class Controller
{
protected $c;
public function __construct(ContainerInterface $c)
{
$this->c = $c;
}
protected function render($response, $route, $data)
{
// $data = $this->c->dispatcher->dispatch('onPageReady', new OnPageReady($data))->getData();
if(isset($_SESSION['old']))
{
unset($_SESSION['old']);
}
$response = $response->withoutHeader('Server');
$response = $response->withoutHeader('X-Powered-By');
if($this->c->request->getUri()->getScheme() == 'https')
{
$response = $response->withAddedHeader('Strict-Transport-Security', 'max-age=63072000');
}
$response = $response->withAddedHeader('X-Content-Type-Options', 'nosniff');
$response = $response->withAddedHeader('X-Frame-Options', 'SAMEORIGIN');
$response = $response->withAddedHeader('X-XSS-Protection', '1;mode=block');
$response = $response->withAddedHeader('Referrer-Policy', 'no-referrer-when-downgrade');
return $this->c->view->render($response, $route, $data);
}
protected function render404($response, $data = NULL)
{
return $this->c->view->render($response->withStatus(404), '/404.twig', $data);
}
<?php
namespace Typemill\Controllers;
/* Use the slim-container */
use Slim\Http\Request;
use Slim\Http\Response;
use Slim\Views\Twig;
use Interop\Container\ContainerInterface;
use Typemill\Events\OnPageReady;
abstract class Controller
{
protected $c;
public function __construct(ContainerInterface $c)
{
$this->c = $c;
}
protected function render($response, $route, $data)
{
# why commented this out??
$data = $this->c->dispatcher->dispatch('onPageReady', new OnPageReady($data))->getData();
if(isset($_SESSION['old']))
{
unset($_SESSION['old']);
}
$response = $response->withoutHeader('Server');
$response = $response->withoutHeader('X-Powered-By');
if($this->c->request->getUri()->getScheme() == 'https')
{
$response = $response->withAddedHeader('Strict-Transport-Security', 'max-age=63072000');
}
$response = $response->withAddedHeader('X-Content-Type-Options', 'nosniff');
$response = $response->withAddedHeader('X-Frame-Options', 'SAMEORIGIN');
$response = $response->withAddedHeader('X-XSS-Protection', '1;mode=block');
$response = $response->withAddedHeader('Referrer-Policy', 'no-referrer-when-downgrade');
return $this->c->view->render($response, $route, $data);
}
protected function render404($response, $data = NULL)
{
return $this->c->view->render($response->withStatus(404), '/404.twig', $data);
}
}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -1,31 +1,31 @@
<?php
namespace Typemill\Extensions;
use Slim\Csrf\Guard;
class TwigCsrfExtension extends \Twig_Extension
{
protected $guard;
public function __construct(Guard $guard)
{
$this->guard = $guard;
}
public function getFunctions()
{
return [
new \Twig_SimpleFunction('csrf_field', array($this, 'csrfField' ))
];
}
public function csrfField()
{
return '
<input type="hidden" id="' . $this->guard->getTokenNameKey() . '" name="' . $this->guard->getTokenNameKey() . '" value="' . $this->guard->getTokenName() . '">
<input type="hidden" id="' . $this->guard->getTokenValueKey() . '" name="' . $this->guard->getTokenValueKey() . '" value="' . $this->guard->getTokenValue() . '">
';
}
<?php
namespace Typemill\Extensions;
use Slim\Csrf\Guard;
class TwigCsrfExtension extends \Twig_Extension
{
protected $guard;
public function __construct(Guard $guard)
{
$this->guard = $guard;
}
public function getFunctions()
{
return [
new \Twig_SimpleFunction('csrf_field', array($this, 'csrfField' ))
];
}
public function csrfField()
{
return '
<input type="hidden" id="' . $this->guard->getTokenNameKey() . '" name="' . $this->guard->getTokenNameKey() . '" value="' . $this->guard->getTokenName() . '">
<input type="hidden" id="' . $this->guard->getTokenValueKey() . '" name="' . $this->guard->getTokenValueKey() . '" value="' . $this->guard->getTokenValue() . '">
';
}
}

View File

@ -1,24 +1,24 @@
<?php
namespace Typemill\Extensions;
use Typemill\Extensions\ParsedownExtension;
class TwigMarkdownExtension extends \Twig_Extension
{
public function getFunctions()
{
return [
new \Twig_SimpleFunction('markdown', array($this, 'renderMarkdown' ))
];
}
public function renderMarkdown($markdown)
{
$parsedown = new ParsedownExtension();
$markdownArray = $parsedown->text($markdown);
return $parsedown->markup($markdownArray, false);
}
<?php
namespace Typemill\Extensions;
use Typemill\Extensions\ParsedownExtension;
class TwigMarkdownExtension extends \Twig_Extension
{
public function getFunctions()
{
return [
new \Twig_SimpleFunction('markdown', array($this, 'renderMarkdown' ))
];
}
public function renderMarkdown($markdown)
{
$parsedown = new ParsedownExtension();
$markdownArray = $parsedown->text($markdown);
return $parsedown->markup($markdownArray, false);
}
}

View File

@ -1,34 +1,34 @@
<?php
namespace Typemill\Extensions;
class TwigUserExtension extends \Twig_Extension
{
public function getFunctions()
{
return [
new \Twig_SimpleFunction('is_role', array($this, 'isRole' )),
new \Twig_SimpleFunction('get_username', array($this, 'getUsername' ))
];
}
public function isRole($role)
{
if(isset($_SESSION['role']) && $_SESSION['role'] == $role)
{
return true;
}
return false;
}
public function getUsername()
{
if(isset($_SESSION['user']))
{
return $_SESSION['user'];
}
return false;
}
<?php
namespace Typemill\Extensions;
class TwigUserExtension extends \Twig_Extension
{
public function getFunctions()
{
return [
new \Twig_SimpleFunction('is_role', array($this, 'isRole' )),
new \Twig_SimpleFunction('get_username', array($this, 'getUsername' ))
];
}
public function isRole($role)
{
if(isset($_SESSION['role']) && $_SESSION['role'] == $role)
{
return true;
}
return false;
}
public function getUsername()
{
if(isset($_SESSION['user']))
{
return $_SESSION['user'];
}
return false;
}
}

View File

@ -1,20 +1,20 @@
The MIT License (MIT)
Copyright © 2016 Taufik Nurrohman, latitudu.com
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the “Software”), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
The MIT License (MIT)
Copyright © 2016 Taufik Nurrohman, latitudu.com
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the “Software”), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,21 +1,21 @@
MIT License
Copyright (c) 2019 Benjamin Høegh
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
MIT License
Copyright (c) 2019 Benjamin Høegh
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,32 +1,32 @@
<?php
namespace Typemill\Middleware;
use Slim\Views\Twig;
use Slim\Http\Request;
use Slim\Http\Response;
class OldInputMiddleware
{
protected $view;
public function __construct(Twig $view)
{
$this->view = $view;
}
public function __invoke(Request $request, Response $response, $next)
{
if(isset($_SESSION['old']))
{
$this->view->getEnvironment()->addGlobal('old', $_SESSION['old']);
}
if(!empty($request->getParams()))
{
$_SESSION['old'] = $request->getParams();
}
$response = $next($request, $response);
return $response;
}
<?php
namespace Typemill\Middleware;
use Slim\Views\Twig;
use Slim\Http\Request;
use Slim\Http\Response;
class OldInputMiddleware
{
protected $view;
public function __construct(Twig $view)
{
$this->view = $view;
}
public function __invoke(Request $request, Response $response, $next)
{
if(isset($_SESSION['old']))
{
$this->view->getEnvironment()->addGlobal('old', $_SESSION['old']);
}
if(!empty($request->getParams()))
{
$_SESSION['old'] = $request->getParams();
}
$response = $next($request, $response);
return $response;
}
}

View File

@ -1,27 +1,27 @@
<?php
namespace Typemill\Middleware;
use Slim\Interfaces\RouterInterface;
use Slim\Http\Request;
use Slim\Http\Response;
class RedirectIfAuthenticated
{
protected $router;
public function __construct(RouterInterface $router)
{
$this->router = $router;
}
public function __invoke(Request $request, Response $response, $next)
{
if(isset($_SESSION['login']))
{
$response = $response->withRedirect($this->router->pathFor('content.raw'));
}
return $next($request, $response);
}
<?php
namespace Typemill\Middleware;
use Slim\Interfaces\RouterInterface;
use Slim\Http\Request;
use Slim\Http\Response;
class RedirectIfAuthenticated
{
protected $router;
public function __construct(RouterInterface $router)
{
$this->router = $router;
}
public function __invoke(Request $request, Response $response, $next)
{
if(isset($_SESSION['login']))
{
$response = $response->withRedirect($this->router->pathFor('content.raw'));
}
return $next($request, $response);
}
}

View File

@ -1,32 +1,32 @@
<?php
namespace Typemill\Middleware;
use Slim\Interfaces\RouterInterface;
use Slim\Http\Request;
use Slim\Http\Response;
class RedirectIfNoAdmin
{
protected $router;
public function __construct(RouterInterface $router, $flash)
{
$this->router = $router;
}
public function __invoke(Request $request, Response $response, $next)
{
if(!isset($_SESSION['login']) || !isset($_SESSION['role']))
{
$response = $response->withRedirect($this->router->pathFor('auth.show'));
}
if($_SESSION['role'] != 'administrator')
{
$response = $response->withRedirect($this->router->pathFor('content.raw'));
}
return $next($request, $response);
}
<?php
namespace Typemill\Middleware;
use Slim\Interfaces\RouterInterface;
use Slim\Http\Request;
use Slim\Http\Response;
class RedirectIfNoAdmin
{
protected $router;
public function __construct(RouterInterface $router, $flash)
{
$this->router = $router;
}
public function __invoke(Request $request, Response $response, $next)
{
if(!isset($_SESSION['login']) || !isset($_SESSION['role']))
{
$response = $response->withRedirect($this->router->pathFor('auth.show'));
}
if($_SESSION['role'] != 'administrator')
{
$response = $response->withRedirect($this->router->pathFor('content.raw'));
}
return $next($request, $response);
}
}

View File

@ -1,27 +1,27 @@
<?php
namespace Typemill\Middleware;
use Slim\Interfaces\RouterInterface;
use Slim\Http\Request;
use Slim\Http\Response;
class RedirectIfUnauthenticated
{
protected $router;
public function __construct(RouterInterface $router, $flash)
{
$this->router = $router;
}
public function __invoke(Request $request, Response $response, $next)
{
if(!isset($_SESSION['login']))
{
return $response->withRedirect($this->router->pathFor('auth.show'));
}
return $next($request, $response);
}
<?php
namespace Typemill\Middleware;
use Slim\Interfaces\RouterInterface;
use Slim\Http\Request;
use Slim\Http\Response;
class RedirectIfUnauthenticated
{
protected $router;
public function __construct(RouterInterface $router, $flash)
{
$this->router = $router;
}
public function __invoke(Request $request, Response $response, $next)
{
if(!isset($_SESSION['login']))
{
return $response->withRedirect($this->router->pathFor('auth.show'));
}
return $next($request, $response);
}
}

View File

@ -1,26 +1,26 @@
<?php
namespace Typemill\Middleware;
use Slim\Interfaces\RouterInterface;
use Slim\Http\Request;
use Slim\Http\Response;
class RestrictApiAccess
{
protected $router;
public function __construct(RouterInterface $router)
{
$this->router = $router;
}
public function __invoke(Request $request, Response $response, $next)
{
if(!isset($_SESSION['login']) || !isset($_SESSION['role']))
{
return $response->withJson(['errors' => ['access denied']], 403);
}
return $next($request, $response);
}
<?php
namespace Typemill\Middleware;
use Slim\Interfaces\RouterInterface;
use Slim\Http\Request;
use Slim\Http\Response;
class RestrictApiAccess
{
protected $router;
public function __construct(RouterInterface $router)
{
$this->router = $router;
}
public function __invoke(Request $request, Response $response, $next)
{
if(!isset($_SESSION['login']) || !isset($_SESSION['role']))
{
return $response->withJson(['errors' => ['access denied']], 403);
}
return $next($request, $response);
}
}

View File

@ -1,29 +1,29 @@
<?php
namespace Typemill\Middleware;
use Slim\Views\Twig;
use Slim\Http\Request;
use Slim\Http\Response;
class ValidationErrorsMiddleware
{
protected $view;
public function __construct(Twig $view)
{
$this->view = $view;
}
public function __invoke(Request $request, Response $response, $next)
{
if(isset($_SESSION['errors']))
{
$this->view->getEnvironment()->addGlobal('errors', $_SESSION['errors']);
unset($_SESSION['errors']);
}
return $next($request, $response);
}
<?php
namespace Typemill\Middleware;
use Slim\Views\Twig;
use Slim\Http\Request;
use Slim\Http\Response;
class ValidationErrorsMiddleware
{
protected $view;
public function __construct(Twig $view)
{
$this->view = $view;
}
public function __invoke(Request $request, Response $response, $next)
{
if(isset($_SESSION['errors']))
{
$this->view->getEnvironment()->addGlobal('errors', $_SESSION['errors']);
unset($_SESSION['errors']);
}
return $next($request, $response);
}
}

View File

@ -1,286 +1,286 @@
<?php
namespace Typemill\Models;
class Field
{
private $type;
private $label;
private $name;
private $content;
/* holds all simple attributes for this field like "required" */
private $attributes = array();
/* holds all attribute value pairs for this field like "id=''" */
private $attributeValues = array();
/* holds all options for this field (e.g. select options) */
private $options = array();
/* defines all field types, that are allowed */
private $types = array(
'checkbox',
'checkboxlist',
'color',
'date',
'datetime',
'datetime-local',
'email',
'file',
'hidden',
'image',
'month',
'number',
'password',
'radio',
'range',
'tel',
'text',
'time',
'url',
'week',
'textarea',
'select',
'paragraph'
);
/* defines all boolean attributes, that are allowed for fields */
private $attr = array(
'autofocus',
'checked',
'disabled',
'formnovalidate',
'multiple',
'readonly',
'required'
);
/* defines all attribute value paires, that are allowed for fields */
private $attrValues = array(
'id',
'autocomplete',
'placeholder',
'size',
'rows',
'cols',
'min',
'max',
'class',
'pattern'
);
/* defines additional data, that are allowed for fields */
private $helpers = array(
'help',
'description'
);
public function __construct($fieldName, array $fieldConfigs)
{
$this->setName($fieldName);
$type = isset($fieldConfigs['type']) ? $fieldConfigs['type'] : false;
$this->setType($type);
$label = isset($fieldConfigs['label']) ? $fieldConfigs['label'] : false;
$this->setLabel($label);
$checkboxlabel = isset($fieldConfigs['checkboxlabel']) ? $fieldConfigs['checkboxlabel'] : false;
$this->setCheckboxLabel($checkboxlabel);
$options = isset($fieldConfigs['options']) ? $fieldConfigs['options'] : array();
$this->setOptions($options);
$this->setAttributes($fieldConfigs);
$this->setAttributeValues($fieldConfigs);
$this->setHelpers($fieldConfigs);
}
private function setName($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
private function setType($type)
{
if(in_array($type, $this->types))
{
$this->type = $type;
}
}
public function getType()
{
return $this->type;
}
public function setLabel($label)
{
$this->label = $label;
}
public function getLabel()
{
return $this->label;
}
public function setCheckboxLabel($label)
{
$this->checkboxLabel = $label;
}
public function getCheckboxLabel()
{
return $this->checkboxLabel;
}
public function setContent($content)
{
$this->content = $content;
}
public function getContent()
{
return $this->content;
}
private function setOptions(array $options)
{
foreach($options as $key => $value)
{
$this->options[$key] = $value;
}
}
public function getOptions()
{
if(isset($this->options))
{
return $this->options;
}
return false;
}
private function setAttributes($fieldConfigs)
{
foreach($fieldConfigs as $key => $value)
{
if(is_string($key) && in_array($key, $this->attr))
{
$this->attributes[$key] = $value;
}
}
}
/* get all attributes of the field and return them as a string. For usage in templates */
public function getAttributes()
{
$string = false;
foreach($this->attributes as $key => $attribute)
{
$string .= ' ' . $key;
}
return $string;
}
/* set a single attribute. Used e.g. in controller to change the value */
public function setAttribute($key, $value)
{
$this->attributes[$key] = $value;
}
public function unsetAttribute($key)
{
unset($this->attributes[$key]);
}
/* get a single attribute, if it is defined. For usage in templates like getAttribute('required') */
public function getAttribute($key)
{
if(isset($this->attributes[$key]))
{
return $this->attributes[$key];
}
return false;
}
private function setAttributeValues($fieldConfigs)
{
foreach($fieldConfigs as $key => $value)
{
if(is_string($key) && in_array($key, $this->attrValues))
{
$this->attributeValues[$key] = $value;
}
}
}
/* get all attributes as string. For usage in template */
public function getAttributeValues()
{
$string = false;
foreach($this->attributeValues as $key => $attribute)
{
$string .= ' ' . $key . '="' . $attribute . '"';
}
return $string;
}
public function setAttributeValue($key, $value)
{
/* pretty dirty, but you should not add a value for a simple checkbox */
if($key == 'value' && $this->type == 'checkbox')
{
return;
}
$this->attributeValues[$key] = $value;
}
public function getAttributeValue($key)
{
if(isset($this->attributeValues[$key]))
{
return $this->attributeValues[$key];
}
return false;
}
public function setHelpers($fieldConfigs)
{
foreach($fieldConfigs as $key => $config)
{
if(is_string($key) && in_array($key, $this->helpers))
{
$this->$key = $config;
}
}
}
public function getHelper($helperName)
{
if(isset($this->$helperName))
{
return $this->$helperName;
}
return false;
}
<?php
namespace Typemill\Models;
class Field
{
private $type;
private $label;
private $name;
private $content;
/* holds all simple attributes for this field like "required" */
private $attributes = array();
/* holds all attribute value pairs for this field like "id=''" */
private $attributeValues = array();
/* holds all options for this field (e.g. select options) */
private $options = array();
/* defines all field types, that are allowed */
private $types = array(
'checkbox',
'checkboxlist',
'color',
'date',
'datetime',
'datetime-local',
'email',
'file',
'hidden',
'image',
'month',
'number',
'password',
'radio',
'range',
'tel',
'text',
'time',
'url',
'week',
'textarea',
'select',
'paragraph'
);
/* defines all boolean attributes, that are allowed for fields */
private $attr = array(
'autofocus',
'checked',
'disabled',
'formnovalidate',
'multiple',
'readonly',
'required'
);
/* defines all attribute value paires, that are allowed for fields */
private $attrValues = array(
'id',
'autocomplete',
'placeholder',
'size',
'rows',
'cols',
'min',
'max',
'class',
'pattern'
);
/* defines additional data, that are allowed for fields */
private $helpers = array(
'help',
'description'
);
public function __construct($fieldName, array $fieldConfigs)
{
$this->setName($fieldName);
$type = isset($fieldConfigs['type']) ? $fieldConfigs['type'] : false;
$this->setType($type);
$label = isset($fieldConfigs['label']) ? $fieldConfigs['label'] : false;
$this->setLabel($label);
$checkboxlabel = isset($fieldConfigs['checkboxlabel']) ? $fieldConfigs['checkboxlabel'] : false;
$this->setCheckboxLabel($checkboxlabel);
$options = isset($fieldConfigs['options']) ? $fieldConfigs['options'] : array();
$this->setOptions($options);
$this->setAttributes($fieldConfigs);
$this->setAttributeValues($fieldConfigs);
$this->setHelpers($fieldConfigs);
}
private function setName($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
private function setType($type)
{
if(in_array($type, $this->types))
{
$this->type = $type;
}
}
public function getType()
{
return $this->type;
}
public function setLabel($label)
{
$this->label = $label;
}
public function getLabel()
{
return $this->label;
}
public function setCheckboxLabel($label)
{
$this->checkboxLabel = $label;
}
public function getCheckboxLabel()
{
return $this->checkboxLabel;
}
public function setContent($content)
{
$this->content = $content;
}
public function getContent()
{
return $this->content;
}
private function setOptions(array $options)
{
foreach($options as $key => $value)
{
$this->options[$key] = $value;
}
}
public function getOptions()
{
if(isset($this->options))
{
return $this->options;
}
return false;
}
private function setAttributes($fieldConfigs)
{
foreach($fieldConfigs as $key => $value)
{
if(is_string($key) && in_array($key, $this->attr))
{
$this->attributes[$key] = $value;
}
}
}
/* get all attributes of the field and return them as a string. For usage in templates */
public function getAttributes()
{
$string = false;
foreach($this->attributes as $key => $attribute)
{
$string .= ' ' . $key;
}
return $string;
}
/* set a single attribute. Used e.g. in controller to change the value */
public function setAttribute($key, $value)
{
$this->attributes[$key] = $value;
}
public function unsetAttribute($key)
{
unset($this->attributes[$key]);
}
/* get a single attribute, if it is defined. For usage in templates like getAttribute('required') */
public function getAttribute($key)
{
if(isset($this->attributes[$key]))
{
return $this->attributes[$key];
}
return false;
}
private function setAttributeValues($fieldConfigs)
{
foreach($fieldConfigs as $key => $value)
{
if(is_string($key) && in_array($key, $this->attrValues))
{
$this->attributeValues[$key] = $value;
}
}
}
/* get all attributes as string. For usage in template */
public function getAttributeValues()
{
$string = false;
foreach($this->attributeValues as $key => $attribute)
{
$string .= ' ' . $key . '="' . $attribute . '"';
}
return $string;
}
public function setAttributeValue($key, $value)
{
/* pretty dirty, but you should not add a value for a simple checkbox */
if($key == 'value' && $this->type == 'checkbox')
{
return;
}
$this->attributeValues[$key] = $value;
}
public function getAttributeValue($key)
{
if(isset($this->attributeValues[$key]))
{
return $this->attributeValues[$key];
}
return false;
}
public function setHelpers($fieldConfigs)
{
foreach($fieldConfigs as $key => $config)
{
if(is_string($key) && in_array($key, $this->helpers))
{
$this->$key = $config;
}
}
}
public function getHelper($helperName)
{
if(isset($this->$helperName))
{
return $this->$helperName;
}
return false;
}
}

View File

@ -1,104 +1,104 @@
<?php
namespace Typemill\Models;
use Typemill\Models\Field;
class Fields
{
public function getFields($userSettings, $objectType, $objectName, $objectSettings, $formType = false)
{
# hold all fields in array
$fields = array();
# formtype are backend forms or public forms, only relevant for plugins for now
$formType = $formType ? $formType : 'forms';
# iterate through all fields of the objectSetting (theme or plugin)
foreach($objectSettings[$formType]['fields'] as $fieldName => $fieldConfigurations)
{
if($fieldConfigurations['type'] == 'fieldset')
{
# if it is a fieldset, then create a subset for the containing field and read them with a recursive function
$subSettings = $objectSettings;
$subSettings['forms'] = $fieldConfigurations;
$fieldset = array();
$fieldset['type'] = 'fieldset';
$fieldset['legend'] = $fieldConfigurations['legend'];
$fieldset['fields'] = $this->getFields($userSettings, $objectType, $objectName, $subSettings, $formType);
$fields[] = $fieldset;
}
else
{
# For label, helptext and description you can use the value of another field. This is useful e.g. to localize the label of public forms via plugin settings.
if(isset($fieldConfigurations['label']) && isset($userSettings[$objectType][$objectName][$fieldConfigurations['label']]))
{
$fieldConfigurations['label'] = $userSettings[$objectType][$objectName][$fieldConfigurations['label']];
}
if(isset($fieldConfigurations['help']) && isset($userSettings[$objectType][$objectName][$fieldConfigurations['help']]))
{
$fieldConfigurations['help'] = $userSettings[$objectType][$objectName][$fieldConfigurations['help']];
}
if(isset($fieldConfigurations['description']) && isset($userSettings[$objectType][$objectName][$fieldConfigurations['description']]))
{
$fieldConfigurations['description'] = $userSettings[$objectType][$objectName][$fieldConfigurations['description']];
}
# for each field generate a new field object with the field name and the field configurations
$field = new Field($fieldName, $fieldConfigurations);
# handle the value for the field
$userValue = false;
# first, add the default value from the original plugin or theme settings
if(isset($objectSettings['settings'][$fieldName]))
{
$userValue = $objectSettings['settings'][$fieldName];
}
# now overwrite the default values with the user values stored in the user settings
if(isset($userSettings[$objectType][$objectName][$fieldName]))
{
$userValue = $userSettings[$objectType][$objectName][$fieldName];
}
# now overwrite user-values, if there are old-input values from the actual form (e.g. after input error)
if(isset($_SESSION['old'][$objectName][$fieldName]))
{
$userValue = $_SESSION['old'][$objectName][$fieldName];
}
# Now prepopulate the field object with the value */
if($field->getType() == "textarea" || $field->getType() == "paragraph")
{
if($userValue)
{
$field->setContent($userValue);
}
}
elseif($field->getType() == "checkbox")
{
# checkboxes need a special treatment, because field does not exist in settings if unchecked by user
if(isset($userSettings[$objectType][$objectName][$fieldName]))
{
$field->setAttribute('checked', 'true');
}
else
{
$field->unsetAttribute('chhecked');
}
}
else
{
$field->setAttributeValue('value', $userValue);
}
# add the field to the field-List
$fields[] = $field;
}
}
return $fields;
}
<?php
namespace Typemill\Models;
use Typemill\Models\Field;
class Fields
{
public function getFields($userSettings, $objectType, $objectName, $objectSettings, $formType = false)
{
# hold all fields in array
$fields = array();
# formtype are backend forms or public forms, only relevant for plugins for now
$formType = $formType ? $formType : 'forms';
# iterate through all fields of the objectSetting (theme or plugin)
foreach($objectSettings[$formType]['fields'] as $fieldName => $fieldConfigurations)
{
if($fieldConfigurations['type'] == 'fieldset')
{
# if it is a fieldset, then create a subset for the containing field and read them with a recursive function
$subSettings = $objectSettings;
$subSettings['forms'] = $fieldConfigurations;
$fieldset = array();
$fieldset['type'] = 'fieldset';
$fieldset['legend'] = $fieldConfigurations['legend'];
$fieldset['fields'] = $this->getFields($userSettings, $objectType, $objectName, $subSettings, $formType);
$fields[] = $fieldset;
}
else
{
# For label, helptext and description you can use the value of another field. This is useful e.g. to localize the label of public forms via plugin settings.
if(isset($fieldConfigurations['label']) && isset($userSettings[$objectType][$objectName][$fieldConfigurations['label']]))
{
$fieldConfigurations['label'] = $userSettings[$objectType][$objectName][$fieldConfigurations['label']];
}
if(isset($fieldConfigurations['help']) && isset($userSettings[$objectType][$objectName][$fieldConfigurations['help']]))
{
$fieldConfigurations['help'] = $userSettings[$objectType][$objectName][$fieldConfigurations['help']];
}
if(isset($fieldConfigurations['description']) && isset($userSettings[$objectType][$objectName][$fieldConfigurations['description']]))
{
$fieldConfigurations['description'] = $userSettings[$objectType][$objectName][$fieldConfigurations['description']];
}
# for each field generate a new field object with the field name and the field configurations
$field = new Field($fieldName, $fieldConfigurations);
# handle the value for the field
$userValue = false;
# first, add the default value from the original plugin or theme settings
if(isset($objectSettings['settings'][$fieldName]))
{
$userValue = $objectSettings['settings'][$fieldName];
}
# now overwrite the default values with the user values stored in the user settings
if(isset($userSettings[$objectType][$objectName][$fieldName]))
{
$userValue = $userSettings[$objectType][$objectName][$fieldName];
}
# now overwrite user-values, if there are old-input values from the actual form (e.g. after input error)
if(isset($_SESSION['old'][$objectName][$fieldName]))
{
$userValue = $_SESSION['old'][$objectName][$fieldName];
}
# Now prepopulate the field object with the value */
if($field->getType() == "textarea" || $field->getType() == "paragraph")
{
if($userValue)
{
$field->setContent($userValue);
}
}
elseif($field->getType() == "checkbox")
{
# checkboxes need a special treatment, because field does not exist in settings if unchecked by user
if(isset($userSettings[$objectType][$objectName][$fieldName]))
{
$field->setAttribute('checked', 'true');
}
else
{
$field->unsetAttribute('chhecked');
}
}
else
{
$field->setAttributeValue('value', $userValue);
}
# add the field to the field-List
$fields[] = $field;
}
}
return $fields;
}
}

View File

@ -1,434 +1,434 @@
<?php
namespace Typemill\Models;
use \URLify;
class Folder
{
/*
* scans content of a folder (without recursion)
* vars: folder path as string
* returns: one-dimensional array with names of folders and files
*/
public static function scanFolderFlat($folderPath)
{
$folderItems = scandir($folderPath);
$folderContent = array();
foreach ($folderItems as $key => $item)
{
if (!in_array($item, array(".","..")))
{
$nameParts = self::getStringParts($item);
$fileType = array_pop($nameParts);
if($fileType == 'md' OR $fileType == 'txt' )
{
$folderContent[] = $item;
}
}
}
return $folderContent;
}
/*
* scans content of a folder recursively
* vars: folder path as string
* returns: multi-dimensional array with names of folders and files
*/
public static function scanFolder($folderPath, $draft = false)
{
$folderItems = scandir($folderPath);
$folderContent = array();
# if it is the live version and if it is a folder that is not published, then do not show the folder and its content.
if(!$draft && !in_array('index.md', $folderItems)){ return false; }
foreach ($folderItems as $key => $item)
{
if (!in_array($item, array(".","..")))
{
if (is_dir($folderPath . DIRECTORY_SEPARATOR . $item))
{
$subFolder = $item;
$folderContent[$subFolder] = self::scanFolder($folderPath . DIRECTORY_SEPARATOR . $subFolder, $draft);
}
else
{
$nameParts = self::getStringParts($item);
$fileType = array_pop($nameParts);
if($fileType == 'md')
{
$folderContent[] = $item;
}
if($draft === true && $fileType == 'txt')
{
if(isset($last) && ($last == implode($nameParts)) )
{
array_pop($folderContent);
$item = $item . 'md';
}
$folderContent[] = $item;
}
/* store the name of the last file */
$last = implode($nameParts);
}
}
}
return $folderContent;
}
/*
* Transforms array of folder item into an array of item-objects with additional information for each item
* vars: multidimensional array with folder- and file-names
* returns: array of objects. Each object contains information about an item (file or folder).
*/
public static function getFolderContentDetails(array $folderContent, $baseUrl, $fullSlugWithFolder = NULL, $fullSlugWithoutFolder = NULL, $fullPath = NULL, $keyPath = NULL, $chapter = NULL)
{
$contentDetails = [];
$iteration = 0;
$chapternr = 1;
foreach($folderContent as $key => $name)
{
$item = new \stdClass();
if(is_array($name))
{
$nameParts = self::getStringParts($key);
$fileType = '';
if(in_array('index.md', $name))
{
$fileType = 'md';
$status = 'published';
}
if(in_array('index.txt', $name))
{
$fileType = 'txt';
$status = 'unpublished';
}
if(in_array('index.txtmd', $name))
{
$fileType = 'txt';
$status = 'modified';
}
$item->originalName = $key;
$item->elementType = 'folder';
$item->status = $status;
$item->fileType = $fileType;
$item->order = count($nameParts) > 1 ? array_shift($nameParts) : NULL;
$item->name = implode(" ",$nameParts);
$item->name = iconv(mb_detect_encoding($item->name, mb_detect_order(), true), "UTF-8", $item->name);
$item->slug = implode("-",$nameParts);
$item->slug = URLify::filter(iconv(mb_detect_encoding($item->slug, mb_detect_order(), true), "UTF-8", $item->slug));
$item->path = $fullPath . DIRECTORY_SEPARATOR . $key;
$item->urlRelWoF = $fullSlugWithoutFolder . '/' . $item->slug;
$item->urlRel = $fullSlugWithFolder . '/' . $item->slug;
$item->urlAbs = $baseUrl . $fullSlugWithoutFolder . '/' . $item->slug;
$item->key = $iteration;
$item->keyPath = isset($keyPath) ? $keyPath . '.' . $iteration : $iteration;
$item->keyPathArray = explode('.', $item->keyPath);
$item->chapter = $chapter ? $chapter . '.' . $chapternr : $chapternr;
$item->active = false;
$item->activeParent = false;
$item->folderContent = self::getFolderContentDetails($name, $baseUrl, $item->urlRel, $item->urlRelWoF, $item->path, $item->keyPath, $item->chapter);
}
else
{
# do not use files in base folder (only folders are allowed)
if(!isset($keyPath)) continue;
# do not use index files
if($name == 'index.md' || $name == 'index.txt' || $name == 'index.txtmd' ) continue;
$nameParts = self::getStringParts($name);
$fileType = array_pop($nameParts);
if($fileType == 'md')
{
$status = 'published';
}
elseif($fileType == 'txt')
{
$status = 'unpublished';
}
else
{
$fileType = 'txt';
$status = 'modified';
}
$item->originalName = $name;
$item->elementType = 'file';
$item->status = $status;
$item->fileType = $fileType;
$item->order = count($nameParts) > 1 ? array_shift($nameParts) : NULL;
$item->name = implode(" ",$nameParts);
$item->name = iconv(mb_detect_encoding($item->name, mb_detect_order(), true), "UTF-8", $item->name);
$item->slug = implode("-",$nameParts);
$item->slug = URLify::filter(iconv(mb_detect_encoding($item->slug, mb_detect_order(), true), "UTF-8", $item->slug));
$item->path = $fullPath . DIRECTORY_SEPARATOR . $name;
$item->key = $iteration;
$item->keyPath = $keyPath . '.' . $iteration;
$item->keyPathArray = explode('.',$item->keyPath);
$item->chapter = $chapter . '.' . $chapternr;
$item->urlRelWoF = $fullSlugWithoutFolder . '/' . $item->slug;
$item->urlRel = $fullSlugWithFolder . '/' . $item->slug;
$item->urlAbs = $baseUrl . $fullSlugWithoutFolder . '/' . $item->slug;
$item->active = false;
$item->activeParent = false;
}
$iteration++;
$chapternr++;
$contentDetails[] = $item;
}
return $contentDetails;
}
public static function getItemForUrl($folderContentDetails, $url, $result = NULL)
{
foreach($folderContentDetails as $key => $item)
{
if($item->urlRel === $url)
{
# set item active, needed for move item in navigation
$item->active = true;
$result = $item;
}
elseif($item->elementType === "folder")
{
$result = self::getItemForUrl($item->folderContent, $url, $result);
}
}
return $result;
}
public static function getPagingForItem($content, $item)
{
$keyPos = count($item->keyPathArray)-1;
$thisChapArray = $item->keyPathArray;
$nextItemArray = $item->keyPathArray;
$prevItemArray = $item->keyPathArray;
$item->thisChapter = false;
$item->prevItem = false;
$item->nextItem = false;
/************************
* ADD THIS CHAPTER *
************************/
if($keyPos > 0)
{
array_pop($thisChapArray);
$item->thisChapter = self::getItemWithKeyPath($content, $thisChapArray);
}
/************************
* ADD NEXT ITEM *
************************/
if($item->elementType == 'folder')
{
/* get the first element in the folder */
$item->nextItem = isset($item->folderContent[0]) ? clone($item->folderContent[0]) : false;
}
if(!$item->nextItem)
{
$nextItemArray[$keyPos]++;
$item->nextItem = self::getItemWithKeyPath($content, $nextItemArray);
}
while(!$item->nextItem)
{
array_pop($nextItemArray);
if(empty($nextItemArray)) break;
$newKeyPos = count($nextItemArray)-1;
$nextItemArray[$newKeyPos]++;
$item->nextItem = self::getItemWithKeyPath($content, $nextItemArray);
}
/************************
* ADD PREVIOUS ITEM *
************************/
if($prevItemArray[$keyPos] > 0)
{
$prevItemArray[$keyPos]--;
$item->prevItem = self::getItemWithKeyPath($content, $prevItemArray);
if($item->prevItem && $item->prevItem->elementType == 'folder' && !empty($item->prevItem->folderContent))
{
/* get last item in folder */
$item->prevItem = self::getLastItemOfFolder($item->prevItem);
}
}
else
{
$item->prevItem = $item->thisChapter;
}
if($item->prevItem && $item->prevItem->elementType == 'folder'){ unset($item->prevItem->folderContent); }
if($item->nextItem && $item->nextItem->elementType == 'folder'){ unset($item->nextItem->folderContent); }
if($item->thisChapter){unset($item->thisChapter->folderContent); }
return $item;
}
/*
* Gets a copy of an item with a key
* @param array $content with the full structure of the content as multidimensional array
* @param array $searchArray with the key as a one-dimensional array like array(0,3,4)
* @return array $item
*/
public static function getItemWithKeyPath($content, array $searchArray)
{
$item = false;
foreach($searchArray as $key => $itemKey)
{
$item = isset($content[$itemKey]) ? clone($content[$itemKey]) : false;
unset($searchArray[$key]);
if(!empty($searchArray) && $item)
{
return self::getItemWithKeyPath($item->folderContent, $searchArray);
}
}
return $item;
}
# https://www.quora.com/Learning-PHP-Is-there-a-way-to-get-the-value-of-multi-dimensional-array-by-specifying-the-key-with-a-variable
# NOT IN USE
public static function getItemWithKeyPathNew($array, array $keys)
{
$item = $array;
foreach ($keys as $key)
{
$item = isset($item[$key]->folderContent) ? $item[$key]->folderContent : $item[$key];
}
return $item;
}
/*
* Extracts an item with a key https://stackoverflow.com/questions/52097092/php-delete-value-of-array-with-dynamic-key
* @param array $content with the full structure of the content as multidimensional array
* @param array $searchArray with the key as a one-dimensional array like array(0,3,4)
* @return array $item
* NOT IN USE ??
*/
public static function extractItemWithKeyPath($structure, array $keys)
{
$result = &$structure;
$last = array_pop($keys);
foreach ($keys as $key) {
if(isset($result[$key]->folderContent))
{
$result = &$result[$key]->folderContent;
}
else
{
$result = &$result[$key];
}
}
$item = $result[$last];
unset($result[$last]);
return array('structure' => $structure, 'item' => $item);
}
/* get breadcrumb as copied array, set elements active in original and mark parent element in original */
public static function getBreadcrumb($content, $searchArray, $i = NULL, $breadcrumb = NULL)
{
if(!$i){ $i = 0; $breadcrumb = array();}
while($i < count($searchArray))
{
$item = $content[$searchArray[$i]];
if($i == count($searchArray)-1)
{
$item->active = true;
}
else
{
$item->activeParent = true;
}
/*
$item->active = true;
if($i == count($searchArray)-2)
{
$item->activeParent = true;
}
*/
$copy = clone($item);
if($copy->elementType == 'folder')
{
unset($copy->folderContent);
$content = $item->folderContent;
}
$breadcrumb[] = $copy;
$i++;
return self::getBreadcrumb($content, $searchArray, $i++, $breadcrumb);
}
return $breadcrumb;
}
public static function getParentItem($content, $searchArray, $iteration = NULL)
{
if(!$iteration){ $iteration = 0; }
while($iteration < count($searchArray)-2)
{
$content = $content[$searchArray[$iteration]]->folderContent;
$iteration++;
return self::getParentItem($content, $searchArray, $iteration);
}
return $content[$searchArray[$iteration]];
}
private static function getLastItemOfFolder($folder)
{
$lastItem = end($folder->folderContent);
if(is_object($lastItem) && $lastItem->elementType == 'folder' && !empty($lastItem->folderContent))
{
return self::getLastItemOfFolder($lastItem);
}
return $lastItem;
}
public static function getStringParts($name)
{
return preg_split('/[\-\.\_\=\+\?\!\*\#\(\)\/ ]/',$name);
}
public static function getFileType($fileName)
{
$parts = preg_split('/\./',$fileName);
return end($parts);
}
public static function splitFileName($fileName)
{
$parts = preg_split('/\./',$fileName);
return $parts;
}
<?php
namespace Typemill\Models;
use \URLify;
class Folder
{
/*
* scans content of a folder (without recursion)
* vars: folder path as string
* returns: one-dimensional array with names of folders and files
*/
public static function scanFolderFlat($folderPath)
{
$folderItems = scandir($folderPath);
$folderContent = array();
foreach ($folderItems as $key => $item)
{
if (!in_array($item, array(".","..")))
{
$nameParts = self::getStringParts($item);
$fileType = array_pop($nameParts);
if($fileType == 'md' OR $fileType == 'txt' )
{
$folderContent[] = $item;
}
}
}
return $folderContent;
}
/*
* scans content of a folder recursively
* vars: folder path as string
* returns: multi-dimensional array with names of folders and files
*/
public static function scanFolder($folderPath, $draft = false)
{
$folderItems = scandir($folderPath);
$folderContent = array();
# if it is the live version and if it is a folder that is not published, then do not show the folder and its content.
if(!$draft && !in_array('index.md', $folderItems)){ return false; }
foreach ($folderItems as $key => $item)
{
if (!in_array($item, array(".","..")))
{
if (is_dir($folderPath . DIRECTORY_SEPARATOR . $item))
{
$subFolder = $item;
$folderContent[$subFolder] = self::scanFolder($folderPath . DIRECTORY_SEPARATOR . $subFolder, $draft);
}
else
{
$nameParts = self::getStringParts($item);
$fileType = array_pop($nameParts);
if($fileType == 'md')
{
$folderContent[] = $item;
}
if($draft === true && $fileType == 'txt')
{
if(isset($last) && ($last == implode($nameParts)) )
{
array_pop($folderContent);
$item = $item . 'md';
}
$folderContent[] = $item;
}
/* store the name of the last file */
$last = implode($nameParts);
}
}
}
return $folderContent;
}
/*
* Transforms array of folder item into an array of item-objects with additional information for each item
* vars: multidimensional array with folder- and file-names
* returns: array of objects. Each object contains information about an item (file or folder).
*/
public static function getFolderContentDetails(array $folderContent, $baseUrl, $fullSlugWithFolder = NULL, $fullSlugWithoutFolder = NULL, $fullPath = NULL, $keyPath = NULL, $chapter = NULL)
{
$contentDetails = [];
$iteration = 0;
$chapternr = 1;
foreach($folderContent as $key => $name)
{
$item = new \stdClass();
if(is_array($name))
{
$nameParts = self::getStringParts($key);
$fileType = '';
if(in_array('index.md', $name))
{
$fileType = 'md';
$status = 'published';
}
if(in_array('index.txt', $name))
{
$fileType = 'txt';
$status = 'unpublished';
}
if(in_array('index.txtmd', $name))
{
$fileType = 'txt';
$status = 'modified';
}
$item->originalName = $key;
$item->elementType = 'folder';
$item->status = $status;
$item->fileType = $fileType;
$item->order = count($nameParts) > 1 ? array_shift($nameParts) : NULL;
$item->name = implode(" ",$nameParts);
$item->name = iconv(mb_detect_encoding($item->name, mb_detect_order(), true), "UTF-8", $item->name);
$item->slug = implode("-",$nameParts);
$item->slug = URLify::filter(iconv(mb_detect_encoding($item->slug, mb_detect_order(), true), "UTF-8", $item->slug));
$item->path = $fullPath . DIRECTORY_SEPARATOR . $key;
$item->urlRelWoF = $fullSlugWithoutFolder . '/' . $item->slug;
$item->urlRel = $fullSlugWithFolder . '/' . $item->slug;
$item->urlAbs = $baseUrl . $fullSlugWithoutFolder . '/' . $item->slug;
$item->key = $iteration;
$item->keyPath = isset($keyPath) ? $keyPath . '.' . $iteration : $iteration;
$item->keyPathArray = explode('.', $item->keyPath);
$item->chapter = $chapter ? $chapter . '.' . $chapternr : $chapternr;
$item->active = false;
$item->activeParent = false;
$item->folderContent = self::getFolderContentDetails($name, $baseUrl, $item->urlRel, $item->urlRelWoF, $item->path, $item->keyPath, $item->chapter);
}
else
{
# do not use files in base folder (only folders are allowed)
if(!isset($keyPath)) continue;
# do not use index files
if($name == 'index.md' || $name == 'index.txt' || $name == 'index.txtmd' ) continue;
$nameParts = self::getStringParts($name);
$fileType = array_pop($nameParts);
if($fileType == 'md')
{
$status = 'published';
}
elseif($fileType == 'txt')
{
$status = 'unpublished';
}
else
{
$fileType = 'txt';
$status = 'modified';
}
$item->originalName = $name;
$item->elementType = 'file';
$item->status = $status;
$item->fileType = $fileType;
$item->order = count($nameParts) > 1 ? array_shift($nameParts) : NULL;
$item->name = implode(" ",$nameParts);
$item->name = iconv(mb_detect_encoding($item->name, mb_detect_order(), true), "UTF-8", $item->name);
$item->slug = implode("-",$nameParts);
$item->slug = URLify::filter(iconv(mb_detect_encoding($item->slug, mb_detect_order(), true), "UTF-8", $item->slug));
$item->path = $fullPath . DIRECTORY_SEPARATOR . $name;
$item->key = $iteration;
$item->keyPath = $keyPath . '.' . $iteration;
$item->keyPathArray = explode('.',$item->keyPath);
$item->chapter = $chapter . '.' . $chapternr;
$item->urlRelWoF = $fullSlugWithoutFolder . '/' . $item->slug;
$item->urlRel = $fullSlugWithFolder . '/' . $item->slug;
$item->urlAbs = $baseUrl . $fullSlugWithoutFolder . '/' . $item->slug;
$item->active = false;
$item->activeParent = false;
}
$iteration++;
$chapternr++;
$contentDetails[] = $item;
}
return $contentDetails;
}
public static function getItemForUrl($folderContentDetails, $url, $result = NULL)
{
foreach($folderContentDetails as $key => $item)
{
if($item->urlRel === $url)
{
# set item active, needed for move item in navigation
$item->active = true;
$result = $item;
}
elseif($item->elementType === "folder")
{
$result = self::getItemForUrl($item->folderContent, $url, $result);
}
}
return $result;
}
public static function getPagingForItem($content, $item)
{
$keyPos = count($item->keyPathArray)-1;
$thisChapArray = $item->keyPathArray;
$nextItemArray = $item->keyPathArray;
$prevItemArray = $item->keyPathArray;
$item->thisChapter = false;
$item->prevItem = false;
$item->nextItem = false;
/************************
* ADD THIS CHAPTER *
************************/
if($keyPos > 0)
{
array_pop($thisChapArray);
$item->thisChapter = self::getItemWithKeyPath($content, $thisChapArray);
}
/************************
* ADD NEXT ITEM *
************************/
if($item->elementType == 'folder')
{
/* get the first element in the folder */
$item->nextItem = isset($item->folderContent[0]) ? clone($item->folderContent[0]) : false;
}
if(!$item->nextItem)
{
$nextItemArray[$keyPos]++;
$item->nextItem = self::getItemWithKeyPath($content, $nextItemArray);
}
while(!$item->nextItem)
{
array_pop($nextItemArray);
if(empty($nextItemArray)) break;
$newKeyPos = count($nextItemArray)-1;
$nextItemArray[$newKeyPos]++;
$item->nextItem = self::getItemWithKeyPath($content, $nextItemArray);
}
/************************
* ADD PREVIOUS ITEM *
************************/
if($prevItemArray[$keyPos] > 0)
{
$prevItemArray[$keyPos]--;
$item->prevItem = self::getItemWithKeyPath($content, $prevItemArray);
if($item->prevItem && $item->prevItem->elementType == 'folder' && !empty($item->prevItem->folderContent))
{
/* get last item in folder */
$item->prevItem = self::getLastItemOfFolder($item->prevItem);
}
}
else
{
$item->prevItem = $item->thisChapter;
}
if($item->prevItem && $item->prevItem->elementType == 'folder'){ unset($item->prevItem->folderContent); }
if($item->nextItem && $item->nextItem->elementType == 'folder'){ unset($item->nextItem->folderContent); }
if($item->thisChapter){unset($item->thisChapter->folderContent); }
return $item;
}
/*
* Gets a copy of an item with a key
* @param array $content with the full structure of the content as multidimensional array
* @param array $searchArray with the key as a one-dimensional array like array(0,3,4)
* @return array $item
*/
public static function getItemWithKeyPath($content, array $searchArray)
{
$item = false;
foreach($searchArray as $key => $itemKey)
{
$item = isset($content[$itemKey]) ? clone($content[$itemKey]) : false;
unset($searchArray[$key]);
if(!empty($searchArray) && $item)
{
return self::getItemWithKeyPath($item->folderContent, $searchArray);
}
}
return $item;
}
# https://www.quora.com/Learning-PHP-Is-there-a-way-to-get-the-value-of-multi-dimensional-array-by-specifying-the-key-with-a-variable
# NOT IN USE
public static function getItemWithKeyPathNew($array, array $keys)
{
$item = $array;
foreach ($keys as $key)
{
$item = isset($item[$key]->folderContent) ? $item[$key]->folderContent : $item[$key];
}
return $item;
}
/*
* Extracts an item with a key https://stackoverflow.com/questions/52097092/php-delete-value-of-array-with-dynamic-key
* @param array $content with the full structure of the content as multidimensional array
* @param array $searchArray with the key as a one-dimensional array like array(0,3,4)
* @return array $item
* NOT IN USE ??
*/
public static function extractItemWithKeyPath($structure, array $keys)
{
$result = &$structure;
$last = array_pop($keys);
foreach ($keys as $key) {
if(isset($result[$key]->folderContent))
{
$result = &$result[$key]->folderContent;
}
else
{
$result = &$result[$key];
}
}
$item = $result[$last];
unset($result[$last]);
return array('structure' => $structure, 'item' => $item);
}
/* get breadcrumb as copied array, set elements active in original and mark parent element in original */
public static function getBreadcrumb($content, $searchArray, $i = NULL, $breadcrumb = NULL)
{
if(!$i){ $i = 0; $breadcrumb = array();}
while($i < count($searchArray))
{
$item = $content[$searchArray[$i]];
if($i == count($searchArray)-1)
{
$item->active = true;
}
else
{
$item->activeParent = true;
}
/*
$item->active = true;
if($i == count($searchArray)-2)
{
$item->activeParent = true;
}
*/
$copy = clone($item);
if($copy->elementType == 'folder')
{
unset($copy->folderContent);
$content = $item->folderContent;
}
$breadcrumb[] = $copy;
$i++;
return self::getBreadcrumb($content, $searchArray, $i++, $breadcrumb);
}
return $breadcrumb;
}
public static function getParentItem($content, $searchArray, $iteration = NULL)
{
if(!$iteration){ $iteration = 0; }
while($iteration < count($searchArray)-2)
{
$content = $content[$searchArray[$iteration]]->folderContent;
$iteration++;
return self::getParentItem($content, $searchArray, $iteration);
}
return $content[$searchArray[$iteration]];
}
private static function getLastItemOfFolder($folder)
{
$lastItem = end($folder->folderContent);
if(is_object($lastItem) && $lastItem->elementType == 'folder' && !empty($lastItem->folderContent))
{
return self::getLastItemOfFolder($lastItem);
}
return $lastItem;
}
public static function getStringParts($name)
{
return preg_split('/[\-\.\_\=\+\?\!\*\#\(\)\/ ]/',$name);
}
public static function getFileType($fileName)
{
$parts = preg_split('/\./',$fileName);
return end($parts);
}
public static function splitFileName($fileName)
{
$parts = preg_split('/\./',$fileName);
return $parts;
}
}

View File

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

View File

@ -1,336 +1,336 @@
<?php
namespace Typemill\Models;
class ProcessImage
{
public function createImage(string $image, array $desiredSizes)
{
# fix error from jpeg-library
ini_set ('gd.jpeg_ignore_warning', 1);
error_reporting(E_ALL & ~E_NOTICE);
# clear temporary folder
$this->clearTempFolder();
# decode the image from base64-string
$imageDecoded = $this->decodeImage($image);
$imageData = $imageDecoded["image"];
$imageType = $imageDecoded["type"];
# transform image-stream into image
$image = imagecreatefromstring($imageData);
# get the size of the original image
$imageSize = $this->getImageSize($image);
# check the desired sizes and calculate the height, if not set
$desiredSizes = $this->setHeight($imageSize, $desiredSizes);
# 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);
return true;
}
# temporary store resized images
foreach($resizedImages as $key => $resizedImage)
{
$this->saveImage($tmpFolder, $resizedImage, $key, $imageType);
}
return true;
}
public function detectAnimatedGif($image_file_contents)
{
$is_animated = preg_match('#(\x00\x21\xF9\x04.{4}\x00\x2C.*){2,}#s', $image_file_contents);
if ($is_animated == 1)
{
return true;
}
return false;
}
public function publishImage(array $desiredSizes, $name = false)
{
/* 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;
if(!file_exists($originalFolder)){ mkdir($originalFolder, 0774, true); }
if(!file_exists($liveFolder)){ mkdir($liveFolder, 0774, true); }
$name = $name ? $name : uniqid();
$files = scandir($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);
}
else
{
$success = rename($tmpFolder . $file, $liveFolder . $name . '-' . $file);
}
}
}
if($success)
{
return 'media/live/' . $name . '-live.' . $tmpfilename[1];
}
return false;
}
public function decodeImage(string $image)
{
$imageParts = explode(";base64,", $image);
$imageType = explode("/", $imageParts[0]);
$imageData = base64_decode($imageParts[1]);
if ($imageData !== false)
{
return array("image" => $imageData, "type" => $imageType[1]);
}
return false;
}
public function getImageSize($image)
{
$width = imagesx($image);
$height = imagesy($image);
return array('width' => $width, 'height' => $height);
}
public function setHeight(array $imageSize, array $desiredSizes)
{
foreach($desiredSizes as $key => $desiredSize)
{
if($desiredSize['width'] > $imageSize['width'])
{
$desiredSizes[$key] = $imageSize;
continue;
}
if(!isset($desiredSize['height']))
{
$resizeFactor = $imageSize['width'] / $desiredSize['width'];
$desiredSizes[$key]['height'] = round( ($imageSize['height'] / $resizeFactor), 0);
}
}
return $desiredSizes;
}
public function imageResize($imageData, array $imageSize, array $desiredSizes, $imageType)
{
$copiedImages = array();
$source_aspect_ratio = $imageSize['width'] / $imageSize['height'];
foreach($desiredSizes as $key => $desiredSize)
{
$desired_aspect_ratio = $desiredSize['width'] / $desiredSize['height'];
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);
}
# Create a temporary GD image with desired size
$temp_gdim = imagecreatetruecolor( $temp_width, $temp_height );
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']
);
$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;
*/
}
return $copiedImages;
}
public function saveOriginal($folder, $image, $name, $type)
{
if(!file_exists($folder))
{
mkdir($folder, 0774, true);
}
$path = $folder . $name . '.' . $type;
file_put_contents($path, $image);
}
public function saveImage($folder, $image, $name, $type)
{
if(!file_exists($folder))
{
mkdir($folder, 0774, true);
}
if($type == "png")
{
$result = imagepng( $image, $folder . '/' . $name . '.png' );
}
elseif($type == "gif")
{
$result = imagegif( $image, $folder . '/' . $name . '.gif' );
}
else
{
$result = imagejpeg( $image, $folder . '/' . $name . '.jpeg' );
$type = 'jpeg';
}
imagedestroy($image);
if($result)
{
return $name . '.' . $type;
}
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)
{
if(!unlink($image))
{
$success = false;
}
}
foreach(glob($live) as $image)
{
if(!unlink($image))
{
$success = false;
}
}
return $success;
}
}
<?php
namespace Typemill\Models;
class ProcessImage
{
public function createImage(string $image, array $desiredSizes)
{
# fix error from jpeg-library
ini_set ('gd.jpeg_ignore_warning', 1);
error_reporting(E_ALL & ~E_NOTICE);
# clear temporary folder
$this->clearTempFolder();
# decode the image from base64-string
$imageDecoded = $this->decodeImage($image);
$imageData = $imageDecoded["image"];
$imageType = $imageDecoded["type"];
# transform image-stream into image
$image = imagecreatefromstring($imageData);
# get the size of the original image
$imageSize = $this->getImageSize($image);
# check the desired sizes and calculate the height, if not set
$desiredSizes = $this->setHeight($imageSize, $desiredSizes);
# 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);
return true;
}
# temporary store resized images
foreach($resizedImages as $key => $resizedImage)
{
$this->saveImage($tmpFolder, $resizedImage, $key, $imageType);
}
return true;
}
public function detectAnimatedGif($image_file_contents)
{
$is_animated = preg_match('#(\x00\x21\xF9\x04.{4}\x00\x2C.*){2,}#s', $image_file_contents);
if ($is_animated == 1)
{
return true;
}
return false;
}
public function publishImage(array $desiredSizes, $name = false)
{
/* 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;
if(!file_exists($originalFolder)){ mkdir($originalFolder, 0774, true); }
if(!file_exists($liveFolder)){ mkdir($liveFolder, 0774, true); }
$name = $name ? $name : uniqid();
$files = scandir($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);
}
else
{
$success = rename($tmpFolder . $file, $liveFolder . $name . '-' . $file);
}
}
}
if($success)
{
return 'media/live/' . $name . '-live.' . $tmpfilename[1];
}
return false;
}
public function decodeImage(string $image)
{
$imageParts = explode(";base64,", $image);
$imageType = explode("/", $imageParts[0]);
$imageData = base64_decode($imageParts[1]);
if ($imageData !== false)
{
return array("image" => $imageData, "type" => $imageType[1]);
}
return false;
}
public function getImageSize($image)
{
$width = imagesx($image);
$height = imagesy($image);
return array('width' => $width, 'height' => $height);
}
public function setHeight(array $imageSize, array $desiredSizes)
{
foreach($desiredSizes as $key => $desiredSize)
{
if($desiredSize['width'] > $imageSize['width'])
{
$desiredSizes[$key] = $imageSize;
continue;
}
if(!isset($desiredSize['height']))
{
$resizeFactor = $imageSize['width'] / $desiredSize['width'];
$desiredSizes[$key]['height'] = round( ($imageSize['height'] / $resizeFactor), 0);
}
}
return $desiredSizes;
}
public function imageResize($imageData, array $imageSize, array $desiredSizes, $imageType)
{
$copiedImages = array();
$source_aspect_ratio = $imageSize['width'] / $imageSize['height'];
foreach($desiredSizes as $key => $desiredSize)
{
$desired_aspect_ratio = $desiredSize['width'] / $desiredSize['height'];
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);
}
# Create a temporary GD image with desired size
$temp_gdim = imagecreatetruecolor( $temp_width, $temp_height );
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']
);
$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;
*/
}
return $copiedImages;
}
public function saveOriginal($folder, $image, $name, $type)
{
if(!file_exists($folder))
{
mkdir($folder, 0774, true);
}
$path = $folder . $name . '.' . $type;
file_put_contents($path, $image);
}
public function saveImage($folder, $image, $name, $type)
{
if(!file_exists($folder))
{
mkdir($folder, 0774, true);
}
if($type == "png")
{
$result = imagepng( $image, $folder . '/' . $name . '.png' );
}
elseif($type == "gif")
{
$result = imagegif( $image, $folder . '/' . $name . '.gif' );
}
else
{
$result = imagejpeg( $image, $folder . '/' . $name . '.jpeg' );
$type = 'jpeg';
}
imagedestroy($image);
if($result)
{
return $name . '.' . $type;
}
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)
{
if(!unlink($image))
{
$success = false;
}
}
foreach(glob($live) as $image)
{
if(!unlink($image))
{
$success = false;
}
}
return $success;
}
}
?>

View File

@ -1,98 +1,98 @@
<?php
namespace Typemill\Models;
class User extends WriteYaml
{
public function getUsers()
{
$userDir = __DIR__ . '/../../settings/users';
/* check if plugins directory exists */
if(!is_dir($userDir)){ return array(); }
/* get all plugins folder */
$users = array_diff(scandir($userDir), array('..', '.'));
$cleanUser = array();
foreach($users as $key => $user)
{
if($user == '.logins'){ continue; }
$cleanUser[] = str_replace('.yaml', '', $user);
}
return $cleanUser;
}
public function getUser($username)
{
$user = $this->getYaml('settings/users', $username . '.yaml');
return $user;
}
public function createUser($params)
{
$userdata = array(
'username' => $params['username'],
'email' => $params['email'],
'password' => $this->generatePassword($params['password']),
'userrole' => $params['userrole']
);
if($this->updateYaml('settings/users', $userdata['username'] . '.yaml', $userdata))
{
return $userdata['username'];
}
return false;
}
public function updateUser($params)
{
$userdata = $this->getUser($params['username']);
if(isset($params['password']))
{
$params['password'] = $this->generatePassword($params['password']);
}
$update = array_merge($userdata, $params);
$this->updateYaml('settings/users', $userdata['username'] . '.yaml', $update);
return $userdata['username'];
}
public function deleteUser($username)
{
if($this->getUser($username))
{
unlink('settings/users/' . $username . '.yaml');
}
}
public function getUserroles()
{
return array('administrator', 'editor');
}
public function login($username)
{
$user = $this->getUser($username);
if($user)
{
$user['lastlogin'] = time();
unset($user['password']);
$this->updateUser($user);
$_SESSION['user'] = $user['username'];
$_SESSION['role'] = $user['userrole'];
$_SESSION['login'] = $user['lastlogin'];
}
}
public function generatePassword($password)
{
return \password_hash($password, PASSWORD_DEFAULT, ['cost' => 10]);
}
<?php
namespace Typemill\Models;
class User extends WriteYaml
{
public function getUsers()
{
$userDir = __DIR__ . '/../../settings/users';
/* check if plugins directory exists */
if(!is_dir($userDir)){ return array(); }
/* get all plugins folder */
$users = array_diff(scandir($userDir), array('..', '.'));
$cleanUser = array();
foreach($users as $key => $user)
{
if($user == '.logins'){ continue; }
$cleanUser[] = str_replace('.yaml', '', $user);
}
return $cleanUser;
}
public function getUser($username)
{
$user = $this->getYaml('settings/users', $username . '.yaml');
return $user;
}
public function createUser($params)
{
$userdata = array(
'username' => $params['username'],
'email' => $params['email'],
'password' => $this->generatePassword($params['password']),
'userrole' => $params['userrole']
);
if($this->updateYaml('settings/users', $userdata['username'] . '.yaml', $userdata))
{
return $userdata['username'];
}
return false;
}
public function updateUser($params)
{
$userdata = $this->getUser($params['username']);
if(isset($params['password']))
{
$params['password'] = $this->generatePassword($params['password']);
}
$update = array_merge($userdata, $params);
$this->updateYaml('settings/users', $userdata['username'] . '.yaml', $update);
return $userdata['username'];
}
public function deleteUser($username)
{
if($this->getUser($username))
{
unlink('settings/users/' . $username . '.yaml');
}
}
public function getUserroles()
{
return array('administrator', 'editor');
}
public function login($username)
{
$user = $this->getUser($username);
if($user)
{
$user['lastlogin'] = time();
unset($user['password']);
$this->updateUser($user);
$_SESSION['user'] = $user['username'];
$_SESSION['role'] = $user['userrole'];
$_SESSION['login'] = $user['lastlogin'];
}
}
public function generatePassword($password)
{
return \password_hash($password, PASSWORD_DEFAULT, ['cost' => 10]);
}
}

View File

@ -1,418 +1,418 @@
<?php
namespace Typemill\Models;
use Typemill\Models\User;
use Valitron\Validator;
class Validation
{
/**
* Constructor with custom validation rules
*
* @param obj $db the database connection.
*/
public function __construct()
{
$user = new User();
Validator::langDir(__DIR__.'/../vendor/vlucas/valitron/lang'); // always set langDir before lang.
Validator::lang('en');
Validator::addRule('userAvailable', function($field, $value, array $params, array $fields) use ($user)
{
$userdata = $user->getUser($value);
if($userdata){ return false; }
return true;
}, 'taken');
Validator::addRule('userExists', function($field, $value, array $params, array $fields) use ($user)
{
$userdata = $user->getUser($value);
if($userdata){ return true; }
return false;
}, 'does not exist');
Validator::addRule('checkPassword', function($field, $value, array $params, array $fields) use ($user)
{
$userdata = $user->getUser($fields['username']);
if($userdata && password_verify($value, $userdata['password'])){ return true; }
return false;
}, 'wrong password');
Validator::addRule('emailAvailable', function($field, $value, array $params, array $fields)
{
$email = 'testmail@gmail.com';
if($email){ return false; }
return true;
}, 'taken');
Validator::addRule('emailKnown', function($field, $value, array $params, array $fields)
{
$email = 'testmail@gmail.com';
if(!$email){ return false; }
return true;
}, 'unknown');
Validator::addRule('noSpecialChars', function($field, $value, array $params, array $fields)
{
$format = '/[!@#$%^&*()_+=\[\]{};\':"\\|,.<>\/?]/';
if ( preg_match($format, $value))
{
return false;
}
return true;
}, 'contains special characters');
Validator::addRule('noHTML', function($field, $value, array $params, array $fields)
{
if ( $value == strip_tags($value) )
{
return true;
}
return false;
}, 'contains html');
Validator::addRule('markdownSecure', function($field, $value, array $params, array $fields)
{
/* strip out code blocks and blockquotes */
$value = preg_replace('/`{4,}[\s\S]+?`{4,}/', '', $value);
$value = preg_replace('/`{3,}[\s\S]+?`{3,}/', '', $value);
$value = preg_replace('/`{2,}[\s\S]+?`{2,}/', '', $value);
$value = preg_replace('/`{1,}[\s\S]+?`{1,}/', '', $value);
$value = preg_replace('/>[\s\S]+?[\n\r]/', '', $value);
if ( $value == strip_tags($value) )
{
return true;
}
return false;
}, 'not secure. For code please use markdown `inline-code` or ````fenced code blocks````.');
}
/**
* validation for signup form
*
* @param array $params with form data.
* @return obj $v the validation object passed to a result method.
*/
public function signin(array $params)
{
$v = new Validator($params);
$v->rule('required', ['username', 'password'])->message("Required");
$v->rule('alphaNum', 'username')->message("Invalid characters");
$v->rule('lengthBetween', 'password', 5, 20)->message("Length between 5 - 20");
$v->rule('lengthBetween', 'username', 3, 20)->message("Length between 3 - 20");
if($v->validate())
{
return true;
}
else
{
return false;
}
}
/**
* validation for signup form
*
* @param array $params with form data.
* @return obj $v the validation object passed to a result method.
*/
public function newUser(array $params, $userroles)
{
$v = new Validator($params);
$v->rule('required', ['username', 'email', 'password'])->message("required");
$v->rule('alphaNum', 'username')->message("invalid characters");
$v->rule('lengthBetween', 'password', 5, 20)->message("Length between 5 - 20");
$v->rule('lengthBetween', 'username', 3, 20)->message("Length between 3 - 20");
$v->rule('userAvailable', 'username')->message("User already exists");
$v->rule('email', 'email')->message("e-mail is invalid");
$v->rule('in', 'userrole', $userroles);
return $this->validationResult($v);
}
public function existingUser(array $params, $userroles)
{
$v = new Validator($params);
$v->rule('required', ['username', 'email', 'userrole'])->message("required");
$v->rule('alphaNum', 'username')->message("invalid");
$v->rule('lengthBetween', 'username', 3, 20)->message("Length between 3 - 20");
$v->rule('userExists', 'username')->message("user does not exist");
$v->rule('email', 'email')->message("e-mail is invalid");
$v->rule('in', 'userrole', $userroles);
return $this->validationResult($v);
}
public function username($username)
{
$v = new Validator($username);
$v->rule('alphaNum', 'username')->message("Only alpha-numeric characters allowed");
$v->rule('lengthBetween', 'username', 3, 20)->message("Length between 3 - 20");
return $this->validationResult($v);
}
/**
* validation for changing the password
*
* @param array $params with form data.
* @return obj $v the validation object passed to a result method.
*/
public function newPassword(array $params)
{
$v = new Validator($params);
$v->rule('required', ['password', 'newpassword']);
$v->rule('lengthBetween', 'newpassword', 5, 20);
$v->rule('checkPassword', 'password')->message("Password is wrong");
return $this->validationResult($v);
}
/**
* validation for system settings
*
* @param array $params with form data.
* @return obj $v the validation object passed to a result method.
*/
public function settings(array $params, array $copyright, $name = false)
{
$v = new Validator($params);
$v->rule('required', ['title', 'author', 'copyright', 'year', 'editor']);
$v->rule('lengthBetween', 'title', 2, 20);
$v->rule('lengthBetween', 'author', 2, 40);
$v->rule('regex', 'title', '/^[\pL0-9_ \-]*$/u');
$v->rule('regex', 'author', '/^[\pL_ \-]*$/u');
$v->rule('integer', 'year');
$v->rule('length', 'year', 4);
$v->rule('in', 'editor', ['raw', 'visual']);
$v->rule('in', 'copyright', $copyright);
return $this->validationResult($v, $name);
}
/**
* validation for content editor
*
* @param array $params with form data.
* @return true or $v->errors with array of errors to use in json-response
*/
public function editorInput(array $params)
{
$v = new Validator($params);
$v->rule('required', ['title', 'content', 'url']);
$v->rule('lengthBetween', 'title', 2, 100);
$v->rule('noHTML', 'title');
$v->rule('markdownSecure', 'content');
if($v->validate())
{
return true;
}
else
{
return $v->errors();
}
}
public function blockInput(array $params)
{
$v = new Validator($params);
$v->rule('required', ['markdown', 'block_id', 'url']);
$v->rule('markdownSecure', 'markdown');
$v->rule('regex', 'block_id', '/^[0-9.]+$/i');
if($v->validate())
{
return true;
}
else
{
return $v->errors();
}
}
/**
* validation for resort navigation
*
* @param array $params with form data.
* @return true or $v->errors with array of errors to use in json-response
*/
public function navigationSort(array $params)
{
$v = new Validator($params);
$v->rule('required', ['item_id', 'parent_id_from', 'parent_id_to']);
$v->rule('regex', 'item_id', '/^[0-9.]+$/i');
$v->rule('regex', 'parent_id_from', '/^[a-zA-Z0-9.]+$/i');
$v->rule('regex', 'parent_id_to', '/^[a-zA-Z0-9.]+$/i');
$v->rule('integer', 'index_new');
if($v->validate())
{
return true;
}
else
{
return $v->errors();
}
}
/**
* validation for new navigation items
*
* @param array $params with form data.
* @return true or $v->errors with array of errors to use in json-response
*/
public function navigationItem(array $params)
{
$v = new Validator($params);
$v->rule('required', ['folder_id', 'item_name', 'type', 'url']);
$v->rule('regex', 'folder_id', '/^[0-9.]+$/i');
$v->rule('noSpecialChars', 'item_name');
$v->rule('lengthBetween', 'item_name', 1, 40);
$v->rule('in', 'type', ['file', 'folder']);
if($v->validate())
{
return true;
}
else
{
return $v->errors();
}
}
/**
* validation for dynamic fields ( settings for themes and plugins)
*
* @param string $fieldName with the name of the field.
* @param array or string $fieldValue with the values of the field.
* @param string $objectName with the name of the plugin or theme.
* @param array $fieldDefinitions with the field definitions as multi-dimensional array.
* @return obj $v the validation object passed to a result method.
*/
public function objectField($fieldName, $fieldValue, $objectName, $fieldDefinitions)
{
$v = new Validator(array($fieldName => $fieldValue));
if(isset($fieldDefinitions['required']))
{
$v->rule('required', $fieldName);
}
switch($fieldDefinitions['type'])
{
case "select":
/* create array with option keys as value */
$options = array();
foreach($fieldDefinitions['options'] as $key => $value){ $options[] = $key; }
$v->rule('in', $fieldName, $options);
break;
case "radio":
$v->rule('in', $fieldName, $fieldDefinitions['options']);
break;
case "checkboxlist":
/* create array with option keys as value */
$options = array();
foreach($fieldDefinitions['options'] as $key => $value){ $options[] = $key; }
/* loop over input values and check, if the options of the field definitions (options for checkboxlist) contains the key (input from user, key is used as value, value is used as label) */
foreach($fieldValue as $key => $value)
{
$v->rule('in', $key, $options);
}
break;
case "color":
$v->rule('regex', $fieldName, '/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/');
break;
case "email":
$v->rule('email', $fieldName);
break;
case "date":
$v->rule('date', $fieldName);
break;
case "checkbox":
$v->rule('accepted', $fieldName);
break;
case "url":
$v->rule('lengthMax', $fieldName, 200);
$v->rule('url', $fieldName);
break;
case "text":
$v->rule('lengthMax', $fieldName, 200);
$v->rule('regex', $fieldName, '/^[\pL0-9_ \-\.\?\!\/\:]*$/u');
break;
case "textarea":
$v->rule('lengthMax', $fieldName, 1000);
$v->rule('noHTML', $fieldName);
// $v->rule('regex', $fieldName, '/<[^<]+>/');
break;
case "paragraph":
$v->rule('lengthMax', $fieldName, 1000);
$v->rule('noHTML', $fieldName);
break;
case "password":
$v->rule('lengthMax', $fieldName, 100);
break;
default:
$v->rule('lengthMax', $fieldName, 1000);
$v->rule('regex', $fieldName, '/^[\pL0-9_ \-]*$/u');
}
return $this->validationResult($v, $objectName);
}
/**
* result for validation
*
* @param obj $v the validation object.
* @return bool
*/
public function validationResult($v, $name = false)
{
if($v->validate())
{
return true;
}
else
{
if($name)
{
if(isset($_SESSION['errors'][$name]))
{
foreach ($v->errors() as $key => $val)
{
$_SESSION['errors'][$name][$key] = $val;
break;
}
}
else
{
$_SESSION['errors'][$name] = $v->errors();
}
}
else
{
$_SESSION['errors'] = $v->errors();
}
return false;
}
}
<?php
namespace Typemill\Models;
use Typemill\Models\User;
use Valitron\Validator;
class Validation
{
/**
* Constructor with custom validation rules
*
* @param obj $db the database connection.
*/
public function __construct()
{
$user = new User();
Validator::langDir(__DIR__.'/../vendor/vlucas/valitron/lang'); // always set langDir before lang.
Validator::lang('en');
Validator::addRule('userAvailable', function($field, $value, array $params, array $fields) use ($user)
{
$userdata = $user->getUser($value);
if($userdata){ return false; }
return true;
}, 'taken');
Validator::addRule('userExists', function($field, $value, array $params, array $fields) use ($user)
{
$userdata = $user->getUser($value);
if($userdata){ return true; }
return false;
}, 'does not exist');
Validator::addRule('checkPassword', function($field, $value, array $params, array $fields) use ($user)
{
$userdata = $user->getUser($fields['username']);
if($userdata && password_verify($value, $userdata['password'])){ return true; }
return false;
}, 'wrong password');
Validator::addRule('emailAvailable', function($field, $value, array $params, array $fields)
{
$email = 'testmail@gmail.com';
if($email){ return false; }
return true;
}, 'taken');
Validator::addRule('emailKnown', function($field, $value, array $params, array $fields)
{
$email = 'testmail@gmail.com';
if(!$email){ return false; }
return true;
}, 'unknown');
Validator::addRule('noSpecialChars', function($field, $value, array $params, array $fields)
{
$format = '/[!@#$%^&*()_+=\[\]{};\':"\\|,.<>\/?]/';
if ( preg_match($format, $value))
{
return false;
}
return true;
}, 'contains special characters');
Validator::addRule('noHTML', function($field, $value, array $params, array $fields)
{
if ( $value == strip_tags($value) )
{
return true;
}
return false;
}, 'contains html');
Validator::addRule('markdownSecure', function($field, $value, array $params, array $fields)
{
/* strip out code blocks and blockquotes */
$value = preg_replace('/`{4,}[\s\S]+?`{4,}/', '', $value);
$value = preg_replace('/`{3,}[\s\S]+?`{3,}/', '', $value);
$value = preg_replace('/`{2,}[\s\S]+?`{2,}/', '', $value);
$value = preg_replace('/`{1,}[\s\S]+?`{1,}/', '', $value);
$value = preg_replace('/>[\s\S]+?[\n\r]/', '', $value);
if ( $value == strip_tags($value) )
{
return true;
}
return false;
}, 'not secure. For code please use markdown `inline-code` or ````fenced code blocks````.');
}
/**
* validation for signup form
*
* @param array $params with form data.
* @return obj $v the validation object passed to a result method.
*/
public function signin(array $params)
{
$v = new Validator($params);
$v->rule('required', ['username', 'password'])->message("Required");
$v->rule('alphaNum', 'username')->message("Invalid characters");
$v->rule('lengthBetween', 'password', 5, 20)->message("Length between 5 - 20");
$v->rule('lengthBetween', 'username', 3, 20)->message("Length between 3 - 20");
if($v->validate())
{
return true;
}
else
{
return false;
}
}
/**
* validation for signup form
*
* @param array $params with form data.
* @return obj $v the validation object passed to a result method.
*/
public function newUser(array $params, $userroles)
{
$v = new Validator($params);
$v->rule('required', ['username', 'email', 'password'])->message("required");
$v->rule('alphaNum', 'username')->message("invalid characters");
$v->rule('lengthBetween', 'password', 5, 20)->message("Length between 5 - 20");
$v->rule('lengthBetween', 'username', 3, 20)->message("Length between 3 - 20");
$v->rule('userAvailable', 'username')->message("User already exists");
$v->rule('email', 'email')->message("e-mail is invalid");
$v->rule('in', 'userrole', $userroles);
return $this->validationResult($v);
}
public function existingUser(array $params, $userroles)
{
$v = new Validator($params);
$v->rule('required', ['username', 'email', 'userrole'])->message("required");
$v->rule('alphaNum', 'username')->message("invalid");
$v->rule('lengthBetween', 'username', 3, 20)->message("Length between 3 - 20");
$v->rule('userExists', 'username')->message("user does not exist");
$v->rule('email', 'email')->message("e-mail is invalid");
$v->rule('in', 'userrole', $userroles);
return $this->validationResult($v);
}
public function username($username)
{
$v = new Validator($username);
$v->rule('alphaNum', 'username')->message("Only alpha-numeric characters allowed");
$v->rule('lengthBetween', 'username', 3, 20)->message("Length between 3 - 20");
return $this->validationResult($v);
}
/**
* validation for changing the password
*
* @param array $params with form data.
* @return obj $v the validation object passed to a result method.
*/
public function newPassword(array $params)
{
$v = new Validator($params);
$v->rule('required', ['password', 'newpassword']);
$v->rule('lengthBetween', 'newpassword', 5, 20);
$v->rule('checkPassword', 'password')->message("Password is wrong");
return $this->validationResult($v);
}
/**
* validation for system settings
*
* @param array $params with form data.
* @return obj $v the validation object passed to a result method.
*/
public function settings(array $params, array $copyright, $name = false)
{
$v = new Validator($params);
$v->rule('required', ['title', 'author', 'copyright', 'year', 'editor']);
$v->rule('lengthBetween', 'title', 2, 20);
$v->rule('lengthBetween', 'author', 2, 40);
$v->rule('regex', 'title', '/^[\pL0-9_ \-]*$/u');
$v->rule('regex', 'author', '/^[\pL_ \-]*$/u');
$v->rule('integer', 'year');
$v->rule('length', 'year', 4);
$v->rule('in', 'editor', ['raw', 'visual']);
$v->rule('in', 'copyright', $copyright);
return $this->validationResult($v, $name);
}
/**
* validation for content editor
*
* @param array $params with form data.
* @return true or $v->errors with array of errors to use in json-response
*/
public function editorInput(array $params)
{
$v = new Validator($params);
$v->rule('required', ['title', 'content', 'url']);
$v->rule('lengthBetween', 'title', 2, 100);
$v->rule('noHTML', 'title');
$v->rule('markdownSecure', 'content');
if($v->validate())
{
return true;
}
else
{
return $v->errors();
}
}
public function blockInput(array $params)
{
$v = new Validator($params);
$v->rule('required', ['markdown', 'block_id', 'url']);
$v->rule('markdownSecure', 'markdown');
$v->rule('regex', 'block_id', '/^[0-9.]+$/i');
if($v->validate())
{
return true;
}
else
{
return $v->errors();
}
}
/**
* validation for resort navigation
*
* @param array $params with form data.
* @return true or $v->errors with array of errors to use in json-response
*/
public function navigationSort(array $params)
{
$v = new Validator($params);
$v->rule('required', ['item_id', 'parent_id_from', 'parent_id_to']);
$v->rule('regex', 'item_id', '/^[0-9.]+$/i');
$v->rule('regex', 'parent_id_from', '/^[a-zA-Z0-9.]+$/i');
$v->rule('regex', 'parent_id_to', '/^[a-zA-Z0-9.]+$/i');
$v->rule('integer', 'index_new');
if($v->validate())
{
return true;
}
else
{
return $v->errors();
}
}
/**
* validation for new navigation items
*
* @param array $params with form data.
* @return true or $v->errors with array of errors to use in json-response
*/
public function navigationItem(array $params)
{
$v = new Validator($params);
$v->rule('required', ['folder_id', 'item_name', 'type', 'url']);
$v->rule('regex', 'folder_id', '/^[0-9.]+$/i');
$v->rule('noSpecialChars', 'item_name');
$v->rule('lengthBetween', 'item_name', 1, 40);
$v->rule('in', 'type', ['file', 'folder']);
if($v->validate())
{
return true;
}
else
{
return $v->errors();
}
}
/**
* validation for dynamic fields ( settings for themes and plugins)
*
* @param string $fieldName with the name of the field.
* @param array or string $fieldValue with the values of the field.
* @param string $objectName with the name of the plugin or theme.
* @param array $fieldDefinitions with the field definitions as multi-dimensional array.
* @return obj $v the validation object passed to a result method.
*/
public function objectField($fieldName, $fieldValue, $objectName, $fieldDefinitions)
{
$v = new Validator(array($fieldName => $fieldValue));
if(isset($fieldDefinitions['required']))
{
$v->rule('required', $fieldName);
}
switch($fieldDefinitions['type'])
{
case "select":
/* create array with option keys as value */
$options = array();
foreach($fieldDefinitions['options'] as $key => $value){ $options[] = $key; }
$v->rule('in', $fieldName, $options);
break;
case "radio":
$v->rule('in', $fieldName, $fieldDefinitions['options']);
break;
case "checkboxlist":
/* create array with option keys as value */
$options = array();
foreach($fieldDefinitions['options'] as $key => $value){ $options[] = $key; }
/* loop over input values and check, if the options of the field definitions (options for checkboxlist) contains the key (input from user, key is used as value, value is used as label) */
foreach($fieldValue as $key => $value)
{
$v->rule('in', $key, $options);
}
break;
case "color":
$v->rule('regex', $fieldName, '/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/');
break;
case "email":
$v->rule('email', $fieldName);
break;
case "date":
$v->rule('date', $fieldName);
break;
case "checkbox":
$v->rule('accepted', $fieldName);
break;
case "url":
$v->rule('lengthMax', $fieldName, 200);
$v->rule('url', $fieldName);
break;
case "text":
$v->rule('lengthMax', $fieldName, 200);
$v->rule('regex', $fieldName, '/^[\pL0-9_ \-\.\?\!\/\:]*$/u');
break;
case "textarea":
$v->rule('lengthMax', $fieldName, 1000);
$v->rule('noHTML', $fieldName);
// $v->rule('regex', $fieldName, '/<[^<]+>/');
break;
case "paragraph":
$v->rule('lengthMax', $fieldName, 1000);
$v->rule('noHTML', $fieldName);
break;
case "password":
$v->rule('lengthMax', $fieldName, 100);
break;
default:
$v->rule('lengthMax', $fieldName, 1000);
$v->rule('regex', $fieldName, '/^[\pL0-9_ \-]*$/u');
}
return $this->validationResult($v, $objectName);
}
/**
* result for validation
*
* @param obj $v the validation object.
* @return bool
*/
public function validationResult($v, $name = false)
{
if($v->validate())
{
return true;
}
else
{
if($name)
{
if(isset($_SESSION['errors'][$name]))
{
foreach ($v->errors() as $key => $val)
{
$_SESSION['errors'][$name][$key] = $val;
break;
}
}
else
{
$_SESSION['errors'][$name] = $v->errors();
}
}
else
{
$_SESSION['errors'] = $v->errors();
}
return false;
}
}
}

View File

@ -1,25 +1,25 @@
<?php
namespace Typemill\Models;
class VersionCheck
{
function checkVersion($url)
{
$opts = array(
'http'=>array(
'method' => "GET",
'header' => "Referer: $url\r\n"
)
);
$context = stream_context_create($opts);
if(false === ($version = @file_get_contents('http://typemill.net/api/v1/checkversion', false, $context)))
{
return false;
}
$version = json_decode($version);
return $version->system->typemill;
}
<?php
namespace Typemill\Models;
class VersionCheck
{
function checkVersion($url)
{
$opts = array(
'http'=>array(
'method' => "GET",
'header' => "Referer: $url\r\n"
)
);
$context = stream_context_create($opts);
if(false === ($version = @file_get_contents('http://typemill.net/api/v1/checkversion', false, $context)))
{
return false;
}
$version = json_decode($version);
return $version->system->typemill;
}
}

View File

@ -1,131 +1,160 @@
<?php
namespace Typemill\Models;
class Write
{
protected $basePath;
public function __construct()
{
$basePath = getcwd() . DIRECTORY_SEPARATOR;
$this->basePath = $basePath;
}
public function checkPath($folder)
{
$folderPath = $this->basePath . $folder;
if(!is_dir($folderPath))
{
if(@mkdir($folderPath, 0774, true))
{
return true;
}
else
{
# throw new \Exception("The folder '{$folder}' is missing and we could not create it. Please create the folder manually on your server.");
return false;
}
}
if(@is_writable($folderPath))
{
return true;
}
else
{
# throw new \Exception("Please make the folder '{$folder}' writable.");
return false;
}
return true;
}
protected function checkFile($folder, $file)
{
if(!file_exists($this->basePath . $folder . DIRECTORY_SEPARATOR . $file))
{
return false;
}
return true;
}
public function writeFile($folder, $file, $data)
{
if($this->checkPath($folder))
{
$filePath = $this->basePath . $folder . DIRECTORY_SEPARATOR . $file;
$openFile = @fopen($filePath, "w");
if(!$openFile)
{
return false;
}
fwrite($openFile, $data);
fclose($openFile);
return true;
}
return false;
}
public function getFile($folderName, $fileName)
{
if($this->checkFile($folderName, $fileName))
{
$fileContent = file_get_contents($folderName . DIRECTORY_SEPARATOR . $fileName);
return $fileContent;
}
return false;
}
public function moveElement($item, $folderPath, $index)
{
$filetypes = array('md', 'txt');
# set new order as string
$newOrder = ($index < 10) ? '0' . $index : $index;
# create new path with foldername or filename but without file-type
$newPath = $this->basePath . 'content' . $folderPath . DIRECTORY_SEPARATOR . $newOrder . '-' . str_replace(" ", "-", $item->name);
if($item->elementType == 'folder')
{
$oldPath = $this->basePath . 'content' . $item->path;
if(@rename($oldPath, $newPath))
{
return true;
}
return false;
}
# create old path but without filetype
$oldPath = substr($item->path, 0, strpos($item->path, "."));
$oldPath = $this->basePath . 'content' . $oldPath;
$result = true;
foreach($filetypes as $filetype)
{
$oldFilePath = $oldPath . '.' . $filetype;
$newFilePath = $newPath . '.' . $filetype;
#check if file with filetype exists and rename
if($oldFilePath != $newFilePath && file_exists($oldFilePath))
{
if(@rename($oldFilePath, $newFilePath))
{
$result = $result;
}
else
{
$result = false;
}
}
}
return $result;
}
<?php
namespace Typemill\Models;
class Write
{
protected $basePath;
public function __construct()
{
$basePath = getcwd() . DIRECTORY_SEPARATOR;
$this->basePath = $basePath;
}
public function checkPath($folder)
{
$folderPath = $this->basePath . $folder;
if(!is_dir($folderPath))
{
if(@mkdir($folderPath, 0774, true))
{
return true;
}
else
{
# throw new \Exception("The folder '{$folder}' is missing and we could not create it. Please create the folder manually on your server.");
return false;
}
}
if(@is_writable($folderPath))
{
return true;
}
else
{
# throw new \Exception("Please make the folder '{$folder}' writable.");
return false;
}
return true;
}
protected function checkFile($folder, $file)
{
if(!file_exists($this->basePath . $folder . DIRECTORY_SEPARATOR . $file))
{
return false;
}
return true;
}
protected function checkFileWithPath($filepath)
{
if(!file_exists($this->basePath . $filepath))
{
return false;
}
return true;
}
public function writeFile($folder, $file, $data)
{
if($this->checkPath($folder))
{
$filePath = $this->basePath . $folder . DIRECTORY_SEPARATOR . $file;
$openFile = @fopen($filePath, "w");
if(!$openFile)
{
return false;
}
fwrite($openFile, $data);
fclose($openFile);
return true;
}
return false;
}
public function getFile($folderName, $fileName)
{
if($this->checkFile($folderName, $fileName))
{
$fileContent = file_get_contents($folderName . DIRECTORY_SEPARATOR . $fileName);
return $fileContent;
}
return false;
}
public function getFileWithPath($filepath)
{
if($this->checkFileWithPath($filepath))
{
$fileContent = file_get_contents($filepath);
return $fileContent;
}
return false;
}
public function deleteFileWithPath($filepath)
{
if($this->checkFileWithPath($filepath))
{
unlink($this->basePath . $filepath);
return true;
}
return false;
}
public function moveElement($item, $folderPath, $index)
{
$filetypes = array('md', 'txt');
# set new order as string
$newOrder = ($index < 10) ? '0' . $index : $index;
# create new path with foldername or filename but without file-type
$newPath = $this->basePath . 'content' . $folderPath . DIRECTORY_SEPARATOR . $newOrder . '-' . str_replace(" ", "-", $item->name);
if($item->elementType == 'folder')
{
$oldPath = $this->basePath . 'content' . $item->path;
if(@rename($oldPath, $newPath))
{
return true;
}
return false;
}
# create old path but without filetype
$oldPath = substr($item->path, 0, strpos($item->path, "."));
$oldPath = $this->basePath . 'content' . $oldPath;
$result = true;
foreach($filetypes as $filetype)
{
$oldFilePath = $oldPath . '.' . $filetype;
$newFilePath = $newPath . '.' . $filetype;
#check if file with filetype exists and rename
if($oldFilePath != $newFilePath && file_exists($oldFilePath))
{
if(@rename($oldFilePath, $newFilePath))
{
$result = $result;
}
else
{
$result = false;
}
}
}
return $result;
}
}

View File

@ -1,84 +1,84 @@
<?php
namespace Typemill\Models;
class WriteCache extends Write
{
/**
* Validates, if the cache is valid or invalid and has to be refreshed
* @param int $duration how many seconds the cache is valid.
* @return boolean for an invalid cache (false) and for a valid cache (true).
*/
public function validate($folderName, $fileName, $duration)
{
if(isset($_SERVER['HTTP_CACHE_CONTROL']) && $_SERVER['HTTP_CACHE_CONTROL'] == 'max-age=0')
{
return false;
}
if(!$this->checkPath($folderName))
{
return false;
}
if(!$this->checkFile($folderName, $fileName))
{
$this->writeFile($folderName, $fileName, time());
return false;
}
$lastRefresh = file_get_contents($folderName . DIRECTORY_SEPARATOR . $fileName);
if(time() - $lastRefresh > $duration)
{
return false;
}
return true;
}
/**
* Updates a cache file.
* Serializes an object and writes it to the cache file together with a file that holds the last refresh time.
* @param object $cacheData has to be an object (e.g. navigation object).
* @param string $cacheFile has to be the name of the file you want to update (in case there are more than one cache files.
*/
public function updateCache($folderName, $cacheFileName, $requestFileName, $cacheData)
{
$sCacheData = serialize($cacheData);
$this->writeFile($folderName, $cacheFileName, $sCacheData);
if($requestFileName)
{
$this->writeFile($folderName, $requestFileName, time());
}
}
/**
* Get the recent cache.
* Takes a filename, gets the file and unserializes the cache into an object.
* @param string $fileName is the name of the cache file.
*/
public function getCache($folderName, $cacheFileName)
{
$sCacheData = $this->getFile($folderName, $cacheFileName);
if($sCacheData)
{
return unserialize($sCacheData);
}
return false;
}
/**
* @todo Create a function to clear a specific cache file
*/
public function clearCache($name)
{
}
/**
* @todo Create a function to clear all cache files
*/
public function clearAllCacheFiles()
{
}
<?php
namespace Typemill\Models;
class WriteCache extends Write
{
/**
* Validates, if the cache is valid or invalid and has to be refreshed
* @param int $duration how many seconds the cache is valid.
* @return boolean for an invalid cache (false) and for a valid cache (true).
*/
public function validate($folderName, $fileName, $duration)
{
if(isset($_SERVER['HTTP_CACHE_CONTROL']) && $_SERVER['HTTP_CACHE_CONTROL'] == 'max-age=0')
{
return false;
}
if(!$this->checkPath($folderName))
{
return false;
}
if(!$this->checkFile($folderName, $fileName))
{
$this->writeFile($folderName, $fileName, time());
return false;
}
$lastRefresh = file_get_contents($folderName . DIRECTORY_SEPARATOR . $fileName);
if(time() - $lastRefresh > $duration)
{
return false;
}
return true;
}
/**
* Updates a cache file.
* Serializes an object and writes it to the cache file together with a file that holds the last refresh time.
* @param object $cacheData has to be an object (e.g. navigation object).
* @param string $cacheFile has to be the name of the file you want to update (in case there are more than one cache files.
*/
public function updateCache($folderName, $cacheFileName, $requestFileName, $cacheData)
{
$sCacheData = serialize($cacheData);
$this->writeFile($folderName, $cacheFileName, $sCacheData);
if($requestFileName)
{
$this->writeFile($folderName, $requestFileName, time());
}
}
/**
* Get the recent cache.
* Takes a filename, gets the file and unserializes the cache into an object.
* @param string $fileName is the name of the cache file.
*/
public function getCache($folderName, $cacheFileName)
{
$sCacheData = $this->getFile($folderName, $cacheFileName);
if($sCacheData)
{
return unserialize($sCacheData);
}
return false;
}
/**
* @todo Create a function to clear a specific cache file
*/
public function clearCache($name)
{
}
/**
* @todo Create a function to clear all cache files
*/
public function clearAllCacheFiles()
{
}
}

View File

@ -1,45 +1,45 @@
<?php
namespace Typemill\Models;
class WriteSitemap extends Write
{
public function updateSitemap($folderName, $sitemapFileName, $requestFileName, $data, $baseUrl)
{
$sitemap = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
$sitemap .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
$sitemap = $this->addUrlSet($sitemap, $baseUrl);
$sitemap .= $this->generateUrlSets($data);
$sitemap .= '</urlset>';
$this->writeFile($folderName, $sitemapFileName, $sitemap);
$this->writeFile($folderName, $requestFileName, time());
}
public function generateUrlSets($data)
{
$urlset = '';
foreach($data as $item)
{
if($item->elementType == 'folder')
{
$urlset = $this->addUrlSet($urlset, $item->urlAbs);
$urlset .= $this->generateUrlSets($item->folderContent, $urlset);
}
else
{
$urlset = $this->addUrlSet($urlset, $item->urlAbs);
}
}
return $urlset;
}
public function addUrlSet($urlset, $url)
{
$urlset .= ' <url>' . "\n";
$urlset .= ' <loc>' . $url . '</loc>' . "\n";
$urlset .= ' </url>' . "\n";
return $urlset;
}
<?php
namespace Typemill\Models;
class WriteSitemap extends Write
{
public function updateSitemap($folderName, $sitemapFileName, $requestFileName, $data, $baseUrl)
{
$sitemap = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
$sitemap .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
$sitemap = $this->addUrlSet($sitemap, $baseUrl);
$sitemap .= $this->generateUrlSets($data);
$sitemap .= '</urlset>';
$this->writeFile($folderName, $sitemapFileName, $sitemap);
$this->writeFile($folderName, $requestFileName, time());
}
public function generateUrlSets($data)
{
$urlset = '';
foreach($data as $item)
{
if($item->elementType == 'folder')
{
$urlset = $this->addUrlSet($urlset, $item->urlAbs);
$urlset .= $this->generateUrlSets($item->folderContent, $urlset);
}
else
{
$urlset = $this->addUrlSet($urlset, $item->urlAbs);
}
}
return $urlset;
}
public function addUrlSet($urlset, $url)
{
$urlset .= ' <url>' . "\n";
$urlset .= ' <loc>' . $url . '</loc>' . "\n";
$urlset .= ' </url>' . "\n";
return $urlset;
}
}

View File

@ -1,38 +1,38 @@
<?php
namespace Typemill\Models;
class WriteYaml extends Write
{
/**
* Get the a yaml file.
* @param string $fileName is the name of the Yaml Folder.
* @param string $yamlFileName is the name of the Yaml File.
*/
public function getYaml($folderName, $yamlFileName)
{
$yaml = $this->getFile($folderName, $yamlFileName);
if($yaml)
{
return \Symfony\Component\Yaml\Yaml::parse($yaml);
}
return false;
}
/**
* Writes a yaml file.
* @param string $fileName is the name of the Yaml Folder.
* @param string $yamlFileName is the name of the Yaml File.
* @param array $contentArray is the content as an array.
*/
public function updateYaml($folderName, $yamlFileName, $contentArray)
{
$yaml = \Symfony\Component\Yaml\Yaml::dump($contentArray,6);
if($this->writeFile($folderName, $yamlFileName, $yaml))
{
return true;
}
return false;
}
<?php
namespace Typemill\Models;
class WriteYaml extends Write
{
/**
* Get the a yaml file.
* @param string $fileName is the name of the Yaml Folder.
* @param string $yamlFileName is the name of the Yaml File.
*/
public function getYaml($folderName, $yamlFileName)
{
$yaml = $this->getFile($folderName, $yamlFileName);
if($yaml)
{
return \Symfony\Component\Yaml\Yaml::parse($yaml);
}
return false;
}
/**
* Writes a yaml file.
* @param string $fileName is the name of the Yaml Folder.
* @param string $yamlFileName is the name of the Yaml File.
* @param array $contentArray is the content as an array.
*/
public function updateYaml($folderName, $yamlFileName, $contentArray)
{
$yaml = \Symfony\Component\Yaml\Yaml::dump($contentArray,6);
if($this->writeFile($folderName, $yamlFileName, $yaml))
{
return true;
}
return false;
}
}

View File

@ -1,149 +1,194 @@
<?php
namespace Typemill;
use \Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Typemill\Models\Fields;
use Typemill\Models\WriteYaml;
use Typemill\Extensions\ParsedownExtension;
abstract class Plugin implements EventSubscriberInterface
{
protected $container;
/**
* Constructor
*
*/
public function __construct($container)
{
$this->container = $container;
}
protected function getSettings()
{
return $this->container->get('settings');
}
protected function getPluginSettings($plugin)
{
return $this->container->get('settings')['plugins'][$plugin];
}
protected function getRoute()
{
return $this->container['request']->getUri();
}
protected function getPath()
{
return $this->container['request']->getUri()->getPath();
}
protected function getDispatcher()
{
return $this->$dispatcher;
}
protected function getTwig()
{
return $this->container['view'];
}
protected function addTwigGlobal($name, $class)
{
$this->container->view->getEnvironment()->addGlobal($name, $class);
}
protected function addTwigFilter($name, $filter)
{
$filter = new \Twig_SimpleFilter($name, $filter);
$this->container->view->getEnvironment()->addFilter($filter);
}
protected function addTwigFunction($name, $function)
{
$function = new \Twig_SimpleFunction($name, $function);
$this->container->view->getEnvironment()->addFunction($function);
}
protected function addJS($JS)
{
$this->container->assets->addJS($JS);
}
protected function addInlineJS($JS)
{
$this->container->assets->addInlineJS($JS);
}
protected function addCSS($CSS)
{
$this->container->assets->addCSS($CSS);
}
protected function addInlineCSS($CSS)
{
$this->container->assets->addInlineCSS($CSS);
}
protected function markdownToHtml($markdown)
{
$parsedown = new ParsedownExtension();
$contentArray = $parsedown->text($markdown);
$html = $parsedown->markup($contentArray,false);
return $html;
}
protected function getFormData($pluginName)
{
$flash = $this->container->flash->getMessages();
if(isset($flash['formdata']))
{
$yaml = new Models\WriteYaml();
$formdata = $yaml->getYaml('settings', 'formdata.yaml');
$yaml->updateYaml('settings', 'formdata.yaml', '');
if($flash['formdata'][0] == $pluginName && isset($formdata[$pluginName]))
{
return $formdata[$pluginName];
}
}
elseif(isset($flash['publicform']) && $flash['publicform'][0] == 'bot')
{
return 'bot';
}
return false;
}
protected function generateForm($pluginName)
{
$fieldsModel = new Fields();
$pluginDefinitions = \Typemill\Settings::getObjectSettings('plugins', $pluginName);
$settings = $this->getSettings();
$buttonlabel = isset($settings['plugins'][$pluginName]['button_label']) ? $settings['plugins'][$pluginName]['button_label'] : false;
$recaptcha = isset($settings['plugins'][$pluginName]['recaptcha']) ? $settings['plugins'][$pluginName]['recaptcha_webkey'] : false;
if(isset($pluginDefinitions['public']['fields']))
{
# add simple honeypot spam protection
$pluginDefinitions['public']['fields']['personal-mail'] = ['type' => 'text', 'class' => 'personal-mail'];
# get all the fields and prefill them with the dafault-data, the user-data or old input data
$fields = $fieldsModel->getFields($settings, 'plugins', $pluginName, $pluginDefinitions, 'public');
# get Twig Instance
$twig = $this->getTwig();
# render each field and add it to the form
$form = $twig->fetch('/partials/form.twig', ['fields' => $fields, 'itemName' => $pluginName, 'object' => 'plugins', 'recaptcha_webkey' => $recaptcha, 'buttonlabel' => $buttonlabel]);
}
return $form;
}
<?php
namespace Typemill;
use \Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Typemill\Models\Fields;
use Typemill\Models\WriteYaml;
use Typemill\Extensions\ParsedownExtension;
abstract class Plugin implements EventSubscriberInterface
{
protected $container;
/**
* Constructor
*
*/
public function __construct($container)
{
$this->container = $container;
}
protected function isXhr()
{
if($this->container['request']->isXhr())
{
return true;
}
return false;
}
protected function getParams()
{
return $this->container['request']->getParams();
}
protected function returnJson($data)
{
return $this->container['response']
->withHeader("Content-Type", "application/json")
->withStatus(200)
->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
}
protected function returnJsonError($data)
{
return $this->container['response']
->withHeader("Content-Type", "application/json")
->withStatus(400)
->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
}
protected function getSettings()
{
return $this->container->get('settings');
}
protected function getPluginSettings($plugin)
{
return $this->container->get('settings')['plugins'][$plugin];
}
protected function getRoute()
{
return $this->container['request']->getUri();
}
protected function getPath()
{
return $this->container['request']->getUri()->getPath();
}
protected function getDispatcher()
{
return $this->$dispatcher;
}
protected function getTwig()
{
return $this->container['view'];
}
protected function addTwigGlobal($name, $class)
{
$this->container->view->getEnvironment()->addGlobal($name, $class);
}
protected function addTwigFilter($name, $filter)
{
$filter = new \Twig_SimpleFilter($name, $filter);
$this->container->view->getEnvironment()->addFilter($filter);
}
protected function addTwigFunction($name, $function)
{
$function = new \Twig_SimpleFunction($name, $function);
$this->container->view->getEnvironment()->addFunction($function);
}
protected function addJS($JS)
{
$this->container->assets->addJS($JS);
}
protected function addInlineJS($JS)
{
$this->container->assets->addInlineJS($JS);
}
protected function addCSS($CSS)
{
$this->container->assets->addCSS($CSS);
}
protected function addInlineCSS($CSS)
{
$this->container->assets->addInlineCSS($CSS);
}
protected function activateAxios()
{
$this->container->assets->activateAxios();
}
protected function activateVue()
{
$this->container->assets->activateVue();
}
protected function activateTachyons()
{
$this->container->assets->activateTachyons();
}
protected function markdownToHtml($markdown)
{
$parsedown = new ParsedownExtension();
$contentArray = $parsedown->text($markdown);
$html = $parsedown->markup($contentArray,false);
return $html;
}
protected function getFormData($pluginName)
{
$flash = $this->container->flash->getMessages();
if(isset($flash['formdata']))
{
$yaml = new Models\WriteYaml();
$formdata = $yaml->getYaml('settings', 'formdata.yaml');
$yaml->updateYaml('settings', 'formdata.yaml', '');
if($flash['formdata'][0] == $pluginName && isset($formdata[$pluginName]))
{
return $formdata[$pluginName];
}
}
elseif(isset($flash['publicform']) && $flash['publicform'][0] == 'bot')
{
return 'bot';
}
return false;
}
protected function generateForm($pluginName)
{
$fieldsModel = new Fields();
$pluginDefinitions = \Typemill\Settings::getObjectSettings('plugins', $pluginName);
$settings = $this->getSettings();
$buttonlabel = isset($settings['plugins'][$pluginName]['button_label']) ? $settings['plugins'][$pluginName]['button_label'] : false;
$recaptcha = isset($settings['plugins'][$pluginName]['recaptcha']) ? $settings['plugins'][$pluginName]['recaptcha_webkey'] : false;
if(isset($pluginDefinitions['public']['fields']))
{
# add simple honeypot spam protection
$pluginDefinitions['public']['fields']['personal-mail'] = ['type' => 'text', 'class' => 'personal-mail'];
# get all the fields and prefill them with the dafault-data, the user-data or old input data
$fields = $fieldsModel->getFields($settings, 'plugins', $pluginName, $pluginDefinitions, 'public');
# get Twig Instance
$twig = $this->getTwig();
# render each field and add it to the form
$form = $twig->fetch('/partials/form.twig', ['fields' => $fields, 'itemName' => $pluginName, 'object' => 'plugins', 'recaptcha_webkey' => $recaptcha, 'buttonlabel' => $buttonlabel]);
}
return $form;
}
}

View File

@ -1,112 +1,112 @@
<?php
namespace Typemill;
class Plugins
{
public function load()
{
$pluginFolder = $this->scanPluginFolder();
$classNames = array();
/* iterate over plugin folders */
foreach($pluginFolder as $plugin)
{
$className = '\\Plugins\\' . $plugin . '\\' . $plugin;
/* if plugin-class and subscribe-method exists, add classname to array */
if(class_exists($className) /* && method_exists($className, 'getSubscribedEvents') */)
{
$classNames[] = array('className' => $className, 'name' => $plugin);
}
}
return $classNames;
}
public function getNewRoutes($className, $routes)
{
/* if route-method exists in plugin-class */
if(method_exists($className, 'addNewRoutes'))
{
/* add the routes */
$pluginRoutes = $className::addNewRoutes();
/* multi-dimensional or simple array of routes */
if(isset($pluginRoutes[0]))
{
/* if they are properly formatted, add them to routes array */
foreach($pluginRoutes as $pluginRoute)
{
if($this->checkRouteArray($routes,$pluginRoute))
{
$pluginRoute['route'] = strtolower($pluginRoute['route']);
$routes[] = $pluginRoute;
}
}
}
elseif(is_array($routes))
{
if($this->checkRouteArray($routes,$pluginRoutes))
{
$pluginRoutes['route'] = strtolower($pluginRoutes['route']);
$routes[] = $pluginRoutes;
}
}
}
return $routes;
}
public function getNewMiddleware($className, $middleware)
{
if(method_exists($className, 'addNewMiddleware'))
{
$pluginMiddleware = $className::addNewMiddleware();
if($pluginMiddleware)
{
$middleware[] = $pluginMiddleware;
}
}
return $middleware;
}
private function checkRouteArray($routes,$route)
{
if(
isset($route['httpMethod']) AND in_array($route['httpMethod'], array('get','post','put','delete','head','patch','options'))
AND isset($route['route']) AND is_string($route['route'])
AND isset($route['class']) AND is_string($route['class'])
AND !$this->in_array_r(strtolower($route['route']),$routes))
{
return true;
}
return false;
}
private function in_array_r($needle, $haystack, $strict = false)
{
foreach ($haystack as $item)
{
if (($strict ? $item === $needle : $item == $needle) || (is_array($item) && $this->in_array_r($needle, $item, $strict)))
{
return true;
}
}
return false;
}
private function scanPluginFolder()
{
$pluginsDir = __DIR__ . '/../plugins';
/* check if plugins directory exists */
if(!is_dir($pluginsDir)){ return array(); }
/* get all plugins folder */
$plugins = array_diff(scandir($pluginsDir), array('..', '.'));
return $plugins;
}
<?php
namespace Typemill;
class Plugins
{
public function load()
{
$pluginFolder = $this->scanPluginFolder();
$classNames = array();
/* iterate over plugin folders */
foreach($pluginFolder as $plugin)
{
$className = '\\Plugins\\' . $plugin . '\\' . $plugin;
/* if plugin-class and subscribe-method exists, add classname to array */
if(class_exists($className) /* && method_exists($className, 'getSubscribedEvents') */)
{
$classNames[] = array('className' => $className, 'name' => $plugin);
}
}
return $classNames;
}
public function getNewRoutes($className, $routes)
{
/* if route-method exists in plugin-class */
if(method_exists($className, 'addNewRoutes'))
{
/* add the routes */
$pluginRoutes = $className::addNewRoutes();
/* multi-dimensional or simple array of routes */
if(isset($pluginRoutes[0]))
{
/* if they are properly formatted, add them to routes array */
foreach($pluginRoutes as $pluginRoute)
{
if($this->checkRouteArray($routes,$pluginRoute))
{
$pluginRoute['route'] = strtolower($pluginRoute['route']);
$routes[] = $pluginRoute;
}
}
}
elseif(is_array($routes))
{
if($this->checkRouteArray($routes,$pluginRoutes))
{
$pluginRoutes['route'] = strtolower($pluginRoutes['route']);
$routes[] = $pluginRoutes;
}
}
}
return $routes;
}
public function getNewMiddleware($className, $middleware)
{
if(method_exists($className, 'addNewMiddleware'))
{
$pluginMiddleware = $className::addNewMiddleware();
if($pluginMiddleware)
{
$middleware[] = $pluginMiddleware;
}
}
return $middleware;
}
private function checkRouteArray($routes,$route)
{
if(
isset($route['httpMethod']) AND in_array($route['httpMethod'], array('get','post','put','delete','head','patch','options'))
AND isset($route['route']) AND is_string($route['route'])
AND isset($route['class']) AND is_string($route['class'])
AND !$this->in_array_r(strtolower($route['route']),$routes))
{
return true;
}
return false;
}
private function in_array_r($needle, $haystack, $strict = false)
{
foreach ($haystack as $item)
{
if (($strict ? $item === $needle : $item == $needle) || (is_array($item) && $this->in_array_r($needle, $item, $strict)))
{
return true;
}
}
return false;
}
private function scanPluginFolder()
{
$pluginsDir = __DIR__ . '/../plugins';
/* check if plugins directory exists */
if(!is_dir($pluginsDir)){ return array(); }
/* get all plugins folder */
$plugins = array_diff(scandir($pluginsDir), array('..', '.'));
return $plugins;
}
}

View File

@ -1,29 +1,29 @@
<?php
use Typemill\Controllers\SettingsController;
use Typemill\Controllers\ContentController;
use Typemill\Controllers\ContentApiController;
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->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/basefolder', ContentApiController::class . ':createBaseFolder')->setName('api.basefolder.create')->add(new RestrictApiAccess($container['router']));
$app->get('/api/v1/navigation', ContentApiController::class . ':getNavigation')->setName('api.navigation.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/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']));
<?php
use Typemill\Controllers\SettingsController;
use Typemill\Controllers\ContentController;
use Typemill\Controllers\ContentApiController;
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->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/basefolder', ContentApiController::class . ':createBaseFolder')->setName('api.basefolder.create')->add(new RestrictApiAccess($container['router']));
$app->get('/api/v1/navigation', ContentApiController::class . ':getNavigation')->setName('api.navigation.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/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']));

View File

@ -1,72 +1,72 @@
<?php
use Typemill\Controllers\PageController;
use Typemill\Controllers\FormController;
use Typemill\Controllers\SetupController;
use Typemill\Controllers\AuthController;
use Typemill\Controllers\SettingsController;
use Typemill\Controllers\ContentBackendController;
use Typemill\Middleware\RedirectIfUnauthenticated;
use Typemill\Middleware\RedirectIfAuthenticated;
use Typemill\Middleware\RedirectIfNoAdmin;
if($settings['settings']['setup'])
{
$app->get('/setup', SetupController::class . ':show')->setName('setup.show');
$app->post('/setup', SetupController::class . ':create')->setName('setup.create');
}
else
{
$app->get('/setup', AuthController::class . ':redirect');
}
if($settings['settings']['welcome'])
{
$app->get('/setup/welcome', SetupController::class . ':welcome')->setName('setup.welcome')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
}
else
{
$app->get('/setup/welcome', AuthController::class . ':redirect')->setName('setup.welcome');
}
$app->post('/tm/formpost', FormController::class . ':savePublicForm')->setName('form.save');
$app->get('/tm', AuthController::class . ':redirect');
$app->get('/tm/login', AuthController::class . ':show')->setName('auth.show')->add(new RedirectIfAuthenticated($container['router']));
$app->post('/tm/login', AuthController::class . ':login')->setName('auth.login')->add(new RedirectIfAuthenticated($container['router']));
$app->get('/tm/logout', AuthController::class . ':logout')->setName('auth.logout')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
$app->get('/tm/settings', SettingsController::class . ':showSettings')->setName('settings.show')->add(new RedirectIfNoAdmin($container['router'], $container['flash']));
$app->post('/tm/settings', SettingsController::class . ':saveSettings')->setName('settings.save')->add(new RedirectIfNoAdmin($container['router'], $container['flash']));
$app->get('/tm/themes', SettingsController::class . ':showThemes')->setName('themes.show')->add(new RedirectIfNoAdmin($container['router'], $container['flash']));
$app->post('/tm/themes', SettingsController::class . ':saveThemes')->setName('themes.save')->add(new RedirectIfNoAdmin($container['router'], $container['flash']));
$app->get('/tm/plugins', SettingsController::class . ':showPlugins')->setName('plugins.show')->add(new RedirectIfNoAdmin($container['router'], $container['flash']));
$app->post('/tm/plugins', SettingsController::class . ':savePlugins')->setName('plugins.save')->add(new RedirectIfNoAdmin($container['router'], $container['flash']));
$app->get('/tm/user/new', SettingsController::class . ':newUser')->setName('user.new')->add(new RedirectIfNoAdmin($container['router'], $container['flash']));
$app->post('/tm/user/create', SettingsController::class . ':createUser')->setName('user.create')->add(new RedirectIfNoAdmin($container['router'], $container['flash']));
$app->post('/tm/user/update', SettingsController::class . ':updateUser')->setName('user.update')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
$app->post('/tm/user/delete', SettingsController::class . ':deleteUser')->setName('user.delete')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
$app->get('/tm/user/{username}', SettingsController::class . ':showUser')->setName('user.show')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
$app->get('/tm/user', SettingsController::class . ':listUser')->setName('user.list')->add(new RedirectIfNoAdmin($container['router'], $container['flash']));
$app->get('/tm/content/raw[/{params:.*}]', ContentBackendController::class . ':showContent')->setName('content.raw')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
$app->get('/tm/content/visual[/{params:.*}]', ContentBackendController::class . ':showBlox')->setName('content.visual')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
$app->get('/tm/content[/{params:.*}]', ContentBackendController::class . ':showEmpty')->setName('content.empty')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
foreach($routes as $pluginRoute)
{
$method = $pluginRoute['httpMethod'];
$route = $pluginRoute['route'];
$class = $pluginRoute['class'];
if(isset($pluginRoute['name']))
{
$app->{$method}($route, $class)->setName($pluginRoute['name']);
}
else
{
$app->{$method}($route, $class);
}
}
<?php
use Typemill\Controllers\PageController;
use Typemill\Controllers\FormController;
use Typemill\Controllers\SetupController;
use Typemill\Controllers\AuthController;
use Typemill\Controllers\SettingsController;
use Typemill\Controllers\ContentBackendController;
use Typemill\Middleware\RedirectIfUnauthenticated;
use Typemill\Middleware\RedirectIfAuthenticated;
use Typemill\Middleware\RedirectIfNoAdmin;
if($settings['settings']['setup'])
{
$app->get('/setup', SetupController::class . ':show')->setName('setup.show');
$app->post('/setup', SetupController::class . ':create')->setName('setup.create');
}
else
{
$app->get('/setup', AuthController::class . ':redirect');
}
if($settings['settings']['welcome'])
{
$app->get('/setup/welcome', SetupController::class . ':welcome')->setName('setup.welcome')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
}
else
{
$app->get('/setup/welcome', AuthController::class . ':redirect')->setName('setup.welcome');
}
$app->post('/tm/formpost', FormController::class . ':savePublicForm')->setName('form.save');
$app->get('/tm', AuthController::class . ':redirect');
$app->get('/tm/login', AuthController::class . ':show')->setName('auth.show')->add(new RedirectIfAuthenticated($container['router']));
$app->post('/tm/login', AuthController::class . ':login')->setName('auth.login')->add(new RedirectIfAuthenticated($container['router']));
$app->get('/tm/logout', AuthController::class . ':logout')->setName('auth.logout')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
$app->get('/tm/settings', SettingsController::class . ':showSettings')->setName('settings.show')->add(new RedirectIfNoAdmin($container['router'], $container['flash']));
$app->post('/tm/settings', SettingsController::class . ':saveSettings')->setName('settings.save')->add(new RedirectIfNoAdmin($container['router'], $container['flash']));
$app->get('/tm/themes', SettingsController::class . ':showThemes')->setName('themes.show')->add(new RedirectIfNoAdmin($container['router'], $container['flash']));
$app->post('/tm/themes', SettingsController::class . ':saveThemes')->setName('themes.save')->add(new RedirectIfNoAdmin($container['router'], $container['flash']));
$app->get('/tm/plugins', SettingsController::class . ':showPlugins')->setName('plugins.show')->add(new RedirectIfNoAdmin($container['router'], $container['flash']));
$app->post('/tm/plugins', SettingsController::class . ':savePlugins')->setName('plugins.save')->add(new RedirectIfNoAdmin($container['router'], $container['flash']));
$app->get('/tm/user/new', SettingsController::class . ':newUser')->setName('user.new')->add(new RedirectIfNoAdmin($container['router'], $container['flash']));
$app->post('/tm/user/create', SettingsController::class . ':createUser')->setName('user.create')->add(new RedirectIfNoAdmin($container['router'], $container['flash']));
$app->post('/tm/user/update', SettingsController::class . ':updateUser')->setName('user.update')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
$app->post('/tm/user/delete', SettingsController::class . ':deleteUser')->setName('user.delete')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
$app->get('/tm/user/{username}', SettingsController::class . ':showUser')->setName('user.show')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
$app->get('/tm/user', SettingsController::class . ':listUser')->setName('user.list')->add(new RedirectIfNoAdmin($container['router'], $container['flash']));
$app->get('/tm/content/raw[/{params:.*}]', ContentBackendController::class . ':showContent')->setName('content.raw')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
$app->get('/tm/content/visual[/{params:.*}]', ContentBackendController::class . ':showBlox')->setName('content.visual')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
$app->get('/tm/content[/{params:.*}]', ContentBackendController::class . ':showEmpty')->setName('content.empty')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
foreach($routes as $pluginRoute)
{
$method = $pluginRoute['httpMethod'];
$route = $pluginRoute['route'];
$class = $pluginRoute['class'];
if(isset($pluginRoute['name']))
{
$app->{$method}($route, $class)->setName($pluginRoute['name']);
}
else
{
$app->{$method}($route, $class);
}
}
$app->get('/[{params:.*}]', PageController::class . ':index')->setName('home');

View File

@ -1,124 +1,124 @@
<?php
namespace Typemill;
class Settings
{
public static function loadSettings()
{
$defaultSettings = self::getDefaultSettings();
$userSettings = self::getUserSettings();
$settings = $defaultSettings;
if($userSettings)
{
$settings = array_merge($defaultSettings, $userSettings);
}
return array('settings' => $settings);
}
public static function getDefaultSettings()
{
$rootPath = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR;
return [
'determineRouteBeforeAppMiddleware' => true,
'displayErrorDetails' => false,
'title' => 'TYPEMILL',
'author' => 'Unknown',
'copyright' => 'Copyright',
'language' => 'en',
'startpage' => true,
'rootPath' => $rootPath,
'theme' => ($theme = 'typemill'),
'themeFolder' => ($themeFolder = 'themes'),
'themeBasePath' => $rootPath,
'themePath' => $rootPath . $themeFolder . DIRECTORY_SEPARATOR . $theme,
'settingsPath' => $rootPath . 'settings',
'userPath' => $rootPath . 'settings' . DIRECTORY_SEPARATOR . 'users',
'authorPath' => __DIR__ . DIRECTORY_SEPARATOR . 'author' . DIRECTORY_SEPARATOR,
'editor' => 'raw',
'contentFolder' => 'content',
'cache' => true,
'cachePath' => $rootPath . 'cache',
'version' => '1.2.15',
'setup' => true,
'welcome' => true,
'images' => ['live' => ['width' => 820], 'mlibrary' => ['width' => 50, 'height' => 50]],
];
}
public static function getUserSettings()
{
$yaml = new Models\WriteYaml();
$userSettings = $yaml->getYaml('settings', 'settings.yaml');
return $userSettings;
}
public static function getObjectSettings($objectType, $objectName)
{
$yaml = new Models\WriteYaml();
$objectFolder = $objectType . DIRECTORY_SEPARATOR . $objectName;
$objectFile = $objectName . '.yaml';
$objectSettings = $yaml->getYaml($objectFolder, $objectFile);
return $objectSettings;
}
public static function createSettings()
{
$yaml = new Models\WriteYaml();
# create initial settings file with only setup false
if($yaml->updateYaml('settings', 'settings.yaml', array('setup' => false)))
{
return true;
}
return false;
}
public static function updateSettings($settings)
{
# only allow if usersettings already exists (setup has been done)
$userSettings = self::getUserSettings();
if($userSettings)
{
# whitelist settings that can be stored in usersettings (values are not relevant here, only keys)
$allowedUserSettings = ['displayErrorDetails' => false,
'title' => false,
'copyright' => false,
'language' => false,
'startpage' => false,
'author' => false,
'year' => false,
'theme' => false,
'editor' => false,
'setup' => false,
'welcome' => false,
'images' => false,
'plugins' => false,
'themes' => false,
'latestVersion' => false
];
# cleanup the existing usersettings
$userSettings = array_intersect_key($userSettings, $allowedUserSettings);
# cleanup the new settings passed as an argument
$settings = array_intersect_key($settings, $allowedUserSettings);
# merge usersettings with new settings
$settings = array_merge($userSettings, $settings);
/* write settings to yaml */
$yaml = new Models\WriteYaml();
$yaml->updateYaml('settings', 'settings.yaml', $settings);
}
}
<?php
namespace Typemill;
class Settings
{
public static function loadSettings()
{
$defaultSettings = self::getDefaultSettings();
$userSettings = self::getUserSettings();
$settings = $defaultSettings;
if($userSettings)
{
$settings = array_merge($defaultSettings, $userSettings);
}
return array('settings' => $settings);
}
public static function getDefaultSettings()
{
$rootPath = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR;
return [
'determineRouteBeforeAppMiddleware' => true,
'displayErrorDetails' => false,
'title' => 'TYPEMILL',
'author' => 'Unknown',
'copyright' => 'Copyright',
'language' => 'en',
'startpage' => true,
'rootPath' => $rootPath,
'theme' => ($theme = 'typemill'),
'themeFolder' => ($themeFolder = 'themes'),
'themeBasePath' => $rootPath,
'themePath' => $rootPath . $themeFolder . DIRECTORY_SEPARATOR . $theme,
'settingsPath' => $rootPath . 'settings',
'userPath' => $rootPath . 'settings' . DIRECTORY_SEPARATOR . 'users',
'authorPath' => __DIR__ . DIRECTORY_SEPARATOR . 'author' . DIRECTORY_SEPARATOR,
'editor' => 'raw',
'contentFolder' => 'content',
'cache' => true,
'cachePath' => $rootPath . 'cache',
'version' => '1.2.15',
'setup' => true,
'welcome' => true,
'images' => ['live' => ['width' => 820], 'mlibrary' => ['width' => 50, 'height' => 50]],
];
}
public static function getUserSettings()
{
$yaml = new Models\WriteYaml();
$userSettings = $yaml->getYaml('settings', 'settings.yaml');
return $userSettings;
}
public static function getObjectSettings($objectType, $objectName)
{
$yaml = new Models\WriteYaml();
$objectFolder = $objectType . DIRECTORY_SEPARATOR . $objectName;
$objectFile = $objectName . '.yaml';
$objectSettings = $yaml->getYaml($objectFolder, $objectFile);
return $objectSettings;
}
public static function createSettings()
{
$yaml = new Models\WriteYaml();
# create initial settings file with only setup false
if($yaml->updateYaml('settings', 'settings.yaml', array('setup' => false)))
{
return true;
}
return false;
}
public static function updateSettings($settings)
{
# only allow if usersettings already exists (setup has been done)
$userSettings = self::getUserSettings();
if($userSettings)
{
# whitelist settings that can be stored in usersettings (values are not relevant here, only keys)
$allowedUserSettings = ['displayErrorDetails' => false,
'title' => false,
'copyright' => false,
'language' => false,
'startpage' => false,
'author' => false,
'year' => false,
'theme' => false,
'editor' => false,
'setup' => false,
'welcome' => false,
'images' => false,
'plugins' => false,
'themes' => false,
'latestVersion' => false
];
# cleanup the existing usersettings
$userSettings = array_intersect_key($userSettings, $allowedUserSettings);
# cleanup the new settings passed as an argument
$settings = array_intersect_key($settings, $allowedUserSettings);
# merge usersettings with new settings
$settings = array_merge($userSettings, $settings);
/* write settings to yaml */
$yaml = new Models\WriteYaml();
$yaml->updateYaml('settings', 'settings.yaml', $settings);
}
}
}

View File

@ -1,47 +1,47 @@
{% extends 'layouts/layoutAuth.twig' %}
{% block title %}Login{% endblock %}
{% block content %}
<div class="setupWrapper">
<div class="setupContent">
<p><i class="icon-bookmark-empty"></i>Remember to bookmark this page</p>
</div>
<div class="authformWrapper">
<form method="POST" action="{{ path_for("auth.login") }}" autocomplete="off">
<fieldset class="auth">
<div class="formElement{{ errors.username ? ' errors' : '' }}">
<label for="username">Username <abbr title="required">*</abbr></label>
<input type="text" name="username" value="{{ old.username }}" required>
{% if errors.signup_username %}
<span class="error">{{ errors.username | first }}</span>
{% endif %}
</div>
<div class="formElement{{ errors.password ? ' errors' : '' }}">
<label for="password">Password <abbr title="required">*</abbr></label>
<input type="password" name="password" required autoomplete="off">
{% if errors.password %}
<span class="error">{{ errors.password | first }}</span>
{% endif %}
</div>
</fieldset>
<div class="loginarea" id="loginarea">
<input type="submit" value="Login" id="loginbutton" class="loginbutton" />
{{ csrf_field() | raw }}
{% if messages.time %}
<div id="counter" class="counter">wait <span id="wait">{{ messages.time }}</span> sec</div>
<div class="forgotpw"><a href="https://typemill.net/writers/forgot-password" target="_blank">Forgot password?</a></div>
{% endif %}
</div>
</form>
</div>
<div class="setupContent">
<p><a href="{{ base_url() }}">back to startpage</a></p>
</div>
</div>
<footer></footer>
{% extends 'layouts/layoutAuth.twig' %}
{% block title %}Login{% endblock %}
{% block content %}
<div class="setupWrapper">
<div class="setupContent">
<p><i class="icon-bookmark-empty"></i>Remember to bookmark this page</p>
</div>
<div class="authformWrapper">
<form method="POST" action="{{ path_for("auth.login") }}" autocomplete="off">
<fieldset class="auth">
<div class="formElement{{ errors.username ? ' errors' : '' }}">
<label for="username">Username <abbr title="required">*</abbr></label>
<input type="text" name="username" value="{{ old.username }}" required>
{% if errors.signup_username %}
<span class="error">{{ errors.username | first }}</span>
{% endif %}
</div>
<div class="formElement{{ errors.password ? ' errors' : '' }}">
<label for="password">Password <abbr title="required">*</abbr></label>
<input type="password" name="password" required autoomplete="off">
{% if errors.password %}
<span class="error">{{ errors.password | first }}</span>
{% endif %}
</div>
</fieldset>
<div class="loginarea" id="loginarea">
<input type="submit" value="Login" id="loginbutton" class="loginbutton" />
{{ csrf_field() | raw }}
{% if messages.time %}
<div id="counter" class="counter">wait <span id="wait">{{ messages.time }}</span> sec</div>
<div class="forgotpw"><a href="https://typemill.net/writers/forgot-password" target="_blank">Forgot password?</a></div>
{% endif %}
</div>
</form>
</div>
<div class="setupContent">
<p><a href="{{ base_url() }}">back to startpage</a></p>
</div>
</div>
<footer></footer>
{% endblock %}

View File

@ -1,41 +1,41 @@
{% extends 'layouts/layoutAuth.twig' %}
{% block title %}Setup{% endblock %}
{% block content %}
<div class="setupWrapper">
<div class="authformWrapper">
<form method="POST" action="{{ path_for('setup.create') }}" autocomplete="off">
<fieldset class="auth">
<div class="formElement{{ errors.username ? ' errors' : '' }}">
<label for="username">Username <abbr title="required">*</abbr></label>
<input type="text" name="username" value="{{ old.username }}" required>
{% if errors.username %}
<span class="error">{{ errors.username | first }}</span>
{% endif %}
</div>
<div class="formElement{{ errors.email ? ' errors' : '' }}">
<label for="email">E-Mail <abbr title="required">*</abbr></label>
<input type="text" name="email" value="{{ old.email }}" required>
{% if errors.email %}
<span class="error">{{ errors.email | first }}</span>
{% endif %}
</div>
<div class="formElement{{ errors.password ? ' errors' : '' }}">
<label for="password">Password <abbr title="required">*</abbr></label>
<input type="password" name="password" required autocomplete="off">
{% if errors.password %}
<span class="error">{{ errors.password | first }}</span>
{% endif %}
</div>
</fieldset>
<input type="submit" value="Create User" />
{{ csrf_field() | raw }}
</form>
</div>
</div>
{% extends 'layouts/layoutAuth.twig' %}
{% block title %}Setup{% endblock %}
{% block content %}
<div class="setupWrapper">
<div class="authformWrapper">
<form method="POST" action="{{ path_for('setup.create') }}" autocomplete="off">
<fieldset class="auth">
<div class="formElement{{ errors.username ? ' errors' : '' }}">
<label for="username">Username <abbr title="required">*</abbr></label>
<input type="text" name="username" value="{{ old.username }}" required>
{% if errors.username %}
<span class="error">{{ errors.username | first }}</span>
{% endif %}
</div>
<div class="formElement{{ errors.email ? ' errors' : '' }}">
<label for="email">E-Mail <abbr title="required">*</abbr></label>
<input type="text" name="email" value="{{ old.email }}" required>
{% if errors.email %}
<span class="error">{{ errors.email | first }}</span>
{% endif %}
</div>
<div class="formElement{{ errors.password ? ' errors' : '' }}">
<label for="password">Password <abbr title="required">*</abbr></label>
<input type="password" name="password" required autocomplete="off">
{% if errors.password %}
<span class="error">{{ errors.password | first }}</span>
{% endif %}
</div>
</fieldset>
<input type="submit" value="Create User" />
{{ csrf_field() | raw }}
</form>
</div>
</div>
{% endblock %}

View File

@ -1,49 +1,49 @@
{% extends 'layouts/layoutAuth.twig' %}
{% block title %}Setup Welcome{% endblock %}
{% block content %}
<div class="welcome">
<div class="medium">
<div class="welcomeIntro">
<h1>Hurra!</h1>
<p>Your account has been created and you are logged in now.</p>
<p><strong>Next step:</strong> Visit the author panel and setup your new website. You can configure the system, choose themes and add plugins.</p>
<p><strong>New:</strong> Write beautiful math formulas with markdown with direct preview in the visual editor. We completely refactored it!!</p>
<p><strong>Get help:</strong> If you have any questions, please consult the <a target="_blank" href="https://typemill.net/typemill"><i class="icon-link-ext"></i> docs</a> or open a new issue on <a target="_blank" href="https://github.com/typemill/typemill"><i class="icon-link-ext"></i> github</a>.</p>
</div>
<a class="button" href="{{ path_for('settings.show') }}">Configure your website</a>
</div>
<div class="small">
<div class="welcomeCard">
<a href="{{ path_for('settings.show') }}">
<div class="welcomeInner">
<h3>System</h3>
<p>Give your new website a name, add the author and choose a copyright.</p>
</div>
</a>
</div>
<div class="welcomeCard">
<a href="{{ path_for('themes.show') }}">
<div class="welcomeInner">
<h3>Themes</h3>
<p>Choose a theme for your website and configure the theme details.</p>
</div>
</a>
</div>
<div class="welcomeCard">
<a href="{{ path_for('plugins.show') }}">
<div class="welcomeInner">
<h3>Plugins</h3>
<p>Add new features to your website with plugins and configure them.</p>
</div>
</a>
</div>
</div>
</div>
{% extends 'layouts/layoutAuth.twig' %}
{% block title %}Setup Welcome{% endblock %}
{% block content %}
<div class="welcome">
<div class="medium">
<div class="welcomeIntro">
<h1>Hurra!</h1>
<p>Your account has been created and you are logged in now.</p>
<p><strong>Next step:</strong> Visit the author panel and setup your new website. You can configure the system, choose themes and add plugins.</p>
<p><strong>New:</strong> Write beautiful math formulas with markdown with direct preview in the visual editor. We completely refactored it!!</p>
<p><strong>Get help:</strong> If you have any questions, please consult the <a target="_blank" href="https://typemill.net/typemill"><i class="icon-link-ext"></i> docs</a> or open a new issue on <a target="_blank" href="https://github.com/typemill/typemill"><i class="icon-link-ext"></i> github</a>.</p>
</div>
<a class="button" href="{{ path_for('settings.show') }}">Configure your website</a>
</div>
<div class="small">
<div class="welcomeCard">
<a href="{{ path_for('settings.show') }}">
<div class="welcomeInner">
<h3>System</h3>
<p>Give your new website a name, add the author and choose a copyright.</p>
</div>
</a>
</div>
<div class="welcomeCard">
<a href="{{ path_for('themes.show') }}">
<div class="welcomeInner">
<h3>Themes</h3>
<p>Choose a theme for your website and configure the theme details.</p>
</div>
</a>
</div>
<div class="welcomeCard">
<a href="{{ path_for('plugins.show') }}">
<div class="welcomeInner">
<h3>Plugins</h3>
<p>Add new features to your website with plugins and configure them.</p>
</div>
</a>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,202 +1,202 @@
{
"name": "",
"css_prefix_text": "icon-",
"css_use_suffix": false,
"hinting": true,
"units_per_em": 1000,
"ascent": 850,
"glyphs": [
{
"uid": "381da2c2f7fd51f8de877c044d7f439d",
"css": "picture",
"code": 59392,
"src": "fontawesome"
},
{
"uid": "5211af474d3a9848f67f945e2ccaf143",
"css": "cancel",
"code": 59393,
"src": "fontawesome"
},
{
"uid": "44e04715aecbca7f266a17d5a7863c68",
"css": "plus",
"code": 59394,
"src": "fontawesome"
},
{
"uid": "d7271d490b71df4311e32cdacae8b331",
"css": "home",
"code": 59395,
"src": "fontawesome"
},
{
"uid": "0ddd3e8201ccc7d41f7b7c9d27eca6c1",
"css": "link",
"code": 59396,
"src": "fontawesome"
},
{
"uid": "e99461abfef3923546da8d745372c995",
"css": "cog",
"code": 59397,
"src": "fontawesome"
},
{
"uid": "a8cb1c217f02b073db3670c061cc54d2",
"css": "italic",
"code": 59398,
"src": "fontawesome"
},
{
"uid": "02cca871bb69da75e8ee286b7055832c",
"css": "bold",
"code": 59399,
"src": "fontawesome"
},
{
"uid": "8b9e6a8dd8f67f7c003ed8e7e5ee0857",
"css": "off",
"code": 59400,
"src": "fontawesome"
},
{
"uid": "872d9516df93eb6b776cc4d94bd97dac",
"css": "video",
"code": 59401,
"src": "fontawesome"
},
{
"uid": "47a1f80457068fbeab69fdb83d7d0817",
"css": "youtube-play",
"code": 61802,
"src": "fontawesome"
},
{
"uid": "c5fd68d8253e605e7a78a0c75255b692",
"css": "math",
"code": 61466,
"src": "mfglabs"
},
{
"uid": "6605ee6441bf499ffa3c63d3c7409471",
"css": "move",
"code": 61511,
"src": "fontawesome"
},
{
"uid": "e15f0d620a7897e2035c18c80142f6d9",
"css": "link-ext",
"code": 61582,
"src": "fontawesome"
},
{
"uid": "a2a74f5e7b7d9ba054897d8c795a326a",
"css": "list-bullet",
"code": 61642,
"src": "fontawesome"
},
{
"uid": "f6766a8b042c2453a4e153af03294383",
"css": "list-numbered",
"code": 61643,
"src": "fontawesome"
},
{
"uid": "d4a4a38a40b728f46dad1de4ac950231",
"css": "underline",
"code": 61645,
"src": "fontawesome"
},
{
"uid": "8fb55fd696d9a0f58f3b27c1d8633750",
"css": "table",
"code": 61646,
"src": "fontawesome"
},
{
"uid": "5408be43f7c42bccee419c6be53fdef5",
"css": "doc-text",
"code": 61686,
"src": "fontawesome"
},
{
"uid": "ab95e1351ebaec5850101097cbf7097f",
"css": "quote-left",
"code": 61709,
"src": "fontawesome"
},
{
"uid": "b091a8bd0fdade174951f17d936f51e4",
"css": "folder-empty",
"code": 61716,
"src": "fontawesome"
},
{
"uid": "7034e4d22866af82bef811f52fb1ba46",
"css": "code",
"code": 61729,
"src": "fontawesome"
},
{
"uid": "4e88371fb8857dacc1f66afe6314e426",
"css": "superscript",
"code": 61739,
"src": "fontawesome"
},
{
"uid": "0c708edd8fae2376b3370aa56d40cf9e",
"css": "header",
"code": 61916,
"src": "fontawesome"
},
{
"uid": "c5845105a87df2ee1999826d90622f6a",
"css": "paragraph",
"code": 61917,
"src": "fontawesome"
},
{
"uid": "eeec3208c90b7b48e804919d0d2d4a41",
"css": "upload",
"code": 59402,
"src": "fontawesome"
},
{
"uid": "861ab06e455e2de3232ebef67d60d708",
"css": "minus",
"code": 59403,
"src": "fontawesome"
},
{
"uid": "2qh229aneb95ds2afi7dbdsxz9jrbhcl",
"css": "colon",
"code": 59404,
"src": "modernpics"
},
{
"uid": "c819c6225685bae2eed1b8da13e629fa",
"css": "list-alt",
"code": 59406,
"src": "fontawesome"
},
{
"uid": "d2d6ab0dd4fb9365b1d5756380484bbb",
"css": "pi",
"code": 59405,
"src": "typicons"
},
{
"uid": "d3b3f17bc3eb7cd809a07bbd4d178bee",
"css": "resize-vertical",
"code": 59407,
"src": "fontawesome"
},
{
"uid": "2f5ef6f6b7aaebc56458ab4e865beff5",
"css": "bookmark-empty",
"code": 61591,
"src": "fontawesome"
}
]
{
"name": "",
"css_prefix_text": "icon-",
"css_use_suffix": false,
"hinting": true,
"units_per_em": 1000,
"ascent": 850,
"glyphs": [
{
"uid": "381da2c2f7fd51f8de877c044d7f439d",
"css": "picture",
"code": 59392,
"src": "fontawesome"
},
{
"uid": "5211af474d3a9848f67f945e2ccaf143",
"css": "cancel",
"code": 59393,
"src": "fontawesome"
},
{
"uid": "44e04715aecbca7f266a17d5a7863c68",
"css": "plus",
"code": 59394,
"src": "fontawesome"
},
{
"uid": "d7271d490b71df4311e32cdacae8b331",
"css": "home",
"code": 59395,
"src": "fontawesome"
},
{
"uid": "0ddd3e8201ccc7d41f7b7c9d27eca6c1",
"css": "link",
"code": 59396,
"src": "fontawesome"
},
{
"uid": "e99461abfef3923546da8d745372c995",
"css": "cog",
"code": 59397,
"src": "fontawesome"
},
{
"uid": "a8cb1c217f02b073db3670c061cc54d2",
"css": "italic",
"code": 59398,
"src": "fontawesome"
},
{
"uid": "02cca871bb69da75e8ee286b7055832c",
"css": "bold",
"code": 59399,
"src": "fontawesome"
},
{
"uid": "8b9e6a8dd8f67f7c003ed8e7e5ee0857",
"css": "off",
"code": 59400,
"src": "fontawesome"
},
{
"uid": "872d9516df93eb6b776cc4d94bd97dac",
"css": "video",
"code": 59401,
"src": "fontawesome"
},
{
"uid": "47a1f80457068fbeab69fdb83d7d0817",
"css": "youtube-play",
"code": 61802,
"src": "fontawesome"
},
{
"uid": "c5fd68d8253e605e7a78a0c75255b692",
"css": "math",
"code": 61466,
"src": "mfglabs"
},
{
"uid": "6605ee6441bf499ffa3c63d3c7409471",
"css": "move",
"code": 61511,
"src": "fontawesome"
},
{
"uid": "e15f0d620a7897e2035c18c80142f6d9",
"css": "link-ext",
"code": 61582,
"src": "fontawesome"
},
{
"uid": "a2a74f5e7b7d9ba054897d8c795a326a",
"css": "list-bullet",
"code": 61642,
"src": "fontawesome"
},
{
"uid": "f6766a8b042c2453a4e153af03294383",
"css": "list-numbered",
"code": 61643,
"src": "fontawesome"
},
{
"uid": "d4a4a38a40b728f46dad1de4ac950231",
"css": "underline",
"code": 61645,
"src": "fontawesome"
},
{
"uid": "8fb55fd696d9a0f58f3b27c1d8633750",
"css": "table",
"code": 61646,
"src": "fontawesome"
},
{
"uid": "5408be43f7c42bccee419c6be53fdef5",
"css": "doc-text",
"code": 61686,
"src": "fontawesome"
},
{
"uid": "ab95e1351ebaec5850101097cbf7097f",
"css": "quote-left",
"code": 61709,
"src": "fontawesome"
},
{
"uid": "b091a8bd0fdade174951f17d936f51e4",
"css": "folder-empty",
"code": 61716,
"src": "fontawesome"
},
{
"uid": "7034e4d22866af82bef811f52fb1ba46",
"css": "code",
"code": 61729,
"src": "fontawesome"
},
{
"uid": "4e88371fb8857dacc1f66afe6314e426",
"css": "superscript",
"code": 61739,
"src": "fontawesome"
},
{
"uid": "0c708edd8fae2376b3370aa56d40cf9e",
"css": "header",
"code": 61916,
"src": "fontawesome"
},
{
"uid": "c5845105a87df2ee1999826d90622f6a",
"css": "paragraph",
"code": 61917,
"src": "fontawesome"
},
{
"uid": "eeec3208c90b7b48e804919d0d2d4a41",
"css": "upload",
"code": 59402,
"src": "fontawesome"
},
{
"uid": "861ab06e455e2de3232ebef67d60d708",
"css": "minus",
"code": 59403,
"src": "fontawesome"
},
{
"uid": "2qh229aneb95ds2afi7dbdsxz9jrbhcl",
"css": "colon",
"code": 59404,
"src": "modernpics"
},
{
"uid": "c819c6225685bae2eed1b8da13e629fa",
"css": "list-alt",
"code": 59406,
"src": "fontawesome"
},
{
"uid": "d2d6ab0dd4fb9365b1d5756380484bbb",
"css": "pi",
"code": 59405,
"src": "typicons"
},
{
"uid": "d3b3f17bc3eb7cd809a07bbd4d178bee",
"css": "resize-vertical",
"code": 59407,
"src": "fontawesome"
},
{
"uid": "2f5ef6f6b7aaebc56458ab4e865beff5",
"css": "bookmark-empty",
"code": 61591,
"src": "fontawesome"
}
]
}

View File

@ -1,33 +1,33 @@
.icon-picture:before { content: '\e800'; } /* '' */
.icon-cancel:before { content: '\e801'; } /* '' */
.icon-plus:before { content: '\e802'; } /* '' */
.icon-home:before { content: '\e803'; } /* '' */
.icon-link:before { content: '\e804'; } /* '' */
.icon-cog:before { content: '\e805'; } /* '' */
.icon-italic:before { content: '\e806'; } /* '' */
.icon-bold:before { content: '\e807'; } /* '' */
.icon-off:before { content: '\e808'; } /* '' */
.icon-video:before { content: '\e809'; } /* '' */
.icon-upload:before { content: '\e80a'; } /* '' */
.icon-minus:before { content: '\e80b'; } /* '' */
.icon-colon:before { content: '\e80c'; } /* '' */
.icon-pi:before { content: '\e80d'; } /* '' */
.icon-list-alt:before { content: '\e80e'; } /* '' */
.icon-resize-vertical:before { content: '\e80f'; } /* '' */
.icon-math:before { content: '\f01a'; } /* '' */
.icon-move:before { content: '\f047'; } /* '' */
.icon-link-ext:before { content: '\f08e'; } /* '' */
.icon-bookmark-empty:before { content: '\f097'; } /* '' */
.icon-list-bullet:before { content: '\f0ca'; } /* '' */
.icon-list-numbered:before { content: '\f0cb'; } /* '' */
.icon-underline:before { content: '\f0cd'; } /* '' */
.icon-table:before { content: '\f0ce'; } /* '' */
.icon-doc-text:before { content: '\f0f6'; } /* '' */
.icon-quote-left:before { content: '\f10d'; } /* '' */
.icon-folder-empty:before { content: '\f114'; } /* '' */
.icon-code:before { content: '\f121'; } /* '' */
.icon-superscript:before { content: '\f12b'; } /* '' */
.icon-youtube-play:before { content: '\f16a'; } /* '' */
.icon-header:before { content: '\f1dc'; } /* '' */
.icon-picture:before { content: '\e800'; } /* '' */
.icon-cancel:before { content: '\e801'; } /* '' */
.icon-plus:before { content: '\e802'; } /* '' */
.icon-home:before { content: '\e803'; } /* '' */
.icon-link:before { content: '\e804'; } /* '' */
.icon-cog:before { content: '\e805'; } /* '' */
.icon-italic:before { content: '\e806'; } /* '' */
.icon-bold:before { content: '\e807'; } /* '' */
.icon-off:before { content: '\e808'; } /* '' */
.icon-video:before { content: '\e809'; } /* '' */
.icon-upload:before { content: '\e80a'; } /* '' */
.icon-minus:before { content: '\e80b'; } /* '' */
.icon-colon:before { content: '\e80c'; } /* '' */
.icon-pi:before { content: '\e80d'; } /* '' */
.icon-list-alt:before { content: '\e80e'; } /* '' */
.icon-resize-vertical:before { content: '\e80f'; } /* '' */
.icon-math:before { content: '\f01a'; } /* '' */
.icon-move:before { content: '\f047'; } /* '' */
.icon-link-ext:before { content: '\f08e'; } /* '' */
.icon-bookmark-empty:before { content: '\f097'; } /* '' */
.icon-list-bullet:before { content: '\f0ca'; } /* '' */
.icon-list-numbered:before { content: '\f0cb'; } /* '' */
.icon-underline:before { content: '\f0cd'; } /* '' */
.icon-table:before { content: '\f0ce'; } /* '' */
.icon-doc-text:before { content: '\f0f6'; } /* '' */
.icon-quote-left:before { content: '\f10d'; } /* '' */
.icon-folder-empty:before { content: '\f114'; } /* '' */
.icon-code:before { content: '\f121'; } /* '' */
.icon-superscript:before { content: '\f12b'; } /* '' */
.icon-youtube-play:before { content: '\f16a'; } /* '' */
.icon-header:before { content: '\f1dc'; } /* '' */
.icon-paragraph:before { content: '\f1dd'; } /* '' */

File diff suppressed because one or more lines are too long

View File

@ -1,33 +1,33 @@
.icon-picture { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&nbsp;'); }
.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); }
.icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); }
.icon-home { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); }
.icon-link { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); }
.icon-cog { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe805;&nbsp;'); }
.icon-italic { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); }
.icon-bold { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe807;&nbsp;'); }
.icon-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe808;&nbsp;'); }
.icon-video { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe809;&nbsp;'); }
.icon-upload { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80a;&nbsp;'); }
.icon-minus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80b;&nbsp;'); }
.icon-colon { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80c;&nbsp;'); }
.icon-pi { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80d;&nbsp;'); }
.icon-list-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80e;&nbsp;'); }
.icon-resize-vertical { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80f;&nbsp;'); }
.icon-math { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf01a;&nbsp;'); }
.icon-move { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf047;&nbsp;'); }
.icon-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08e;&nbsp;'); }
.icon-bookmark-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf097;&nbsp;'); }
.icon-list-bullet { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0ca;&nbsp;'); }
.icon-list-numbered { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0cb;&nbsp;'); }
.icon-underline { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0cd;&nbsp;'); }
.icon-table { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0ce;&nbsp;'); }
.icon-doc-text { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0f6;&nbsp;'); }
.icon-quote-left { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf10d;&nbsp;'); }
.icon-folder-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf114;&nbsp;'); }
.icon-code { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf121;&nbsp;'); }
.icon-superscript { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf12b;&nbsp;'); }
.icon-youtube-play { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf16a;&nbsp;'); }
.icon-header { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1dc;&nbsp;'); }
.icon-picture { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&nbsp;'); }
.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); }
.icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); }
.icon-home { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); }
.icon-link { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); }
.icon-cog { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe805;&nbsp;'); }
.icon-italic { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); }
.icon-bold { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe807;&nbsp;'); }
.icon-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe808;&nbsp;'); }
.icon-video { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe809;&nbsp;'); }
.icon-upload { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80a;&nbsp;'); }
.icon-minus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80b;&nbsp;'); }
.icon-colon { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80c;&nbsp;'); }
.icon-pi { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80d;&nbsp;'); }
.icon-list-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80e;&nbsp;'); }
.icon-resize-vertical { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80f;&nbsp;'); }
.icon-math { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf01a;&nbsp;'); }
.icon-move { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf047;&nbsp;'); }
.icon-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08e;&nbsp;'); }
.icon-bookmark-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf097;&nbsp;'); }
.icon-list-bullet { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0ca;&nbsp;'); }
.icon-list-numbered { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0cb;&nbsp;'); }
.icon-underline { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0cd;&nbsp;'); }
.icon-table { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0ce;&nbsp;'); }
.icon-doc-text { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0f6;&nbsp;'); }
.icon-quote-left { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf10d;&nbsp;'); }
.icon-folder-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf114;&nbsp;'); }
.icon-code { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf121;&nbsp;'); }
.icon-superscript { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf12b;&nbsp;'); }
.icon-youtube-play { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf16a;&nbsp;'); }
.icon-header { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1dc;&nbsp;'); }
.icon-paragraph { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1dd;&nbsp;'); }

View File

@ -1,44 +1,44 @@
[class^="icon-"], [class*=" icon-"] {
font-family: 'fontello';
font-style: normal;
font-weight: normal;
/* fix buttons height */
line-height: 1em;
/* you can be more comfortable with increased icons size */
/* font-size: 120%; */
}
.icon-picture { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&nbsp;'); }
.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); }
.icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); }
.icon-home { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); }
.icon-link { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); }
.icon-cog { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe805;&nbsp;'); }
.icon-italic { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); }
.icon-bold { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe807;&nbsp;'); }
.icon-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe808;&nbsp;'); }
.icon-video { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe809;&nbsp;'); }
.icon-upload { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80a;&nbsp;'); }
.icon-minus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80b;&nbsp;'); }
.icon-colon { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80c;&nbsp;'); }
.icon-pi { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80d;&nbsp;'); }
.icon-list-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80e;&nbsp;'); }
.icon-resize-vertical { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80f;&nbsp;'); }
.icon-math { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf01a;&nbsp;'); }
.icon-move { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf047;&nbsp;'); }
.icon-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08e;&nbsp;'); }
.icon-bookmark-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf097;&nbsp;'); }
.icon-list-bullet { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0ca;&nbsp;'); }
.icon-list-numbered { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0cb;&nbsp;'); }
.icon-underline { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0cd;&nbsp;'); }
.icon-table { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0ce;&nbsp;'); }
.icon-doc-text { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0f6;&nbsp;'); }
.icon-quote-left { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf10d;&nbsp;'); }
.icon-folder-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf114;&nbsp;'); }
.icon-code { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf121;&nbsp;'); }
.icon-superscript { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf12b;&nbsp;'); }
.icon-youtube-play { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf16a;&nbsp;'); }
.icon-header { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1dc;&nbsp;'); }
[class^="icon-"], [class*=" icon-"] {
font-family: 'fontello';
font-style: normal;
font-weight: normal;
/* fix buttons height */
line-height: 1em;
/* you can be more comfortable with increased icons size */
/* font-size: 120%; */
}
.icon-picture { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe800;&nbsp;'); }
.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe801;&nbsp;'); }
.icon-plus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe802;&nbsp;'); }
.icon-home { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe803;&nbsp;'); }
.icon-link { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe804;&nbsp;'); }
.icon-cog { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe805;&nbsp;'); }
.icon-italic { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe806;&nbsp;'); }
.icon-bold { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe807;&nbsp;'); }
.icon-off { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe808;&nbsp;'); }
.icon-video { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe809;&nbsp;'); }
.icon-upload { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80a;&nbsp;'); }
.icon-minus { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80b;&nbsp;'); }
.icon-colon { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80c;&nbsp;'); }
.icon-pi { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80d;&nbsp;'); }
.icon-list-alt { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80e;&nbsp;'); }
.icon-resize-vertical { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xe80f;&nbsp;'); }
.icon-math { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf01a;&nbsp;'); }
.icon-move { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf047;&nbsp;'); }
.icon-link-ext { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf08e;&nbsp;'); }
.icon-bookmark-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf097;&nbsp;'); }
.icon-list-bullet { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0ca;&nbsp;'); }
.icon-list-numbered { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0cb;&nbsp;'); }
.icon-underline { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0cd;&nbsp;'); }
.icon-table { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0ce;&nbsp;'); }
.icon-doc-text { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf0f6;&nbsp;'); }
.icon-quote-left { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf10d;&nbsp;'); }
.icon-folder-empty { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf114;&nbsp;'); }
.icon-code { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf121;&nbsp;'); }
.icon-superscript { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf12b;&nbsp;'); }
.icon-youtube-play { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf16a;&nbsp;'); }
.icon-header { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1dc;&nbsp;'); }
.icon-paragraph { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '&#xf1dd;&nbsp;'); }

View File

@ -1,89 +1,89 @@
@font-face {
font-family: 'fontello';
src: url('../font/fontello.eot?35517051');
src: url('../font/fontello.eot?35517051#iefix') format('embedded-opentype'),
url('../font/fontello.woff2?35517051') format('woff2'),
url('../font/fontello.woff?35517051') format('woff'),
url('../font/fontello.ttf?35517051') format('truetype'),
url('../font/fontello.svg?35517051#fontello') format('svg');
font-weight: normal;
font-style: normal;
}
/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
/*
@media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face {
font-family: 'fontello';
src: url('../font/fontello.svg?35517051#fontello') format('svg');
}
}
*/
[class^="icon-"]:before, [class*=" icon-"]:before {
font-family: "fontello";
font-style: normal;
font-weight: normal;
speak: none;
display: inline-block;
text-decoration: inherit;
width: 1em;
margin-right: .2em;
text-align: center;
/* opacity: .8; */
/* For safety - reset parent styles, that can break glyph codes*/
font-variant: normal;
text-transform: none;
/* fix buttons height, for twitter bootstrap */
line-height: 1em;
/* Animation center compensation - margins should be symmetric */
/* remove if not needed */
margin-left: .2em;
/* you can be more comfortable with increased icons size */
/* font-size: 120%; */
/* Font smoothing. That was taken from TWBS */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* Uncomment for 3D effect */
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
}
.icon-picture:before { content: '\e800'; } /* '' */
.icon-cancel:before { content: '\e801'; } /* '' */
.icon-plus:before { content: '\e802'; } /* '' */
.icon-home:before { content: '\e803'; } /* '' */
.icon-link:before { content: '\e804'; } /* '' */
.icon-cog:before { content: '\e805'; } /* '' */
.icon-italic:before { content: '\e806'; } /* '' */
.icon-bold:before { content: '\e807'; } /* '' */
.icon-off:before { content: '\e808'; } /* '' */
.icon-video:before { content: '\e809'; } /* '' */
.icon-upload:before { content: '\e80a'; } /* '' */
.icon-minus:before { content: '\e80b'; } /* '' */
.icon-colon:before { content: '\e80c'; } /* '' */
.icon-pi:before { content: '\e80d'; } /* '' */
.icon-list-alt:before { content: '\e80e'; } /* '' */
.icon-resize-vertical:before { content: '\e80f'; } /* '' */
.icon-math:before { content: '\f01a'; } /* '' */
.icon-move:before { content: '\f047'; } /* '' */
.icon-link-ext:before { content: '\f08e'; } /* '' */
.icon-bookmark-empty:before { content: '\f097'; } /* '' */
.icon-list-bullet:before { content: '\f0ca'; } /* '' */
.icon-list-numbered:before { content: '\f0cb'; } /* '' */
.icon-underline:before { content: '\f0cd'; } /* '' */
.icon-table:before { content: '\f0ce'; } /* '' */
.icon-doc-text:before { content: '\f0f6'; } /* '' */
.icon-quote-left:before { content: '\f10d'; } /* '' */
.icon-folder-empty:before { content: '\f114'; } /* '' */
.icon-code:before { content: '\f121'; } /* '' */
.icon-superscript:before { content: '\f12b'; } /* '' */
.icon-youtube-play:before { content: '\f16a'; } /* '' */
.icon-header:before { content: '\f1dc'; } /* '' */
@font-face {
font-family: 'fontello';
src: url('../font/fontello.eot?35517051');
src: url('../font/fontello.eot?35517051#iefix') format('embedded-opentype'),
url('../font/fontello.woff2?35517051') format('woff2'),
url('../font/fontello.woff?35517051') format('woff'),
url('../font/fontello.ttf?35517051') format('truetype'),
url('../font/fontello.svg?35517051#fontello') format('svg');
font-weight: normal;
font-style: normal;
}
/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
/*
@media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face {
font-family: 'fontello';
src: url('../font/fontello.svg?35517051#fontello') format('svg');
}
}
*/
[class^="icon-"]:before, [class*=" icon-"]:before {
font-family: "fontello";
font-style: normal;
font-weight: normal;
speak: none;
display: inline-block;
text-decoration: inherit;
width: 1em;
margin-right: .2em;
text-align: center;
/* opacity: .8; */
/* For safety - reset parent styles, that can break glyph codes*/
font-variant: normal;
text-transform: none;
/* fix buttons height, for twitter bootstrap */
line-height: 1em;
/* Animation center compensation - margins should be symmetric */
/* remove if not needed */
margin-left: .2em;
/* you can be more comfortable with increased icons size */
/* font-size: 120%; */
/* Font smoothing. That was taken from TWBS */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* Uncomment for 3D effect */
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
}
.icon-picture:before { content: '\e800'; } /* '' */
.icon-cancel:before { content: '\e801'; } /* '' */
.icon-plus:before { content: '\e802'; } /* '' */
.icon-home:before { content: '\e803'; } /* '' */
.icon-link:before { content: '\e804'; } /* '' */
.icon-cog:before { content: '\e805'; } /* '' */
.icon-italic:before { content: '\e806'; } /* '' */
.icon-bold:before { content: '\e807'; } /* '' */
.icon-off:before { content: '\e808'; } /* '' */
.icon-video:before { content: '\e809'; } /* '' */
.icon-upload:before { content: '\e80a'; } /* '' */
.icon-minus:before { content: '\e80b'; } /* '' */
.icon-colon:before { content: '\e80c'; } /* '' */
.icon-pi:before { content: '\e80d'; } /* '' */
.icon-list-alt:before { content: '\e80e'; } /* '' */
.icon-resize-vertical:before { content: '\e80f'; } /* '' */
.icon-math:before { content: '\f01a'; } /* '' */
.icon-move:before { content: '\f047'; } /* '' */
.icon-link-ext:before { content: '\f08e'; } /* '' */
.icon-bookmark-empty:before { content: '\f097'; } /* '' */
.icon-list-bullet:before { content: '\f0ca'; } /* '' */
.icon-list-numbered:before { content: '\f0cb'; } /* '' */
.icon-underline:before { content: '\f0cd'; } /* '' */
.icon-table:before { content: '\f0ce'; } /* '' */
.icon-doc-text:before { content: '\f0f6'; } /* '' */
.icon-quote-left:before { content: '\f10d'; } /* '' */
.icon-folder-empty:before { content: '\f114'; } /* '' */
.icon-code:before { content: '\f121'; } /* '' */
.icon-superscript:before { content: '\f12b'; } /* '' */
.icon-youtube-play:before { content: '\f16a'; } /* '' */
.icon-header:before { content: '\f1dc'; } /* '' */
.icon-paragraph:before { content: '\f1dd'; } /* '' */

Some files were not shown because too many files have changed in this diff Show More