mirror of
https://github.com/typemill/typemill.git
synced 2025-07-16 12:06:21 +02:00
finish v2.0 and delete old v1.X files
This commit is contained in:
18
.gitignore
vendored
18
.gitignore
vendored
@ -1,11 +1,4 @@
|
||||
cache/lastCache.txt
|
||||
cache/lastSitemap.txt
|
||||
cache/metatabs.yaml
|
||||
cache/navigation.txt
|
||||
cache/sitemap.xml
|
||||
cache/structure-draft.txt
|
||||
cache/structure-extended.yaml
|
||||
cache/structure.txt
|
||||
content/index.yaml
|
||||
content/00-welcome/index.yaml
|
||||
content/00-welcome/00-setup.yaml
|
||||
@ -17,12 +10,15 @@ content/01-cyanine-theme/00-landingpage.yaml
|
||||
content/01-cyanine-theme/01-colors-and-fonts.yaml
|
||||
content/01-cyanine-theme/02-footer.yaml
|
||||
content/01-cyanine-theme/03-content-elements.yaml
|
||||
cypress
|
||||
data/navigation
|
||||
data/css
|
||||
node_modules
|
||||
plugins/demo
|
||||
plugins/search
|
||||
settings/settings.yaml
|
||||
settings/license.yaml
|
||||
settings/users
|
||||
system/vendor
|
||||
plugins/demo
|
||||
plugins/search
|
||||
zips
|
||||
build.php
|
||||
node_modules
|
||||
cypress.json
|
@ -1,3 +0,0 @@
|
||||
RewriteEngine on
|
||||
RewriteRule ^$ public/ [L]
|
||||
RewriteRule (.*) public/$1 [L]
|
30
cache/cyanine-custom.css
vendored
30
cache/cyanine-custom.css
vendored
@ -1,30 +0,0 @@
|
||||
.landingpageintro h1{
|
||||
display: inline-block;
|
||||
background: white;
|
||||
padding: 4px 10px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.landingpageintro p{
|
||||
display: inline;
|
||||
background: white;
|
||||
line-height: 2.1rem;
|
||||
padding: 6px 4px 4px;
|
||||
}
|
||||
article a[href^="http"]::after,
|
||||
article a[href^="https://"]::after
|
||||
{
|
||||
content: "";
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
margin-left: 4px;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5z'/%3E%3Cpath fill-rule='evenodd' d='M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0v-5z'/%3E%3C/svg%3E");
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
display: inline-block;
|
||||
}
|
||||
a[href^="https://www.electrictoolbox.com"] {
|
||||
background: none;
|
||||
padding-right: 0;
|
||||
}
|
@ -4,26 +4,13 @@ Typemill provides detailed settings, and you have access to nearly all settings
|
||||
|
||||
{#7yvlwXJL9dc .youtube}
|
||||
|
||||
You will find all configurations and settings under the main navigation point `settings` with the following sub-navigation:
|
||||
You will find all configurations and settings under the main navigation point `system` with the following sub-navigation:
|
||||
|
||||
* System settings
|
||||
* Theme settings
|
||||
* Plugin settings
|
||||
* User settings
|
||||
* System
|
||||
* Themes
|
||||
* Plugins
|
||||
* Account
|
||||
* Users
|
||||
|
||||
All settings are stored in the `\settings` folder of Typemill. It is not recommended to edit the settings manually, because it might crash the system if done wrong.
|
||||
|
||||
## Developer Settings
|
||||
|
||||
As of version 1.4.0 you will find some advanced developer settings in the author panel under `settings`. See the details below.
|
||||
|
||||
! **Only for devs**
|
||||
!
|
||||
! These options are for developers only. Make sure that you fully understand what happens. For example, you should never activate the error reporting on live systems because this is a security risk.
|
||||
|
||||
* **Error Reporting**: You can switch the error reporting of the slim-framework on and off here. This can be helpful for bug-analysis, but you should NEVER switch it on (or keep it active) on a productive system.
|
||||
* **Twig cache**: You can activate the cache for the twig templates. This will speed up the page rendering a bit, but it can also produce a headace if you changed something in your theme. The best option is to clear the cache if something does not work.
|
||||
* **Clear cache**: This will clear the cache for Twig templates and delete all cache files of Typemill. If you clear the cache, then some details might not work or look strange, for example the navigation is set back to the original state. Everything will work again when the cache has been rebuild. This happens every 10 minutes. If you want to spead up the process, then refresh your browser cache with F12 on windows machines, because it will also trigger the recreation of the Typemill cache.
|
||||
* **Image sizes**: All images in the content area will be resized to 820px width. If you want to change it, then add another value in the width-field. If you additionally add a height for your images, then the images will be resized first and then cropped to the correct aspect ratio.
|
||||
* **Proxy**: If you run Typemill behind a proxy (which is a common usecase in companies), then you can activate the proxy detection. This will read the `X-Forwarded-Proto`, `X-Forwarded-Host` and `X-Forwarded-Port` Headers and return the html with the correct urls. Optionally you can also add a comma separated list of trusted IP-addresses.
|
||||
|
||||
|
@ -1,17 +1,17 @@
|
||||
# Manage Access
|
||||
|
||||
Typemill has a build-in system to restrict access to pages or to the whole websites. You can activate both features in the system settings under the section "access rights". If you activate one of the features, then Typemill will use session cookies on all frontend pages. Learn all the details in the following video tutorial:
|
||||
Typemill has a build-in system to restrict access to pages or to the whole websites. You can activate both features in the system settings under the tab "restrictions". If you activate one of the features, then Typemill will use session cookies on all frontend pages. Learn all the details in the following video tutorial:
|
||||
|
||||
{#UW_m-4g1kAA .youtube}
|
||||
|
||||
## Restrict Access for the Website
|
||||
|
||||
This feature is perfect, if you want to lock down the whole website and only grant access for authenticated users. All non-authenticated users will be redirected to the login-page. There are two main use cases for this feature:
|
||||
This feature is handy, if you want to lock down the whole website and only grant access for authenticated users. All non-authenticated users will be redirected to the login-page. There are two main use cases for this feature:
|
||||
|
||||
* **Launch the website later**: You want to create your website first and launch it to the public later, for example if you have finished the website design or if you have polished your content.
|
||||
* **Share website internally**: You want to share your typemill website only with certain users, for example with the company stuff or only with the members of your it-unit.
|
||||
* **Share website internally**: You want to share your typemill website only with certain users, for example with the company stuff, or only with the members of your business-unit.
|
||||
|
||||
You can activate the feature with a simple checkbox under "Website Restrictions".
|
||||
You can activate the feature with a simple checkbox under "website restrictions".
|
||||
|
||||
## Restrict Access for Pages
|
||||
|
||||
@ -20,7 +20,7 @@ If you need a more fine-tuned access and if you want to restrict access only for
|
||||
* **Minimum role for access**: Here you can select a miminum role that the user needs to view the page content. Be aware that the roles have a hierarchy, so if you choose the role "author", then the "editor" will also have access.
|
||||
* **Usernames**: Here you can add one or more usernames (separated with comma) that have access to this page.
|
||||
|
||||
If you don't choose anything of it, then the page has no restrictions and everybody can see the content.
|
||||
If you don't choose anything, then the page has no restrictions and everybody can see the content.
|
||||
|
||||
You have some more features in the settings area:
|
||||
|
||||
|
@ -4,7 +4,7 @@ meta:
|
||||
description: ' Restrict Access for the Website'
|
||||
heroimage: null
|
||||
heroimagealt: null
|
||||
owner: Sebastian
|
||||
owner: trendschau
|
||||
author: null
|
||||
allowedrole: null
|
||||
alloweduser: null
|
||||
|
@ -1,6 +1,13 @@
|
||||
# Write Content
|
||||
|
||||
Typemill provides easy and intuitive authoring tools and we work hard to create a good author experience. With the interactive navigation you can create pages and structure your websites. The visual markdown editor will help you to create content in a wysiwyg mode. The publish bar gives you full control over the status of each page. Watch the following video tutorial to learn all the details.
|
||||
Typemill provides easy and intuitive authoring tools and we work hard to create a good author experience.
|
||||
|
||||
* With the **interactive navigation** you can create pages and structure your websites.
|
||||
* The **visual markdown editor** will help you to create content in a wysiwyg mode.
|
||||
* With the **raw markdown editor** you can write markdown syntax in textarea.
|
||||
* The **publish bar** gives you full control over the status of each page.
|
||||
|
||||
Watch the following video tutorial to learn all the details.
|
||||
|
||||
{#6I2-uV88GkE .youtube}
|
||||
|
||||
|
@ -2,17 +2,9 @@ meta:
|
||||
navtitle: 'write content'
|
||||
title: 'Write Content'
|
||||
description: 'Typemill provides easy and intuitive authoring tools and we work hard to create a good author experience. With the interactive navigation you can create pages'
|
||||
heroimage: null
|
||||
heroimagealt: null
|
||||
owner: Sebastian
|
||||
author: null
|
||||
allowedrole: null
|
||||
alloweduser: null
|
||||
manualdate: null
|
||||
owner: trendschau
|
||||
modified: '2023-05-11'
|
||||
created: '2023-06-12'
|
||||
time: 22-09-48
|
||||
reference: null
|
||||
referencetype: null
|
||||
hide: true
|
||||
hide: false
|
||||
noindex: false
|
||||
|
@ -1,200 +0,0 @@
|
||||
# ToDos Version 2
|
||||
|
||||
[TOC]
|
||||
|
||||
## System settings
|
||||
|
||||
* DONE: Migrate from backend to frontend with vue and api
|
||||
* DONE: Redesign
|
||||
* DONE: License feature
|
||||
* DONE: Enhance with plugins
|
||||
|
||||
## Visual Editor
|
||||
|
||||
* DONE: Refactor and redesign
|
||||
* DONE: Fix toc component in new block
|
||||
* DONE: Fix hr component in new block
|
||||
* DONE: finish shortcode component
|
||||
* DONE: Fix inline formats
|
||||
* DONE: fix lenght of page
|
||||
* DONE: Fix design of new block at the end (background color)
|
||||
* DONE: Move Block
|
||||
* DONE: Fix headline design
|
||||
* DONE: Fix save on two enter
|
||||
* DONE: fix quote design
|
||||
* DONE: Fix toc preview
|
||||
* DONE: disable enable
|
||||
* DONE: Add load sign (from navigation)
|
||||
* DONE: File is not published from tmp to media/files if you save the block.
|
||||
|
||||
## Raw Editor
|
||||
|
||||
* DONE: Refactor and redesign
|
||||
* DONE: Integrate highlighting
|
||||
|
||||
## Navigation
|
||||
|
||||
* DONE: Refactor and redesign
|
||||
* DONE: fix status in navigation
|
||||
* DONE: refresh navigation after changes
|
||||
|
||||
## Publish Controller
|
||||
|
||||
* DONE: Refactor and redesign
|
||||
* DONE: Create
|
||||
* DONE: publish
|
||||
* DONE: unpublish
|
||||
* DONE: discard
|
||||
* DONE: delete
|
||||
* DONE: save draft
|
||||
* DONE: switch to raw
|
||||
|
||||
## Meta Tabs
|
||||
|
||||
* DONE: Refactor and redesign
|
||||
* DONE: Enhance with plugins
|
||||
|
||||
## Medialib
|
||||
|
||||
* DONE: Refactor and redesign
|
||||
|
||||
## Posts
|
||||
|
||||
* DONE: Refactor and redesign
|
||||
|
||||
## Plugins
|
||||
|
||||
* Asset Class in progress
|
||||
|
||||
## Frontend
|
||||
|
||||
* DONE: Refactor
|
||||
* DONE: Test restrictions
|
||||
|
||||
## Other big tasks
|
||||
|
||||
* DONE: System setup
|
||||
* DONE: Recover Password
|
||||
|
||||
## Medium tasks
|
||||
|
||||
* DONE: Merge processAssets modell
|
||||
* DONE: Table of content duplicated for published pages
|
||||
* DONE: Session handling: csrf fail and session start error if restrictions are active
|
||||
* DONE: Image and files for meta
|
||||
|
||||
## Open tasks
|
||||
|
||||
* DONE: Sitemap and ping
|
||||
* DONE: Version check
|
||||
* DONE: Proxy support
|
||||
* DONE: SVG checker: https://github.com/TribalSystems/SVG-Sanitizer
|
||||
* DONE: Backend form builder
|
||||
* DONE: Image generation on the fly
|
||||
* DONE: Delete folder in base level
|
||||
* DONE: Make folder delete easier with glob or scandir
|
||||
* DONE: fix error messages (check models)
|
||||
* DONE: error status codes (check middleware)
|
||||
* DONE: Warn if open another block
|
||||
* DONE: Customfields not styled yet
|
||||
* DOING: Fix error api systemnavi + validate
|
||||
* FIXED: System stores html or sends wrong error messsages
|
||||
* FIXED: Wrong frontend navigation if unpublished pages
|
||||
* DONE: Icon for hidden pages
|
||||
* DOING: Responsive design
|
||||
* DONE: Captcha integration
|
||||
* DONE: Solution for logo and favicon
|
||||
* FIXED: Raw editor jumps if you edit long text at the end
|
||||
* DONE: Typemill Utilities
|
||||
* DONE: Update CSS for themes
|
||||
* DONE: test with different user rights
|
||||
* Markdown secure rendering
|
||||
* finish youtube component
|
||||
* BUG: Error fields in account form not styled correctly
|
||||
* BUG: Codefield jumps on editing
|
||||
* False for owner on live?
|
||||
|
||||
## Dark Mode
|
||||
|
||||
* DONE: system
|
||||
* DONE: content-navigation
|
||||
* DONE: visual editor preview
|
||||
* DONE: visual editor edit modes
|
||||
* DONE: raw editor
|
||||
* DONE: meta
|
||||
* DONE: other tabs
|
||||
* DONE: modals
|
||||
* DONE: medialib
|
||||
* DONE: publish-bar.
|
||||
|
||||
## Feedback GitHub
|
||||
|
||||
* FIXED: Website restriction
|
||||
* NO ERROR: Change slug of blog
|
||||
* FIXED: undefined array key "title" in TwigMetaExtension on line 25
|
||||
* FIXED: CSS for navigation
|
||||
* DONE: Test with 8.2.7 (deprecation reports)
|
||||
* NOT REPRODUCED: Meta from home folder?
|
||||
* automatic generated password in firefox
|
||||
* FIXED: upload hero image in landinpage
|
||||
* FIXED: Restriction for custom css to 10000 characters
|
||||
* NOT REPRODUCED: Custom css löschen => false
|
||||
|
||||
## later
|
||||
|
||||
* Handle formdata centrally ???
|
||||
* Reference Feature
|
||||
* Clear cache
|
||||
* Show security Log
|
||||
* User search only for +10 users
|
||||
* For api translations should be done completely in backoffice
|
||||
* Change translation files so they are loaded in settings instead of adding them manually to settings-defaults.yaml
|
||||
|
||||
## Cleanups:
|
||||
|
||||
* DONE: Events
|
||||
* DONE: Error messages
|
||||
* DONE: Translations
|
||||
|
||||
## Info: Select userroles
|
||||
|
||||
* Userroles for file restriction: in vue-blox-components loaded via api
|
||||
* Userroles for userfields: in php model user getUserFields()
|
||||
* Userroles for meta: in php controller apiAuthorMeta getMeta()
|
||||
* Plugins and themes: in php model extension getThemeDefinitions()
|
||||
|
||||
## Info: License Check
|
||||
|
||||
* On activation in apiControllerExtension. It checks the license in yaml.
|
||||
* In plugin php code with setPremiumLicense
|
||||
* In static plugins, it checks manual premium list and method setPremiumLicense and more
|
||||
|
||||
## Plugins
|
||||
|
||||
* MAKER: Rebuild search
|
||||
* MAKER: Rebuild contactform with shortcode
|
||||
|
||||
## Status codes
|
||||
|
||||
| Status code | Description |
|
||||
|---|---|
|
||||
| 200 ok | cell |
|
||||
| 400 bad request | The request was unacceptable due to missing or invalid parameter. |
|
||||
| 401 unauthorized | The request requires an authorization. |
|
||||
| (402 request failed) | The parameters where there but the request failed for other reasons. |
|
||||
| 403 forbidden | The user is authenticated but he has not enough rights. |
|
||||
| 404 not found | new |
|
||||
| 500 internal server error | new |
|
||||
|
||||
## Upgrade
|
||||
|
||||
* Switch server to php 8.0 at least
|
||||
* Delete content of system folders
|
||||
* Upload new content of system folder with folders typemill and vendor
|
||||
* Backup and delete settings file
|
||||
* upload new index.php file
|
||||
* Upload new htaccess file
|
||||
* Delete theme folder
|
||||
* Uplload new cyanine-theme
|
||||
* Deactivate and delete all plugins.
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,14 +0,0 @@
|
||||
meta:
|
||||
navtitle: 'To Dos'
|
||||
title: 'A list of open tasks'
|
||||
description: ' Visual Editor with more stuff'
|
||||
heroimage: ''
|
||||
heroimagealt: ''
|
||||
owner: 'Sebastian, testauthor'
|
||||
author: ''
|
||||
allowedrole: ''
|
||||
modified: '2023-06-19'
|
||||
created: '2023-06-19'
|
||||
time: 15-15-01
|
||||
hide: false
|
||||
noindex: false
|
@ -1,30 +0,0 @@
|
||||
.landingpageintro h1{
|
||||
display: inline-block;
|
||||
background: white;
|
||||
padding: 4px 10px;
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.landingpageintro p{
|
||||
display: inline;
|
||||
background: white;
|
||||
line-height: 2.1rem;
|
||||
padding: 6px 4px 4px;
|
||||
}
|
||||
article a[href^="http"]::after,
|
||||
article a[href^="https://"]::after
|
||||
{
|
||||
content: "";
|
||||
width: 11px;
|
||||
height: 11px;
|
||||
margin-left: 4px;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='currentColor' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5z'/%3E%3Cpath fill-rule='evenodd' d='M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0v-5z'/%3E%3C/svg%3E");
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
display: inline-block;
|
||||
}
|
||||
a[href^="https://www.electrictoolbox.com"] {
|
||||
background: none;
|
||||
padding-right: 0;
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
title: thank
|
||||
subtitle: 'another php book'
|
||||
author: chiko
|
||||
edition: new
|
@ -1,22 +0,0 @@
|
||||
flatfilecms:
|
||||
title: 'Flat File CMS for simple projects'
|
||||
cover: media/live/cover-report.png
|
||||
description: 'Another publication from cmsstash that will uncover the secrets of database-less web publishing. Read about all flat file cms and make an informed choice.'
|
||||
downloadlabel: ''
|
||||
downloadurl: ''
|
||||
firstbuttonlabel: ''
|
||||
firstbuttonurl: ''
|
||||
secondbuttonlabel: ''
|
||||
secondbuttonurl: ''
|
||||
downloadlabel1: 'Download now'
|
||||
downloadlabel2: 'Buy on amazon'
|
||||
enterprisecms:
|
||||
title: 'Das CMS Drupal: Open Source für Enterprise'
|
||||
cover: ''
|
||||
description: ''
|
||||
downloadlabel: ''
|
||||
downloadurl: ''
|
||||
firstbuttonlabel: ''
|
||||
firstbuttonurl: ''
|
||||
secondbuttonlabel: ''
|
||||
secondbuttonurl: ''
|
File diff suppressed because one or more lines are too long
@ -18,7 +18,7 @@
|
||||
keyPath: '0.1'
|
||||
/welcome/write-content:
|
||||
navtitle: 'write content'
|
||||
hide: true
|
||||
hide: false
|
||||
noindex: false
|
||||
path: /00-welcome/02-write-content.md
|
||||
keyPath: '0.2'
|
||||
@ -34,12 +34,6 @@
|
||||
noindex: false
|
||||
path: /00-welcome/04-markdown-test.md
|
||||
keyPath: '0.4'
|
||||
/welcome/todos:
|
||||
navtitle: 'To Dos'
|
||||
hide: false
|
||||
noindex: false
|
||||
path: /00-welcome/05-todos.txtmd
|
||||
keyPath: '0.5'
|
||||
/cyanine-theme:
|
||||
navtitle: 'cyanine theme'
|
||||
hide: false
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,18 +0,0 @@
|
||||
127.0.0.1;2023-07-26 15:15:18;wrong input for password recovery
|
||||
127.0.0.1;2023-07-26 15:24:27;wrong input for password recovery
|
||||
127.0.0.1;2023-07-26 15:25:30;wrong input for password recovery
|
||||
127.0.0.1;2023-07-29 22:34:30;wrong login
|
||||
127.0.0.1;2023-09-13 21:18:22;honeypot http://localhost/typemill/tm/login
|
||||
127.0.0.1;2023-09-13 21:26:34;honeypot http://localhost/typemill/tm/login
|
||||
127.0.0.1;2023-09-13 21:29:10;honeypot http://localhost/typemill/tm/login
|
||||
127.0.0.1;2023-09-13 21:42:59;wrong captcha http://localhost/typemill/tm/login
|
||||
127.0.0.1;2023-09-13 22:18:42;wrong captcha http://localhost/typemill/tm/login
|
||||
127.0.0.1;2023-09-13 22:20:17;wrong captcha http://localhost/typemill/tm/login
|
||||
127.0.0.1;2023-09-15 06:06:46;wrong login
|
||||
127.0.0.1;2023-09-15 22:03:38;wrong login
|
||||
127.0.0.1;2023-09-16 08:49:53;wrong login
|
||||
127.0.0.1;2023-10-23 20:42:13;wrong login
|
||||
127.0.0.1;2023-10-23 21:26:26;wrong login
|
||||
127.0.0.1;2023-10-24 21:52:19;wrong login
|
||||
127.0.0.1;2023-10-26 20:57:09;wrong login
|
||||
127.0.0.1;2023-10-28 12:18:34;wrong login
|
@ -1,4 +0,0 @@
|
||||
RewriteEngine On
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^ index.php [QSA,L]
|
@ -1,5 +0,0 @@
|
||||
<?php
|
||||
|
||||
$rootpath = __DIR__ . DIRECTORY_SEPARATOR . '..';
|
||||
|
||||
require $rootpath . '/system/typemill/system.php';
|
79
readme.md
79
readme.md
@ -1,33 +1,74 @@
|
||||
# Typemill Version 2
|
||||
# Typemill
|
||||
|
||||
This is the repository of Typemill V2. The alpha-release for version 2 is planned for July 2023.
|
||||
Typemill is a simple, fast, and flexible flat file cms with a focus on writing and publishing websites and ebooks with Markdown.
|
||||
|
||||
If you are looking for version 1 of Typemill, please visit the [master branch](https://github.com/typemill/typemill.
|
||||

|
||||
|
||||
Join the [newsletter](https://typemill.net/news) to get updates for version 2.
|
||||
With a flat file approach, Typemill does not require a database. With a modern tech-stack (vue.js, tailwind-css, and slim-php) and a lightweight approach (about 2mb gzip), it provides a high performance for small websites.
|
||||
|
||||
If you want to contribute, then please check the code of Typemill 2 in the folder system > [typemill](https://github.com/typemill/typemill/tree/tm2-dev/system/typemill).
|
||||
Typemill is often used for documentations, manuals, and other websites with a focus on content and text.
|
||||
|
||||
## Tech-stack of Typemill 2
|
||||
## Resources
|
||||
|
||||
Typemill 2 will use the following tech-stack:
|
||||
* Download and documentation: https:typemill.net
|
||||
* Plugins: https://plugins.typemill.net
|
||||
* Themes: https://themes.typemill.net
|
||||
* Book-layouts: https://books.typemill.net
|
||||
* Issues and bug-reports: https://github.com/typemill/typemill/issues
|
||||
* Newsletter: https://typemill.net/news
|
||||
|
||||
## Requirements
|
||||
|
||||
* Webserver (apache, not tested on other servers)
|
||||
* PHP 8.0 or higher
|
||||
* Some standard PHP-libraries like mod_rewrite, gd-image, mbstring, fileinfo, session, iconv, and more.
|
||||
|
||||
## Installation
|
||||
|
||||
### With zip-file and ftp
|
||||
|
||||
* Download and upack the latest version of typemill as zip-file from https://typemill.net.
|
||||
* Upload all files to your server.
|
||||
* Visit your new website www.your-typemill-website.com/tm/setup and create and admin user.
|
||||
* login and start publishing.
|
||||
|
||||
### With github and composer
|
||||
|
||||
Clone this repository:
|
||||
|
||||
```
|
||||
git clone "https://github.com/typemill/typemill.git"
|
||||
```
|
||||
Then update composer to load the libraries:
|
||||
|
||||
```
|
||||
composer update
|
||||
```
|
||||
|
||||
### With docker
|
||||
|
||||
Will follow soon ...
|
||||
|
||||
## Folder permissions
|
||||
|
||||
make sure that the following folders are writable:
|
||||
|
||||
* /cache
|
||||
* /content
|
||||
* /data
|
||||
* /media
|
||||
* /settings
|
||||
|
||||
## Tech-stack
|
||||
|
||||
* Slim framework version 4
|
||||
* Vue.js version 3
|
||||
* Tailwind css
|
||||
|
||||
With slim framework 4 Typemill 2 will run on PHP 8.0 or 8.1 and higher.
|
||||
## Security issues
|
||||
|
||||
The code of Typemill 2 will be refactored in most parts.
|
||||
If you discover a possible security issue related to Typemill, please send an email to security@typemill.net and we'll address it as soon as possible.
|
||||
|
||||
## New license system
|
||||
## License
|
||||
|
||||
Typemill 2 will stay under the MIT-license.
|
||||
|
||||
I will try to update all current plugins and themes to version 2, so that the community can proceed with the further development.
|
||||
|
||||
I will also proceed with the development of old and new plugins, but I will use a new commercial license with two different subscription plans. This will help to refinance the open source license of the core system.
|
||||
|
||||
## Follow
|
||||
|
||||
Twitter: https://twitter.com/typemill
|
||||
Typemill is an open source project published under the MIT-license. Plugins, themes, and services are published under MIT and commercial licenses.
|
@ -1,305 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill;
|
||||
|
||||
use Typemill\Models\ProcessImage;
|
||||
|
||||
class Assets
|
||||
{
|
||||
public $baseUrl;
|
||||
|
||||
public function __construct($baseUrl)
|
||||
{
|
||||
$this->baseUrl = $baseUrl;
|
||||
$this->JS = array();
|
||||
$this->CSS = array();
|
||||
$this->inlineJS = array();
|
||||
$this->inlineCSS = array();
|
||||
$this->editorJS = array();
|
||||
$this->editorCSS = array();
|
||||
$this->editorInlineJS = array();
|
||||
$this->svgSymbols = array();
|
||||
$this->meta = array();
|
||||
$this->imageUrl = false;
|
||||
$this->imageFolder = 'original';
|
||||
}
|
||||
|
||||
public function setUri($uri)
|
||||
{
|
||||
$this->uri = $uri;
|
||||
}
|
||||
|
||||
public function setBaseUrl($baseUrl)
|
||||
{
|
||||
$this->baseUrl = $baseUrl;
|
||||
}
|
||||
|
||||
public function image($url)
|
||||
{
|
||||
$this->imageUrl = $url;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function resize($width,$height)
|
||||
{
|
||||
$pathinfo = pathinfo($this->imageUrl);
|
||||
$extension = strtolower($pathinfo['extension']);
|
||||
$imageName = $pathinfo['filename'];
|
||||
|
||||
$desiredSizes = ['custom' => []];
|
||||
|
||||
$resize = '-';
|
||||
|
||||
if(is_int($width) && $width < 10000)
|
||||
{
|
||||
$resize .= $width;
|
||||
$desiredSizes['custom']['width'] = $width;
|
||||
}
|
||||
|
||||
$resize .= 'x';
|
||||
|
||||
if(is_int($height) && $height < 10000)
|
||||
{
|
||||
$resize .= $height;
|
||||
$desiredSizes['custom']['height'] = $height;
|
||||
}
|
||||
|
||||
$processImage = new ProcessImage($desiredSizes);
|
||||
|
||||
$processImage->checkFolders('images');
|
||||
|
||||
$imageNameResized = $imageName . $resize;
|
||||
$imagePathResized = $processImage->customFolder . $imageNameResized . '.' . $extension;
|
||||
$imageUrlResized = 'media/custom/' . $imageNameResized . '.' . $extension;
|
||||
|
||||
if(!file_exists( $imagePathResized ))
|
||||
{
|
||||
# if custom version does not exist, use original version for resizing
|
||||
$imageFolder = ($this->imageFolder == 'original') ? $processImage->originalFolder : $processImage->customFolder;
|
||||
|
||||
$imagePath = $imageFolder . $pathinfo['basename'];
|
||||
|
||||
$resizedImage = $processImage->generateSizesFromImageFile($imageUrlResized, $imagePath);
|
||||
|
||||
$savedImage = $processImage->saveImage($processImage->customFolder, $resizedImage['custom'], $imageNameResized, $extension);
|
||||
|
||||
if(!$savedImage)
|
||||
{
|
||||
# return old image url without resize
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
# set folder to custom, so that the next method uses the correct (resized) version
|
||||
$this->imageFolder = 'custom';
|
||||
|
||||
$this->imageUrl = $imageUrlResized;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function grayscale()
|
||||
{
|
||||
$pathinfo = pathinfo($this->imageUrl);
|
||||
$extension = strtolower($pathinfo['extension']);
|
||||
$imageName = $pathinfo['filename'];
|
||||
|
||||
$processImage = new ProcessImage([]);
|
||||
|
||||
$processImage->checkFolders('images');
|
||||
|
||||
$imageNameGrayscale = $imageName . '-grayscale';
|
||||
$imagePathGrayscale = $processImage->customFolder . $imageNameGrayscale . '.' . $extension;
|
||||
$imageUrlGrayscale = 'media/custom/' . $imageNameGrayscale . '.' . $extension;
|
||||
|
||||
if(!file_exists( $imagePathGrayscale ))
|
||||
{
|
||||
# if custom-version does not exist, use live-version for grayscale-manipulation.
|
||||
$imageFolder = ($this->imageFolder == 'original') ? $processImage->liveFolder : $processImage->customFolder;
|
||||
|
||||
$imagePath = $imageFolder . $pathinfo['basename'];
|
||||
|
||||
$grayscaleImage = $processImage->grayscale($imagePath, $extension);
|
||||
|
||||
$savedImage = $processImage->saveImage($processImage->customFolder, $grayscaleImage, $imageNameGrayscale, $extension);
|
||||
|
||||
if(!$savedImage)
|
||||
{
|
||||
# return old image url without resize
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
# set folder to custom, so that the next method uses the correct (resized) version
|
||||
$this->imageFolder = 'custom';
|
||||
|
||||
$this->imageUrl = $imageUrlGrayscale;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function src()
|
||||
{
|
||||
# when we finish it, we shoud reset all settings
|
||||
$imagePath = $this->baseUrl . '/' . $this->imageUrl;
|
||||
$this->imageUrl = false;
|
||||
$this->imageFolder = 'original';
|
||||
|
||||
return $imagePath;
|
||||
}
|
||||
|
||||
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>';
|
||||
}
|
||||
|
||||
# print_r($this->JS);
|
||||
}
|
||||
|
||||
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 addSvgSymbol($symbol)
|
||||
{
|
||||
$this->svgSymbols[] = $symbol;
|
||||
}
|
||||
|
||||
# add JS to enhance the blox-editor in author area
|
||||
public function addEditorJS($JS)
|
||||
{
|
||||
$JSfile = $this->getFileUrl($JS);
|
||||
|
||||
if($JSfile)
|
||||
{
|
||||
$this->editorJS[] = '<script src="' . $JSfile . '"></script>';
|
||||
}
|
||||
}
|
||||
|
||||
public function addEditorInlineJS($JS)
|
||||
{
|
||||
$this->editorInlineJS[] = '<script>' . $JS . '</script>';
|
||||
}
|
||||
|
||||
public function addEditorCSS($CSS)
|
||||
{
|
||||
$CSSfile = $this->getFileUrl($CSS);
|
||||
|
||||
if($CSSfile)
|
||||
{
|
||||
$this->editorCSS[] = '<link rel="stylesheet" href="' . $CSSfile . '" />';
|
||||
}
|
||||
}
|
||||
|
||||
public function addMeta($key,$meta)
|
||||
{
|
||||
$this->meta[$key] = $meta;
|
||||
}
|
||||
|
||||
public function renderEditorJS()
|
||||
{
|
||||
return implode("\n", $this->editorJS) . implode("\n", $this->editorInlineJS);
|
||||
}
|
||||
|
||||
public function renderEditorCSS()
|
||||
{
|
||||
return implode("\n", $this->editorCSS);
|
||||
}
|
||||
|
||||
public function renderCSS()
|
||||
{
|
||||
return implode("\n", $this->CSS) . implode("\n", $this->inlineCSS);
|
||||
}
|
||||
|
||||
public function renderJS()
|
||||
{
|
||||
return implode("\n", $this->JS) . implode("\n", $this->inlineJS);
|
||||
}
|
||||
|
||||
public function renderSvg()
|
||||
{
|
||||
return implode('', $this->svgSymbols);
|
||||
}
|
||||
|
||||
public function renderMeta()
|
||||
{
|
||||
$metaLines = '';
|
||||
foreach($this->meta as $meta)
|
||||
{
|
||||
$metaLines .= "\n";
|
||||
$metaLines .= $meta;
|
||||
}
|
||||
return $metaLines;
|
||||
}
|
||||
/**
|
||||
* 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)
|
||||
{
|
||||
# check system path of file without parameter for fingerprinting
|
||||
$internalFile = __DIR__ . '/../plugins' . strtok($path, "?");
|
||||
|
||||
if(file_exists($internalFile))
|
||||
{
|
||||
return $this->baseUrl . '/plugins' . $path;
|
||||
}
|
||||
|
||||
return $path;
|
||||
|
||||
if(fopen($path, "r"))
|
||||
{
|
||||
return $path;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,340 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Controllers;
|
||||
|
||||
use Typemill\Models\Folder;
|
||||
use Typemill\Models\WriteMeta;
|
||||
use Typemill\Models\WriteYaml;
|
||||
use Typemill\Models\Validation;
|
||||
|
||||
class ControllerAuthor extends ControllerShared
|
||||
{
|
||||
# holds the params from request
|
||||
protected $params;
|
||||
|
||||
# holds the slim-uri-object from request
|
||||
protected $uri;
|
||||
|
||||
# holds the errors to output in frontend
|
||||
protected $errors = false;
|
||||
|
||||
# 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;
|
||||
|
||||
# holds the ownership (my content or not my content)
|
||||
protected $mycontent = false;
|
||||
|
||||
# author
|
||||
protected function getValidator()
|
||||
{
|
||||
return new Validation();
|
||||
}
|
||||
|
||||
# author
|
||||
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;
|
||||
}
|
||||
|
||||
# author
|
||||
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;
|
||||
}
|
||||
|
||||
# author
|
||||
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;
|
||||
}
|
||||
|
||||
# author
|
||||
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;
|
||||
}
|
||||
|
||||
# author
|
||||
protected function validateBaseNaviItem()
|
||||
{
|
||||
$validate = new Validation();
|
||||
$vResult = $validate->navigationBaseItem($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;
|
||||
}
|
||||
|
||||
# only backoffice
|
||||
protected function setHomepage($args)
|
||||
{
|
||||
$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'] == '/' || (is_array($args) && empty($args)))
|
||||
{
|
||||
$active = 'active';
|
||||
}
|
||||
|
||||
$this->homepage = ['status' => $status, 'active' => $active];
|
||||
}
|
||||
|
||||
# only backoffice
|
||||
protected function setItem()
|
||||
{
|
||||
# home is only set by backend controller, not by api calls
|
||||
$home = isset($this->homepage['active']) ? $this->homepage['active'] : false;
|
||||
|
||||
# search for the url in the structure
|
||||
$item = Folder::getItemForUrl($this->structureDraft, $this->params['url'], $this->uri->getBaseUrl(), NULL, $home);
|
||||
|
||||
if($item)
|
||||
{
|
||||
$this->item = $item;
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->errors = ['errors' => ['message' => 'requested page-url not found']];
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
# only backoffice
|
||||
# determine if you want to write to published file (md) or to draft (txt)
|
||||
protected function setItemPath($fileType)
|
||||
{
|
||||
$this->path = $this->item->pathWithoutType . '.' . $fileType;
|
||||
}
|
||||
|
||||
# only backoffice
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
# only backoffice
|
||||
protected function setContent()
|
||||
{
|
||||
# if the file exists
|
||||
if($this->item->published OR $this->item->drafted)
|
||||
{
|
||||
$content = $this->writeCache->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;
|
||||
}
|
||||
|
||||
# only backoffice
|
||||
protected function checkContentOwnership()
|
||||
{
|
||||
# get page meta
|
||||
$writeMeta = new writeMeta();
|
||||
$pagemeta = $writeMeta->getPageMeta($this->settings, $this->item);
|
||||
|
||||
# check ownership
|
||||
if(isset($pagemeta['meta']['owner']) && $pagemeta['meta']['owner'] && $pagemeta['meta']['owner'] !== '' )
|
||||
{
|
||||
$allowedusers = array_map('trim', explode(",", $pagemeta['meta']['owner']));
|
||||
if(isset($_SESSION['user']) && in_array($_SESSION['user'], $allowedusers))
|
||||
{
|
||||
$this->mycontent = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
# only backoffice
|
||||
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;
|
||||
}
|
||||
|
||||
# only backoffice
|
||||
protected function deleteContentFolder()
|
||||
{
|
||||
$basePath = $this->settings['rootPath'] . $this->settings['contentFolder'];
|
||||
$path = $basePath . $this->item->path;
|
||||
|
||||
if(file_exists($path))
|
||||
{
|
||||
$files = array_diff(scandir($path), array('.', '..'));
|
||||
|
||||
# check if there are published pages or folders inside, 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(substr($file, -3) == '.md' && $file != 'index.md')
|
||||
{
|
||||
$this->errors = ['message' => 'Please unpublish all pages in the folder first.'];
|
||||
}
|
||||
}
|
||||
|
||||
if(!$this->errors)
|
||||
{
|
||||
foreach ($files as $file)
|
||||
{
|
||||
unlink(realpath($path) . DIRECTORY_SEPARATOR . $file);
|
||||
}
|
||||
return rmdir($path);
|
||||
}
|
||||
|
||||
# delete all files from the extended file
|
||||
$this->deleteFromExtended();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,597 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Controllers;
|
||||
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
use Typemill\Extensions\ParsedownExtension;
|
||||
use Typemill\Events\OnShortcodeFound;
|
||||
|
||||
class ControllerAuthorBlockApi extends ControllerAuthor
|
||||
{
|
||||
public function addBlock(Request $request, Response $response, $args)
|
||||
{
|
||||
/* get params from call */
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
|
||||
# minimum permission is that user is allowed to update his own content
|
||||
if(!$this->c->acl->isAllowed($_SESSION['role'], 'mycontent', 'update'))
|
||||
{
|
||||
return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to publish content.']), 403);
|
||||
}
|
||||
|
||||
/* validate input */
|
||||
if(!$this->validateBlockInput()){ return $response->withJson($this->errors,422); }
|
||||
|
||||
# set structure
|
||||
if(!$this->setStructureDraft()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
|
||||
|
||||
$this->setHomepage($args = false);
|
||||
|
||||
/* set item */
|
||||
if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
|
||||
|
||||
# if user has no right to delete content from others (eg admin or editor)
|
||||
if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'update'))
|
||||
{
|
||||
# check ownership. This code should nearly never run, because there is no button/interface to trigger it.
|
||||
if(!$this->checkContentOwnership())
|
||||
{
|
||||
return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to edit content.']), 403);
|
||||
}
|
||||
}
|
||||
|
||||
# set the status for published and drafted
|
||||
$this->setPublishStatus();
|
||||
|
||||
# set path
|
||||
$this->setItemPath($this->item->fileType);
|
||||
|
||||
# read content from file
|
||||
if(!$this->setContent()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
|
||||
|
||||
# make it more clear which content we have
|
||||
$pageMarkdown = $this->content;
|
||||
|
||||
$blockMarkdown = $this->params['markdown'];
|
||||
|
||||
# standardize line breaks
|
||||
$blockMarkdown = str_replace(array("\r\n", "\r"), "\n", $blockMarkdown);
|
||||
|
||||
# remove surrounding line breaks
|
||||
$blockMarkdown = trim($blockMarkdown, "\n");
|
||||
|
||||
if($pageMarkdown == '')
|
||||
{
|
||||
$pageMarkdown = [];
|
||||
}
|
||||
|
||||
# initialize parsedown extension
|
||||
$parsedown = new ParsedownExtension($this->uri->getBaseUrl(), $settings = false, $this->c->dispatcher);
|
||||
|
||||
# if content is not an array, then transform it
|
||||
if(!is_array($pageMarkdown))
|
||||
{
|
||||
# turn markdown into an array of markdown-blocks
|
||||
$pageMarkdown = $parsedown->markdownToArrayBlocks($pageMarkdown);
|
||||
}
|
||||
|
||||
# if it is a new content-block
|
||||
if($this->params['block_id'] == 99999)
|
||||
{
|
||||
# set the id of the markdown-block (it will be one more than the actual array, so count is perfect)
|
||||
$id = count($pageMarkdown);
|
||||
|
||||
# add the new markdown block to the page content
|
||||
$pageMarkdown[] = $blockMarkdown;
|
||||
}
|
||||
elseif(($this->params['block_id'] == 0) OR !isset($pageMarkdown[$this->params['block_id']]))
|
||||
{
|
||||
# if the block does not exists, return an error
|
||||
return $response->withJson(array('data' => false, 'errors' => ['message' => 'The ID of the content-block is wrong.']), 404);
|
||||
}
|
||||
else
|
||||
{
|
||||
# insert new markdown block
|
||||
array_splice( $pageMarkdown, $this->params['block_id'], 0, $blockMarkdown );
|
||||
$id = $this->params['block_id'];
|
||||
}
|
||||
|
||||
# encode the content into json
|
||||
$pageJson = json_encode($pageMarkdown);
|
||||
|
||||
# set path for the file (or folder)
|
||||
$this->setItemPath('txt');
|
||||
|
||||
/* update the file */
|
||||
if($this->writeCache->writeFile($this->settings['contentFolder'], $this->path, $pageJson))
|
||||
{
|
||||
# update the internal structure
|
||||
$this->setFreshStructureDraft();
|
||||
$this->content = $pageMarkdown;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Could not write to file. Please check if the file is writable']], 404);
|
||||
}
|
||||
|
||||
/* set safe mode to escape javascript and html in markdown */
|
||||
$parsedown->setSafeMode(true);
|
||||
|
||||
/* parse markdown-file to content-array */
|
||||
$blockArray = $parsedown->text($blockMarkdown);
|
||||
|
||||
# we assume that toc is not relevant
|
||||
$toc = false;
|
||||
|
||||
# needed for ToC links
|
||||
$relurl = '/tm/content/' . $this->settings['editor'] . '/' . $this->item->urlRel;
|
||||
|
||||
if($blockMarkdown == '[TOC]')
|
||||
{
|
||||
# if block is table of content itself, then generate the table of content
|
||||
$tableofcontent = $this->generateToc();
|
||||
|
||||
# and only use the html-markup
|
||||
$blockHTML = $tableofcontent['html'];
|
||||
}
|
||||
else
|
||||
{
|
||||
# parse markdown-content-array to content-string
|
||||
$blockHTML = $parsedown->markup($blockArray);
|
||||
|
||||
# if it is a headline
|
||||
if($blockMarkdown[0] == '#')
|
||||
{
|
||||
# then the TOC holds either false (if no toc used in the page) or it holds an object with the id and toc-markup
|
||||
$toc = $this->generateToc();
|
||||
}
|
||||
}
|
||||
|
||||
return $response->withJson(array('content' => [ 'id' => $id, 'html' => $blockHTML ] , 'markdown' => $blockMarkdown, 'id' => $id, 'toc' => $toc, 'errors' => false));
|
||||
}
|
||||
|
||||
protected function generateToc()
|
||||
{
|
||||
# we assume that page has no table of content
|
||||
$toc = false;
|
||||
|
||||
# make sure $this->content is updated
|
||||
$content = $this->content;
|
||||
|
||||
if($content == '')
|
||||
{
|
||||
$content = [];
|
||||
}
|
||||
|
||||
# initialize parsedown extension
|
||||
$parsedown = new ParsedownExtension($this->uri->getBaseUrl());
|
||||
|
||||
# if content is not an array, then transform it
|
||||
if(!is_array($content))
|
||||
{
|
||||
# turn markdown into an array of markdown-blocks
|
||||
$content = $parsedown->markdownToArrayBlocks($content);
|
||||
}
|
||||
|
||||
# needed for ToC links
|
||||
$relurl = '/tm/content/' . $this->settings['editor'] . '/' . $this->item->urlRel;
|
||||
|
||||
# loop through mardkown-array and create html-blocks
|
||||
foreach($content as $key => $block)
|
||||
{
|
||||
# parse markdown-file to content-array
|
||||
$contentArray = $parsedown->text($block);
|
||||
|
||||
if($block == '[TOC]')
|
||||
{
|
||||
# toc is true and holds the key of the table of content now
|
||||
$toc = $key;
|
||||
}
|
||||
|
||||
# parse markdown-content-array to content-string
|
||||
$content[$key] = ['id' => $key, 'html' => $parsedown->markup($contentArray)];
|
||||
}
|
||||
|
||||
# if page has a table of content
|
||||
if($toc)
|
||||
{
|
||||
# generate the toc markup
|
||||
$tocMarkup = $parsedown->buildTOC($parsedown->headlines);
|
||||
|
||||
# toc holds the id of the table of content and the html-markup now
|
||||
$toc = ['id' => $toc, 'html' => $tocMarkup];
|
||||
}
|
||||
|
||||
return $toc;
|
||||
}
|
||||
|
||||
public function updateBlock(Request $request, Response $response, $args)
|
||||
{
|
||||
/* get params from call */
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
|
||||
# minimum permission is that user is allowed to update his own content
|
||||
if(!$this->c->acl->isAllowed($_SESSION['role'], 'mycontent', 'update'))
|
||||
{
|
||||
return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to publish content.']), 403);
|
||||
}
|
||||
|
||||
/* validate input */
|
||||
if(!$this->validateBlockInput()){ return $response->withJson($this->errors,422); }
|
||||
|
||||
# set structure
|
||||
if(!$this->setStructureDraft()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
|
||||
|
||||
$this->setHomepage($args = false);
|
||||
|
||||
/* set item */
|
||||
if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
|
||||
|
||||
# if user has no right to delete content from others (eg admin or editor)
|
||||
if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'update'))
|
||||
{
|
||||
# check ownership. This code should nearly never run, because there is no button/interface to trigger it.
|
||||
if(!$this->checkContentOwnership())
|
||||
{
|
||||
return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to edit content.']), 403);
|
||||
}
|
||||
}
|
||||
|
||||
# set the status for published and drafted
|
||||
$this->setPublishStatus();
|
||||
|
||||
# set path
|
||||
$this->setItemPath($this->item->fileType);
|
||||
|
||||
# read content from file
|
||||
if(!$this->setContent()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
|
||||
|
||||
# make it more clear which content we have
|
||||
$pageMarkdown = $this->content;
|
||||
|
||||
$blockMarkdown = $this->params['markdown'];
|
||||
|
||||
# standardize line breaks
|
||||
$blockMarkdown = str_replace(array("\r\n", "\r"), "\n", $blockMarkdown);
|
||||
|
||||
# remove surrounding line breaks
|
||||
$blockMarkdown = trim($blockMarkdown, "\n");
|
||||
|
||||
if($pageMarkdown == '')
|
||||
{
|
||||
$pageMarkdown = [];
|
||||
}
|
||||
|
||||
# initialize parsedown extension
|
||||
$parsedown = new ParsedownExtension($this->uri->getBaseUrl(), $settings = false, $this->c->dispatcher);
|
||||
$parsedown->setVisualMode();
|
||||
|
||||
# if content is not an array, then transform it
|
||||
if(!is_array($pageMarkdown))
|
||||
{
|
||||
# turn markdown into an array of markdown-blocks
|
||||
$pageMarkdown = $parsedown->markdownToArrayBlocks($pageMarkdown);
|
||||
}
|
||||
|
||||
if(!isset($pageMarkdown[$this->params['block_id']]))
|
||||
{
|
||||
# if the block does not exists, return an error
|
||||
return $response->withJson(array('data' => false, 'errors' => ['message' => 'The ID of the content-block is wrong.']), 404);
|
||||
}
|
||||
elseif($this->params['block_id'] == 0)
|
||||
{
|
||||
# if it is the title, then delete the "# " if it exists
|
||||
$blockMarkdown = trim($blockMarkdown, "# ");
|
||||
|
||||
# store the markdown-headline in a separate variable
|
||||
$blockMarkdownTitle = '# ' . $blockMarkdown;
|
||||
|
||||
# add the markdown-headline to the page-markdown
|
||||
$pageMarkdown[0] = $blockMarkdownTitle;
|
||||
$id = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
# update the markdown block in the page content
|
||||
$pageMarkdown[$this->params['block_id']] = $blockMarkdown;
|
||||
$id = $this->params['block_id'];
|
||||
}
|
||||
|
||||
# encode the content into json
|
||||
$pageJson = json_encode($pageMarkdown);
|
||||
|
||||
# set path for the file (or folder)
|
||||
$this->setItemPath('txt');
|
||||
|
||||
# update the file
|
||||
if($this->writeCache->writeFile($this->settings['contentFolder'], $this->path, $pageJson))
|
||||
{
|
||||
# update the internal structure
|
||||
$this->setFreshStructureDraft();
|
||||
|
||||
# updated the content variable
|
||||
$this->content = $pageMarkdown;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Could not write to file. Please check if the file is writable']], 404);
|
||||
}
|
||||
|
||||
# parse markdown-file to content-array, if title parse title.
|
||||
if($this->params['block_id'] == 0)
|
||||
{
|
||||
$blockArray = $parsedown->text($blockMarkdownTitle);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* set safe mode to escape javascript and html in markdown */
|
||||
$parsedown->setSafeMode(true);
|
||||
|
||||
$blockArray = $parsedown->text($blockMarkdown);
|
||||
}
|
||||
|
||||
# we assume that toc is not relevant
|
||||
$toc = false;
|
||||
|
||||
# needed for ToC links
|
||||
$relurl = '/tm/content/' . $this->settings['editor'] . '/' . $this->item->urlRel;
|
||||
|
||||
if($blockMarkdown == '[TOC]')
|
||||
{
|
||||
# if block is table of content itself, then generate the table of content
|
||||
$tableofcontent = $this->generateToc();
|
||||
|
||||
# and only use the html-markup
|
||||
$blockHTML = $tableofcontent['html'];
|
||||
}
|
||||
else
|
||||
{
|
||||
# parse markdown-content-array to content-string
|
||||
$blockHTML = $parsedown->markup($blockArray);
|
||||
|
||||
# if it is a headline
|
||||
if($blockMarkdown[0] == '#')
|
||||
{
|
||||
# then the TOC holds either false (if no toc used in the page) or it holds an object with the id and toc-markup
|
||||
$toc = $this->generateToc();
|
||||
}
|
||||
}
|
||||
|
||||
return $response->withJson(array('content' => [ 'id' => $id, 'html' => $blockHTML ] , 'markdown' => $blockMarkdown, 'id' => $id, 'toc' => $toc, 'errors' => false));
|
||||
}
|
||||
|
||||
public function moveBlock(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
|
||||
# minimum permission is that user is allowed to update his own content
|
||||
if(!$this->c->acl->isAllowed($_SESSION['role'], 'mycontent', 'update'))
|
||||
{
|
||||
return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to publish content.']), 403);
|
||||
}
|
||||
|
||||
# set structure
|
||||
if(!$this->setStructureDraft()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
|
||||
|
||||
$this->setHomepage($args = false);
|
||||
|
||||
# set item
|
||||
if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
|
||||
|
||||
# if user has no right to delete content from others (eg admin or editor)
|
||||
if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'update'))
|
||||
{
|
||||
# check ownership. This code should nearly never run, because there is no button/interface to trigger it.
|
||||
if(!$this->checkContentOwnership())
|
||||
{
|
||||
return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to delete content.']), 403);
|
||||
}
|
||||
}
|
||||
|
||||
# set the status for published and drafted
|
||||
$this->setPublishStatus();
|
||||
|
||||
# set path
|
||||
$this->setItemPath($this->item->fileType);
|
||||
|
||||
# read content from file
|
||||
if(!$this->setContent()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
|
||||
|
||||
# make it more clear which content we have
|
||||
$pageMarkdown = $this->content;
|
||||
|
||||
if($pageMarkdown == '')
|
||||
{
|
||||
$pageMarkdown = [];
|
||||
}
|
||||
|
||||
# initialize parsedown extension
|
||||
$parsedown = new ParsedownExtension($this->uri->getBaseUrl());
|
||||
|
||||
# if content is not an array, then transform it
|
||||
if(!is_array($pageMarkdown))
|
||||
{
|
||||
# turn markdown into an array of markdown-blocks
|
||||
$pageMarkdown = $parsedown->markdownToArrayBlocks($pageMarkdown);
|
||||
}
|
||||
|
||||
$oldIndex = ($this->params['old_index'] + 1);
|
||||
$newIndex = ($this->params['new_index'] + 1);
|
||||
|
||||
if(!isset($pageMarkdown[$oldIndex]))
|
||||
{
|
||||
# if the block does not exists, return an error
|
||||
return $response->withJson(array('data' => false, 'errors' => ['message' => 'The ID of the content-block is wrong.']), 404);
|
||||
}
|
||||
|
||||
$extract = array_splice($pageMarkdown, $oldIndex, 1);
|
||||
array_splice($pageMarkdown, $newIndex, 0, $extract);
|
||||
|
||||
# encode the content into json
|
||||
$pageJson = json_encode($pageMarkdown);
|
||||
|
||||
# set path for the file (or folder)
|
||||
$this->setItemPath('txt');
|
||||
|
||||
/* update the file */
|
||||
if($this->writeCache->writeFile($this->settings['contentFolder'], $this->path, $pageJson))
|
||||
{
|
||||
# update the internal structure
|
||||
$this->setFreshStructureDraft();
|
||||
|
||||
# update this content
|
||||
$this->content = $pageMarkdown;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Could not write to file. Please check if the file is writable']], 404);
|
||||
}
|
||||
|
||||
# we assume that toc is not relevant
|
||||
$toc = false;
|
||||
|
||||
# needed for ToC links
|
||||
$relurl = '/tm/content/' . $this->settings['editor'] . '/' . $this->item->urlRel;
|
||||
|
||||
# if the moved item is a headline
|
||||
if($extract[0][0] == '#')
|
||||
{
|
||||
$toc = $this->generateToc();
|
||||
}
|
||||
|
||||
# if it is the title, then delete the "# " if it exists
|
||||
$pageMarkdown[0] = trim($pageMarkdown[0], "# ");
|
||||
|
||||
return $response->withJson(array('markdown' => $pageMarkdown, 'toc' => $toc, 'errors' => false));
|
||||
}
|
||||
|
||||
public function deleteBlock(Request $request, Response $response, $args)
|
||||
{
|
||||
/* get params from call */
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
$errors = false;
|
||||
|
||||
# minimum permission is that user is allowed to update his own content
|
||||
if(!$this->c->acl->isAllowed($_SESSION['role'], 'mycontent', 'update'))
|
||||
{
|
||||
return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to delete this content.']), 403);
|
||||
}
|
||||
|
||||
# set structure
|
||||
if(!$this->setStructureDraft()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
|
||||
|
||||
$this->setHomepage($args = false);
|
||||
|
||||
# set item
|
||||
if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
|
||||
|
||||
# if user has no right to delete content from others (eg admin or editor)
|
||||
if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'update'))
|
||||
{
|
||||
# check ownership. This code should nearly never run, because there is no button/interface to trigger it.
|
||||
if(!$this->checkContentOwnership())
|
||||
{
|
||||
return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to delete this content.']), 403);
|
||||
}
|
||||
}
|
||||
|
||||
# set the status for published and drafted
|
||||
$this->setPublishStatus();
|
||||
|
||||
# set path
|
||||
$this->setItemPath($this->item->fileType);
|
||||
|
||||
# read content from file
|
||||
if(!$this->setContent()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
|
||||
|
||||
# get content
|
||||
$this->content;
|
||||
|
||||
if($this->content == '')
|
||||
{
|
||||
$this->content = [];
|
||||
}
|
||||
|
||||
# initialize parsedown extension
|
||||
$parsedown = new ParsedownExtension($this->uri->getBaseUrl());
|
||||
|
||||
# if content is not an array, then transform it
|
||||
if(!is_array($this->content))
|
||||
{
|
||||
# turn markdown into an array of markdown-blocks
|
||||
$this->content = $parsedown->markdownToArrayBlocks($this->content);
|
||||
}
|
||||
|
||||
# check if id exists
|
||||
if(!isset($this->content[$this->params['block_id']])){ return $response->withJson(array('data' => false, 'errors' => 'The ID of the content-block is wrong.'), 404); }
|
||||
|
||||
$contentBlock = $this->content[$this->params['block_id']];
|
||||
|
||||
# delete the block
|
||||
unset($this->content[$this->params['block_id']]);
|
||||
$this->content = array_values($this->content);
|
||||
|
||||
$pageMarkdown = $this->content;
|
||||
|
||||
# delete markdown from title
|
||||
if(isset($pageMarkdown[0]))
|
||||
{
|
||||
$pageMarkdown[0] = trim($pageMarkdown[0], "# ");
|
||||
}
|
||||
|
||||
# encode the content into json
|
||||
$pageJson = json_encode($this->content);
|
||||
|
||||
# set path for the file (or folder)
|
||||
$this->setItemPath('txt');
|
||||
|
||||
/* update the file */
|
||||
if($this->writeCache->writeFile($this->settings['contentFolder'], $this->path, $pageJson))
|
||||
{
|
||||
# update the internal structure
|
||||
$this->setFreshStructureDraft();
|
||||
}
|
||||
else
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Could not write to file. Please check if the file is writable']], 404);
|
||||
}
|
||||
|
||||
$toc = false;
|
||||
|
||||
if($contentBlock[0] == '#')
|
||||
{
|
||||
$toc = $this->generateToc();
|
||||
}
|
||||
|
||||
return $response->withJson(array('markdown' => $pageMarkdown, 'toc' => $toc, 'errors' => $errors));
|
||||
}
|
||||
|
||||
public function getShortcodeData(Request $request, Response $response, $args)
|
||||
{
|
||||
/* get params from call */
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
$errors = false;
|
||||
|
||||
# minimum permission is that user is allowed to update his own content
|
||||
if(!$this->c->acl->isAllowed($_SESSION['role'], 'mycontent', 'update'))
|
||||
{
|
||||
return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to delete this content.']), 403);
|
||||
}
|
||||
|
||||
$shortcodeData = $this->c->dispatcher->dispatch('onShortcodeFound', new OnShortcodeFound(['name' => 'registershortcode', 'data' => []]))->getData();
|
||||
|
||||
if(empty($shortcodeData['data']))
|
||||
{
|
||||
return $response->withJson(array('shortcodedata' => false));
|
||||
}
|
||||
|
||||
return $response->withJson(array('shortcodedata' => $shortcodeData['data']));
|
||||
}
|
||||
}
|
@ -1,181 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Controllers;
|
||||
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
use Typemill\Models\Folder;
|
||||
use Typemill\Extensions\ParsedownExtension;
|
||||
|
||||
class ControllerAuthorEditor extends ControllerAuthor
|
||||
{
|
||||
/**
|
||||
* 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()->withUserInfo('');
|
||||
$this->params = isset($args['params']) ? ['url' => $this->uri->getBasePath() . '/' . $args['params']] : ['url' => $this->uri->getBasePath()];
|
||||
|
||||
# set structure
|
||||
if(!$this->setStructureDraft()){ return $this->renderIntern404($response, array( 'navigation' => true, 'content' => $this->errors )); }
|
||||
|
||||
# set information for homepage
|
||||
$this->setHomepage($args);
|
||||
|
||||
# set item
|
||||
if(!$this->setItem()){ return $this->renderIntern404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); }
|
||||
|
||||
# we have to check ownership here to use it for permission-check in tempates
|
||||
$this->checkContentOwnership();
|
||||
|
||||
# get the breadcrumb (here we need it only to mark the actual item active in navigation)
|
||||
$breadcrumb = isset($this->item->keyPathArray) ? Folder::getBreadcrumb($this->structureDraft, $this->item->keyPathArray) : false;
|
||||
|
||||
# set the status for published and drafted
|
||||
$this->setPublishStatus();
|
||||
|
||||
# set path
|
||||
$this->setItemPath($this->item->fileType);
|
||||
|
||||
# add the modified date for the file
|
||||
$this->item->modified = ($this->item->published OR $this->item->drafted) ? filemtime($this->settings['contentFolder'] . $this->path) : false;
|
||||
|
||||
# read content from file
|
||||
if(!$this->setContent()){ return $this->renderIntern404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); }
|
||||
|
||||
$content = $this->content;
|
||||
$title = false;
|
||||
|
||||
# if content is an array, then it is a draft
|
||||
if(is_array($content))
|
||||
{
|
||||
# transform array to markdown
|
||||
$parsedown = new ParsedownExtension($this->uri->getBaseUrl());
|
||||
$content = $parsedown->arrayBlocksToMarkdown($content);
|
||||
}
|
||||
|
||||
# if there is content
|
||||
if($content != '')
|
||||
{
|
||||
# normalize linebreaks
|
||||
$content = str_replace(array("\r\n", "\r"), "\n", $content);
|
||||
$content = trim($content, "\n");
|
||||
|
||||
# and strip out title
|
||||
if($content[0] == '#')
|
||||
{
|
||||
$contentParts = explode("\n", $content, 2);
|
||||
$title = trim($contentParts[0], "# \t\n\r\0\x0B");
|
||||
$content = trim($contentParts[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->renderIntern($response, 'editor/editor-raw.twig', array(
|
||||
'acl' => $this->c->acl,
|
||||
'mycontent' => $this->mycontent,
|
||||
'navigation' => $this->structureDraft,
|
||||
'homepage' => $this->homepage,
|
||||
'title' => $title,
|
||||
'content' => $content,
|
||||
'item' => $this->item,
|
||||
'settings' => $this->settings
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()->withUserInfo('');
|
||||
$this->params = isset($args['params']) ? ['url' => $this->uri->getBasePath() . '/' . $args['params']] : ['url' => $this->uri->getBasePath()];
|
||||
|
||||
# set structure
|
||||
if(!$this->setStructureDraft()){ return $this->renderIntern404($response, array( 'navigation' => true, 'content' => $this->errors )); }
|
||||
|
||||
# set information for homepage
|
||||
$this->setHomepage($args);
|
||||
|
||||
# set item
|
||||
if(!$this->setItem()){ return $this->renderIntern404($response, array( 'navigation' => $this->structureDraft, 'settings' => $this->settings, 'content' => $this->errors )); }
|
||||
|
||||
# we have to check ownership here to use it for permission-check in tempates
|
||||
$this->checkContentOwnership();
|
||||
|
||||
# set the status for published and drafted
|
||||
$this->setPublishStatus();
|
||||
|
||||
# set path
|
||||
$this->setItemPath($this->item->fileType);
|
||||
|
||||
# add the modified date for the file
|
||||
$this->item->modified = ($this->item->published OR $this->item->drafted) ? filemtime($this->settings['contentFolder'] . $this->path) : false;
|
||||
|
||||
# read content from file
|
||||
if(!$this->setContent()){ return $this->renderIntern404($response, array( 'navigation' => $this->structure, 'settings' => $this->settings, 'content' => $this->errors )); }
|
||||
|
||||
$content = $this->content;
|
||||
|
||||
if($content == '')
|
||||
{
|
||||
$content = [];
|
||||
}
|
||||
|
||||
# initialize parsedown extension
|
||||
$parsedown = new ParsedownExtension($this->uri->getBaseUrl());
|
||||
|
||||
# to fix footnote-logic in parsedown, set visual mode to true
|
||||
$parsedown->setVisualMode();
|
||||
|
||||
# if content is not an array, then transform it
|
||||
if(!is_array($content))
|
||||
{
|
||||
# turn markdown into an array of markdown-blocks
|
||||
$content = $parsedown->markdownToArrayBlocks($content);
|
||||
}
|
||||
|
||||
# needed for ToC links
|
||||
$relurl = '/tm/content/' . $this->settings['editor'] . '/' . $this->item->urlRel;
|
||||
|
||||
foreach($content as $key => $block)
|
||||
{
|
||||
/* parse markdown-file to content-array */
|
||||
$contentArray = $parsedown->text($block);
|
||||
|
||||
/* parse markdown-content-array to content-string */
|
||||
$content[$key] = $parsedown->markup($contentArray);
|
||||
}
|
||||
|
||||
# extract title and delete from content array, array will start at 1 after that.
|
||||
$title = '# add title';
|
||||
if(isset($content[0]))
|
||||
{
|
||||
$title = $content[0];
|
||||
unset($content[0]);
|
||||
}
|
||||
|
||||
return $this->renderIntern($response, 'editor/editor-blox.twig', array(
|
||||
'acl' => $this->c->acl,
|
||||
'mycontent' => $this->mycontent,
|
||||
'navigation' => $this->structureDraft,
|
||||
'homepage' => $this->homepage,
|
||||
'title' => $title,
|
||||
'content' => $content,
|
||||
'item' => $this->item,
|
||||
'settings' => $this->settings
|
||||
));
|
||||
}
|
||||
}
|
@ -1,639 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Controllers;
|
||||
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
use Typemill\Models\ProcessImage;
|
||||
use Typemill\Models\ProcessFile;
|
||||
use Typemill\Models\WriteYaml;
|
||||
use Typemill\Controllers\ControllerAuthorBlockApi;
|
||||
|
||||
class ControllerAuthorMediaApi extends ControllerAuthor
|
||||
{
|
||||
public function getMediaLibImages(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
if(!$imageProcessor->checkFolders('images'))
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
$imagelist = $imageProcessor->scanMediaFlat();
|
||||
|
||||
return $response->withJson(['images' => $imagelist]);
|
||||
}
|
||||
|
||||
public function getMediaLibFiles(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
|
||||
$fileProcessor = new ProcessFile();
|
||||
if(!$fileProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
$filelist = $fileProcessor->scanFilesFlat();
|
||||
|
||||
return $response->withJson(['files' => $filelist]);
|
||||
}
|
||||
|
||||
public function getImage(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
|
||||
$this->setStructureDraft();
|
||||
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
if(!$imageProcessor->checkFolders('images'))
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
$imageDetails = $imageProcessor->getImageDetails($this->params['name'], $this->structureDraft);
|
||||
|
||||
if($imageDetails)
|
||||
{
|
||||
return $response->withJson(['image' => $imageDetails]);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => 'Image not found or image name not valid.'], 404);
|
||||
}
|
||||
|
||||
public function getFile(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
|
||||
$this->setStructureDraft();
|
||||
|
||||
$fileProcessor = new ProcessFile();
|
||||
if(!$fileProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
$fileDetails = $fileProcessor->getFileDetails($this->params['name'], $this->structureDraft);
|
||||
|
||||
if($fileDetails)
|
||||
{
|
||||
return $response->withJson(['file' => $fileDetails]);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => 'file not found or file name invalid'],404);
|
||||
}
|
||||
|
||||
public function getFileRestrictions(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
$restriction = 'all';
|
||||
|
||||
$userroles = $this->c->acl->getRoles();
|
||||
|
||||
if(isset($this->params['filename']) && $this->params['filename'] != '')
|
||||
{
|
||||
$writeYaml = new WriteYaml();
|
||||
$restrictions = $writeYaml->getYaml('media' . DIRECTORY_SEPARATOR . 'files', 'filerestrictions.yaml');
|
||||
if(isset($restrictions[$this->params['filename']]))
|
||||
{
|
||||
$restriction = $restrictions[$this->params['filename']];
|
||||
}
|
||||
}
|
||||
|
||||
return $response->withJson(['userroles' => $userroles, 'restriction' => $restriction]);
|
||||
}
|
||||
|
||||
public function updateFileRestrictions(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
$filename = isset($this->params['filename']) ? $this->params['filename'] : false;
|
||||
$role = isset($this->params['role']) ? $this->params['role'] : false;
|
||||
|
||||
if(!$filename OR !$role)
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Filename or userrole is missing.']], 422);
|
||||
}
|
||||
|
||||
$userroles = $this->c->acl->getRoles();
|
||||
|
||||
if($role != 'all' AND !in_array($role, $userroles))
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Userrole is unknown.']], 422);
|
||||
}
|
||||
|
||||
$writeYaml = new WriteYaml();
|
||||
$restrictions = $writeYaml->getYaml('media' . DIRECTORY_SEPARATOR . 'files', 'filerestrictions.yaml');
|
||||
if(!$restrictions)
|
||||
{
|
||||
$restrictions = [];
|
||||
}
|
||||
|
||||
if($role == 'all')
|
||||
{
|
||||
unset($restrictions[$filename]);
|
||||
}
|
||||
else
|
||||
{
|
||||
$restrictions[$filename] = $role;
|
||||
}
|
||||
|
||||
$writeYaml->updateYaml('media' . DIRECTORY_SEPARATOR . 'files', 'filerestrictions.yaml', $restrictions);
|
||||
|
||||
return $response->withJson(['restrictions' => $restrictions]);
|
||||
}
|
||||
|
||||
public function createImage(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
|
||||
if(!$imageProcessor->checkFolders('images'))
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Please check if your media-folder exists and all folders inside are writable.']], 500);
|
||||
}
|
||||
|
||||
$imageParts = explode(";base64,", $this->params['image']);
|
||||
$imageType = explode("/", $imageParts[0]);
|
||||
|
||||
if(!isset($imageType[1]))
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'We did not find an image type, the file might be corrupted.']], 422);
|
||||
}
|
||||
|
||||
$acceptedTypes = [
|
||||
'png' => true,
|
||||
'jpg' => true,
|
||||
'jpeg' => true,
|
||||
'gif' => true,
|
||||
'webp' => true,
|
||||
];
|
||||
|
||||
if(isset($this->settings['svg']) && $this->settings['svg'])
|
||||
{
|
||||
$acceptedTypes['svg+xml'] = true;
|
||||
}
|
||||
|
||||
if(!isset($acceptedTypes[$imageType[1]]))
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'The image type is not supported.']], 422);
|
||||
}
|
||||
|
||||
$imageResult = $imageProcessor->createImage($this->params['image'], $this->params['name'], $this->settings['images']);
|
||||
|
||||
if($imageResult)
|
||||
{
|
||||
if(is_array($imageResult) && isset($imageResult['errors']))
|
||||
{
|
||||
return $response->withJson($imageResult,422);
|
||||
}
|
||||
|
||||
# publish image directly, used for example by image field for meta-tabs
|
||||
if($this->params['publish'])
|
||||
{
|
||||
$imageProcessor->publishImage();
|
||||
}
|
||||
return $response->withJson(['name' => 'media/live/' . $imageProcessor->getFullName(),'errors' => false]);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => 'could not store image to temporary folder'],422);
|
||||
}
|
||||
|
||||
public function uploadFile(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
|
||||
if (!isset($this->params['file']))
|
||||
{
|
||||
return $response->withJson(['errors' => 'No file found.'],404);
|
||||
}
|
||||
|
||||
$size = (int) (strlen(rtrim($this->params['file'], '=')) * 3 / 4);
|
||||
$extension = pathinfo($this->params['name'], PATHINFO_EXTENSION);
|
||||
$finfo = finfo_open( FILEINFO_MIME_TYPE );
|
||||
$mtype = @finfo_file( $finfo, $this->params['file'] );
|
||||
finfo_close($finfo);
|
||||
|
||||
if ($size === 0)
|
||||
{
|
||||
return $response->withJson(['errors' => 'File is empty.'],422);
|
||||
}
|
||||
|
||||
# 20 MB (1 byte * 1024 * 1024 * 20 (for 20 MB))
|
||||
if ($size > 20971520)
|
||||
{
|
||||
return $response->withJson(['errors' => 'File is bigger than 20MB.'],422);
|
||||
}
|
||||
|
||||
# check extension first
|
||||
if (!$this->checkAllowedExtensions($extension))
|
||||
{
|
||||
return $response->withJson(['errors' => 'File is not allowed.'],422);
|
||||
}
|
||||
|
||||
# check mimetype and extension if there is a mimetype.
|
||||
# in some environments the finfo_file does not work with a base64 string.
|
||||
if($mtype)
|
||||
{
|
||||
if(!$this->checkAllowedMimeTypes($mtype, $extension))
|
||||
{
|
||||
return $response->withJson(['errors' => 'The mime-type or file extension is not allowed.'],422);
|
||||
}
|
||||
}
|
||||
|
||||
$fileProcessor = new ProcessFile();
|
||||
|
||||
if(!$fileProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
$fileinfo = $fileProcessor->storeFile($this->params['file'], $this->params['name']);
|
||||
|
||||
if($fileinfo)
|
||||
{
|
||||
# if the previous check of the mtype with the base64 string failed, then do it now again with the temporary file
|
||||
if(!$mtype)
|
||||
{
|
||||
$filePath = str_replace('media/files', 'media/tmp', $fileinfo['url']);
|
||||
$fullPath = $this->settings['rootPath'] . $filePath;
|
||||
$finfo = finfo_open( FILEINFO_MIME_TYPE );
|
||||
$mtype = @finfo_file( $finfo, $fullPath );
|
||||
finfo_close($finfo);
|
||||
|
||||
if(!$mtype OR !$this->checkAllowedMimeTypes($mtype, $extension))
|
||||
{
|
||||
$fileProcessor->clearTempFolder();
|
||||
|
||||
return $response->withJson(['errors' => 'The mime-type is missing, not allowed or does not fit to the file extension.'],422);
|
||||
}
|
||||
}
|
||||
|
||||
# publish file directly, used for example by file field for meta-tabs
|
||||
if(isset($this->params['publish']) && $this->params['publish'])
|
||||
{
|
||||
$fileProcessor->publishFile();
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => false, 'info' => $fileinfo]);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => 'could not store file to temporary folder'],500);
|
||||
}
|
||||
|
||||
public function publishImage(Request $request, Response $response, $args)
|
||||
{
|
||||
$params = $request->getParsedBody();
|
||||
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
if(!$imageProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
# check the resize modifier in the image markdown, set it to true and delete it from markdown
|
||||
$noresize = false;
|
||||
$markdown = isset($params['markdown']) ? $params['markdown'] : false;
|
||||
|
||||
if($markdown && (strlen($markdown) > 9) && (substr($markdown, -9) == '|noresize') )
|
||||
{
|
||||
$noresize = true;
|
||||
$params['markdown'] = substr($markdown,0,-9);
|
||||
}
|
||||
|
||||
if($imageProcessor->publishImage($noresize))
|
||||
{
|
||||
$request = $request->withParsedBody($params);
|
||||
|
||||
$block = new ControllerAuthorBlockApi($this->c);
|
||||
if($params['new'])
|
||||
{
|
||||
return $block->addBlock($request, $response, $args);
|
||||
}
|
||||
return $block->updateBlock($request, $response, $args);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => 'could not store image to media folder'],500);
|
||||
}
|
||||
|
||||
public function publishFile(Request $request, Response $response, $args)
|
||||
{
|
||||
$params = $request->getParsedBody();
|
||||
|
||||
$fileProcessor = new ProcessFile();
|
||||
if(!$fileProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
if($fileProcessor->publishFile())
|
||||
{
|
||||
$request = $request->withParsedBody($params);
|
||||
|
||||
$block = new ControllerAuthorBlockApi($this->c);
|
||||
if($params['new'])
|
||||
{
|
||||
return $block->addBlock($request, $response, $args);
|
||||
}
|
||||
return $block->updateBlock($request, $response, $args);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => 'could not store file to media folder'],500);
|
||||
}
|
||||
|
||||
public function deleteImage(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
|
||||
# minimum permission is that user is allowed to delete content
|
||||
if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'delete'))
|
||||
{
|
||||
return $response->withJson(array('data' => false, 'errors' => 'You are not allowed to delete images.'), 403);
|
||||
}
|
||||
|
||||
if(!isset($this->params['name']))
|
||||
{
|
||||
return $response->withJson(['errors' => 'image name is missing'],500);
|
||||
}
|
||||
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
if(!$imageProcessor->checkFolders('images'))
|
||||
{
|
||||
return $response->withJson(['errors' => 'Please check if your media-folder exists and all folders inside are writable.'], 500);
|
||||
}
|
||||
|
||||
if($imageProcessor->deleteImage($this->params['name']))
|
||||
{
|
||||
return $response->withJson(['errors' => false]);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => 'Oops, looks like we could not delete all sizes of that image.'], 500);
|
||||
}
|
||||
|
||||
public function deleteFile(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
|
||||
# minimum permission is that user is allowed to delete content
|
||||
if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'delete'))
|
||||
{
|
||||
return $response->withJson(array('data' => false, 'errors' => 'You are not allowed to delete files.'), 403);
|
||||
}
|
||||
|
||||
if(!isset($this->params['name']))
|
||||
{
|
||||
return $response->withJson(['errors' => 'file name is missing'],500);
|
||||
}
|
||||
|
||||
$fileProcessor = new ProcessFile();
|
||||
|
||||
if($fileProcessor->deleteFile($this->params['name']))
|
||||
{
|
||||
return $response->withJson(['errors' => false]);
|
||||
}
|
||||
|
||||
return $response->withJson(['errors' => 'could not delete the file'],500);
|
||||
}
|
||||
|
||||
public function saveVideoImage(Request $request, Response $response, $args)
|
||||
{
|
||||
/* get params from call */
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
$class = false;
|
||||
|
||||
$imageUrl = $this->params['markdown'];
|
||||
|
||||
if(strpos($imageUrl, 'https://www.youtube.com/watch?v=') !== false)
|
||||
{
|
||||
$videoID = str_replace('https://www.youtube.com/watch?v=', '', $imageUrl);
|
||||
$videoID = strpos($videoID, '&') ? substr($videoID, 0, strpos($videoID, '&')) : $videoID;
|
||||
$class = 'youtube';
|
||||
}
|
||||
if(strpos($imageUrl, 'https://youtu.be/') !== false)
|
||||
{
|
||||
$videoID = str_replace('https://youtu.be/', '', $imageUrl);
|
||||
$videoID = strpos($videoID, '?') ? substr($videoID, 0, strpos($videoID, '?')) : $videoID;
|
||||
$class = 'youtube';
|
||||
}
|
||||
|
||||
if($class == 'youtube')
|
||||
{
|
||||
$videoURLmaxres = 'https://i1.ytimg.com/vi/' . $videoID . '/maxresdefault.jpg';
|
||||
$videoURL0 = 'https://i1.ytimg.com/vi/' . $videoID . '/0.jpg';
|
||||
}
|
||||
|
||||
$ctx = stream_context_create(array(
|
||||
'https' => array(
|
||||
'timeout' => 1
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$imageData = @file_get_contents($videoURLmaxres, 0, $ctx);
|
||||
if($imageData === false)
|
||||
{
|
||||
$imageData = @file_get_contents($videoURL0, 0, $ctx);
|
||||
if($imageData === false)
|
||||
{
|
||||
return $response->withJson(array('errors' => 'could not get the video image'));
|
||||
}
|
||||
}
|
||||
|
||||
$imageData64 = 'data:image/jpeg;base64,' . base64_encode($imageData);
|
||||
$desiredSizes = ['live' => ['width' => 560, 'height' => 315]];
|
||||
$imageProcessor = new ProcessImage($this->settings['images']);
|
||||
if(!$imageProcessor->checkFolders())
|
||||
{
|
||||
return $response->withJson(['errors' => ['message' => 'Please check if your media-folder exists and all folders inside are writable.']], 500);
|
||||
}
|
||||
|
||||
$tmpImage = $imageProcessor->createImage($imageData64, $videoID, $desiredSizes);
|
||||
|
||||
if(!$tmpImage)
|
||||
{
|
||||
return $response->withJson(array('errors' => 'could not create temporary image'));
|
||||
}
|
||||
|
||||
$imageUrl = $imageProcessor->publishImage();
|
||||
if($imageUrl)
|
||||
{
|
||||
$this->params['markdown'] = '{#' . $videoID. ' .' . $class . '}';
|
||||
|
||||
$request = $request->withParsedBody($this->params);
|
||||
$block = new ControllerAuthorBlockApi($this->c);
|
||||
if($this->params['new'])
|
||||
{
|
||||
return $block->addBlock($request, $response, $args);
|
||||
}
|
||||
return $block->updateBlock($request, $response, $args);
|
||||
}
|
||||
|
||||
return $response->withJson(array('errors' => 'could not store the preview image'));
|
||||
}
|
||||
|
||||
# https://www.sitepoint.com/mime-types-complete-list/
|
||||
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
|
||||
# https://wiki.selfhtml.org/wiki/MIME-Type/%C3%9Cbersicht
|
||||
# http://www.mime-type.net/application/x-latex/
|
||||
private function getAllowedMtypes()
|
||||
{
|
||||
return array(
|
||||
'application/vnd.oasis.opendocument.chart' => 'odc',
|
||||
'application/vnd.oasis.opendocument.formula' => 'odf',
|
||||
'application/vnd.oasis.opendocument.graphics' => 'odg',
|
||||
'application/vnd.oasis.opendocument.image' => 'odi',
|
||||
'application/vnd.oasis.opendocument.presentation' => 'odp',
|
||||
'application/vnd.oasis.opendocument.spreadsheet' => 'ods',
|
||||
'application/vnd.oasis.opendocument.text' => 'odt',
|
||||
'application/vnd.oasis.opendocument.text-master' => 'odm',
|
||||
|
||||
'application/powerpoint' => 'ppt',
|
||||
'application/mspowerpoint' => ['ppt','ppz','pps','pot'],
|
||||
'application/x-mspowerpoint' => 'ppt',
|
||||
'application/vnd.ms-powerpoint' => 'ppt',
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation' => 'pptx',
|
||||
|
||||
'application/x-visio' => ['vsd','vst','msw'],
|
||||
'application/vnd.visio' => ['vsd','vst','msw'],
|
||||
'application/x-project' => ['mpc','mpt','mpv','mpx'],
|
||||
'application/vnd.ms-project' => 'mpp',
|
||||
|
||||
'application/excel' => ['xla','xlb','xlc','xld','xlk','xll','xlm','xls','xlt','xlv','xlw'],
|
||||
'application/msexcel' => ['xls','xla'],
|
||||
'application/x-excel' => ['xla','xlb','xlc','xld','xlk','xll','xlm','xls','xlt','xlv','xlw'],
|
||||
'application/x-msexcel' => ['xls', 'xla','xlw'],
|
||||
'application/vnd.ms-excel' => ['xlb','xlc','xll','xlm','xls','xlw'],
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx',
|
||||
|
||||
'application/mshelp' => ['hlp','chm'],
|
||||
'application/msword' => ['doc','dot'],
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',
|
||||
|
||||
'application/vnd.apple.keynote' => 'key',
|
||||
'application/vnd.apple.numbers' => 'numbers',
|
||||
'application/vnd.apple.pages' => 'pages',
|
||||
|
||||
'application/x-latex' => ['ltx','latex'],
|
||||
'application/pdf' => 'pdf',
|
||||
|
||||
'application/vnd.amazon.mobi8-ebook' => 'azw3',
|
||||
'application/x-mobipocket-ebook' => 'mobi',
|
||||
'application/epub+zip' => 'epub',
|
||||
|
||||
'application/x-gtar' => 'gtar',
|
||||
'application/x-tar' => 'tar',
|
||||
'application/zip' => 'zip',
|
||||
'application/gzip' => 'gz',
|
||||
'application/x-gzip' => ['gz', 'gzip'],
|
||||
'application/x-compressed' => ['gz','tgz','z','zip'],
|
||||
'application/x-zip-compressed' => 'zip',
|
||||
'application/vnd.rar' => 'rar',
|
||||
'application/x-7z-compressed' => '7z',
|
||||
|
||||
'application/rtf' => 'rtf',
|
||||
'application/x-rtf' => 'rtf',
|
||||
|
||||
'text/calendar' => 'ics',
|
||||
'text/comma-separated-values' => 'csv',
|
||||
'text/css' => 'css',
|
||||
'text/plain' => 'txt',
|
||||
'text/richtext' => 'rtx',
|
||||
'text/rtf' => 'rtf',
|
||||
|
||||
'audio/basic' => ['au','snd'],
|
||||
'audio/mpeg' => 'mp3',
|
||||
'audio/mp4' => 'mp4',
|
||||
'audio/ogg' => 'ogg',
|
||||
'audio/wav' => 'wav',
|
||||
'audio/x-aiff' => ['aif','aiff','aifc'],
|
||||
'audio/x-midi' => ['mid','midi'],
|
||||
'audio/x-mpeg' => 'mp2',
|
||||
'audio/x-pn-realaudio' => ['ram','ra'],
|
||||
|
||||
'image/png' => 'png',
|
||||
'image/jpeg' => ['jpeg','jpe','jpg'],
|
||||
'image/gif' => 'gif',
|
||||
'image/tiff' => ['tiff','tif'],
|
||||
'image/svg+xml' => 'svg',
|
||||
'image/x-icon' => 'ico',
|
||||
'image/webp' => 'webp',
|
||||
|
||||
'video/mpeg' => ['mpeg','mpg','mpe'],
|
||||
'video/mp4' => 'mp4',
|
||||
'video/ogg' => ['ogg','ogv'],
|
||||
'video/quicktime' => ['qt','mov'],
|
||||
'video/vnd.vivo' => ['viv','vivo'],
|
||||
'video/webm' => 'webm',
|
||||
'video/x-msvideo' => 'avi',
|
||||
'video/x-sgi-movie' => 'movie',
|
||||
'video/3gpp' => '3gp',
|
||||
);
|
||||
}
|
||||
|
||||
protected function checkAllowedMimeTypes($mtype, $extension)
|
||||
{
|
||||
$allowedMimes = $this->getAllowedMtypes();
|
||||
|
||||
if(!isset($allowedMimes[$mtype]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(
|
||||
(is_array($allowedMimes[$mtype]) && !in_array($extension, $allowedMimes[$mtype])) OR
|
||||
(!is_array($allowedMimes[$mtype]) && $allowedMimes[$mtype] != $extension )
|
||||
)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function checkAllowedExtensions($extension)
|
||||
{
|
||||
$mtypes = $this->getAllowedMtypes();
|
||||
foreach($mtypes as $mtExtension)
|
||||
{
|
||||
if(is_array($mtExtension))
|
||||
{
|
||||
if(in_array($extension, $mtExtension))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if($extension == $mtExtension)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,424 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Controllers;
|
||||
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
use Typemill\Models\WriteYaml;
|
||||
use Typemill\Models\WriteMeta;
|
||||
use Typemill\Models\Folder;
|
||||
use Typemill\Events\OnMetaDefinitionsLoaded;
|
||||
|
||||
class ControllerAuthorMetaApi extends ControllerAuthor
|
||||
{
|
||||
# get the standard meta-definitions and the meta-definitions from plugins (same for all sites)
|
||||
public function getMetaDefinitions(Request $request, Response $response, $args)
|
||||
{
|
||||
$metatabs = $this->aggregateMetaDefinitions();
|
||||
|
||||
return $response->withJson(array('definitions' => $metatabs, 'errors' => false));
|
||||
}
|
||||
|
||||
# get the standard meta-definitions and the meta-definitions from plugins (same for all sites)
|
||||
public function aggregateMetaDefinitions($folder = null)
|
||||
{
|
||||
$writeYaml = new writeYaml();
|
||||
|
||||
$metatabs = $writeYaml->getYaml('system' . DIRECTORY_SEPARATOR . 'author', 'metatabs.yaml');
|
||||
|
||||
# the fields for user or role based access
|
||||
if(!isset($this->settings['pageaccess']) || $this->settings['pageaccess'] === NULL )
|
||||
{
|
||||
unset($metatabs['meta']['fields']['fieldsetrights']);
|
||||
}
|
||||
|
||||
# add radio buttons to choose posts or pages for folder.
|
||||
if(!$folder)
|
||||
{
|
||||
unset($metatabs['meta']['fields']['contains']);
|
||||
}
|
||||
|
||||
# loop through all plugins
|
||||
if(!empty($this->settings['plugins']))
|
||||
{
|
||||
foreach($this->settings['plugins'] as $name => $plugin)
|
||||
{
|
||||
if($plugin['active'])
|
||||
{
|
||||
$pluginSettings = \Typemill\Settings::getObjectSettings('plugins', $name);
|
||||
if($pluginSettings && isset($pluginSettings['metatabs']))
|
||||
{
|
||||
$metatabs = array_merge_recursive($metatabs, $pluginSettings['metatabs']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# add the meta from theme settings here
|
||||
$themeSettings = \Typemill\Settings::getObjectSettings('themes', $this->settings['theme']);
|
||||
|
||||
if($themeSettings && isset($themeSettings['metatabs']))
|
||||
{
|
||||
$metatabs = array_merge_recursive($metatabs, $themeSettings['metatabs']);
|
||||
}
|
||||
|
||||
# dispatch meta
|
||||
$metatabs = $this->c->dispatcher->dispatch('onMetaDefinitionsLoaded', new OnMetaDefinitionsLoaded($metatabs))->getData();
|
||||
|
||||
return $metatabs;
|
||||
}
|
||||
|
||||
public function getArticleMetaObject(Request $request, Response $response, $args)
|
||||
{
|
||||
/* get params from call */
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
|
||||
# set structure
|
||||
if(!$this->setStructureDraft()){ return $response->withJson($this->errors, 404); }
|
||||
|
||||
# set information for homepage
|
||||
$this->setHomepage($args = false);
|
||||
|
||||
# set item
|
||||
if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
|
||||
|
||||
$writeMeta = new writeMeta();
|
||||
|
||||
$pagemeta = $writeMeta->getPageMeta($this->settings, $this->item);
|
||||
|
||||
if(!$pagemeta)
|
||||
{
|
||||
# set the status for published and drafted
|
||||
$this->setPublishStatus();
|
||||
|
||||
# set path
|
||||
$this->setItemPath($this->item->fileType);
|
||||
|
||||
# read content from file
|
||||
if(!$this->setContent()){ return $response->withJson(array('data' => false, 'errors' => $this->errors), 404); }
|
||||
|
||||
$pagemeta = $writeMeta->getPageMetaBlank($this->content, $this->settings, $this->item);
|
||||
}
|
||||
|
||||
# if item is a folder
|
||||
if($this->item->elementType == "folder" && isset($this->item->contains))
|
||||
{
|
||||
|
||||
$pagemeta['meta']['contains'] = isset($pagemeta['meta']['contains']) ? $pagemeta['meta']['contains'] : $this->item->contains;
|
||||
|
||||
# get global metadefinitions
|
||||
$metadefinitions = $this->aggregateMetaDefinitions($folder = true);
|
||||
}
|
||||
else
|
||||
{
|
||||
# get global metadefinitions
|
||||
$metadefinitions = $this->aggregateMetaDefinitions();
|
||||
}
|
||||
|
||||
$metadata = [];
|
||||
$metascheme = [];
|
||||
|
||||
foreach($metadefinitions as $tabname => $tab )
|
||||
{
|
||||
$metadata[$tabname] = [];
|
||||
|
||||
$tab = $this->flattenTabFields($tab['fields'],[]);
|
||||
|
||||
foreach($tab as $fieldname => $fielddefinitions)
|
||||
{
|
||||
$metascheme[$tabname][$fieldname] = true;
|
||||
$metadata[$tabname][$fieldname] = isset($pagemeta[$tabname][$fieldname]) ? $pagemeta[$tabname][$fieldname] : null;
|
||||
|
||||
# check if there is a selectfield for userroles
|
||||
if(isset($fielddefinitions['type']) && ($fielddefinitions['type'] == 'select' ) && isset($fielddefinitions['dataset']) && ($fielddefinitions['dataset'] == 'userroles' ) )
|
||||
{
|
||||
$userroles = [null => null];
|
||||
foreach($this->c->acl->getRoles() as $userrole)
|
||||
{
|
||||
$userroles[$userrole] = $userrole;
|
||||
}
|
||||
|
||||
if(isset($fielddefinitions['fieldset']))
|
||||
{
|
||||
$metadefinitions[$tabname]['fields'][$fielddefinitions['fieldset']]['fields'][$fieldname]['options'] = $userroles;
|
||||
}
|
||||
else
|
||||
{
|
||||
$metadefinitions[$tabname]['fields'][$fieldname]['options'] = $userroles;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# store the metascheme in cache for frontend
|
||||
$writeMeta->updateYaml('cache', 'metatabs.yaml', $metascheme);
|
||||
|
||||
return $response->withJson(array('metadata' => $metadata, 'metadefinitions' => $metadefinitions, 'item' => $this->item, 'errors' => false));
|
||||
}
|
||||
|
||||
public function updateArticleMeta(Request $request, Response $response, $args)
|
||||
{
|
||||
# get params from call
|
||||
$this->params = $request->getParams();
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
|
||||
# minimum permission is that user is allowed to update his own content
|
||||
if(!$this->c->acl->isAllowed($_SESSION['role'], 'mycontent', 'update'))
|
||||
{
|
||||
return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to update content.']), 403);
|
||||
}
|
||||
|
||||
$tab = isset($this->params['tab']) ? $this->params['tab'] : false;
|
||||
$metaInput = isset($this->params['data']) ? $this->params['data'] : false ;
|
||||
$objectName = 'meta';
|
||||
$errors = false;
|
||||
|
||||
if(!$tab or !$metaInput)
|
||||
{
|
||||
return $response->withJson($this->errors, 404);
|
||||
}
|
||||
|
||||
# set structure
|
||||
if(!$this->setStructureDraft()){ return $response->withJson($this->errors, 404); }
|
||||
|
||||
# set information for homepage
|
||||
$this->setHomepage($args = false);
|
||||
|
||||
# set item
|
||||
if(!$this->setItem()){ return $response->withJson($this->errors, 404); }
|
||||
|
||||
# if user has no right to delete content from others (eg admin or editor)
|
||||
if(!$this->c->acl->isAllowed($_SESSION['role'], 'content', 'update'))
|
||||
{
|
||||
# check ownership. This code should nearly never run, because there is no button/interface to trigger it.
|
||||
if(!$this->checkContentOwnership())
|
||||
{
|
||||
return $response->withJson(array('data' => false, 'errors' => ['message' => 'You are not allowed to edit content.']), 403);
|
||||
}
|
||||
}
|
||||
|
||||
# if item is a folder
|
||||
if($this->item->elementType == "folder" && isset($this->item->contains))
|
||||
{
|
||||
$pagemeta['meta']['contains'] = isset($pagemeta['meta']['contains']) ? $pagemeta['meta']['contains'] : $this->item->contains;
|
||||
|
||||
# get global metadefinitions
|
||||
$metaDefinitions = $this->aggregateMetaDefinitions($folder = true);
|
||||
}
|
||||
else
|
||||
{
|
||||
# get global metadefinitions
|
||||
$metaDefinitions = $this->aggregateMetaDefinitions();
|
||||
}
|
||||
|
||||
$tabFieldDefinitions = $this->flattenTabFields($metaDefinitions[$tab]['fields'], []);
|
||||
|
||||
# create validation object
|
||||
$validate = $this->getValidator();
|
||||
|
||||
# take the user input data and iterate over all fields and values
|
||||
foreach($metaInput as $fieldName => $fieldValue)
|
||||
{
|
||||
# get the corresponding field definition from original plugin settings */
|
||||
$fieldDefinition = isset($tabFieldDefinitions[$fieldName]) ? $tabFieldDefinitions[$fieldName] : false;
|
||||
|
||||
if(!$fieldDefinition)
|
||||
{
|
||||
$errors[$tab][$fieldName] = 'This field is not defined';
|
||||
}
|
||||
else
|
||||
{
|
||||
if($fieldDefinition && isset($fieldDefinition['type']) && ($fieldDefinition['type'] == 'select' ) && isset($fieldDefinition['dataset']) && ($fieldDefinition['dataset'] == 'userroles' ) )
|
||||
{
|
||||
$userroles = [null => null];
|
||||
foreach($this->c->acl->getRoles() as $userrole)
|
||||
{
|
||||
$userroles[$userrole] = $userrole;
|
||||
}
|
||||
$fieldDefinition['options'] = $userroles;
|
||||
}
|
||||
|
||||
# validate user input for this field
|
||||
$result = $validate->objectField($fieldName, $fieldValue, $objectName, $fieldDefinition);
|
||||
|
||||
if($result !== true)
|
||||
{
|
||||
$errors[$tab][$fieldName] = $result[$fieldName][0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# return validation errors
|
||||
if($errors){ return $response->withJson(array('errors' => $errors),422); }
|
||||
|
||||
$writeMeta = new writeMeta();
|
||||
|
||||
# get existing metadata for page
|
||||
$metaPage = $writeMeta->getYaml($this->settings['contentFolder'], $this->item->pathWithoutType . '.yaml');
|
||||
|
||||
# get extended structure
|
||||
$extended = $writeMeta->getYaml('cache', 'structure-extended.yaml');
|
||||
|
||||
# flag for changed structure
|
||||
$structure = false;
|
||||
|
||||
if($tab == 'meta')
|
||||
{
|
||||
# if manual date has been modified
|
||||
if( $this->hasChanged($metaInput, $metaPage['meta'], 'manualdate'))
|
||||
{
|
||||
# update the time
|
||||
$metaInput['time'] = date('H-i-s', time());
|
||||
|
||||
# if it is a post, then rename the post
|
||||
if($this->item->elementType == "file" && strlen($this->item->order) == 12)
|
||||
{
|
||||
# create file-prefix with date
|
||||
$metadate = $metaInput['manualdate'];
|
||||
if($metadate == ''){ $metadate = $metaPage['meta']['created']; }
|
||||
$datetime = $metadate . '-' . $metaInput['time'];
|
||||
$datetime = implode(explode('-', $datetime));
|
||||
$datetime = substr($datetime,0,12);
|
||||
|
||||
# create the new filename
|
||||
$pathWithoutFile = str_replace($this->item->originalName, "", $this->item->path);
|
||||
$newPathWithoutType = $pathWithoutFile . $datetime . '-' . $this->item->slug;
|
||||
|
||||
$writeMeta->renamePost($this->item->pathWithoutType, $newPathWithoutType);
|
||||
|
||||
# recreate the draft structure
|
||||
$this->setFreshStructureDraft();
|
||||
|
||||
# update item
|
||||
$this->setItem();
|
||||
}
|
||||
}
|
||||
|
||||
# if folder has changed and contains pages instead of posts or posts instead of pages
|
||||
if($this->item->elementType == "folder" && isset($metaInput['contains']) && isset($metaPage['meta']['contains']) && $this->hasChanged($metaInput, $metaPage['meta'], 'contains'))
|
||||
{
|
||||
$structure = true;
|
||||
|
||||
if($writeMeta->folderContainsFolders($this->item))
|
||||
{
|
||||
return $response->withJson(array('errors' => ['message' => 'The folder contains another folder so we cannot transform it. Please make sure there are only files in this folder.']),422);
|
||||
}
|
||||
|
||||
if($metaInput['contains'] == "posts")
|
||||
{
|
||||
if(!$writeMeta->transformPagesToPosts($this->item))
|
||||
{
|
||||
return $response->withJson(array('errors' => ['message' => 'One or more files could not be transformed.']),422);
|
||||
}
|
||||
}
|
||||
if($metaInput['contains'] == "pages")
|
||||
{
|
||||
if(!$writeMeta->transformPostsToPages($this->item))
|
||||
{
|
||||
return $response->withJson(array('errors' => ['message' => 'One or more files could not be transformed.']),422);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# normalize the meta-input
|
||||
$metaInput['navtitle'] = (isset($metaInput['navtitle']) && $metaInput['navtitle'] !== null )? $metaInput['navtitle'] : '';
|
||||
$metaInput['hide'] = (isset($metaInput['hide']) && $metaInput['hide'] !== null) ? $metaInput['hide'] : false;
|
||||
$metaInput['noindex'] = (isset($metaInput['noindex']) && $metaInput['noindex'] !== null) ? $metaInput['noindex'] : false;
|
||||
|
||||
# input values are empty but entry in structure exists
|
||||
if(!$metaInput['hide'] && $metaInput['navtitle'] == "" && isset($extended[$this->item->urlRelWoF]))
|
||||
{
|
||||
# delete the entry in the structure
|
||||
unset($extended[$this->item->urlRelWoF]);
|
||||
|
||||
$structure = true;
|
||||
}
|
||||
elseif(
|
||||
# check if navtitle or hide-value has been changed
|
||||
($this->hasChanged($metaInput, $metaPage['meta'], 'navtitle'))
|
||||
OR
|
||||
($this->hasChanged($metaInput, $metaPage['meta'], 'hide'))
|
||||
OR
|
||||
($this->hasChanged($metaInput, $metaPage['meta'], 'noindex'))
|
||||
)
|
||||
{
|
||||
# add new file data. Also makes sure that the value is set.
|
||||
$extended[$this->item->urlRelWoF] = ['hide' => $metaInput['hide'], 'navtitle' => $metaInput['navtitle'], 'noindex' => $metaInput['noindex']];
|
||||
|
||||
$structure = true;
|
||||
}
|
||||
}
|
||||
|
||||
# add the new/edited metadata
|
||||
$metaPage[$tab] = $metaInput;
|
||||
|
||||
# store the metadata
|
||||
$writeMeta->updateYaml($this->settings['contentFolder'], $this->item->pathWithoutType . '.yaml', $metaPage);
|
||||
|
||||
if($structure)
|
||||
{
|
||||
# store the extended file
|
||||
$writeMeta->updateYaml('cache', 'structure-extended.yaml', $extended);
|
||||
|
||||
# recreate the draft structure
|
||||
$this->setFreshStructureDraft();
|
||||
|
||||
# recreate the live structure
|
||||
$this->setFreshStructureLive();
|
||||
|
||||
# recreate the navigation
|
||||
$this->setFreshNavigation();
|
||||
|
||||
# update the sitemap
|
||||
$this->updateSitemap();
|
||||
|
||||
# update item
|
||||
$this->setItem();
|
||||
|
||||
# set item in navigation active again
|
||||
$activeItem = Folder::getItemForUrl($this->structureDraft, $this->item->urlRel, $this->uri->getBaseUrl());
|
||||
|
||||
# send new structure to frontend
|
||||
$structure = $this->structureDraft;
|
||||
}
|
||||
|
||||
# return with the new metadata
|
||||
return $response->withJson(array('metadata' => $metaInput, 'structure' => $structure, 'item' => $this->item, 'errors' => false));
|
||||
}
|
||||
|
||||
# we have to flatten field definitions for tabs if there are fieldsets in it
|
||||
public function flattenTabFields($tabfields, $flattab, $fieldset = null)
|
||||
{
|
||||
foreach($tabfields as $name => $field)
|
||||
{
|
||||
if($field['type'] == 'fieldset')
|
||||
{
|
||||
$flattab = $this->flattenTabFields($field['fields'], $flattab, $name);
|
||||
}
|
||||
else
|
||||
{
|
||||
# add the name of the fieldset so we know to which fieldset it belongs for references
|
||||
if($fieldset)
|
||||
{
|
||||
$field['fieldset'] = $fieldset;
|
||||
}
|
||||
$flattab[$name] = $field;
|
||||
}
|
||||
}
|
||||
return $flattab;
|
||||
}
|
||||
|
||||
protected function hasChanged($input, $page, $field)
|
||||
{
|
||||
if(isset($input[$field]) && isset($page[$field]) && $input[$field] == $page[$field])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if(!isset($input[$field]) && !isset($input[$field]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,147 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Controllers;
|
||||
|
||||
use Typemill\Models\WriteYaml;
|
||||
|
||||
class ControllerDownload extends ControllerShared
|
||||
{
|
||||
public function download($request, $response, $args)
|
||||
{
|
||||
|
||||
$filename = isset($args['params']) ? $args['params'] : false;
|
||||
$root = $this->c->get('settings')['rootPath'];
|
||||
$mediapath = 'media/files/';
|
||||
$filepath = $root . $mediapath;
|
||||
|
||||
if(!$filename)
|
||||
{
|
||||
die('the requested file does not exist.');
|
||||
}
|
||||
|
||||
# validate
|
||||
$allowedFiletypes = [];
|
||||
if(!$this->validate($filepath, $filename, $allowedFiletypes))
|
||||
{
|
||||
die('the requested file is not allowed.');
|
||||
}
|
||||
|
||||
$writeYaml = new WriteYaml();
|
||||
$restrictions = $writeYaml->getYaml('media' . DIRECTORY_SEPARATOR . 'files', 'filerestrictions.yaml');
|
||||
|
||||
if($restrictions && isset($restrictions[$mediapath . $filename]))
|
||||
{
|
||||
$allowedrole = $restrictions[$mediapath . $filename];
|
||||
|
||||
if(!isset($_SESSION['role']))
|
||||
{
|
||||
$this->c->flash->addMessage('error', "You have to be an authenticated $allowedrole to download this file.");
|
||||
return $response->withRedirect($this->c->router->pathFor('auth.show'));
|
||||
}
|
||||
elseif(
|
||||
$_SESSION['role'] != 'administrator'
|
||||
AND $_SESSION['role'] != $allowedrole
|
||||
AND !$this->c->acl->inheritsRole($_SESSION['role'], $allowedrole)
|
||||
)
|
||||
{
|
||||
$this->c->flash->addMessage('error', "You have to be a $allowedrole to download this file.");
|
||||
return $response->withRedirect($this->c->router->pathFor('auth.show'));
|
||||
}
|
||||
}
|
||||
|
||||
$file = $filepath . $filename;
|
||||
|
||||
# for now we only allow one download
|
||||
$this->sendDownload($file);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate if the file exists and if
|
||||
* there is a permission (download dir) to download this file
|
||||
*
|
||||
* You should ALWAYS call this method if you don't want
|
||||
* somebody to download files not intended to be for the public.
|
||||
*
|
||||
* @param string $file GET parameter
|
||||
* @param array $allowedFiletypes (defined in the head of this file)
|
||||
* @return bool true if validation was successfull
|
||||
*/
|
||||
private function validate($path, $file, $allowedFiletypes)
|
||||
{
|
||||
$filepath = $path . $file;
|
||||
|
||||
# check if file exists
|
||||
if (!isset($filepath) || empty($filepath) || !file_exists($filepath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
# check allowed filetypes
|
||||
if(!empty($allowedFiletypes))
|
||||
{
|
||||
$fileAllowed = false;
|
||||
foreach ($allowedFiletypes as $filetype)
|
||||
{
|
||||
if (strpos($file, $filetype) === (strlen($file) - strlen($filetype)))
|
||||
{
|
||||
$fileAllowed = true; //ends with $filetype
|
||||
}
|
||||
}
|
||||
|
||||
if (!$fileAllowed) return false;
|
||||
}
|
||||
|
||||
# check download directory
|
||||
if (strpos($file, '..') !== false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download function.
|
||||
* Sets the HTTP header and supplies the given file
|
||||
* as a download to the browser.
|
||||
*
|
||||
* @param string $file path to file
|
||||
*/
|
||||
private function sendDownload($file)
|
||||
{
|
||||
# Parse information
|
||||
$pathinfo = pathinfo($file);
|
||||
$extension = strtolower($pathinfo['extension']);
|
||||
$mimetype = null;
|
||||
|
||||
# Get mimetype for extension
|
||||
# This list can be extended as you need it.
|
||||
# A good start to find mimetypes is the apache mime.types list
|
||||
# http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
|
||||
switch ($extension) {
|
||||
case 'zip': $mimetype = "application/zip"; break;
|
||||
default: $mimetype = "application/force-download";
|
||||
}
|
||||
|
||||
# Required for some browsers like Safari and IE
|
||||
if (ini_get('zlib.output_compression'))
|
||||
{
|
||||
ini_set('zlib.output_compression', 'Off');
|
||||
}
|
||||
|
||||
header('Pragma: public');
|
||||
header('Content-Encoding: none');
|
||||
header('Accept-Ranges: bytes'); # Allow support for download resume
|
||||
header('Expires: 0');
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', filemtime($file)) . ' GMT');
|
||||
header_remove("Last-Modified");
|
||||
header('Cache-Control: max-age=0, no-cache, no-store, must-revalidate');
|
||||
header('Cache-Control: private', false); # required for some browsers
|
||||
header('Content-Type: ' . $mimetype);
|
||||
header('Content-Disposition: attachment; filename="'.basename($file).'";'); # Make the browser display the Save As dialog
|
||||
header('Content-Transfer-Encoding: binary');
|
||||
header('Content-Length: '.filesize($file));
|
||||
ob_end_flush();
|
||||
readfile($file); # This is necessary in order to get it to actually download the file, otherwise it will be 0Kb
|
||||
}
|
||||
}
|
@ -1,341 +0,0 @@
|
||||
<?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;
|
||||
use Typemill\Extensions\ParsedownExtension;
|
||||
|
||||
class ControllerFrontendAuth extends ControllerShared
|
||||
{
|
||||
# redirect if visit /setup route
|
||||
public function redirect(Request $request, Response $response)
|
||||
{
|
||||
if(isset($_SESSION['login']))
|
||||
{
|
||||
return $response->withRedirect($this->c->router->pathFor('content.raw'));
|
||||
}
|
||||
else
|
||||
{
|
||||
return $response->withRedirect($this->c->router->pathFor('auth.show'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* show login form
|
||||
*
|
||||
* @param obj $request the slim request object.
|
||||
* @param obj $response the slim response object.
|
||||
* @param array $args with arguments past to the slim router
|
||||
* @return obj $response and string route.
|
||||
*/
|
||||
|
||||
public function show(Request $request, Response $response, $args)
|
||||
{
|
||||
$settings = $this->c->get('settings');
|
||||
|
||||
return $this->render($response, '/auth/login.twig', ['settings' => $settings]);
|
||||
}
|
||||
|
||||
/**
|
||||
* signin an existing user
|
||||
*
|
||||
* @param obj $request the slim request object with form data in the post params.
|
||||
* @param obj $response the slim response object.
|
||||
* @return obj $response with redirect to route.
|
||||
*/
|
||||
|
||||
public function login(Request $request, Response $response)
|
||||
{
|
||||
if( ( null !== $request->getattribute('csrf_result') ) OR ( $request->getattribute('csrf_result') === false ) )
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'The form has a timeout, please try again.');
|
||||
return $response->withRedirect($this->c->router->pathFor('auth.show'));
|
||||
}
|
||||
|
||||
/* authentication */
|
||||
$params = $request->getParams();
|
||||
$validation = new Validation();
|
||||
$settings = $this->c->get('settings');
|
||||
|
||||
if($validation->signin($params))
|
||||
{
|
||||
$user = new User();
|
||||
$userdata = $user->getUser($params['username']);
|
||||
|
||||
if($userdata && password_verify($params['password'], $userdata['password']))
|
||||
{
|
||||
# check if user has confirmed the account
|
||||
if(isset($userdata['optintoken']) && $userdata['optintoken'])
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'Your registration is not confirmed yet. Please check your e-mails and use the confirmation link.');
|
||||
return $response->withRedirect($this->c->router->pathFor('auth.show'));
|
||||
}
|
||||
|
||||
$user->login($userdata['username']);
|
||||
|
||||
# if user is allowed to view content-area
|
||||
if($this->c->acl->hasRole($userdata['userrole']) && $this->c->acl->isAllowed($userdata['userrole'], 'content', 'view'))
|
||||
{
|
||||
$settings = $this->c->get('settings');
|
||||
$editor = (isset($settings['editor']) && $settings['editor'] == 'visual') ? 'visual' : 'raw';
|
||||
|
||||
return $response->withRedirect($this->c->router->pathFor('content.' . $editor));
|
||||
}
|
||||
return $response->withRedirect($this->c->router->pathFor('user.account'));
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($this->settings['securitylog']) && $this->settings['securitylog'])
|
||||
{
|
||||
\Typemill\Models\Helpers::addLogEntry('wrong login');
|
||||
}
|
||||
|
||||
$this->c->flash->addMessage('error', 'Ups, wrong password or username, please try again.');
|
||||
return $response->withRedirect($this->c->router->pathFor('auth.show'));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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'));
|
||||
}
|
||||
|
||||
public function showRecoverPassword(Request $request, Response $response, $args)
|
||||
{
|
||||
$data = array();
|
||||
|
||||
return $this->render($response, '/auth/recoverpw.twig', $data);
|
||||
}
|
||||
|
||||
public function recoverPassword(Request $request, Response $response, $args)
|
||||
{
|
||||
$params = $request->getParams();
|
||||
$validation = new Validation();
|
||||
$settings = $this->c->get('settings');
|
||||
$uri = $request->getUri()->withUserInfo('');
|
||||
$base_url = $uri->getBaseUrl();
|
||||
|
||||
if(!isset($params['email']) OR filter_var($params['email'], \FILTER_VALIDATE_EMAIL) === false )
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'Please enter a valid email.');
|
||||
return $response->withRedirect($this->c->router->pathFor('auth.recoverpwshow'));
|
||||
}
|
||||
|
||||
$user = new User();
|
||||
$requiredUser = $user->findUsersByEmail($params['email']);
|
||||
|
||||
if(!$requiredUser)
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'The email address is unknown.');
|
||||
return $response->withRedirect($this->c->router->pathFor('auth.recoverpwshow'));
|
||||
}
|
||||
|
||||
$requiredUser = $user->getSecureUser($requiredUser[0]);
|
||||
|
||||
$requiredUser['recoverdate'] = date("Y-m-d H:i:s");
|
||||
$requiredUser['recovertoken'] = bin2hex(random_bytes(32));
|
||||
|
||||
$url = $base_url . '/tm/recoverpwnew?username=' . $requiredUser['username'] . '&recovertoken=' . $requiredUser['recovertoken'];
|
||||
$link = '<a href="'. $url . '">' . $url . '</a>';
|
||||
|
||||
# define the headers
|
||||
$headers = 'Content-Type: text/html; charset=utf-8' . "\r\n";
|
||||
$headers .= 'Content-Transfer-Encoding: base64' . "\r\n";
|
||||
if(isset($settings['recoverfrom']) && $settings['recoverfrom'] != '')
|
||||
{
|
||||
$headers .= 'From: ' . $settings['recoverfrom'];
|
||||
}
|
||||
|
||||
$subjectline = (isset($settings['recoversubject']) && ($settings['recoversubject'] != '') ) ? $settings['recoversubject'] : 'Recover your password';
|
||||
$subject = '=?UTF-8?B?' . base64_encode($subjectline) . '?=';
|
||||
|
||||
$messagetext = "Dear user,<br/><br/>please use the following link to set a new password:";
|
||||
if(isset($settings['recovermessage']) && ($settings['recovermessage'] != ''))
|
||||
{
|
||||
$parsedown = new ParsedownExtension($base_url);
|
||||
$parsedown->setSafeMode(true);
|
||||
|
||||
$contentArray = $parsedown->text($settings['recovermessage']);
|
||||
$messagetext = $parsedown->markup($contentArray);
|
||||
}
|
||||
|
||||
$message = base64_encode($messagetext . "<br/><br/>" . $link);
|
||||
|
||||
# store user
|
||||
$user->updateUser($requiredUser);
|
||||
|
||||
$send = mail($requiredUser['email'], $subject, $message, $headers);
|
||||
|
||||
if(!$send)
|
||||
{
|
||||
$data = [
|
||||
'title' => 'We could not send the email',
|
||||
'message' => 'Dear ' . $requiredUser['username'] . ', we could not send the email with the password instructions to your address. You can try it again but chances are low that it will work next time. Please contact the website owner and ask for help.',
|
||||
];
|
||||
}
|
||||
else
|
||||
{
|
||||
# store user
|
||||
$user->updateUser($requiredUser);
|
||||
|
||||
$data = [
|
||||
'title' => 'Please check your inbox',
|
||||
'message' => 'Dear ' . $requiredUser['username'] . ', please check the inbox of your email account. We have sent you some short instructions how to recover your password. Do not forget to check your spam-folder if your inbox is empty.',
|
||||
];
|
||||
}
|
||||
|
||||
return $this->render($response, '/auth/recoverpwsend.twig', $data);
|
||||
}
|
||||
|
||||
public function showRecoverPasswordNew(Request $request, Response $response, $args)
|
||||
{
|
||||
$params = $request->getParams();
|
||||
|
||||
if(!isset($params['username']) OR !isset($params['recovertoken']))
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'Ups, you tried to open the password recovery page but the link was invalid.');
|
||||
return $response->withRedirect($this->c->router->pathFor('auth.show'));
|
||||
}
|
||||
|
||||
$user = new user();
|
||||
|
||||
$requiredUser = $user->getSecureUser($params['username']);
|
||||
|
||||
if(!$requiredUser)
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'Ups, you tried to open the password recovery page but the link was invalid.');
|
||||
return $response->withRedirect($this->c->router->pathFor('auth.show'));
|
||||
}
|
||||
|
||||
if(!isset($requiredUser['recovertoken']) OR $requiredUser['recovertoken'] != $params['recovertoken'] )
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'Ups, you tried to open the password recovery page but the link was invalid.');
|
||||
return $response->withRedirect($this->c->router->pathFor('auth.show'));
|
||||
}
|
||||
|
||||
$recoverdate = isset($requiredUser['recoverdate']) ? $requiredUser['recoverdate'] : false;
|
||||
|
||||
if(!$recoverdate )
|
||||
{
|
||||
$user->unsetFromUser($requiredUser['username'], ['recovertoken']);
|
||||
|
||||
$this->c->flash->addMessage('error', 'The link to recover the password was too old. Please create a new one.');
|
||||
return $response->withRedirect($this->c->router->pathFor('auth.show'));
|
||||
}
|
||||
|
||||
$now = new \DateTime('NOW');
|
||||
$recoverdate = new \DateTime($recoverdate);
|
||||
|
||||
if(!$recoverdate)
|
||||
{
|
||||
$user->unsetFromUser($requiredUser['username'], ['recovertoken', 'recoverdate']);
|
||||
|
||||
$this->c->flash->addMessage('error', 'The link to recover the password was too old. Please create a new one.');
|
||||
return $response->withRedirect($this->c->router->pathFor('auth.show'));
|
||||
}
|
||||
|
||||
$validDate = $recoverdate->add(new \DateInterval('P1D'));
|
||||
|
||||
if($validDate <= $now)
|
||||
{
|
||||
$user->unsetFromUser($requiredUser['username'], ['recovertoken', 'recoverdate']);
|
||||
|
||||
$this->c->flash->addMessage('error', 'The link to recover the password was too old. Please create a new one.');
|
||||
return $response->withRedirect($this->c->router->pathFor('auth.show'));
|
||||
}
|
||||
|
||||
return $this->render($response, '/auth/recoverpwnew.twig', ['recovertoken' => $params['recovertoken'],'username' => $requiredUser['username']]);
|
||||
}
|
||||
|
||||
public function createRecoverPasswordNew(Request $request, Response $response, $args)
|
||||
{
|
||||
$params = $request->getParams();
|
||||
|
||||
if(!isset($params['username']) OR !isset($params['recovertoken']))
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'Ups, you tried to set a new password but username or token was invalid.');
|
||||
return $response->withRedirect($this->c->router->pathFor('auth.show'));
|
||||
}
|
||||
|
||||
$validation = new Validation();
|
||||
|
||||
if(!$validation->recoverPassword($params))
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'Please check your input.');
|
||||
return $response->withRedirect($this->c->router->pathFor('auth.recoverpwshownew',[], ['username' => $params['username'], 'recovertoken' => $params['recovertoken']]));
|
||||
}
|
||||
|
||||
$user = new user();
|
||||
|
||||
$requiredUser = $user->getSecureUser($params['username']);
|
||||
|
||||
if(!$requiredUser)
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'Ups, you tried to create a new password but the username was invalid.');
|
||||
return $response->withRedirect($this->c->router->pathFor('auth.show'));
|
||||
}
|
||||
|
||||
if(!isset($requiredUser['recovertoken']) OR $requiredUser['recovertoken'] != $params['recovertoken'] )
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'Ups, you tried to create a new password but the token was invalid.');
|
||||
return $response->withRedirect($this->c->router->pathFor('auth.show'));
|
||||
}
|
||||
|
||||
$recoverdate = isset($requiredUser['recoverdate']) ? $requiredUser['recoverdate'] : false;
|
||||
|
||||
if(!$recoverdate )
|
||||
{
|
||||
$user->unsetFromUser($requiredUser['username'], ['recovertoken']);
|
||||
|
||||
$this->c->flash->addMessage('error', 'The date for the password reset was invalid. Please create a new one.');
|
||||
return $response->withRedirect($this->c->router->pathFor('auth.show'));
|
||||
}
|
||||
|
||||
$now = new \DateTime('NOW');
|
||||
$recoverdate = new \DateTime($recoverdate);
|
||||
|
||||
if(!$recoverdate)
|
||||
{
|
||||
$user->unsetFromUser($requiredUser['username'], ['recovertoken', 'recoverdate']);
|
||||
|
||||
$this->c->flash->addMessage('error', 'The date for the password reset was too old. Please create a new one.');
|
||||
return $response->withRedirect($this->c->router->pathFor('auth.show'));
|
||||
}
|
||||
|
||||
$validDate = $recoverdate->add(new \DateInterval('P1D'));
|
||||
|
||||
if($validDate <= $now)
|
||||
{
|
||||
$user->unsetFromUser($requiredUser['username'], ['recovertoken', 'recoverdate']);
|
||||
|
||||
$this->c->flash->addMessage('error', 'The link to recover the password was too old. Please create a new one.');
|
||||
return $response->withRedirect($this->c->router->pathFor('auth.show'));
|
||||
}
|
||||
|
||||
$requiredUser['password'] = $params['password'];
|
||||
$user->updateUser($requiredUser);
|
||||
$user->unsetFromUser($requiredUser['username'], ['recovertoken', 'recoverdate']);
|
||||
|
||||
unset($_SESSION['old']);
|
||||
|
||||
$this->c->flash->addMessage('info', 'A new password has been created. Please login.');
|
||||
return $response->withRedirect($this->c->router->pathFor('auth.show'));
|
||||
}
|
||||
}
|
@ -1,99 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Controllers;
|
||||
|
||||
use Typemill\Models\Validation;
|
||||
use Typemill\Models\WriteYaml;
|
||||
|
||||
class ControllerFrontendForms extends ControllerShared
|
||||
{
|
||||
|
||||
public function savePublicForm($request, $response, $args)
|
||||
{
|
||||
if($request->isPost())
|
||||
{
|
||||
$params = $request->getParams();
|
||||
reset($params);
|
||||
$pluginName = key($params);
|
||||
$referer = $request->getHeader('HTTP_REFERER');
|
||||
|
||||
# check csrf protection
|
||||
if($request->getattribute('csrf_result') === false )
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'The form has a timeout. Please try again.');
|
||||
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!');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Controllers;
|
||||
|
||||
use \Symfony\Component\Yaml\Yaml;
|
||||
use Typemill\Models\Validation;
|
||||
use Typemill\Models\User;
|
||||
use Typemill\Models\Write;
|
||||
|
||||
class ControllerFrontendSetup extends ControllerShared
|
||||
{
|
||||
|
||||
# redirect if visit /setup route
|
||||
public function redirect($request, $response)
|
||||
{
|
||||
return $response->withRedirect($this->c->router->pathFor('setup.show'));
|
||||
}
|
||||
|
||||
public function show($request, $response, $args)
|
||||
{
|
||||
/* make some checks befor you install */
|
||||
$checkFolder = new Write();
|
||||
|
||||
$systemcheck = array();
|
||||
|
||||
# check folders and create them if possible
|
||||
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(); }
|
||||
|
||||
|
||||
# check php-version
|
||||
if (version_compare(phpversion(), '7.0.0', '<'))
|
||||
{
|
||||
$systemcheck['error'][] = 'The PHP-version of your server is ' . phpversion() . ' and Typemill needs at least 7.0.0';
|
||||
}
|
||||
|
||||
/* check if mod rewrite is enabled, does not work with PHP-fpm or NGINX
|
||||
$modules = apache_get_modules();
|
||||
if(!in_array('mod_rewrite', $modules))
|
||||
{
|
||||
$systemcheck['error'][] = 'The apache module "mod_rewrite" is not enabled.';
|
||||
}
|
||||
*/
|
||||
|
||||
# check if extensions are loaded
|
||||
if(!extension_loaded('gd')){ $systemcheck['error'][] = 'The php-extension GD for image manipulation is not enabled.'; }
|
||||
if(!extension_loaded('mbstring')){ $systemcheck['error'][] = 'The php-extension mbstring is not enabled.'; }
|
||||
if(!extension_loaded('fileinfo')){ $systemcheck['error'][] = 'The php-extension fileinfo is not enabled.'; }
|
||||
if(!extension_loaded('session')){ $systemcheck['error'][] = 'The php-extension session is not enabled.'; }
|
||||
if(!extension_loaded('iconv')){ $systemcheck['error'][] = 'The php-extension iconv is not enabled.'; }
|
||||
|
||||
$setuperrors = empty($systemcheck) ? false : 'Some system requirements for Typemill are missing.';
|
||||
$systemcheck = empty($systemcheck) ? false : $systemcheck;
|
||||
|
||||
return $this->render($response, 'auth/setup.twig', array( 'messages' => $setuperrors, 'systemcheck' => $systemcheck ));
|
||||
}
|
||||
|
||||
public function create($request, $response, $args)
|
||||
{
|
||||
if($request->isPost())
|
||||
{
|
||||
if( ( null !== $request->getattribute('csrf_result') ) OR ( $request->getattribute('csrf_result') === false ) )
|
||||
{
|
||||
$this->c->flash->addMessage('error', 'The form has a timeout, please try again.');
|
||||
return $response->withRedirect($this->c->router->pathFor('setup.show'));
|
||||
}
|
||||
|
||||
$params = $request->getParams();
|
||||
$validate = new Validation();
|
||||
$user = new User();
|
||||
|
||||
# set user as admin
|
||||
$params['userrole'] = 'administrator';
|
||||
|
||||
# get userroles for validation
|
||||
$userroles = $this->c->acl->getRoles();
|
||||
|
||||
/* 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());
|
||||
}
|
||||
}
|
@ -1,491 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Controllers;
|
||||
|
||||
use Typemill\Models\Folder;
|
||||
use Typemill\Models\WriteMeta;
|
||||
use Typemill\Extensions\ParsedownExtension;
|
||||
use Typemill\Events\OnPagetreeLoaded;
|
||||
use Typemill\Events\OnBreadcrumbLoaded;
|
||||
use Typemill\Events\OnItemLoaded;
|
||||
use Typemill\Events\OnOriginalLoaded;
|
||||
use Typemill\Events\OnMetaLoaded;
|
||||
use Typemill\Events\OnMarkdownLoaded;
|
||||
use Typemill\Events\OnContentArrayLoaded;
|
||||
use Typemill\Events\OnHtmlLoaded;
|
||||
use Typemill\Events\OnRestrictionsLoaded;
|
||||
|
||||
class ControllerFrontendWebsite extends ControllerShared
|
||||
{
|
||||
public function index($request, $response, $args)
|
||||
{
|
||||
# Initiate Variables
|
||||
$contentHTML = false;
|
||||
$item = false;
|
||||
$home = false;
|
||||
$breadcrumb = false;
|
||||
$currentpage = false;
|
||||
$this->pathToContent = $this->settings['rootPath'] . $this->settings['contentFolder'];
|
||||
$this->uri = $request->getUri()->withUserInfo('');
|
||||
$this->base_url = $this->uri->getBaseUrl();
|
||||
|
||||
# if there is no structure at all, the content folder is probably empty
|
||||
if(!$this->setStructureLive())
|
||||
{
|
||||
return $this->render($response, '/index.twig', array( 'content' => '<h1>No Content</h1><p>Your content folder is empty.</p>' ));
|
||||
}
|
||||
|
||||
# we can create an initial sitemap here, but makes no sense for every pagecall. Sitemap will be created on first author interaction (publish/delete/channge page).
|
||||
# $this->checkSitemap();
|
||||
|
||||
# if the admin activated to refresh the cache automatically each 10 minutes (e.g. use without admin area)
|
||||
if(isset($this->settings['refreshcache']) && $this->settings['refreshcache'] && !$this->writeCache->validate('cache', 'lastCache.txt', 600))
|
||||
{
|
||||
# delete the cache
|
||||
$dir = $this->settings['basePath'] . 'cache';
|
||||
$this->writeCache->deleteCacheFiles($dir);
|
||||
|
||||
# update the internal structure
|
||||
$this->setFreshStructureDraft();
|
||||
|
||||
# update the public structure
|
||||
$this->setFreshStructureLive();
|
||||
|
||||
# update the navigation
|
||||
$this->setFreshNavigation();
|
||||
|
||||
# update the sitemap
|
||||
$this->updateSitemap();
|
||||
}
|
||||
|
||||
# dispatch event and let others manipulate the structure
|
||||
$this->structureLive = $this->c->dispatcher->dispatch('onPagetreeLoaded', new OnPagetreeLoaded($this->structureLive))->getData();
|
||||
|
||||
# check if there is a custom theme css
|
||||
$theme = $this->settings['theme'];
|
||||
$customcss = $this->writeCache->checkFile('cache', $theme . '-custom.css');
|
||||
if($customcss)
|
||||
{
|
||||
$this->c->assets->addCSS($this->base_url . '/cache/' . $theme . '-custom.css');
|
||||
}
|
||||
|
||||
$logo = false;
|
||||
if(isset($this->settings['logo']) && $this->settings['logo'] != '')
|
||||
{
|
||||
# check if logo exists
|
||||
if(file_exists($this->settings['rootPath'] . 'media/live/' . $this->settings['logo']))
|
||||
{
|
||||
$logo = 'media/live/' . $this->settings['logo'];
|
||||
}
|
||||
elseif(file_exists($this->settings['rootPath'] . 'media/files/' . $this->settings['logo']))
|
||||
{
|
||||
$logo = 'media/files/' . $this->settings['logo'];
|
||||
}
|
||||
}
|
||||
|
||||
$favicon = false;
|
||||
if(isset($this->settings['favicon']) && $this->settings['favicon'] != '')
|
||||
{
|
||||
$favicon = true;
|
||||
$this->c->assets->addMeta('tilecolor','<meta name="msapplication-TileColor" content="#F9F8F6" />');
|
||||
$this->c->assets->addMeta('tileimage','<meta name="msapplication-TileImage" content="' . $this->base_url . '/media/files/favicon-144.png" />');
|
||||
$this->c->assets->addMeta('icon16','<link rel="icon" type="image/png" href="' . $this->base_url . '/media/files/favicon-16.png" sizes="16x16" />');
|
||||
$this->c->assets->addMeta('icon32','<link rel="icon" type="image/png" href="' . $this->base_url . '/media/files/favicon-32.png" sizes="32x32" />');
|
||||
$this->c->assets->addMeta('icon72','<link rel="apple-touch-icon" sizes="72x72" href="' . $this->base_url . '/media/files/favicon-72.png" />');
|
||||
$this->c->assets->addMeta('icon114','<link rel="apple-touch-icon" sizes="114x114" href="' . $this->base_url . '/media/files/favicon-114.png" />');
|
||||
$this->c->assets->addMeta('icon144','<link rel="apple-touch-icon" sizes="144x144" href="' . $this->base_url . '/media/files/favicon-144.png" />');
|
||||
$this->c->assets->addMeta('icon180','<link rel="apple-touch-icon" sizes="180x180" href="' . $this->base_url . '/media/files/favicon-180.png" />');
|
||||
}
|
||||
|
||||
# the navigation is a copy of the structure without the hidden pages
|
||||
# hint: if the navigation has been deleted from the cache, then we do not recreate it here to save performace. Instead you have to recreate cache in admin or change a page (publish/unpublish/delete/move)
|
||||
$navigation = $this->writeCache->getCache('cache', 'navigation.txt');
|
||||
if(!$navigation)
|
||||
{
|
||||
# use the structure if there is no cached navigation
|
||||
$navigation = $this->structureLive;
|
||||
}
|
||||
|
||||
# start pagination
|
||||
if(isset($args['params']))
|
||||
{
|
||||
$argSegments = explode("/", $args['params']);
|
||||
|
||||
# check if the last url segment is a number
|
||||
$pageNumber = array_pop($argSegments);
|
||||
if(is_numeric($pageNumber) && $pageNumber < 10000)
|
||||
{
|
||||
# then check if the segment before the page is a "p" that indicates a paginator
|
||||
$pageIndicator = array_pop($argSegments);
|
||||
if($pageIndicator == "p")
|
||||
{
|
||||
# use page number as current page variable
|
||||
$currentpage = $pageNumber;
|
||||
|
||||
# set empty args for startpage
|
||||
$args = [];
|
||||
|
||||
# if there are still params
|
||||
if(!empty($argSegments))
|
||||
{
|
||||
# add them to the args again
|
||||
$args['params'] = implode("/", $argSegments);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# if the user is on startpage
|
||||
$home = false;
|
||||
if(empty($args))
|
||||
{
|
||||
$home = true;
|
||||
$item = Folder::getItemForUrl($navigation, $this->uri->getBasePath(), $this->uri->getBaseUrl(), NULL, $home);
|
||||
$urlRel = $this->uri->getBasePath();
|
||||
}
|
||||
else
|
||||
{
|
||||
# get the request url, trim args so physical folders have no trailing slash
|
||||
$urlRel = $this->uri->getBasePath() . '/' . trim($args['params'], "/");
|
||||
|
||||
# find the url in the content-item-tree and return the item-object for the file
|
||||
# important to use the structure here so it is found, even if the item is hidden.
|
||||
$item = Folder::getItemForUrl($this->structureLive, $urlRel, $this->uri->getBasePath());
|
||||
|
||||
# if the item is a folder and if that folder is not hidden
|
||||
if($item && $item->elementType == 'folder' && isset($item->hide) && !$item->hide)
|
||||
{
|
||||
# use the navigation instead of the structure so that hidden elements are erased
|
||||
$item = Folder::getItemForUrl($navigation, $urlRel, $this->uri->getBaseUrl(), NULL, $home);
|
||||
}
|
||||
|
||||
# if there is still no item, return a 404-page
|
||||
if(!$item)
|
||||
{
|
||||
return $this->render404($response, array(
|
||||
'navigation' => $navigation,
|
||||
'settings' => $this->settings,
|
||||
'base_url' => $this->base_url,
|
||||
'title' => false,
|
||||
'content' => false,
|
||||
'item' => false,
|
||||
'breadcrumb' => false,
|
||||
'metatabs' => false,
|
||||
'image' => false,
|
||||
'logo' => $logo,
|
||||
'favicon' => $favicon
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($item->hide))
|
||||
{
|
||||
# if it is a hidden page
|
||||
if($item->hide)
|
||||
{
|
||||
# get breadcrumb for page and set pages active
|
||||
# use structure here because the hidden item is not part of the navigation
|
||||
$breadcrumb = Folder::getBreadcrumb($this->structureLive, $item->keyPathArray);
|
||||
$breadcrumb = $this->c->dispatcher->dispatch('onBreadcrumbLoaded', new OnBreadcrumbLoaded($breadcrumb))->getData();
|
||||
|
||||
# add the paging to the item
|
||||
$item = Folder::getPagingForItem($this->structureLive, $item);
|
||||
}
|
||||
else
|
||||
{
|
||||
# get breadcrumb for page and set pages active
|
||||
# use navigation, because it is used for frontend
|
||||
$breadcrumb = Folder::getBreadcrumb($navigation, $item->keyPathArray);
|
||||
$breadcrumb = $this->c->dispatcher->dispatch('onBreadcrumbLoaded', new OnBreadcrumbLoaded($breadcrumb))->getData();
|
||||
|
||||
# add the paging to the item
|
||||
$item = Folder::getPagingForItem($navigation, $item);
|
||||
}
|
||||
}
|
||||
|
||||
# dispatch the item
|
||||
$item = $this->c->dispatcher->dispatch('onItemLoaded', new OnItemLoaded($item))->getData();
|
||||
|
||||
# set the filepath
|
||||
$filePath = $this->pathToContent . $item->path;
|
||||
|
||||
# check if url is a folder and add index.md
|
||||
if($item->elementType == 'folder')
|
||||
{
|
||||
$filePath = $filePath . DIRECTORY_SEPARATOR . 'index.md';
|
||||
}
|
||||
|
||||
# read the content of the file
|
||||
$contentMD = file_exists($filePath) ? file_get_contents($filePath) : false;
|
||||
|
||||
# dispatch the original content without plugin-manipulations for case anyone wants to use it
|
||||
$this->c->dispatcher->dispatch('onOriginalLoaded', new OnOriginalLoaded($contentMD));
|
||||
|
||||
# initiate object for metadata
|
||||
$writeMeta = new WriteMeta();
|
||||
|
||||
# makes sure that you always have the full meta with title, description and all the rest.
|
||||
$metatabs = $writeMeta->completePageMeta($contentMD, $this->settings, $item);
|
||||
|
||||
# write meta
|
||||
if(isset($metatabs['meta']['noindex']) && $metatabs['meta']['noindex'])
|
||||
{
|
||||
$this->c->assets->addMeta('noindex','<meta name="robots" content="noindex">');
|
||||
}
|
||||
|
||||
$this->c->assets->addMeta('og_site_name','<meta property="og:site_name" content="' . $this->settings['title'] . '">');
|
||||
$this->c->assets->addMeta('og_title','<meta property="og:title" content="' . $metatabs['meta']['title'] . '">');
|
||||
$this->c->assets->addMeta('og_description','<meta property="og:description" content="' . $metatabs['meta']['description'] . '">');
|
||||
$this->c->assets->addMeta('og_type','<meta property="og:type" content="article">');
|
||||
$this->c->assets->addMeta('og_url','<meta property="og:url" content="' . $item->urlAbs . '">');
|
||||
|
||||
# dispatch meta
|
||||
$metatabs = $this->c->dispatcher->dispatch('onMetaLoaded', new OnMetaLoaded($metatabs))->getData();
|
||||
|
||||
# dispatch content
|
||||
$contentMD = $this->c->dispatcher->dispatch('onMarkdownLoaded', new OnMarkdownLoaded($contentMD))->getData();
|
||||
|
||||
$itemUrl = isset($item->urlRel) ? $item->urlRel : false;
|
||||
|
||||
/* initialize parsedown */
|
||||
$parsedown = new ParsedownExtension($this->base_url, $this->settings, $this->c->dispatcher);
|
||||
|
||||
/* set safe mode to escape javascript and html in markdown */
|
||||
$parsedown->setSafeMode(true);
|
||||
|
||||
# check access restriction here
|
||||
$restricted = $this->checkRestrictions($metatabs['meta']);
|
||||
if($restricted)
|
||||
{
|
||||
# convert markdown into array of markdown block-elements
|
||||
$markdownBlocks = $parsedown->markdownToArrayBlocks($contentMD);
|
||||
|
||||
# infos that plugins need to add restriction content
|
||||
$restrictions = [
|
||||
'restricted' => $restricted,
|
||||
'defaultContent' => true,
|
||||
'markdownBlocks' => $markdownBlocks,
|
||||
];
|
||||
|
||||
# dispatch the data
|
||||
$restrictions = $this->c->dispatcher->dispatch('onRestrictionsLoaded', new OnRestrictionsLoaded( $restrictions ))->getData();
|
||||
|
||||
# use the returned markdown
|
||||
$markdownBlocks = $restrictions['markdownBlocks'];
|
||||
|
||||
# if no plugin has disabled the default behavior
|
||||
if($restrictions['defaultContent'])
|
||||
{
|
||||
# cut the restricted content
|
||||
$shortenedPage = $this->cutRestrictedContent($markdownBlocks);
|
||||
|
||||
# check if there is customized content
|
||||
$restrictionnotice = $this->prepareRestrictionNotice();
|
||||
|
||||
# add notice to shortened content
|
||||
$shortenedPage[] = $restrictionnotice;
|
||||
|
||||
# Use the shortened page
|
||||
$markdownBlocks = $shortenedPage;
|
||||
}
|
||||
|
||||
# finally transform the markdown blocks back to pure markdown text
|
||||
$contentMD = $parsedown->arrayBlocksToMarkdown($markdownBlocks);
|
||||
}
|
||||
|
||||
/* parse markdown-file to content-array */
|
||||
$contentArray = $parsedown->text($contentMD);
|
||||
$contentArray = $this->c->dispatcher->dispatch('onContentArrayLoaded', new OnContentArrayLoaded($contentArray))->getData();
|
||||
|
||||
/* parse markdown-content-array to content-string */
|
||||
$contentHTML = $parsedown->markup($contentArray);
|
||||
$contentHTML = $this->c->dispatcher->dispatch('onHtmlLoaded', new OnHtmlLoaded($contentHTML))->getData();
|
||||
|
||||
/* extract the h1 headline*/
|
||||
$contentParts = explode("</h1>", $contentHTML, 2);
|
||||
$title = isset($contentParts[0]) ? strip_tags($contentParts[0]) : $this->settings['title'];
|
||||
|
||||
$contentHTML = isset($contentParts[1]) ? $contentParts[1] : $contentHTML;
|
||||
|
||||
# get the first image from content array */
|
||||
$img_url = isset($metatabs['meta']['heroimage']) ? $metatabs['meta']['heroimage'] : false;
|
||||
$img_alt = isset($metatabs['meta']['heroimagealt']) ? $metatabs['meta']['heroimagealt'] : false;
|
||||
|
||||
# get url and alt-tag for first image, if exists */
|
||||
if(!$img_url OR $img_url == '')
|
||||
{
|
||||
# extract first image from content
|
||||
$firstImageMD = $this->getFirstImage($contentArray);
|
||||
|
||||
if($firstImageMD)
|
||||
{
|
||||
preg_match('#\((.*?)\)#', $firstImageMD, $img_url_result);
|
||||
$img_url = isset($img_url_result[1]) ? $img_url_result[1] : false;
|
||||
|
||||
if($img_url)
|
||||
{
|
||||
preg_match('#\[(.*?)\]#', $firstImageMD, $img_alt_result);
|
||||
$img_alt = isset($img_alt_result[1]) ? $img_alt_result[1] : false;
|
||||
}
|
||||
}
|
||||
elseif($logo)
|
||||
{
|
||||
$img_url = $logo;
|
||||
$pathinfo = pathinfo($this->settings['logo']);
|
||||
$img_alt = $pathinfo['filename'];
|
||||
}
|
||||
}
|
||||
|
||||
$firstImage = false;
|
||||
if($img_url)
|
||||
{
|
||||
$firstImage = array('img_url' => $this->base_url . '/' . $img_url, 'img_alt' => $img_alt);
|
||||
|
||||
$this->c->assets->addMeta('og_image','<meta property="og:image" content="' . $this->base_url . '/' . $img_url . '">');
|
||||
$this->c->assets->addMeta('twitter_image_alt','<meta name="twitter:image:alt" content="' . $img_alt . '">');
|
||||
$this->c->assets->addMeta('twitter_card','<meta name="twitter:card" content="summary_large_image">');
|
||||
}
|
||||
|
||||
$route = empty($args) && isset($this->settings['themes'][$theme]['cover']) ? '/cover.twig' : '/index.twig';
|
||||
|
||||
return $this->render($response, $route, [
|
||||
'home' => $home,
|
||||
'navigation' => $navigation,
|
||||
'title' => $title,
|
||||
'content' => $contentHTML,
|
||||
'item' => $item,
|
||||
'breadcrumb' => $breadcrumb,
|
||||
'settings' => $this->settings,
|
||||
'base_url' => $this->base_url,
|
||||
'metatabs' => $metatabs,
|
||||
'image' => $firstImage,
|
||||
'logo' => $logo,
|
||||
'favicon' => $favicon,
|
||||
'currentpage' => $currentpage
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
protected function getFirstImage(array $contentBlocks)
|
||||
{
|
||||
foreach($contentBlocks as $block)
|
||||
{
|
||||
/* is it a paragraph? */
|
||||
if(isset($block['name']) && $block['name'] == 'p')
|
||||
{
|
||||
if(isset($block['handler']['argument']) && substr($block['handler']['argument'], 0, 2) == '![' )
|
||||
{
|
||||
return $block['handler']['argument'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
# checks if a page has a restriction in meta and if the current user is blocked by that restriction
|
||||
protected function checkRestrictions($meta)
|
||||
{
|
||||
# check if content restrictions are active
|
||||
if(isset($this->settings['pageaccess']) && $this->settings['pageaccess'])
|
||||
{
|
||||
|
||||
# check if page is restricted to certain user
|
||||
if(isset($meta['alloweduser']) && $meta['alloweduser'] && $meta['alloweduser'] !== '' )
|
||||
{
|
||||
$alloweduser = array_map('trim', explode(",", $meta['alloweduser']));
|
||||
if(isset($_SESSION['user']) && in_array($_SESSION['user'], $alloweduser))
|
||||
{
|
||||
# user has access to the page, so there are no restrictions
|
||||
return false;
|
||||
}
|
||||
|
||||
# otherwise return array with type of restriction and allowed username
|
||||
return [ 'alloweduser' => $meta['alloweduser'] ];
|
||||
}
|
||||
|
||||
# check if page is restricted to certain userrole
|
||||
if(isset($meta['allowedrole']) && $meta['allowedrole'] && $meta['allowedrole'] !== '' )
|
||||
{
|
||||
# var_dump($this->c->acl->inheritsRole('editor', 'member'));
|
||||
# die();
|
||||
if(
|
||||
isset($_SESSION['role'])
|
||||
AND (
|
||||
$_SESSION['role'] == 'administrator'
|
||||
OR $_SESSION['role'] == $meta['allowedrole']
|
||||
OR $this->c->acl->inheritsRole($_SESSION['role'], $meta['allowedrole'])
|
||||
)
|
||||
)
|
||||
{
|
||||
# role has access to page, so there are no restrictions
|
||||
return false;
|
||||
}
|
||||
|
||||
return [ 'allowedrole' => $meta['allowedrole'] ];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
protected function cutRestrictedContent($markdown)
|
||||
{
|
||||
#initially add only the title of the page.
|
||||
$restrictedMarkdown = [$markdown[0]];
|
||||
unset($markdown[0]);
|
||||
|
||||
if(isset($this->settings['hrdelimiter']) && $this->settings['hrdelimiter'] !== NULL )
|
||||
{
|
||||
foreach ($markdown as $block)
|
||||
{
|
||||
$firstCharacters = substr($block, 0, 3);
|
||||
if($firstCharacters == '---' OR $firstCharacters == '***')
|
||||
{
|
||||
return $restrictedMarkdown;
|
||||
}
|
||||
$restrictedMarkdown[] = $block;
|
||||
}
|
||||
|
||||
# no delimiter found, so use the title only
|
||||
$restrictedMarkdown = [$restrictedMarkdown[0]];
|
||||
}
|
||||
|
||||
return $restrictedMarkdown;
|
||||
}
|
||||
|
||||
protected function prepareRestrictionNotice()
|
||||
{
|
||||
if( isset($this->settings['restrictionnotice']) && $this->settings['restrictionnotice'] != '' )
|
||||
{
|
||||
$restrictionNotice = $this->settings['restrictionnotice'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$restrictionNotice = 'You are not allowed to access this content.';
|
||||
}
|
||||
|
||||
if( isset($this->settings['wraprestrictionnotice']) && $this->settings['wraprestrictionnotice'] )
|
||||
{
|
||||
# standardize line breaks
|
||||
$text = str_replace(array("\r\n", "\r"), "\n", $restrictionNotice);
|
||||
|
||||
# remove surrounding line breaks
|
||||
$text = trim($text, "\n");
|
||||
|
||||
# split text into lines
|
||||
$lines = explode("\n", $text);
|
||||
|
||||
$restrictionNotice = '';
|
||||
|
||||
foreach($lines as $key => $line)
|
||||
{
|
||||
$restrictionNotice .= "!!!! " . $line . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return $restrictionNotice;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,477 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Controllers;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Typemill\Models\Folder;
|
||||
use Typemill\Models\WriteCache;
|
||||
use Typemill\Models\WriteYaml;
|
||||
use Typemill\Events\OnPageReady;
|
||||
use Typemill\Events\OnPagetreeLoaded;
|
||||
|
||||
abstract class ControllerShared
|
||||
{
|
||||
# holds the pimple container
|
||||
protected $c;
|
||||
|
||||
# holds the settings
|
||||
protected $settings;
|
||||
|
||||
# holds the write cache object
|
||||
protected $writeCache;
|
||||
|
||||
# holds the structure of content folder as a serialized array of objects
|
||||
protected $structureDraft = false;
|
||||
|
||||
# holds the structure of content folder as a serialized array of objects
|
||||
protected $structureLive = false;
|
||||
|
||||
# holds the name of the structure-file with drafts for author environment
|
||||
protected $structureDraftName = 'structure-draft.txt';
|
||||
|
||||
# holds the name of the structure-file without drafts for live site
|
||||
protected $structureLiveName = 'structure.txt';
|
||||
|
||||
# holds the frontend navigation without hidden pages
|
||||
protected $navigation = false;
|
||||
|
||||
# holds the list of pages with navigation titles and hidden pages. It extends the structures and navigations
|
||||
protected $extended = false;
|
||||
|
||||
public function __construct(ContainerInterface $c)
|
||||
{
|
||||
$this->c = $c;
|
||||
$this->settings = $this->c->get('settings');
|
||||
|
||||
# used everywhere so instantiate it
|
||||
$this->writeCache = new writeCache();
|
||||
|
||||
$this->c->dispatcher->dispatch('onTwigLoaded');
|
||||
}
|
||||
|
||||
# render page for frontend
|
||||
protected function render($response, $route, $data)
|
||||
{
|
||||
# why commented this out??
|
||||
$data = $this->c->dispatcher->dispatch('onPageReady', new OnPageReady($data))->getData();
|
||||
|
||||
if(isset($_SESSION['old']))
|
||||
{
|
||||
unset($_SESSION['old']);
|
||||
}
|
||||
|
||||
$response = $response->withoutHeader('Server');
|
||||
$response = $response->withAddedHeader('X-Powered-By', 'Typemill');
|
||||
|
||||
if(!isset($this->settings['headersoff']) or !$this->settings['headersoff'])
|
||||
{
|
||||
$response = $response->withAddedHeader('X-Content-Type-Options', 'nosniff');
|
||||
$response = $response->withAddedHeader('X-Frame-Options', 'SAMEORIGIN');
|
||||
$response = $response->withAddedHeader('X-XSS-Protection', '1;mode=block');
|
||||
$response = $response->withAddedHeader('Referrer-Policy', 'no-referrer-when-downgrade');
|
||||
if($this->c->request->getUri()->getScheme() == 'https')
|
||||
{
|
||||
$response = $response->withAddedHeader('Strict-Transport-Security', 'max-age=63072000');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->c->view->render($response, $route, $data);
|
||||
}
|
||||
|
||||
# render 404 for frontend
|
||||
protected function render404($response, $data = NULL)
|
||||
{
|
||||
return $this->c->view->render($response->withStatus(404), '/404.twig', $data);
|
||||
}
|
||||
|
||||
# render page for authors (admin-area)
|
||||
protected function renderIntern($response, $route, $data)
|
||||
{
|
||||
if(isset($_SESSION['old']))
|
||||
{
|
||||
unset($_SESSION['old']);
|
||||
}
|
||||
|
||||
$response = $response->withoutHeader('Server');
|
||||
$response = $response->withAddedHeader('X-Powered-By', 'Typemill');
|
||||
|
||||
if(!isset($this->settings['headersoff']) or !$this->settings['headersoff'])
|
||||
{
|
||||
$response = $response->withAddedHeader('X-Content-Type-Options', 'nosniff');
|
||||
$response = $response->withAddedHeader('X-Frame-Options', 'SAMEORIGIN');
|
||||
$response = $response->withAddedHeader('X-XSS-Protection', '1;mode=block');
|
||||
$response = $response->withAddedHeader('Referrer-Policy', 'no-referrer-when-downgrade');
|
||||
if($this->c->request->getUri()->getScheme() == 'https')
|
||||
{
|
||||
$response = $response->withAddedHeader('Strict-Transport-Security', 'max-age=63072000');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->c->view->render($response, $route, $data);
|
||||
}
|
||||
|
||||
# render 404 for authors
|
||||
protected function renderIntern404($response, $data = NULL)
|
||||
{
|
||||
return $this->c->view->render($response->withStatus(404), '/intern404.twig', $data);
|
||||
}
|
||||
|
||||
# reads the cached structure with published and non-published pages for the author
|
||||
protected function setStructureDraft()
|
||||
{
|
||||
# get the cached structure
|
||||
$this->structureDraft = $this->writeCache->getCache('cache', $this->structureDraftName);
|
||||
|
||||
# if there is no cached structure
|
||||
if(!$this->structureDraft)
|
||||
{
|
||||
return $this->setFreshStructureDraft();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
# creates a fresh structure with published and non-published pages for the author
|
||||
protected function setFreshStructureDraft()
|
||||
{
|
||||
# scan the content of the folder
|
||||
$pagetreeDraft = Folder::scanFolder($this->settings['rootPath'] . $this->settings['contentFolder'], $draft = true );
|
||||
|
||||
# if there is content, then get the content details
|
||||
if(count($pagetreeDraft) > 0)
|
||||
{
|
||||
# get the extended structure files with changes like navigation title or hidden pages
|
||||
$yaml = new writeYaml();
|
||||
$extended = $this->getExtended();
|
||||
|
||||
# create an array of object with the whole content of the folder and changes from extended file
|
||||
$this->structureDraft = Folder::getFolderContentDetails($pagetreeDraft, $extended, $this->settings, $this->uri->getBaseUrl(), $this->uri->getBasePath());
|
||||
|
||||
# cache structure draft
|
||||
$this->writeCache->updateCache('cache', $this->structureDraftName, 'lastCache.txt', $this->structureDraft);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
# reads the cached structure of published pages
|
||||
protected function setStructureLive()
|
||||
{
|
||||
# get the cached structure
|
||||
$this->structureLive = $this->writeCache->getCache('cache', $this->structureLiveName);
|
||||
|
||||
# if there is no cached structure
|
||||
if(!$this->structureLive)
|
||||
{
|
||||
return $this->setFreshStructureLive();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
# creates a fresh structure with published pages
|
||||
protected function setFreshStructureLive()
|
||||
{
|
||||
# scan the content of the folder
|
||||
$pagetreeLive = Folder::scanFolder($this->settings['rootPath'] . $this->settings['contentFolder'], $draft = false );
|
||||
|
||||
# if there is content, then get the content details
|
||||
if($pagetreeLive && count($pagetreeLive) > 0)
|
||||
{
|
||||
# get the extended structure files with changes like navigation title or hidden pages
|
||||
$yaml = new writeYaml();
|
||||
$extended = $this->getExtended();
|
||||
|
||||
# create an array of object with the whole content of the folder and changes from extended file
|
||||
$this->structureLive = Folder::getFolderContentDetails($pagetreeLive, $extended, $this->settings, $this->uri->getBaseUrl(), $this->uri->getBasePath());
|
||||
|
||||
# cache structure live
|
||||
$this->writeCache->updateCache('cache', $this->structureLiveName, 'lastCache.txt', $this->structureLive);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
# reads the live navigation from cache (live structure without hidden pages)
|
||||
protected function setNavigation()
|
||||
{
|
||||
# get the cached structure
|
||||
$this->navigation = $this->writeCache->getCache('cache', 'navigation.txt');
|
||||
|
||||
# if there is no cached structure
|
||||
if(!$this->navigation)
|
||||
{
|
||||
return $this->setFreshNavigation();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
# creates a fresh live navigation (live structure without hidden pages)
|
||||
protected function setFreshNavigation()
|
||||
{
|
||||
|
||||
if(!$this->extended)
|
||||
{
|
||||
$extended = $this->getExtended();
|
||||
}
|
||||
|
||||
if($this->containsHiddenPages($this->extended))
|
||||
{
|
||||
if(!$this->structureLive)
|
||||
{
|
||||
$this->setStructureLive();
|
||||
}
|
||||
|
||||
$structureLive = $this->c->dispatcher->dispatch('onPagetreeLoaded', new OnPagetreeLoaded($this->structureLive))->getData();
|
||||
$this->navigation = $this->createNavigation($structureLive);
|
||||
|
||||
# cache navigation
|
||||
$this->writeCache->updateCache('cache', 'navigation.txt', false, $this->navigation);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
# make sure no old navigation file is left
|
||||
$this->writeCache->deleteFileWithPath('cache' . DIRECTORY_SEPARATOR . 'navigation.txt');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
# create navigation from structure
|
||||
protected function createNavigation($structureLive)
|
||||
{
|
||||
foreach ($structureLive as $key => $element)
|
||||
{
|
||||
if($element->hide === true)
|
||||
{
|
||||
unset($structureLive[$key]);
|
||||
}
|
||||
elseif(isset($element->folderContent))
|
||||
{
|
||||
$structureLive[$key]->folderContent = $this->createNavigation($element->folderContent);
|
||||
}
|
||||
}
|
||||
|
||||
return $structureLive;
|
||||
}
|
||||
|
||||
# controllerFrontendWebsite, but not in use, makes no sense to check on each page load
|
||||
public function checkSitemap()
|
||||
{
|
||||
if(!$this->writeCache->getCache('cache', 'sitemap.xml'))
|
||||
{
|
||||
if(!$this->structureLive)
|
||||
{
|
||||
$this->setStructureLive();
|
||||
}
|
||||
|
||||
$this->updateSitemap();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function updateSitemap($ping = false)
|
||||
{
|
||||
$sitemap = '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
|
||||
$sitemap .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
|
||||
$sitemap = $this->addUrlSet($sitemap, $this->uri->getBaseUrl());
|
||||
$sitemap .= $this->generateUrlSets($this->structureLive);
|
||||
$sitemap .= '</urlset>';
|
||||
|
||||
$this->writeCache->writeFile('cache', 'sitemap.xml', $sitemap);
|
||||
|
||||
if($ping && isset($this->settings['pingsitemap']) && $this->settings['pingsitemap'])
|
||||
{
|
||||
$sitemapUrl = $this->uri->getBaseUrl() . '/cache/sitemap.xml';
|
||||
|
||||
$pingGoogleUrl = 'http://www.google.com/ping?sitemap=' . urlencode($sitemapUrl);
|
||||
$pingBingUrl = 'http://www.bing.com/ping?sitemap=' . urlencode($sitemapUrl);
|
||||
|
||||
$opts = array(
|
||||
'http'=>array(
|
||||
'method'=>"GET",
|
||||
'ignore_errors' => true,
|
||||
'timeout' => 5
|
||||
)
|
||||
);
|
||||
|
||||
$context = stream_context_create($opts);
|
||||
|
||||
$responseBing = file_get_contents($pingBingUrl, false, $context);
|
||||
$responseGoogle = file_get_contents($pingGoogleUrl, false, $context);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function generateUrlSets($structureLive)
|
||||
{
|
||||
$urlset = '';
|
||||
|
||||
foreach($structureLive as $item)
|
||||
{
|
||||
if($item->elementType == 'folder' && isset($item->noindex) && $item->noindex === true)
|
||||
{
|
||||
$urlset .= $this->generateUrlSets($item->folderContent, $urlset);
|
||||
}
|
||||
elseif($item->elementType == 'folder')
|
||||
{
|
||||
$urlset = $this->addUrlSet($urlset, $item->urlAbs);
|
||||
$urlset .= $this->generateUrlSets($item->folderContent, $urlset);
|
||||
}
|
||||
elseif(isset($item->noindex) && $item->noindex === true )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
$urlset = $this->addUrlSet($urlset, $item->urlAbs);
|
||||
}
|
||||
}
|
||||
return $urlset;
|
||||
}
|
||||
|
||||
public function addUrlSet($urlset, $url)
|
||||
{
|
||||
$urlset .= ' <url>' . "\n";
|
||||
$urlset .= ' <loc>' . $url . '</loc>' . "\n";
|
||||
$urlset .= ' </url>' . "\n";
|
||||
return $urlset;
|
||||
}
|
||||
|
||||
protected function getExtended()
|
||||
{
|
||||
$yaml = new writeYaml();
|
||||
|
||||
if(!$this->extended)
|
||||
{
|
||||
$this->extended = $yaml->getYaml('cache', 'structure-extended.yaml');
|
||||
}
|
||||
|
||||
if(!$this->extended)
|
||||
{
|
||||
# scan the content of the folder
|
||||
$pagetreeDraft = Folder::scanFolder($this->settings['rootPath'] . $this->settings['contentFolder'], $draft = true );
|
||||
|
||||
# if there is content, then get the content details
|
||||
if(count($pagetreeDraft) == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
# create an array of object with the whole content of the folder and changes from extended file
|
||||
$structureDraft = Folder::getFolderContentDetails($pagetreeDraft, $extended = false, $this->settings, $this->uri->getBaseUrl(), $this->uri->getBasePath());
|
||||
|
||||
$this->extended = $this->createExtended($this->settings['rootPath'] . $this->settings['contentFolder'], $yaml, $structureDraft);
|
||||
|
||||
$yaml->updateYaml('cache', 'structure-extended.yaml', $this->extended);
|
||||
}
|
||||
|
||||
return $this->extended;
|
||||
}
|
||||
|
||||
# creates a file that holds all hide flags and navigation titles
|
||||
# reads all meta-files and creates an array with url => ['hide' => bool, 'navtitle' => 'bla']
|
||||
public function createExtended($contentPath, $yaml, $structureLive, $extended = NULL)
|
||||
{
|
||||
if(!$extended)
|
||||
{
|
||||
$extended = [];
|
||||
}
|
||||
|
||||
foreach ($structureLive as $key => $item)
|
||||
{
|
||||
# $filename = ($item->elementType == 'folder') ? DIRECTORY_SEPARATOR . 'index.yaml' : $item->pathWithoutType . '.yaml';
|
||||
$filename = $item->pathWithoutType . '.yaml';
|
||||
|
||||
if(file_exists($contentPath . $filename))
|
||||
{
|
||||
# read file
|
||||
$meta = $yaml->getYaml('content', $filename);
|
||||
|
||||
$extended[$item->urlRelWoF]['hide'] = isset($meta['meta']['hide']) ? $meta['meta']['hide'] : false;
|
||||
$extended[$item->urlRelWoF]['navtitle'] = isset($meta['meta']['navtitle']) ? $meta['meta']['navtitle'] : '';
|
||||
}
|
||||
|
||||
if ($item->elementType == 'folder')
|
||||
{
|
||||
$extended = $this->createExtended($contentPath, $yaml, $item->folderContent, $extended);
|
||||
}
|
||||
}
|
||||
return $extended;
|
||||
}
|
||||
|
||||
# only backoffice
|
||||
protected function renameExtended($item, $newFolder)
|
||||
{
|
||||
# get the extended structure files with changes like navigation title or hidden pages
|
||||
$yaml = new writeYaml();
|
||||
$extended = $yaml->getYaml('cache', 'structure-extended.yaml');
|
||||
|
||||
if(isset($extended[$item->urlRelWoF]))
|
||||
{
|
||||
$newUrl = $newFolder->urlRelWoF . '/' . $item->slug;
|
||||
|
||||
$entry = $extended[$item->urlRelWoF];
|
||||
|
||||
unset($extended[$item->urlRelWoF]);
|
||||
|
||||
$extended[$newUrl] = $entry;
|
||||
$yaml->updateYaml('cache', 'structure-extended.yaml', $extended);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
# only backoffice
|
||||
protected function deleteFromExtended()
|
||||
{
|
||||
# get the extended structure files with changes like navigation title or hidden pages
|
||||
$yaml = new writeYaml();
|
||||
$extended = $yaml->getYaml('cache', 'structure-extended.yaml');
|
||||
|
||||
if($this->item->elementType == "file" && isset($extended[$this->item->urlRelWoF]))
|
||||
{
|
||||
unset($extended[$this->item->urlRelWoF]);
|
||||
$yaml->updateYaml('cache', 'structure-extended.yaml', $extended);
|
||||
}
|
||||
|
||||
if($this->item->elementType == "folder")
|
||||
{
|
||||
$changed = false;
|
||||
|
||||
# delete all entries with that folder url
|
||||
foreach($extended as $url => $entries)
|
||||
{
|
||||
if( strpos($url, $this->item->urlRelWoF) !== false )
|
||||
{
|
||||
$changed = true;
|
||||
unset($extended[$url]);
|
||||
}
|
||||
}
|
||||
|
||||
if($changed)
|
||||
{
|
||||
$yaml->updateYaml('cache', 'structure-extended.yaml', $extended);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# checks if there is a hidden page, returns true on first find
|
||||
protected function containsHiddenPages($extended)
|
||||
{
|
||||
foreach($extended as $element)
|
||||
{
|
||||
if(isset($element['hide']) && $element['hide'] === true)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Events;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event for breadcrumb.
|
||||
*/
|
||||
|
||||
class OnBreadcrumbLoaded extends BaseEvent
|
||||
{
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Events;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event for the page rendering data.
|
||||
*/
|
||||
|
||||
class OnCacheUpdated extends BaseEvent
|
||||
{
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Events;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event for the page rendering data.
|
||||
*/
|
||||
|
||||
class OnContentArrayLoaded extends BaseEvent
|
||||
{
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Events;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event for html page.
|
||||
*/
|
||||
|
||||
class OnHtmlLoaded extends BaseEvent
|
||||
{
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Events;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event for item.
|
||||
*/
|
||||
|
||||
class OnItemLoaded extends BaseEvent
|
||||
{
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Events;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event for markdown.
|
||||
*/
|
||||
|
||||
class OnMarkdownLoaded extends BaseEvent
|
||||
{
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Events;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event for markdown.
|
||||
*/
|
||||
|
||||
class OnMetaDefinitionsLoaded extends BaseEvent
|
||||
{
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Events;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event for markdown.
|
||||
*/
|
||||
|
||||
class OnMetaLoaded extends BaseEvent
|
||||
{
|
||||
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Events;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event for breadcrumb.
|
||||
*/
|
||||
|
||||
class OnPageDeleted extends BaseEvent
|
||||
{
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Events;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event for breadcrumb.
|
||||
*/
|
||||
|
||||
class OnPagePublished extends BaseEvent
|
||||
{
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Events;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event for html data page.
|
||||
*/
|
||||
|
||||
class OnPageReady extends BaseEvent
|
||||
{
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Events;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event for breadcrumb.
|
||||
*/
|
||||
|
||||
class OnPageSorted extends BaseEvent
|
||||
{
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Events;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event for breadcrumb.
|
||||
*/
|
||||
|
||||
class OnPageUnpublished extends BaseEvent
|
||||
{
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Events;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event for the page tree.
|
||||
*/
|
||||
|
||||
class OnPagetreeLoaded extends BaseEvent
|
||||
{
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Events;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event for the folder structure.
|
||||
*/
|
||||
|
||||
class OnPluginsLoaded extends BaseEvent
|
||||
{
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Events;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event for acl.
|
||||
*/
|
||||
|
||||
class OnResourcesLoaded extends BaseEvent
|
||||
{
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Events;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event for page restrictions.
|
||||
*/
|
||||
|
||||
class OnRestrictionsLoaded extends BaseEvent
|
||||
{
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Events;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event for acl.
|
||||
*/
|
||||
|
||||
class OnRolesPermissionsLoaded extends BaseEvent
|
||||
{
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Events;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event for breadcrumb.
|
||||
*/
|
||||
|
||||
class OnSessionSegmentsLoaded extends BaseEvent
|
||||
{
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Events;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event for settings
|
||||
*/
|
||||
|
||||
class OnSettingsLoaded extends BaseEvent
|
||||
{
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Events;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event for breadcrumb.
|
||||
*/
|
||||
|
||||
class OnShortcodeFound extends BaseEvent
|
||||
{
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Events;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event for settings
|
||||
*/
|
||||
|
||||
class OnSystemnaviLoaded extends BaseEvent
|
||||
{
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Events;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event for the page tree.
|
||||
*/
|
||||
|
||||
class OnUserConfirmed extends BaseEvent
|
||||
{
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Events;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event for the page tree.
|
||||
*/
|
||||
|
||||
class OnUserDeleted extends BaseEvent
|
||||
{
|
||||
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Events;
|
||||
|
||||
use Symfony\Component\EventDispatcher\Event;
|
||||
|
||||
/**
|
||||
* Event for the page tree.
|
||||
*/
|
||||
|
||||
class OnUserfieldsLoaded extends BaseEvent
|
||||
{
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Extensions;
|
||||
|
||||
use Gregwar\Captcha\CaptchaBuilder;
|
||||
|
||||
class TwigCaptchaExtension extends \Twig_Extension
|
||||
{
|
||||
public function getFunctions()
|
||||
{
|
||||
return [
|
||||
new \Twig_SimpleFunction('captcha', array($this, 'captchaImage' ))
|
||||
];
|
||||
}
|
||||
|
||||
public function captchaImage($initialize = false)
|
||||
{
|
||||
|
||||
if(isset($_SESSION['captcha']) OR $initialize)
|
||||
{
|
||||
$builder = new CaptchaBuilder;
|
||||
$builder->build();
|
||||
|
||||
$error = '';
|
||||
if(isset($_SESSION['captcha']) && $_SESSION['captcha'] === 'error')
|
||||
{
|
||||
$error = '<span class="error">The captcha was wrong.</span>';
|
||||
}
|
||||
|
||||
$_SESSION['phrase'] = $builder->getPhrase();
|
||||
|
||||
$_SESSION['captcha'] = true;
|
||||
|
||||
$template = '<div class="formElement">' .
|
||||
'<label for="captcha">Captcha</label>' .
|
||||
'<input type="text" name="captcha">' .
|
||||
$error .
|
||||
'<img class="captcha" src="' . $builder->inline() . '" />' .
|
||||
'</div>';
|
||||
|
||||
return $template;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
<?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() . '">
|
||||
';
|
||||
}
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Extensions;
|
||||
|
||||
use Typemill\Models\WriteYaml;
|
||||
|
||||
class TwigLanguageExtension extends \Twig_Extension
|
||||
{
|
||||
protected $labels;
|
||||
|
||||
public function __construct($labels)
|
||||
{
|
||||
$this->labels = $labels;
|
||||
}
|
||||
|
||||
public function getFilters()
|
||||
{
|
||||
return [
|
||||
new \Twig_SimpleFilter('__', [$this,'translate'] ),
|
||||
];
|
||||
}
|
||||
|
||||
public function getFunctions()
|
||||
{
|
||||
return [
|
||||
new \Twig_SimpleFunction('__', array($this, 'translate' ))
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
public function translate( $label, $labels_from_plugin = NULL )
|
||||
{
|
||||
// replaces spaces, dots, comma and dash with underscores
|
||||
$string = str_replace(" ", "_", $label);
|
||||
$string = str_replace(".", "_", $string);
|
||||
$string = str_replace(",", "_", $string);
|
||||
$string = str_replace("-", "_", $string);
|
||||
|
||||
// transforms to uppercase
|
||||
$string = strtoupper( $string );
|
||||
|
||||
//translates the string
|
||||
if(isset($labels_from_plugin)){
|
||||
$translated_label = isset($labels_from_plugin[$string]) ? $labels_from_plugin[$string] : null;
|
||||
} else {
|
||||
$translated_label = isset($this->labels[$string]) ? $this->labels[$string] : null;
|
||||
}
|
||||
|
||||
// if the string is not present, set the original string
|
||||
if( empty($translated_label) ){
|
||||
$translated_label = $label;
|
||||
}
|
||||
|
||||
// returns the string in the set language
|
||||
return $translated_label;
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Extensions;
|
||||
|
||||
use Typemill\Models\WriteMeta;
|
||||
|
||||
class TwigMetaExtension extends \Twig_Extension
|
||||
{
|
||||
public function getFunctions()
|
||||
{
|
||||
return [
|
||||
new \Twig_SimpleFunction('getPageMeta', array($this, 'getMeta' ))
|
||||
];
|
||||
}
|
||||
|
||||
public function getMeta($settings, $item)
|
||||
{
|
||||
$writeMeta = new WriteMeta();
|
||||
|
||||
$meta = $writeMeta->getPageMeta($settings, $item);
|
||||
|
||||
if(!$meta OR $meta['meta']['title'] == '' OR $meta['meta']['description'] == '')
|
||||
{
|
||||
# create path to the file
|
||||
$filePath = $settings['rootPath'] . $settings['contentFolder'] . $item->path;
|
||||
|
||||
# check if url is a folder and add index.md
|
||||
if($item->elementType == 'folder')
|
||||
{
|
||||
$filePath = $filePath . DIRECTORY_SEPARATOR . 'index.md';
|
||||
}
|
||||
|
||||
if(file_exists($filePath))
|
||||
{
|
||||
# get content
|
||||
$content = file_get_contents($filePath);
|
||||
|
||||
# completes title and description or generate default meta values
|
||||
$meta = $writeMeta->completePageMeta($content, $settings, $item);
|
||||
}
|
||||
}
|
||||
|
||||
return $meta;
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Extensions;
|
||||
|
||||
use Typemill\Models\Folder;
|
||||
|
||||
class TwigPagelistExtension extends \Twig_Extension
|
||||
{
|
||||
public function getFunctions()
|
||||
{
|
||||
return [
|
||||
new \Twig_SimpleFunction('getPageList', array($this, 'getList' ))
|
||||
];
|
||||
}
|
||||
|
||||
public function getList($folderContentDetails, $url)
|
||||
{
|
||||
$pagelist = Folder::getItemForUrlFrontend($folderContentDetails, $url);
|
||||
|
||||
return $pagelist;
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
<?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_role', array($this, 'getRole' )),
|
||||
new \Twig_SimpleFunction('get_username', array($this, 'getUsername' )),
|
||||
new \Twig_SimpleFunction('is_loggedin', array($this, 'isLoggedin' ))
|
||||
];
|
||||
}
|
||||
|
||||
public function isLoggedin()
|
||||
{
|
||||
if(isset($_SESSION['login']) && $_SESSION['login'])
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isRole($role)
|
||||
{
|
||||
if(isset($_SESSION['role']) && $_SESSION['role'] == $role)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getRole()
|
||||
{
|
||||
if(isset($_SESSION['role']))
|
||||
{
|
||||
return $_SESSION['role'];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getUsername()
|
||||
{
|
||||
if(isset($_SESSION['user']))
|
||||
{
|
||||
return $_SESSION['user'];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright <20> 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 <20>Software<72>), 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 <20>AS IS<49>, 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.
|
@ -1,21 +0,0 @@
|
||||
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.
|
@ -1,32 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@ -1,30 +0,0 @@
|
||||
<?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, $settings)
|
||||
{
|
||||
$this->router = $router;
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
public function __invoke(Request $request, Response $response, $next)
|
||||
{
|
||||
$editor = (isset($this->settings['editor']) && $this->settings['editor'] == 'visual') ? 'visual' : 'raw';
|
||||
|
||||
if(isset($_SESSION['login']))
|
||||
{
|
||||
$response = $response->withRedirect($this->router->pathFor('content.' . $editor));
|
||||
}
|
||||
|
||||
return $next($request, $response);
|
||||
}
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
<?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(['data' => false, 'errors' => ['message' => 'You are probably logged out. Please login and try again.']], 403);
|
||||
}
|
||||
|
||||
# check csrf protection
|
||||
if( $request->getattribute('csrf_result') === false )
|
||||
{
|
||||
return $response->withJson(array('data' => false, 'errors' => ['message' => 'The form has a timeout. Please reload the page and try again.']), 403);
|
||||
}
|
||||
return $next($request, $response);
|
||||
}
|
||||
}
|
@ -1,117 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Middleware;
|
||||
|
||||
use Slim\Interfaces\RouterInterface;
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
use Gregwar\Captcha\CaptchaBuilder;
|
||||
|
||||
class securityMiddleware
|
||||
{
|
||||
protected $router;
|
||||
protected $settings;
|
||||
protected $flash;
|
||||
|
||||
public function __construct(RouterInterface $router, $settings, $flash)
|
||||
{
|
||||
$this->router = $router;
|
||||
$this->settings = $settings;
|
||||
$this->flash = $flash;
|
||||
}
|
||||
|
||||
public function __invoke(Request $request, Response $response, $next)
|
||||
{
|
||||
if($request->isPost())
|
||||
{
|
||||
$referer = $request->getHeader('HTTP_REFERER');
|
||||
|
||||
# check csrf protection
|
||||
if( $request->getattribute('csrf_result') === false )
|
||||
{
|
||||
$this->flash->addMessage('error', 'The form has a timeout. Please try again.');
|
||||
return $response->withRedirect($referer[0]);
|
||||
}
|
||||
|
||||
# simple bot check with honeypot
|
||||
if( (null !== $request->getParam('personal-honey-mail') ) && ($request->getParam('personal-honey-mail') != '') )
|
||||
{
|
||||
if(isset($this->settings['securitylog']) && $this->settings['securitylog'])
|
||||
{
|
||||
\Typemill\Models\Helpers::addLogEntry('honeypot ' . $referer[0]);
|
||||
}
|
||||
|
||||
$this->flash->addMessage('notice', 'Hey honey, you made it right!');
|
||||
return $response->withRedirect($this->router->pathFor('home'));
|
||||
}
|
||||
|
||||
# check captcha
|
||||
if(isset($_SESSION['captcha']))
|
||||
{
|
||||
# if captcha field was filled correctly
|
||||
if( (null !== $request->getParam('captcha')) && \Gregwar\Captcha\PhraseBuilder::comparePhrases($_SESSION['phrase'], $request->getParam('captcha') ) )
|
||||
{
|
||||
# delete captcha because it is solved and should not show up again
|
||||
unset($_SESSION['captcha']);
|
||||
|
||||
# delete phrase because can't use twice
|
||||
unset($_SESSION['phrase']);
|
||||
}
|
||||
else
|
||||
{
|
||||
# delete phrase because can't use twice, but keep captcha so it shows up again
|
||||
unset($_SESSION['phrase']);
|
||||
|
||||
# set session to error
|
||||
$_SESSION['captcha'] = 'error';
|
||||
|
||||
if(isset($this->settings['securitylog']) && $this->settings['securitylog'])
|
||||
{
|
||||
\Typemill\Models\Helpers::addLogEntry('wrong captcha ' . $referer[0]);
|
||||
}
|
||||
|
||||
# and add message that captcha is empty
|
||||
$this->flash->addMessage('error', 'Captcha is wrong.');
|
||||
return $response->withRedirect($referer[0]);
|
||||
}
|
||||
}
|
||||
|
||||
#check google recaptcha
|
||||
if( null !== $request->getParam('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' => $request->getParam('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)
|
||||
{
|
||||
if(isset($this->settings['securitylog']) && $this->settings['securitylog'])
|
||||
{
|
||||
\Typemill\Models\Helpers::addLogEntry('wrong google recaptcha ' . $referer[0]);
|
||||
}
|
||||
|
||||
# and add message that captcha is empty
|
||||
$this->flash->addMessage('error', 'Captcha is wrong.');
|
||||
return $response->withRedirect($referer[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $next($request, $response);
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
<?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']);
|
||||
}
|
||||
|
||||
if(isset($_SESSION['phrase']))
|
||||
{
|
||||
$this->view->getEnvironment()->addGlobal('errors', ['captcha' => 'the captcha is wrong, please try again']);
|
||||
|
||||
unset($_SESSION['phrase']);
|
||||
}
|
||||
|
||||
return $next($request, $response);
|
||||
}
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Middleware;
|
||||
|
||||
use Slim\Interfaces\RouterInterface;
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
|
||||
class accessMiddleware
|
||||
{
|
||||
protected $router;
|
||||
|
||||
public function __construct(RouterInterface $router, $acl, $resource, $privilege)
|
||||
{
|
||||
$this->router = $router;
|
||||
$this->acl = $acl;
|
||||
$this->resource = $resource;
|
||||
$this->privilege = $privilege;
|
||||
}
|
||||
|
||||
public function __invoke(Request $request, Response $response, $next)
|
||||
{
|
||||
|
||||
if($this->resource == NULL && $this->privilege == NULL)
|
||||
{
|
||||
return $next($request, $response);
|
||||
}
|
||||
|
||||
if(!isset($_SESSION['login']))
|
||||
{
|
||||
return $response->withRedirect($this->router->pathFor('auth.show'));
|
||||
}
|
||||
|
||||
# make sure logged in users do not have captchas
|
||||
if(isset($_SESSION['captcha']))
|
||||
{
|
||||
unset($_SESSION['captcha']);
|
||||
}
|
||||
|
||||
if(!$this->acl->hasRole($_SESSION['role']))
|
||||
{
|
||||
$_SESSION['role'] = 'member';
|
||||
}
|
||||
|
||||
if(!$this->acl->isAllowed($_SESSION['role'], $this->resource, $this->privilege ))
|
||||
{
|
||||
# redirect to frontend startpage
|
||||
# alternatively return an error and show an error page.
|
||||
return $response->withRedirect($this->router->pathFor('home'));
|
||||
}
|
||||
|
||||
return $next($request, $response);
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Middleware;
|
||||
|
||||
use Slim\Views\Twig;
|
||||
use Slim\Http\Request;
|
||||
use Slim\Http\Response;
|
||||
|
||||
class assetMiddleware
|
||||
{
|
||||
protected $view;
|
||||
protected $c;
|
||||
|
||||
public function __construct($container)
|
||||
{
|
||||
# $this->view = $view;
|
||||
$this->container = $container;
|
||||
}
|
||||
|
||||
public function __invoke(Request $request, Response $response, $next)
|
||||
{
|
||||
|
||||
# get the uri after proxy detection
|
||||
$uri = $request->getUri()->withUserInfo('');
|
||||
|
||||
# update the asset object in the container (for plugins) with the new url
|
||||
$this->container->assets->setBaseUrl($uri->getBaseUrl());
|
||||
|
||||
# add the asset object to twig-frontend for themes
|
||||
$this->container['view']->getEnvironment()->addGlobal('assets', $this->container['assets']);
|
||||
|
||||
# use {{ base_url() }} in twig templates
|
||||
$this->container['view']['base_url'] = $uri->getBaseUrl();
|
||||
$this->container['view']['current_url'] = $uri->getPath();
|
||||
|
||||
$response = $next($request, $response);
|
||||
|
||||
return $response;
|
||||
}
|
||||
}
|
@ -1,289 +0,0 @@
|
||||
<?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',
|
||||
'maxlength',
|
||||
'size',
|
||||
'rows',
|
||||
'cols',
|
||||
'min',
|
||||
'max',
|
||||
'class',
|
||||
'pattern',
|
||||
'steps'
|
||||
);
|
||||
|
||||
/* defines additional data, that are allowed for fields */
|
||||
private $helpers = array(
|
||||
'help',
|
||||
'description',
|
||||
'fieldsize'
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,138 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Models;
|
||||
|
||||
use Typemill\Models\Field;
|
||||
|
||||
class Fields
|
||||
{
|
||||
|
||||
protected $c;
|
||||
|
||||
public function __construct($c = NULL)
|
||||
{
|
||||
$this->c = $c;
|
||||
}
|
||||
|
||||
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']];
|
||||
}
|
||||
|
||||
# check if the field is a select field with dataset = userroles
|
||||
if(isset($this->c) && isset($fieldConfigurations['type']) && ($fieldConfigurations['type'] == 'select' ) && isset($fieldConfigurations['dataset']) && ($fieldConfigurations['dataset'] == 'userroles' ) )
|
||||
{
|
||||
$userroles = [null => null];
|
||||
foreach($this->c->acl->getRoles() as $userrole)
|
||||
{
|
||||
$userroles[$userrole] = $userrole;
|
||||
}
|
||||
$fieldConfigurations['options'] = $userroles;
|
||||
}
|
||||
|
||||
# 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")
|
||||
{
|
||||
if($fieldName == "publicformdefinitions" && $userValue == '')
|
||||
{
|
||||
$userValue = $objectSettings['settings'][$fieldName];
|
||||
}
|
||||
if($userValue)
|
||||
{
|
||||
$field->setContent($userValue);
|
||||
}
|
||||
}
|
||||
elseif($field->getType() == 'paragraph')
|
||||
{
|
||||
if(isset($fieldConfigurations['value']))
|
||||
{
|
||||
$field->setContent($fieldConfigurations['value']);
|
||||
}
|
||||
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('checked');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$field->setAttributeValue('value', $userValue);
|
||||
}
|
||||
|
||||
# add the field to the field-List
|
||||
$fields[] = $field;
|
||||
|
||||
}
|
||||
}
|
||||
return $fields;
|
||||
}
|
||||
}
|
@ -1,669 +0,0 @@
|
||||
<?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(".","..")) && substr($item, 0, 1) != '.')
|
||||
{
|
||||
if (is_dir($folderPath . DIRECTORY_SEPARATOR . $item))
|
||||
{
|
||||
|
||||
$subFolder = $item;
|
||||
$folderPublished = file_exists($folderPath . DIRECTORY_SEPARATOR . $item . DIRECTORY_SEPARATOR . 'index.md');
|
||||
|
||||
# scan that folder only if it is a draft or if the folder is published (contains index.md)
|
||||
if($draft OR $folderPublished)
|
||||
{
|
||||
$folderContent[$subFolder] = 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, $extended, $settings, $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->contains = self::getFolderContentType($name, $fullPath . DIRECTORY_SEPARATOR . $key . DIRECTORY_SEPARATOR . 'index.yaml');
|
||||
$item->status = $status;
|
||||
$item->fileType = $fileType;
|
||||
$item->order = count($nameParts) > 1 ? array_shift($nameParts) : NULL;
|
||||
$item->name = implode(" ",$nameParts);
|
||||
$item->name = iconv(mb_detect_encoding($item->name, mb_detect_order(), true), "UTF-8", $item->name);
|
||||
$item->slug = implode("-",$nameParts);
|
||||
$item->slug = self::createSlug($item->slug, $settings);
|
||||
$item->path = $fullPath . DIRECTORY_SEPARATOR . $key;
|
||||
$item->pathWithoutType = $fullPath . DIRECTORY_SEPARATOR . $key . DIRECTORY_SEPARATOR . 'index';
|
||||
$item->urlRelWoF = $fullSlugWithoutFolder . '/' . $item->slug;
|
||||
$item->urlRel = $fullSlugWithFolder . '/' . $item->slug;
|
||||
$item->urlAbs = $baseUrl . $fullSlugWithoutFolder . '/' . $item->slug;
|
||||
$item->key = $iteration;
|
||||
$item->keyPath = isset($keyPath) ? $keyPath . '.' . $iteration : $iteration;
|
||||
$item->keyPathArray = explode('.', $item->keyPath);
|
||||
$item->chapter = $chapter ? $chapter . '.' . $chapternr : $chapternr;
|
||||
$item->active = false;
|
||||
$item->activeParent = false;
|
||||
$item->hide = false;
|
||||
|
||||
# check if there are extended information
|
||||
if($extended && isset($extended[$item->urlRelWoF]))
|
||||
{
|
||||
$item->name = ($extended[$item->urlRelWoF]['navtitle'] != '') ? $extended[$item->urlRelWoF]['navtitle'] : $item->name;
|
||||
$item->hide = ($extended[$item->urlRelWoF]['hide'] === true) ? true : false;
|
||||
$item->noindex = (isset($extended[$item->urlRelWoF]['noindex']) && $extended[$item->urlRelWoF]['noindex'] === true) ? true : false;
|
||||
}
|
||||
|
||||
# sort posts in descending order
|
||||
if($item->contains == "posts")
|
||||
{
|
||||
rsort($name);
|
||||
}
|
||||
|
||||
$item->folderContent = self::getFolderContentDetails($name, $extended, $settings, $baseUrl, $item->urlRel, $item->urlRelWoF, $item->path, $item->keyPath, $item->chapter);
|
||||
}
|
||||
elseif($name)
|
||||
{
|
||||
# do not use files in base folder (only folders are allowed)
|
||||
# if(!isset($keyPath)) continue;
|
||||
|
||||
# do not use index files
|
||||
if($name == 'index.md' || $name == 'index.txt' || $name == 'index.txtmd' ) continue;
|
||||
|
||||
$nameParts = self::getStringParts($name);
|
||||
$fileType = array_pop($nameParts);
|
||||
$nameWithoutType = self::getNameWithoutType($name);
|
||||
|
||||
if($fileType == 'md')
|
||||
{
|
||||
$status = 'published';
|
||||
}
|
||||
elseif($fileType == 'txt')
|
||||
{
|
||||
$status = 'unpublished';
|
||||
}
|
||||
else
|
||||
{
|
||||
$fileType = 'txt';
|
||||
$status = 'modified';
|
||||
}
|
||||
|
||||
$item->originalName = $name;
|
||||
$item->elementType = 'file';
|
||||
$item->status = $status;
|
||||
$item->fileType = $fileType;
|
||||
$item->order = count($nameParts) > 1 ? array_shift($nameParts) : NULL;
|
||||
$item->name = implode(" ",$nameParts);
|
||||
$item->name = iconv(mb_detect_encoding($item->name, mb_detect_order(), true), "UTF-8", $item->name);
|
||||
$item->slug = implode("-",$nameParts);
|
||||
$item->slug = self::createSlug($item->slug, $settings);
|
||||
$item->path = $fullPath . DIRECTORY_SEPARATOR . $name;
|
||||
$item->pathWithoutType = $fullPath . DIRECTORY_SEPARATOR . $nameWithoutType;
|
||||
$item->key = $iteration;
|
||||
$item->keyPath = isset($keyPath) ? $keyPath . '.' . $iteration : $iteration;
|
||||
$item->keyPathArray = explode('.',$item->keyPath);
|
||||
$item->chapter = $chapter . '.' . $chapternr;
|
||||
$item->urlRelWoF = $fullSlugWithoutFolder . '/' . $item->slug;
|
||||
$item->urlRel = $fullSlugWithFolder . '/' . $item->slug;
|
||||
$item->urlAbs = $baseUrl . $fullSlugWithoutFolder . '/' . $item->slug;
|
||||
$item->active = false;
|
||||
$item->activeParent = false;
|
||||
$item->hide = false;
|
||||
|
||||
# check if there are extended information
|
||||
if($extended && isset($extended[$item->urlRelWoF]))
|
||||
{
|
||||
$item->name = ($extended[$item->urlRelWoF]['navtitle'] != '') ? $extended[$item->urlRelWoF]['navtitle'] : $item->name;
|
||||
$item->hide = ($extended[$item->urlRelWoF]['hide'] === true) ? true : false;
|
||||
$item->noindex = (isset($extended[$item->urlRelWoF]['noindex']) && $extended[$item->urlRelWoF]['noindex'] === true) ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
$iteration++;
|
||||
$chapternr++;
|
||||
$contentDetails[] = $item;
|
||||
}
|
||||
return $contentDetails;
|
||||
}
|
||||
|
||||
public static function getFolderContentType($folder, $yamlpath)
|
||||
{
|
||||
# check if folder is empty or has only index.yaml-file. This is a rare case so make it quick and dirty
|
||||
if(count($folder) == 1)
|
||||
{
|
||||
# check if in folder yaml file contains "posts", then return posts
|
||||
$folderyamlpath = getcwd() . DIRECTORY_SEPARATOR . 'content' . DIRECTORY_SEPARATOR . $yamlpath;
|
||||
|
||||
$fileContent = false;
|
||||
if(file_exists($folderyamlpath))
|
||||
{
|
||||
$fileContent = file_get_contents($folderyamlpath);
|
||||
}
|
||||
|
||||
if($fileContent && strpos($fileContent, 'contains: posts') !== false)
|
||||
{
|
||||
return 'posts';
|
||||
}
|
||||
return 'pages';
|
||||
}
|
||||
else
|
||||
{
|
||||
$file = $folder[0];
|
||||
$nameParts = self::getStringParts($file);
|
||||
$order = count($nameParts) > 1 ? array_shift($nameParts) : NULL;
|
||||
$order = substr($order, 0, 7);
|
||||
|
||||
if(\DateTime::createFromFormat('Ymd', $order) !== FALSE)
|
||||
{
|
||||
return "posts";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "pages";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function getItemForUrl($folderContentDetails, $url, $baseUrl, $result = NULL, $home = NULL )
|
||||
{
|
||||
|
||||
# if we are on the homepage
|
||||
if($home)
|
||||
{
|
||||
# return a standard item-object
|
||||
$item = new \stdClass;
|
||||
$item->status = 'published';
|
||||
$item->originalName = 'home';
|
||||
$item->elementType = 'folder';
|
||||
$item->fileType = 'md';
|
||||
$item->order = false;
|
||||
$item->name = 'home';
|
||||
$item->slug = '';
|
||||
$item->path = '';
|
||||
$item->pathWithoutType = DIRECTORY_SEPARATOR . 'index';
|
||||
$item->key = false;
|
||||
$item->keyPath = false;
|
||||
$item->keyPathArray = false;
|
||||
$item->chapter = false;
|
||||
$item->urlRel = '/';
|
||||
$item->urlRelWoF = '/';
|
||||
$item->urlAbs = $baseUrl;
|
||||
$item->name = 'home';
|
||||
$item->active = false;
|
||||
$item->activeParent = false;
|
||||
$item->hide = false;
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
foreach($folderContentDetails as $key => $item)
|
||||
{
|
||||
# set item active, needed to move item in navigation
|
||||
if($item->urlRel === $url)
|
||||
{
|
||||
$item->active = true;
|
||||
$result = $item;
|
||||
}
|
||||
elseif($item->elementType === "folder")
|
||||
{
|
||||
$result = self::getItemForUrl($item->folderContent, $url, $baseUrl, $result);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function getItemForUrlFrontend($folderContentDetails, $url, $result = NULL)
|
||||
{
|
||||
foreach($folderContentDetails as $key => $item)
|
||||
{
|
||||
# set item active, needed to move item in navigation
|
||||
if($item->urlRelWoF === $url)
|
||||
{
|
||||
$item->active = true;
|
||||
$result = $item;
|
||||
}
|
||||
elseif($item->elementType === "folder")
|
||||
{
|
||||
$result = self::getItemForUrlFrontend($item->folderContent, $url, $result);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function getPagingForItem($content, $item)
|
||||
{
|
||||
# if page is home
|
||||
if(trim($item->pathWithoutType, DIRECTORY_SEPARATOR) == 'index')
|
||||
{
|
||||
return $item;
|
||||
}
|
||||
|
||||
$keyPos = count($item->keyPathArray)-1;
|
||||
$thisChapArray = $item->keyPathArray;
|
||||
$nextItemArray = $item->keyPathArray;
|
||||
$prevItemArray = $item->keyPathArray;
|
||||
|
||||
$item->thisChapter = false;
|
||||
$item->prevItem = false;
|
||||
$item->nextItem = false;
|
||||
|
||||
|
||||
/************************
|
||||
* ADD THIS CHAPTER *
|
||||
************************/
|
||||
|
||||
if($keyPos > 0)
|
||||
{
|
||||
array_pop($thisChapArray);
|
||||
$item->thisChapter = 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;
|
||||
}
|
||||
|
||||
# the item is a file or an empty folder
|
||||
if(!$item->nextItem)
|
||||
{
|
||||
# walk to the next file in the same hierarchy
|
||||
$nextItemArray[$keyPos]++;
|
||||
|
||||
# get the key of the last element in this hierarchy level
|
||||
# if there is no chapter, then it is probably an empty first-level-folder. Count content to get the number of first level items
|
||||
$lastKey = $item->thisChapter ? array_key_last($item->thisChapter->folderContent) : count($content);
|
||||
|
||||
# as long as the nextItemArray is smaller than the last key in this hierarchy level, search for the next item
|
||||
# this ensures that it does not stop if key is missing (e.g. if the next page is hidden)
|
||||
while( ($nextItemArray[$keyPos] <= $lastKey) && !$item->nextItem = self::getItemWithKeyPath($content, $nextItemArray) )
|
||||
{
|
||||
$nextItemArray[$keyPos]++;
|
||||
}
|
||||
}
|
||||
|
||||
# there is no next file or folder in this level, so walk up the hierarchy to the next folder or file
|
||||
while(!$item->nextItem)
|
||||
{
|
||||
# delete the array level with the current item, so you are in the parent folder
|
||||
array_pop($nextItemArray);
|
||||
|
||||
# if the array is empty now, then you where in the base level already, so break
|
||||
if(empty($nextItemArray)) break;
|
||||
|
||||
# define the key position where you are right now
|
||||
$newKeyPos = count($nextItemArray)-1;
|
||||
|
||||
# go to the next position
|
||||
$nextItemArray[$newKeyPos]++;
|
||||
|
||||
# search for 5 items in case there are some hidden elements
|
||||
$maxlength = $nextItemArray[$newKeyPos]+5;
|
||||
while( ($nextItemArray[$newKeyPos] <= $maxlength) && !$item->nextItem = self::getItemWithKeyPath($content, $nextItemArray) )
|
||||
{
|
||||
$nextItemArray[$newKeyPos]++;
|
||||
}
|
||||
}
|
||||
|
||||
/************************
|
||||
* ADD PREVIOUS ITEM *
|
||||
************************/
|
||||
|
||||
# check if element is the first in the array
|
||||
$first = ($prevItemArray[$keyPos] == 0) ? true : false;
|
||||
|
||||
if(!$first)
|
||||
{
|
||||
$prevItemArray[$keyPos]--;
|
||||
|
||||
while($prevItemArray[$keyPos] >= 0 && !$item->prevItem = self::getItemWithKeyPath($content, $prevItemArray))
|
||||
{
|
||||
$prevItemArray[$keyPos]--;
|
||||
}
|
||||
|
||||
# if no item is found, then all previous items are hidden, so set first item to true and it will walk up the array later
|
||||
if(!$item->prevItem)
|
||||
{
|
||||
$first = true;
|
||||
}
|
||||
elseif($item->prevItem && $item->prevItem->elementType == 'folder' && !empty($item->prevItem->folderContent))
|
||||
{
|
||||
# if the previous item is a folder, the get the last item of that folder
|
||||
$item->prevItem = self::getLastItemOfFolder($item->prevItem);
|
||||
}
|
||||
}
|
||||
|
||||
# if it is the first item in the folder (or all other files are hidden)
|
||||
if($first)
|
||||
{
|
||||
# then the previous item is the containing chapter
|
||||
$item->prevItem = $item->thisChapter;
|
||||
}
|
||||
|
||||
if($item->prevItem && $item->prevItem->elementType == 'folder'){ unset($item->prevItem->folderContent); }
|
||||
if($item->nextItem && $item->nextItem->elementType == 'folder'){ unset($item->nextItem->folderContent); }
|
||||
if($item->thisChapter){unset($item->thisChapter->folderContent); }
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/*
|
||||
* Gets a copy of an item with a key
|
||||
* @param array $content with the full structure of the content as multidimensional array
|
||||
* @param array $searchArray with the key as a one-dimensional array like array(0,3,4)
|
||||
* @return array $item
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
# NOT IN USE
|
||||
public static function deleteItemWithKeyPath($structure, array $keys)
|
||||
{
|
||||
$result = &$structure;
|
||||
$last = array_pop($keys);
|
||||
|
||||
foreach ($keys as $key)
|
||||
{
|
||||
if(isset($result[$key]->folderContent))
|
||||
{
|
||||
$result = &$result[$key]->folderContent;
|
||||
}
|
||||
else
|
||||
{
|
||||
$result = &$result[$key];
|
||||
}
|
||||
}
|
||||
|
||||
$item = $result[$last];
|
||||
unset($result[$last]);
|
||||
|
||||
return $structure;
|
||||
}
|
||||
|
||||
# get breadcrumb as copied array,
|
||||
# set elements active in original
|
||||
# mark parent element in original
|
||||
public static function getBreadcrumb($content, $searchArray, $i = NULL, $breadcrumb = NULL)
|
||||
{
|
||||
# if it is the first round, create an empty array
|
||||
if(!$i){ $i = 0; $breadcrumb = array();}
|
||||
|
||||
if(!$searchArray){ return $breadcrumb;}
|
||||
|
||||
while($i < count($searchArray))
|
||||
{
|
||||
if(!isset($content[$searchArray[$i]])){ return false; }
|
||||
$item = $content[$searchArray[$i]];
|
||||
|
||||
if($i == count($searchArray)-1)
|
||||
{
|
||||
$item->active = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
$item->activeParent = true;
|
||||
}
|
||||
|
||||
/*
|
||||
$item->active = true;
|
||||
if($i == count($searchArray)-2)
|
||||
{
|
||||
$item->activeParent = true;
|
||||
}
|
||||
*/
|
||||
|
||||
$copy = clone($item);
|
||||
if($copy->elementType == 'folder')
|
||||
{
|
||||
unset($copy->folderContent);
|
||||
$content = $item->folderContent;
|
||||
}
|
||||
$breadcrumb[] = $copy;
|
||||
|
||||
$i++;
|
||||
return 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;
|
||||
}
|
||||
public static function getNameWithoutType($fileName)
|
||||
{
|
||||
$parts = preg_split('/\./',$fileName);
|
||||
return $parts[0];
|
||||
}
|
||||
|
||||
public static function createSlug($name, $settings = NULL)
|
||||
{
|
||||
$name = iconv(mb_detect_encoding($name, mb_detect_order(), true), "UTF-8", $name);
|
||||
|
||||
# prior version 1.5.0 settings was no language and remove stop words from slug
|
||||
$language = "";
|
||||
$use_remove_list = true;
|
||||
|
||||
# if user has not activated the old slug logig < version 1.5.0 style
|
||||
if($settings && ( !isset($settings['oldslug']) OR !$settings['oldslug'] ) )
|
||||
{
|
||||
# then use the language attr and do not remove stop words as default behavior
|
||||
$language = isset($settings['langattr']) ? $settings['langattr'] : "";
|
||||
$use_remove_list = false;
|
||||
}
|
||||
|
||||
return URLify::filter(
|
||||
$name,
|
||||
$length = 60,
|
||||
$language,
|
||||
$file_name = false,
|
||||
$use_remove_list,
|
||||
$lower_case = true,
|
||||
$treat_underscore_as_space = true
|
||||
);
|
||||
}
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Models;
|
||||
|
||||
use Typemill\Models\Write;
|
||||
|
||||
class Helpers{
|
||||
|
||||
public static 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;
|
||||
}
|
||||
|
||||
|
||||
public static function addLogEntry($action)
|
||||
{
|
||||
$line = self::getUserIP();
|
||||
$line .= ';' . date("Y-m-d H:i:s");
|
||||
$line .= ';' . $action;
|
||||
|
||||
$write = new Write();
|
||||
$logfile = $write->getFile('cache', 'securitylog.txt');
|
||||
|
||||
if($logfile)
|
||||
{
|
||||
$logfile .= $line . PHP_EOL;
|
||||
}
|
||||
else
|
||||
{
|
||||
$logfile = $line . PHP_EOL;
|
||||
}
|
||||
|
||||
$write->writeFile('cache', 'securitylog.txt', $logfile);
|
||||
}
|
||||
|
||||
public static function array_sort($array, $on, $order=SORT_ASC)
|
||||
{
|
||||
$new_array = array();
|
||||
$sortable_array = array();
|
||||
|
||||
if (count($array) > 0) {
|
||||
foreach ($array as $k => $v) {
|
||||
if (is_array($v)) {
|
||||
foreach ($v as $k2 => $v2) {
|
||||
if ($k2 == $on) {
|
||||
$sortable_array[$k] = $v2;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$sortable_array[$k] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
switch ($order) {
|
||||
case SORT_ASC:
|
||||
asort($sortable_array);
|
||||
break;
|
||||
case SORT_DESC:
|
||||
arsort($sortable_array);
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($sortable_array as $k => $v) {
|
||||
$new_array[] = $array[$k];
|
||||
}
|
||||
}
|
||||
|
||||
return $new_array;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,254 +0,0 @@
|
||||
<?php
|
||||
namespace Typemill\Models;
|
||||
|
||||
use Typemill\Models\Folder;
|
||||
|
||||
class ProcessAssets
|
||||
{
|
||||
# holds the path to the baseFolder
|
||||
public $baseFolder;
|
||||
|
||||
# holds the path to the mediaFolder
|
||||
public $mediaFolder;
|
||||
|
||||
# holds the path to the temporary image folder
|
||||
public $tmpFolder;
|
||||
|
||||
# holds the path where original images are stored
|
||||
public $originalFolder;
|
||||
|
||||
# holds the path where images for frontend use are stored
|
||||
public $liveFolder;
|
||||
|
||||
# holds the folder where the thumbs for the media library are stored
|
||||
public $thumbFolder;
|
||||
|
||||
# holds the folder where the thumbs for the media library are stored
|
||||
public $fileFolder;
|
||||
|
||||
# holds the desired sizes for image resizing
|
||||
protected $desiredSizes;
|
||||
|
||||
public function __construct($desiredSizes = NULL)
|
||||
{
|
||||
ini_set('memory_limit', '512M');
|
||||
|
||||
$this->baseFolder = getcwd() . DIRECTORY_SEPARATOR;
|
||||
|
||||
$this->mediaFolder = $this->baseFolder . 'media' . DIRECTORY_SEPARATOR;
|
||||
|
||||
$this->tmpFolder = $this->mediaFolder . 'tmp' . DIRECTORY_SEPARATOR;
|
||||
|
||||
$this->originalFolder = $this->mediaFolder . 'original' . DIRECTORY_SEPARATOR;
|
||||
|
||||
$this->liveFolder = $this->mediaFolder . 'live' . DIRECTORY_SEPARATOR;
|
||||
|
||||
$this->thumbFolder = $this->mediaFolder . 'thumbs' . DIRECTORY_SEPARATOR;
|
||||
|
||||
$this->customFolder = $this->mediaFolder . 'custom' . DIRECTORY_SEPARATOR;
|
||||
|
||||
$this->fileFolder = $this->mediaFolder . 'files' . DIRECTORY_SEPARATOR;
|
||||
|
||||
$this->desiredSizes = $desiredSizes;
|
||||
}
|
||||
|
||||
public function checkFolders($forassets = null)
|
||||
{
|
||||
|
||||
$folders = [$this->mediaFolder, $this->tmpFolder, $this->fileFolder];
|
||||
|
||||
if($forassets == 'images')
|
||||
{
|
||||
$folders = [$this->mediaFolder, $this->tmpFolder, $this->originalFolder, $this->liveFolder, $this->thumbFolder, $this->customFolder];
|
||||
}
|
||||
|
||||
foreach($folders as $folder)
|
||||
{
|
||||
if(!file_exists($folder) && !is_dir( $folder ))
|
||||
{
|
||||
if(!mkdir($folder, 0755, true))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if($folder == $this->thumbFolder)
|
||||
{
|
||||
# cleanup old systems
|
||||
$this->cleanupLiveFolder();
|
||||
|
||||
# generate thumbnails from live folder
|
||||
$this->generateThumbs();
|
||||
}
|
||||
}
|
||||
elseif(!is_writeable($folder) OR !is_readable($folder))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
# check if thumb-folder is empty, then generate thumbs from live folder
|
||||
if($folder == $this->thumbFolder && $this->is_dir_empty($folder))
|
||||
{
|
||||
# cleanup old systems
|
||||
$this->cleanupLiveFolder();
|
||||
|
||||
# generate thumbnails from live folder
|
||||
$this->generateThumbs();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function is_dir_empty($dir)
|
||||
{
|
||||
return (count(scandir($dir)) == 2);
|
||||
}
|
||||
|
||||
public function setFileName($originalname, $type, $overwrite = NULL)
|
||||
{
|
||||
$pathinfo = pathinfo($originalname);
|
||||
$this->extension = isset($pathinfo['extension']) ? strtolower($pathinfo['extension']) : null;
|
||||
$this->filename = Folder::createSlug($pathinfo['filename']);
|
||||
|
||||
$filename = $this->filename;
|
||||
|
||||
# check if file name is
|
||||
if(!$overwrite)
|
||||
{
|
||||
$suffix = 1;
|
||||
|
||||
$destination = $this->liveFolder;
|
||||
if($type == 'file')
|
||||
{
|
||||
$destination = $this->fileFolder;
|
||||
}
|
||||
|
||||
while(file_exists($destination . $filename . '.' . $this->extension))
|
||||
{
|
||||
$filename = $this->filename . '-' . $suffix;
|
||||
$suffix++;
|
||||
}
|
||||
}
|
||||
|
||||
$this->filename = $filename;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->filename;
|
||||
}
|
||||
|
||||
public function setExtension($extension)
|
||||
{
|
||||
$this->extension = $extension;
|
||||
}
|
||||
|
||||
public function getExtension()
|
||||
{
|
||||
return $this->extension;
|
||||
}
|
||||
|
||||
public function getFullName()
|
||||
{
|
||||
return $this->filename . '.' . $this->extension;
|
||||
}
|
||||
|
||||
public function clearTempFolder()
|
||||
{
|
||||
$files = scandir($this->tmpFolder);
|
||||
$result = true;
|
||||
|
||||
foreach($files as $file)
|
||||
{
|
||||
if (!in_array($file, array(".","..")))
|
||||
{
|
||||
$filelink = $this->tmpFolder . $file;
|
||||
if(!unlink($filelink))
|
||||
{
|
||||
$success = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function cleanupLiveFolder()
|
||||
{
|
||||
# delete all old thumbs mlibrary in live folder
|
||||
foreach(glob($this->liveFolder . '*mlibrary*') as $filename)
|
||||
{
|
||||
unlink($filename);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function findPagesWithUrl($structure, $url, $result)
|
||||
{
|
||||
foreach ($structure as $key => $item)
|
||||
{
|
||||
if($item->elementType == 'folder')
|
||||
{
|
||||
$result = $this->findPagesWithUrl($item->folderContent, $url, $result);
|
||||
}
|
||||
else
|
||||
{
|
||||
$live = getcwd() . DIRECTORY_SEPARATOR . 'content' . $item->pathWithoutType . '.md';
|
||||
$draft = getcwd() . DIRECTORY_SEPARATOR . 'content' . $item->pathWithoutType . '.txt';
|
||||
|
||||
# check live first
|
||||
if(file_exists($live))
|
||||
{
|
||||
$content = file_get_contents($live);
|
||||
|
||||
if (stripos($content, $url) !== false)
|
||||
{
|
||||
$result[] = $item->urlRelWoF;
|
||||
}
|
||||
# if not in live, check in draft
|
||||
elseif(file_exists($draft))
|
||||
{
|
||||
$content = file_get_contents($draft);
|
||||
|
||||
if (stripos($content, $url) !== false)
|
||||
{
|
||||
$result[] = $item->urlRelWoF;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function formatSizeUnits($bytes)
|
||||
{
|
||||
if ($bytes >= 1073741824)
|
||||
{
|
||||
$bytes = number_format($bytes / 1073741824, 2) . ' GB';
|
||||
}
|
||||
elseif ($bytes >= 1048576)
|
||||
{
|
||||
$bytes = number_format($bytes / 1048576, 2) . ' MB';
|
||||
}
|
||||
elseif ($bytes >= 1024)
|
||||
{
|
||||
$bytes = number_format($bytes / 1024, 2) . ' KB';
|
||||
}
|
||||
elseif ($bytes > 1)
|
||||
{
|
||||
$bytes = $bytes . ' bytes';
|
||||
}
|
||||
elseif ($bytes == 1)
|
||||
{
|
||||
$bytes = $bytes . ' byte';
|
||||
}
|
||||
else
|
||||
{
|
||||
$bytes = '0 bytes';
|
||||
}
|
||||
|
||||
return $bytes;
|
||||
}
|
||||
}
|
@ -1,164 +0,0 @@
|
||||
<?php
|
||||
namespace Typemill\Models;
|
||||
|
||||
use Slim\Http\UploadedFile;
|
||||
use Typemill\Models\Helpers;
|
||||
|
||||
class ProcessFile extends ProcessAssets
|
||||
{
|
||||
/**
|
||||
* Moves the uploaded file to the upload directory. Only used for settings / NON VUE.JS uploads
|
||||
*
|
||||
* @param string $directory directory to which the file is moved
|
||||
* @param UploadedFile $uploadedFile file uploaded file to move
|
||||
* @return string filename of moved file
|
||||
*/
|
||||
public function moveUploadedFile(UploadedFile $uploadedFile, $overwrite = false, $name = false, $folder = NULL)
|
||||
{
|
||||
$this->setFileName($uploadedFile->getClientFilename(), 'file');
|
||||
|
||||
if($name)
|
||||
{
|
||||
$this->setFileName($name . '.' . $this->extension, 'file', $overwrite);
|
||||
}
|
||||
|
||||
$uploadedFile->moveTo($this->fileFolder . $this->getFullName());
|
||||
|
||||
return $this->getFullName();
|
||||
}
|
||||
|
||||
public function storeFile($file, $name)
|
||||
{
|
||||
$this->setFileName($name, 'file');
|
||||
|
||||
$this->clearTempFolder();
|
||||
|
||||
$file = $this->decodeFile($file);
|
||||
|
||||
$path = $this->tmpFolder . $this->getFullName();
|
||||
|
||||
if($file !== false && file_put_contents($path, $file["file"]))
|
||||
{
|
||||
$size = filesize($path);
|
||||
$size = $this->formatSizeUnits($size);
|
||||
|
||||
$title = str_replace('-', ' ', $this->filename);
|
||||
$title = $title . ' (' . strtoupper($this->extension) . ', ' . $size .')';
|
||||
|
||||
return ['title' => $title, 'name' => $this->filename, 'extension' => $this->extension, 'size' => $size, 'url' => 'media/files/' . $this->getFullName()];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function publishFile()
|
||||
{
|
||||
$files = scandir($this->tmpFolder);
|
||||
$success = true;
|
||||
|
||||
foreach($files as $file)
|
||||
{
|
||||
if (!in_array($file, array(".","..")))
|
||||
{
|
||||
$success = rename($this->tmpFolder . $file, $this->fileFolder . $file);
|
||||
}
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
public function decodeFile(string $file)
|
||||
{
|
||||
$fileParts = explode(";base64,", $file);
|
||||
$fileType = explode("/", $fileParts[0]);
|
||||
$fileData = base64_decode($fileParts[1]);
|
||||
|
||||
if ($fileData !== false)
|
||||
{
|
||||
return array("file" => $fileData, "type" => $fileType[1]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function deleteFile($name)
|
||||
{
|
||||
# validate name
|
||||
$name = basename($name);
|
||||
|
||||
if(file_exists($this->fileFolder . $name) && unlink($this->fileFolder . $name))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public function deleteFileWithName($name)
|
||||
{
|
||||
# e.g. delete $name = 'logo';
|
||||
|
||||
$name = basename($name);
|
||||
|
||||
if($name != '' && !in_array($name, array(".","..")))
|
||||
{
|
||||
foreach(glob($this->fileFolder . $name) as $file)
|
||||
{
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* scans content of a folder (without recursion)
|
||||
* vars: folder path as string
|
||||
* returns: one-dimensional array with names of folders and files
|
||||
*/
|
||||
public function scanFilesFlat()
|
||||
{
|
||||
$files = scandir($this->fileFolder);
|
||||
$filelist = array();
|
||||
|
||||
foreach ($files as $key => $name)
|
||||
{
|
||||
if (!in_array($name, array(".","..","filerestrictions.yaml")) && file_exists($this->fileFolder . $name))
|
||||
{
|
||||
$filelist[] = [
|
||||
'name' => $name,
|
||||
'timestamp' => filemtime($this->fileFolder . $name),
|
||||
'info' => pathinfo($this->fileFolder . $name),
|
||||
'url' => 'media/files/' . $name,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$filelist = Helpers::array_sort($filelist, 'timestamp', SORT_DESC);
|
||||
|
||||
return $filelist;
|
||||
}
|
||||
|
||||
|
||||
public function getFileDetails($name, $structure)
|
||||
{
|
||||
$name = basename($name);
|
||||
|
||||
if (!in_array($name, array(".","..")) && file_exists($this->fileFolder . $name))
|
||||
{
|
||||
$filedetails = [
|
||||
'name' => $name,
|
||||
'timestamp' => filemtime($this->fileFolder . $name),
|
||||
'bytes' => filesize($this->fileFolder . $name),
|
||||
'info' => pathinfo($this->fileFolder . $name),
|
||||
'url' => 'media/files/' . $name,
|
||||
'pages' => $this->findPagesWithUrl($structure, $name, $result = [])
|
||||
];
|
||||
|
||||
return $filedetails;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,510 +0,0 @@
|
||||
<?php
|
||||
namespace Typemill\Models;
|
||||
|
||||
use Slim\Http\UploadedFile;
|
||||
use Typemill\Models\Helpers;
|
||||
|
||||
class ProcessImage extends ProcessAssets
|
||||
{
|
||||
public function createImage(string $image, string $name, array $desiredSizes, $overwrite = NULL)
|
||||
{
|
||||
# fix error from jpeg-library
|
||||
ini_set ('gd.jpeg_ignore_warning', 1);
|
||||
error_reporting(E_ALL & ~E_NOTICE);
|
||||
|
||||
# clear temporary folder
|
||||
$this->clearTempFolder();
|
||||
|
||||
# set the name of the image
|
||||
$this->setFileName($name, 'image', $overwrite);
|
||||
|
||||
# decode the image from base64-string
|
||||
$imageDecoded = $this->decodeImage($image);
|
||||
$imageData = $imageDecoded["image"];
|
||||
$imageType = $imageDecoded["type"];
|
||||
|
||||
if($imageType == 'svg+xml')
|
||||
{
|
||||
# store the original name as txt-file
|
||||
$tmpname = fopen($this->tmpFolder . $this->getName() . ".svg.txt", "w");
|
||||
|
||||
# store the same svg file for original, live and thumb.
|
||||
$this->saveOriginal($this->tmpFolder, $imageData, $name = 'original', 'svg');
|
||||
$this->saveOriginal($this->tmpFolder, $imageData, $name = 'live', 'svg');
|
||||
$this->saveOriginal($this->tmpFolder, $imageData, $name = 'thumbs', 'svg');
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->setExtension($imageType);
|
||||
|
||||
# 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);
|
||||
|
||||
# store the original name as txt-file
|
||||
$tmpname = fopen($this->tmpFolder . $this->getName() . '.' . $imageType . ".txt", "w");
|
||||
|
||||
$this->saveOriginal($this->tmpFolder, $imageData, $name = 'original', $imageType);
|
||||
|
||||
# temporary store resized images
|
||||
foreach($resizedImages as $key => $resizedImage)
|
||||
{
|
||||
$this->saveImage($this->tmpFolder, $resizedImage, $key, $imageType);
|
||||
}
|
||||
|
||||
# if the image is an animated gif, then overwrite the resized version for live use with the original version
|
||||
if($imageType == "gif" && $this->detectAnimatedGif($imageData))
|
||||
{
|
||||
$this->saveOriginal($this->tmpFolder, $imageData, $name = 'live', $imageType);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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($noresize = NULL)
|
||||
{
|
||||
# name is stored in temporary folder as name of the .txt-file
|
||||
foreach(glob($this->tmpFolder . '*.txt') as $imagename)
|
||||
{
|
||||
$tmpname = str_replace('.txt', '', basename($imagename));
|
||||
|
||||
# set extension and sanitize name. Overwrite because this has been checked before
|
||||
$this->setFileName($tmpname, 'image', $overwrite = true);
|
||||
|
||||
unlink($imagename);
|
||||
}
|
||||
|
||||
$name = uniqid();
|
||||
|
||||
if($this->filename && $this->extension)
|
||||
{
|
||||
$name = $this->filename;
|
||||
}
|
||||
|
||||
$files = scandir($this->tmpFolder);
|
||||
$success = true;
|
||||
|
||||
foreach($files as $file)
|
||||
{
|
||||
if (!in_array($file, array(".","..")))
|
||||
{
|
||||
$tmpfilename = explode(".", $file);
|
||||
|
||||
if($tmpfilename[0] == 'original')
|
||||
{
|
||||
if($noresize)
|
||||
{
|
||||
$success = copy($this->tmpFolder . $file, $this->liveFolder . $name . '.' . $tmpfilename[1]);
|
||||
}
|
||||
$success = rename($this->tmpFolder . $file, $this->originalFolder . $name . '.' . $tmpfilename[1]);
|
||||
}
|
||||
if($tmpfilename[0] == 'live')
|
||||
{
|
||||
if($noresize)
|
||||
{
|
||||
$success = unlink($this->tmpFolder . $file);
|
||||
}
|
||||
else
|
||||
{
|
||||
$success = rename($this->tmpFolder . $file, $this->liveFolder . $name . '.' . $tmpfilename[1]);
|
||||
}
|
||||
}
|
||||
if($tmpfilename[0] == 'thumbs')
|
||||
{
|
||||
$success = rename($this->tmpFolder . $file, $this->thumbFolder . $name . '.' . $tmpfilename[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if($success)
|
||||
{
|
||||
# return true;
|
||||
return 'media/live/' . $name . '.' . $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 desired size is bigger than the actual image, then drop the desired sizes and use the actual image size instead
|
||||
if($desiredSize['width'] > $imageSize['width'])
|
||||
{
|
||||
$desiredSizes[$key] = $imageSize;
|
||||
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 $source, array $desiredSizes, $imageType)
|
||||
{
|
||||
|
||||
$copiedImages = array();
|
||||
|
||||
foreach($desiredSizes as $key => $desired)
|
||||
{
|
||||
// resize
|
||||
$ratio = max($desired['width']/$source['width'], $desired['height']/$source['height']);
|
||||
$h = $desired['height'] / $ratio;
|
||||
$x = ($source['width'] - $desired['width'] / $ratio) / 2;
|
||||
$y = ($source['height'] - $desired['height'] / $ratio) / 2;
|
||||
$w = $desired['width'] / $ratio;
|
||||
|
||||
$new = imagecreatetruecolor($desired['width'], $desired['height']);
|
||||
|
||||
// preserve transparency
|
||||
if($imageType == "gif" or $imageType == "png" or $imageType == "webp")
|
||||
{
|
||||
imagecolortransparent($new, imagecolorallocatealpha($new, 0, 0, 0, 127));
|
||||
imagealphablending($new, false);
|
||||
imagesavealpha($new, true);
|
||||
}
|
||||
|
||||
imagecopyresampled($new, $imageData, 0, 0, $x, $y, $desired['width'], $desired['height'], $w, $h);
|
||||
|
||||
$copiedImages[$key] = $new;
|
||||
}
|
||||
|
||||
return $copiedImages;
|
||||
}
|
||||
|
||||
# save original in temporary folder
|
||||
public function saveOriginal($folder, $image, $name, $type)
|
||||
{
|
||||
$path = $folder . $name . '.' . $type;
|
||||
|
||||
file_put_contents($path, $image);
|
||||
}
|
||||
|
||||
|
||||
# save resized images in temporary folder
|
||||
public function saveImage($folder, $image, $name, $type)
|
||||
{
|
||||
$type = strtolower($type);
|
||||
|
||||
if($type == "png")
|
||||
{
|
||||
$result = imagepng( $image, $folder . $name . '.png' );
|
||||
}
|
||||
elseif($type == "gif")
|
||||
{
|
||||
$result = imagegif( $image, $folder . $name . '.gif' );
|
||||
}
|
||||
elseif($type == "webp")
|
||||
{
|
||||
$result = imagewebp( $image, $folder . $name . '.webp', 100);
|
||||
}
|
||||
elseif($type == "jpg" OR $type == "jpeg")
|
||||
{
|
||||
$result = imagejpeg( $image, $folder . $name . '.' . $type );
|
||||
}
|
||||
else
|
||||
{
|
||||
# image type not supported
|
||||
return false;
|
||||
}
|
||||
|
||||
imagedestroy($image);
|
||||
|
||||
if($result)
|
||||
{
|
||||
return $name . '.' . $type;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the uploaded file to the upload directory. Only used for settings / NON VUE.JS uploads
|
||||
*
|
||||
* @param string $directory directory to which the file is moved
|
||||
* @param UploadedFile $uploadedFile file uploaded file to move
|
||||
* @return string filename of moved file
|
||||
*/
|
||||
public function moveUploadedImage(UploadedFile $uploadedFile, $overwrite = false, $name = false, $folder = NULL)
|
||||
{
|
||||
$this->setFileName($uploadedFile->getClientFilename(), 'file');
|
||||
|
||||
if($name)
|
||||
{
|
||||
$this->setFileName($name . '.' . $this->extension, 'file', $overwrite);
|
||||
}
|
||||
|
||||
if(!$folder)
|
||||
{
|
||||
$folder = $this->liveFolder;
|
||||
}
|
||||
|
||||
$uploadedFile->moveTo($folder . $this->getFullName());
|
||||
|
||||
return $this->getFullName();
|
||||
}
|
||||
|
||||
public function deleteImage($name)
|
||||
{
|
||||
|
||||
# validate name
|
||||
$name = basename($name);
|
||||
|
||||
$result = true;
|
||||
|
||||
if(!file_exists($this->originalFolder . $name) OR !unlink($this->originalFolder . $name))
|
||||
{
|
||||
$result = false;
|
||||
}
|
||||
|
||||
if(!file_exists($this->liveFolder . $name) OR !unlink($this->liveFolder . $name))
|
||||
{
|
||||
$result = false;
|
||||
}
|
||||
|
||||
if(!file_exists($this->thumbFolder . $name) OR !unlink($this->thumbFolder . $name))
|
||||
{
|
||||
$result = false;
|
||||
}
|
||||
|
||||
# delete custom images (resized and grayscaled)
|
||||
# array_map('unlink', glob("some/dir/*.txt"));
|
||||
$pathinfo = pathinfo($name);
|
||||
|
||||
foreach(glob($this->customFolder . $pathinfo['filename'] . '\-*.' . $pathinfo['extension']) as $image)
|
||||
{
|
||||
# you could check if extension is the same here
|
||||
if(!unlink($image))
|
||||
{
|
||||
$success = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
public function deleteImageWithName($name)
|
||||
{
|
||||
# e.g. delete $name = 'logo...';
|
||||
|
||||
$name = basename($name);
|
||||
|
||||
if($name != '' && !in_array($name, array(".","..")))
|
||||
{
|
||||
foreach(glob($this->liveFolder . $name) as $file)
|
||||
{
|
||||
unlink($file);
|
||||
}
|
||||
foreach(glob($this->originalFolder . $name) as $file)
|
||||
{
|
||||
unlink($file);
|
||||
}
|
||||
foreach(glob($this->thumbFolder . $name) as $file)
|
||||
{
|
||||
unlink($file);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function copyImage($name,$sourcefolder,$targetfolder)
|
||||
{
|
||||
copy($sourcefolder . $name, $targetfolder . $name);
|
||||
}
|
||||
|
||||
/*
|
||||
* scans content of a folder (without recursion)
|
||||
* vars: folder path as string
|
||||
* returns: one-dimensional array with names of folders and files
|
||||
*/
|
||||
public function scanMediaFlat()
|
||||
{
|
||||
$thumbs = array_diff(scandir($this->thumbFolder), array('..', '.'));
|
||||
$imagelist = array();
|
||||
|
||||
foreach ($thumbs as $key => $name)
|
||||
{
|
||||
if (file_exists($this->liveFolder . $name))
|
||||
{
|
||||
$imagelist[] = [
|
||||
'name' => $name,
|
||||
'timestamp' => filemtime($this->liveFolder . $name),
|
||||
'src_thumb' => 'media/thumbs/' . $name,
|
||||
'src_live' => 'media/live/' . $name,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$imagelist = Helpers::array_sort($imagelist, 'timestamp', SORT_DESC);
|
||||
|
||||
return $imagelist;
|
||||
}
|
||||
|
||||
|
||||
public function getImageDetails($name, $structure)
|
||||
{
|
||||
$name = basename($name);
|
||||
|
||||
if (!in_array($name, array(".","..")) && file_exists($this->liveFolder . $name))
|
||||
{
|
||||
$imageinfo = getimagesize($this->liveFolder . $name);
|
||||
|
||||
if(!$imageinfo && pathinfo($this->liveFolder . $name, PATHINFO_EXTENSION) == 'svg')
|
||||
{
|
||||
$imagedetails = [
|
||||
'name' => $name,
|
||||
'timestamp' => filemtime($this->liveFolder . $name),
|
||||
'bytes' => filesize($this->liveFolder . $name),
|
||||
'width' => '---',
|
||||
'height' => '---',
|
||||
'type' => 'svg',
|
||||
'src_thumb' => 'media/thumbs/' . $name,
|
||||
'src_live' => 'media/live/' . $name,
|
||||
'pages' => $this->findPagesWithUrl($structure, $name, $result = [])
|
||||
];
|
||||
}
|
||||
else
|
||||
{
|
||||
$imagedetails = [
|
||||
'name' => $name,
|
||||
'timestamp' => filemtime($this->liveFolder . $name),
|
||||
'bytes' => filesize($this->liveFolder . $name),
|
||||
'width' => $imageinfo[0],
|
||||
'height' => $imageinfo[1],
|
||||
'type' => $imageinfo['mime'],
|
||||
'src_thumb' => 'media/thumbs/' . $name,
|
||||
'src_live' => 'media/live/' . $name,
|
||||
'pages' => $this->findPagesWithUrl($structure, $name, $result = [])
|
||||
];
|
||||
}
|
||||
|
||||
return $imagedetails;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function generateThumbs()
|
||||
{
|
||||
# generate images from live folder to 'tmthumbs'
|
||||
$liveImages = scandir($this->liveFolder);
|
||||
|
||||
$result = false;
|
||||
|
||||
foreach ($liveImages as $key => $name)
|
||||
{
|
||||
if (!in_array($name, array(".","..")))
|
||||
{
|
||||
$result = $this->generateThumbFromImageFile($name);
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function generateThumbFromImageFile($filename)
|
||||
{
|
||||
$this->setFileName($filename, 'image', $overwrite = true);
|
||||
|
||||
# if($this->extension == 'jpg') $this->extension = 'jpeg';
|
||||
|
||||
$image = $this->createImageFromPath($this->liveFolder . $filename, $this->extension);
|
||||
|
||||
$originalSize = $this->getImageSize($image);
|
||||
|
||||
$thumbSize = $this->desiredSizes['thumbs'];
|
||||
|
||||
$thumb = $this->imageResize($image, $originalSize, ['thumbs' => $thumbSize ], $this->extension);
|
||||
|
||||
$saveImage = $this->saveImage($this->thumbFolder, $thumb['thumbs'], $this->filename, $this->extension);
|
||||
if($saveImage)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
# filename and imagepath can be a tmp-version after upload.
|
||||
public function generateSizesFromImageFile($filename, $imagePath)
|
||||
{
|
||||
$this->setFileName($filename, 'image');
|
||||
|
||||
# if($this->extension == 'jpg') $this->extension = 'jpeg';
|
||||
|
||||
$image = $this->createImageFromPath($imagePath, $this->extension);
|
||||
|
||||
$originalSize = $this->getImageSize($image);
|
||||
|
||||
$resizedImages = $this->imageResize($image, $originalSize, $this->desiredSizes, $this->extension);
|
||||
|
||||
return $resizedImages;
|
||||
}
|
||||
|
||||
public function grayscale($imagePath, $extension)
|
||||
{
|
||||
$image = $this->createImageFromPath($imagePath, $extension);
|
||||
|
||||
imagefilter($image, IMG_FILTER_GRAYSCALE);
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
public function createImageFromPath($imagePath, $extension)
|
||||
{
|
||||
switch($extension)
|
||||
{
|
||||
case 'gif': $image = imagecreatefromgif($imagePath); break;
|
||||
case 'jpg' :
|
||||
case 'jpeg': $image = imagecreatefromjpeg($imagePath); break;
|
||||
case 'png': $image = imagecreatefrompng($imagePath); break;
|
||||
case 'webp': $image = imagecreatefromwebp($imagePath); break;
|
||||
default: return 'image type not supported';
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
}
|
@ -1,379 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Models;
|
||||
|
||||
class User extends WriteYaml
|
||||
{
|
||||
|
||||
private $userDir = __DIR__ . '/../../settings/users';
|
||||
|
||||
public function getUsers()
|
||||
{
|
||||
/* check if users directory exists */
|
||||
if(!is_dir($this->userDir)){ return array(); }
|
||||
|
||||
/* get all user files */
|
||||
$userfiles = array_diff(scandir($this->userDir), array('..', '.', '.logins', 'tmuserindex-mail.txt', 'tmuserindex-role.txt'));
|
||||
|
||||
$usernames = array();
|
||||
foreach($userfiles as $key => $userfile)
|
||||
{
|
||||
$usernames[] = str_replace('.yaml', '', $userfile);
|
||||
}
|
||||
|
||||
usort($usernames, 'strnatcasecmp');
|
||||
|
||||
return $usernames;
|
||||
}
|
||||
|
||||
public function getUser($username)
|
||||
{
|
||||
$user = $this->getYaml('settings/users', $username . '.yaml');
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function getSecureUser($username)
|
||||
{
|
||||
$user = $this->getYaml('settings/users', $username . '.yaml');
|
||||
unset($user['password']);
|
||||
return $user;
|
||||
}
|
||||
|
||||
public function createUser($params)
|
||||
{
|
||||
$params['password'] = $this->generatePassword($params['password']);
|
||||
|
||||
if($this->updateYaml('settings/users', $params['username'] . '.yaml', $params))
|
||||
{
|
||||
$this->deleteUserIndex();
|
||||
|
||||
return $params['username'];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function unsetFromUser($username, $keys)
|
||||
{
|
||||
if(empty($keys))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$userdata = $this->getUser($username);
|
||||
|
||||
if(!$userdata)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach($keys as $key)
|
||||
{
|
||||
if(isset($userdata[$key]))
|
||||
{
|
||||
unset($userdata[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->updateYaml('settings/users', $userdata['username'] . '.yaml', $userdata);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function updateUser($params)
|
||||
{
|
||||
$userdata = $this->getUser($params['username']);
|
||||
|
||||
# make sure passwords are not overwritten
|
||||
if(isset($params['newpassword'])){ unset($params['newpassword']); }
|
||||
if(isset($params['password']))
|
||||
{
|
||||
if(empty($params['password']))
|
||||
{
|
||||
unset($params['password']);
|
||||
}
|
||||
else
|
||||
{
|
||||
$params['password'] = $this->generatePassword($params['password']);
|
||||
}
|
||||
}
|
||||
|
||||
$update = array_merge($userdata, $params);
|
||||
|
||||
# cleanup data here
|
||||
|
||||
$this->updateYaml('settings/users', $userdata['username'] . '.yaml', $update);
|
||||
|
||||
$this->deleteUserIndex();
|
||||
|
||||
# if user updated his own profile, update session data
|
||||
if(isset($_SESSION['user']) && $_SESSION['user'] == $params['username'])
|
||||
{
|
||||
$_SESSION['role'] = $update['userrole'];
|
||||
|
||||
if(isset($update['firstname']))
|
||||
{
|
||||
$_SESSION['firstname'] = $update['firstname'];
|
||||
}
|
||||
if(isset($update['lastname']))
|
||||
{
|
||||
$_SESSION['lastname'] = $update['lastname'];
|
||||
}
|
||||
}
|
||||
|
||||
return $userdata['username'];
|
||||
}
|
||||
|
||||
public function deleteUser($username)
|
||||
{
|
||||
if($this->getUser($username))
|
||||
{
|
||||
unlink('settings/users/' . $username . '.yaml');
|
||||
|
||||
$this->deleteUserIndex();
|
||||
}
|
||||
}
|
||||
|
||||
public function login($username)
|
||||
{
|
||||
$user = $this->getSecureUser($username);
|
||||
|
||||
if($user)
|
||||
{
|
||||
$user['lastlogin'] = time();
|
||||
$user['tmpApiKey'] = ;
|
||||
|
||||
$_SESSION['user'] = $user['username'];
|
||||
$_SESSION['role'] = $user['userrole'];
|
||||
$_SESSION['login'] = $user['lastlogin'];
|
||||
|
||||
if(isset($user['firstname']))
|
||||
{
|
||||
$_SESSION['firstname'] = $user['firstname'];
|
||||
}
|
||||
if(isset($user['lastname']))
|
||||
{
|
||||
$_SESSION['lastname'] = $user['lastname'];
|
||||
}
|
||||
|
||||
# update user last login
|
||||
$this->updateUser($user);
|
||||
|
||||
if(isset($user['recovertoken']) OR isset($user['recoverdate']))
|
||||
{
|
||||
$this->unsetFromUser($user['username'], ['recovertoken', 'recoverdate']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function generatePassword($password)
|
||||
{
|
||||
return \password_hash($password, PASSWORD_DEFAULT, ['cost' => 10]);
|
||||
}
|
||||
|
||||
|
||||
# accepts email with or without asterix and returns userdata
|
||||
public function findUsersByEmail($email)
|
||||
{
|
||||
$usernames = [];
|
||||
|
||||
# Make sure that we scan only the first 11 files even if there are some thousand users.
|
||||
if ($dh = opendir($this->userDir))
|
||||
{
|
||||
$count = 1;
|
||||
$exclude = array('..', '.', '.logins', 'tmuserindex-mail.txt', 'tmuserindex-role.txt');
|
||||
|
||||
while ( ($userfile = readdir($dh)) !== false && $count <= 11 )
|
||||
{
|
||||
if(in_array($userfile, $exclude)){ continue; }
|
||||
|
||||
$usernames[] = str_replace('.yaml', '', $userfile);
|
||||
$count++;
|
||||
}
|
||||
|
||||
closedir($dh);
|
||||
}
|
||||
|
||||
$countusers = count($usernames);
|
||||
|
||||
if($countusers == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
# use a simple dirty search if there are less than 10 users (only in use for new user registrations)
|
||||
if($countusers <= 10)
|
||||
{
|
||||
foreach($usernames as $username)
|
||||
{
|
||||
$userdata = $this->getSecureUser($username);
|
||||
|
||||
if($userdata['email'] == $email)
|
||||
{
|
||||
return $userdata;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
# if there are more than 10 users, search with an index
|
||||
$usermails = $this->getUserMailIndex();
|
||||
|
||||
# search with starting asterix, ending asterix or without asterix
|
||||
if($email[0] == '*')
|
||||
{
|
||||
$userdata = [];
|
||||
$search = substr($email, 1);
|
||||
$length = strlen($search);
|
||||
|
||||
foreach($usermails as $usermail => $username)
|
||||
{
|
||||
if(substr($usermail, -$length) == $search)
|
||||
{
|
||||
$userdata[] = $username;
|
||||
}
|
||||
}
|
||||
|
||||
$userdata = empty($userdata) ? false : $userdata;
|
||||
|
||||
return $userdata;
|
||||
}
|
||||
elseif(substr($email, -1) == '*')
|
||||
{
|
||||
$userdata = [];
|
||||
$search = substr($email, 0, -1);
|
||||
$length = strlen($search);
|
||||
|
||||
foreach($usermails as $usermail => $username)
|
||||
{
|
||||
if(substr($usermail, 0, $length) == $search)
|
||||
{
|
||||
$userdata[] = $username;
|
||||
}
|
||||
}
|
||||
|
||||
$userdata = empty($userdata) ? false : $userdata;
|
||||
|
||||
return $userdata;
|
||||
}
|
||||
elseif(isset($usermails[$email]))
|
||||
{
|
||||
$userdata[] = $usermails[$email];
|
||||
return $userdata;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getUserMailIndex()
|
||||
{
|
||||
if(file_exists($this->userDir . DIRECTORY_SEPARATOR . 'tmuserindex-mail.txt'))
|
||||
{
|
||||
# unserialize and return the file
|
||||
$usermailindex = unserialize(file_get_contents($this->userDir . DIRECTORY_SEPARATOR . 'tmuserindex-mail.txt'));
|
||||
|
||||
if($usermailindex)
|
||||
{
|
||||
return $usermailindex;
|
||||
}
|
||||
}
|
||||
|
||||
$usernames = $this->getUsers();
|
||||
$usermailindex = [];
|
||||
|
||||
foreach($usernames as $username)
|
||||
{
|
||||
$userdata = $this->getSecureUser($username);
|
||||
|
||||
$usermailindex[$userdata['email']] = $username;
|
||||
}
|
||||
|
||||
file_put_contents($this->userDir . DIRECTORY_SEPARATOR . 'tmuserindex-mail.txt', serialize($usermailindex));
|
||||
|
||||
return $usermailindex;
|
||||
}
|
||||
|
||||
# accepts email with or without asterix and returns usernames
|
||||
public function findUsersByRole($role)
|
||||
{
|
||||
|
||||
/*
|
||||
# get all user files
|
||||
$usernames = $this->getUsers();
|
||||
|
||||
$countusers = count($usernames);
|
||||
|
||||
if($countusers == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
# use a simple dirty search if there are less than 10 users (not in use right now)
|
||||
if($countusers <= 4)
|
||||
{
|
||||
$userdata = [];
|
||||
foreach($usernames as $key => $username)
|
||||
{
|
||||
$userdetails = $this->getSecureUser($username);
|
||||
|
||||
if($userdetails['userrole'] == $role)
|
||||
{
|
||||
$userdata[] = $userdetails;
|
||||
}
|
||||
}
|
||||
if(empty($userdata))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return $userdata;
|
||||
}
|
||||
*/
|
||||
$userroles = $this->getUserRoleIndex();
|
||||
|
||||
if(isset($userroles[$role]))
|
||||
{
|
||||
return $userroles[$role];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getUserRoleIndex()
|
||||
{
|
||||
if(file_exists($this->userDir . DIRECTORY_SEPARATOR . 'tmuserindex-role.txt'))
|
||||
{
|
||||
# unserialize and return the file
|
||||
$userroleindex = unserialize(file_get_contents($this->userDir . DIRECTORY_SEPARATOR . 'tmuserindex-role.txt'));
|
||||
if($userroleindex)
|
||||
{
|
||||
return $userroleindex;
|
||||
}
|
||||
}
|
||||
|
||||
$usernames = $this->getUsers();
|
||||
$userroleindex = [];
|
||||
|
||||
foreach($usernames as $username)
|
||||
{
|
||||
$userdata = $this->getSecureUser($username);
|
||||
|
||||
$userroleindex[$userdata['userrole']][] = $username;
|
||||
}
|
||||
|
||||
file_put_contents($this->userDir . DIRECTORY_SEPARATOR . 'tmuserindex-role.txt', serialize($userroleindex));
|
||||
|
||||
return $userroleindex;
|
||||
}
|
||||
|
||||
protected function deleteUserIndex()
|
||||
{
|
||||
$userDir = __DIR__ . '/../../settings/users';
|
||||
|
||||
if(file_exists($userDir . DIRECTORY_SEPARATOR . 'tmuserindex-mail.txt'))
|
||||
{
|
||||
# read and return the file
|
||||
unlink($userDir . DIRECTORY_SEPARATOR . 'tmuserindex-mail.txt');
|
||||
}
|
||||
}
|
||||
}
|
@ -1,609 +0,0 @@
|
||||
<?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('values_allowed', function($field, $value, array $params, array $fields) use ($user)
|
||||
{
|
||||
$badvalues = array_diff($value, $params[0]);
|
||||
if(empty($badvalues)){ return true; }
|
||||
return false;
|
||||
}, 'invalid values');
|
||||
|
||||
Validator::addRule('image_types', function($field, $value, array $params, array $fields) use ($user)
|
||||
{
|
||||
$allowed = ['jpg', 'jpeg', 'png', 'webp', 'svg'];
|
||||
$pathinfo = pathinfo($value);
|
||||
$extension = strtolower($pathinfo['extension']);
|
||||
if(in_array($extension, $allowed)){ return true; }
|
||||
return false;
|
||||
}, 'only jpg, jpeg, png, webp, svg allowed');
|
||||
|
||||
# checks if email is available if user is created
|
||||
Validator::addRule('emailAvailable', function($field, $value, array $params, array $fields) use ($user)
|
||||
{
|
||||
$email = trim($value);
|
||||
if($user->findUsersByEmail($email)){ return false; }
|
||||
return true;
|
||||
}, 'taken');
|
||||
|
||||
# checks if email is available if userdata is updated
|
||||
Validator::addRule('emailChanged', function($field, $value, array $params, array $fields) use ($user)
|
||||
{
|
||||
$userdata = $user->getSecureUser($fields['username']);
|
||||
if($userdata['email'] == $value){ return true; } # user has not updated his email
|
||||
|
||||
$email = trim($value);
|
||||
if($user->findUsersByEmail($email)){ return false; }
|
||||
return true;
|
||||
}, 'taken');
|
||||
|
||||
# checks if username is free when create new user
|
||||
Validator::addRule('userAvailable', function($field, $value, array $params, array $fields) use ($user)
|
||||
{
|
||||
$activeUser = $user->getUser($value);
|
||||
$inactiveUser = $user->getUser("_" . $value);
|
||||
if($activeUser OR $inactiveUser){ return false; }
|
||||
return true;
|
||||
}, 'taken');
|
||||
|
||||
# checks if user exists when userdata is updated
|
||||
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('iplist', function($field, $value, array $params, array $fields) use ($user)
|
||||
{
|
||||
$iplist = explode(",", $value);
|
||||
foreach($iplist as $ip)
|
||||
{
|
||||
if( filter_var( trim($ip), \FILTER_VALIDATE_IP) === false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}, 'contains one or more invalid ip-adress.');
|
||||
|
||||
Validator::addRule('customfields', function($field, $customfields, array $params, array $fields) use ($user)
|
||||
{
|
||||
if(empty($customfields))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
foreach($customfields as $key => $value)
|
||||
{
|
||||
if(!isset($key) OR empty($key) OR (preg_match('/^([a-z0-9])+$/i', $key) == false) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isset($value) OR empty($value) OR ( $value != strip_tags($value) ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}, 'some fields are empty or contain invalid values.');
|
||||
|
||||
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('navigation', function($field, $value, array $params, array $fields)
|
||||
{
|
||||
$format = '/[@#^*()=\[\]{};:"\\|,.<>\/]/';
|
||||
if ( preg_match($format, $value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}, 'contains special characters');
|
||||
|
||||
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````.');
|
||||
}
|
||||
|
||||
# return valitron standard object
|
||||
public function returnValidator(array $params)
|
||||
{
|
||||
return new Validator($params);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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('noHTML', 'firstname')->message(" contains HTML");
|
||||
$v->rule('lengthBetween', 'firstname', 2, 40);
|
||||
$v->rule('noHTML', 'lastname')->message(" contains HTML");
|
||||
$v->rule('lengthBetween', 'lastname', 2, 40);
|
||||
$v->rule('email', 'email')->message("e-mail is invalid");
|
||||
$v->rule('emailAvailable', 'email')->message("Email already taken");
|
||||
$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('noHTML', 'firstname')->message(" contains HTML");
|
||||
$v->rule('lengthBetween', 'firstname', 2, 40);
|
||||
$v->rule('noHTML', 'lastname')->message(" contains HTML");
|
||||
$v->rule('lengthBetween', 'lastname', 2, 40);
|
||||
$v->rule('email', 'email')->message("e-mail is invalid");
|
||||
$v->rule('emailChanged', 'email')->message("Email already taken");
|
||||
$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 password recovery
|
||||
*
|
||||
* @param array $params with form data.
|
||||
* @return obj $v the validation object passed to a result method.
|
||||
*/
|
||||
|
||||
public function recoverPassword(array $params)
|
||||
{
|
||||
$v = new Validator($params);
|
||||
$v->rule('required', ['password', 'passwordrepeat']);
|
||||
$v->rule('lengthBetween', 'password', 5, 20);
|
||||
$v->rule('equals', 'passwordrepeat', 'password');
|
||||
|
||||
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, array $formats, $name = false)
|
||||
{
|
||||
$v = new Validator($params);
|
||||
|
||||
$v->rule('required', ['title', 'author', 'copyright', 'year', 'editor']);
|
||||
$v->rule('lengthBetween', 'title', 2, 50);
|
||||
$v->rule('lengthBetween', 'author', 2, 50);
|
||||
$v->rule('noHTML', 'title');
|
||||
# $v->rule('regex', 'title', '/^[\pL0-9_ \-]*$/u');
|
||||
$v->rule('regex', 'author', '/^[\pL_ \-]*$/u');
|
||||
$v->rule('integer', 'year');
|
||||
$v->rule('length', 'year', 4);
|
||||
$v->rule('length', 'langattr', 2);
|
||||
$v->rule('in', 'editor', ['raw', 'visual']);
|
||||
$v->rule('values_allowed', 'formats', $formats);
|
||||
$v->rule('in', 'copyright', $copyright);
|
||||
$v->rule('noHTML', 'restrictionnotice');
|
||||
$v->rule('lengthBetween', 'restrictionnotice', 2, 1000 );
|
||||
$v->rule('email', 'recoverfrom');
|
||||
$v->rule('noHTML', 'recoversubject');
|
||||
$v->rule('lengthBetween', 'recoversubject', 2, 80 );
|
||||
$v->rule('noHTML', 'recovermessage');
|
||||
$v->rule('lengthBetween', 'recovermessage', 2, 1000 );
|
||||
$v->rule('iplist', 'trustedproxies');
|
||||
|
||||
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('navigation', 'item_name');
|
||||
$v->rule('lengthBetween', 'item_name', 1, 60);
|
||||
$v->rule('in', 'type', ['file', 'folder']);
|
||||
|
||||
if($v->validate())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return $v->errors();
|
||||
}
|
||||
}
|
||||
|
||||
public function navigationBaseItem(array $params)
|
||||
{
|
||||
$v = new Validator($params);
|
||||
|
||||
$v->rule('required', ['item_name', 'type', 'url']);
|
||||
# $v->rule('noSpecialChars', 'item_name');
|
||||
$v->rule('navigation', '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, $skiprequired = NULL)
|
||||
{
|
||||
$v = new Validator(array($fieldName => $fieldValue));
|
||||
|
||||
if(isset($fieldDefinitions['required']) && !$skiprequired)
|
||||
{
|
||||
$v->rule('required', $fieldName);
|
||||
}
|
||||
if(isset($fieldDefinitions['maxlength']))
|
||||
{
|
||||
$v->rule('lengthMax', $fieldName, $fieldDefinitions['maxlength']);
|
||||
}
|
||||
if(isset($fieldDefinitions['max']))
|
||||
{
|
||||
$v->rule('max', $fieldName, $fieldDefinitions['max']);
|
||||
}
|
||||
if(isset($fieldDefinitions['min']))
|
||||
{
|
||||
$v->rule('min', $fieldName, $fieldDefinitions['min']);
|
||||
}
|
||||
if(isset($fieldDefinitions['pattern']))
|
||||
{
|
||||
$v->rule('regex', $fieldName, '/^' . $fieldDefinitions['pattern'] . '$/');
|
||||
}
|
||||
|
||||
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":
|
||||
if(isset($fieldValue) && is_array($fieldValue))
|
||||
{
|
||||
/* 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":
|
||||
if(isset($fieldDefinitions['required']))
|
||||
{
|
||||
$v->rule('accepted', $fieldName);
|
||||
}
|
||||
break;
|
||||
case "url":
|
||||
$v->rule('url', $fieldName);
|
||||
$v->rule('lengthMax', $fieldName, 200);
|
||||
break;
|
||||
case "text":
|
||||
$v->rule('noHTML', $fieldName);
|
||||
$v->rule('lengthMax', $fieldName, 500);
|
||||
# $v->rule('regex', $fieldName, '/^[\pL0-9_ \-\.\?\!\/\:]*$/u');
|
||||
break;
|
||||
case "textarea":
|
||||
# it understands array, json, yaml
|
||||
if(is_array($fieldValue))
|
||||
{
|
||||
$v = $this->checkArray($fieldValue, $v);
|
||||
}
|
||||
else
|
||||
{
|
||||
$v->rule('noHTML', $fieldName);
|
||||
$v->rule('lengthMax', $fieldName, 10000);
|
||||
}
|
||||
break;
|
||||
case "paragraph":
|
||||
$v->rule('noHTML', $fieldName);
|
||||
$v->rule('lengthMax', $fieldName, 10000);
|
||||
break;
|
||||
case "password":
|
||||
$v->rule('lengthMax', $fieldName, 100);
|
||||
break;
|
||||
case "image":
|
||||
$v->rule('noHTML', $fieldName);
|
||||
$v->rule('lengthMax', $fieldName, 1000);
|
||||
$v->rule('image_types', $fieldName);
|
||||
break;
|
||||
case "customfields":
|
||||
$v->rule('array', $fieldName);
|
||||
$v->rule('customfields', $fieldName);
|
||||
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 checkArray($arrayvalues, $v)
|
||||
{
|
||||
foreach($arrayvalues as $key => $value)
|
||||
{
|
||||
if(is_array($value))
|
||||
{
|
||||
$this->checkArray($value, $v);
|
||||
}
|
||||
$v->rule('noHTML', $value);
|
||||
$v->rule('lengthMax', $value, 1000);
|
||||
}
|
||||
return $v;
|
||||
}
|
||||
|
||||
public function validationResult($v, $name = false)
|
||||
{
|
||||
if($v->validate())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if($name == 'meta')
|
||||
{
|
||||
return $v->errors();
|
||||
}
|
||||
elseif($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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,212 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
|
||||
public function checkFile($folder, $file)
|
||||
{
|
||||
if(!file_exists($this->basePath . $folder . DIRECTORY_SEPARATOR . $file))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public 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 renameFile($folder, $oldname, $newname)
|
||||
{
|
||||
|
||||
$oldFilePath = $this->basePath . $folder . DIRECTORY_SEPARATOR . $oldname;
|
||||
$newFilePath = $this->basePath . $folder . DIRECTORY_SEPARATOR . $newname;
|
||||
|
||||
if(!file_exists($oldFilePath))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(@rename($oldFilePath, $newFilePath))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function renamePost($oldPathWithoutType, $newPathWithoutType)
|
||||
{
|
||||
$filetypes = array('md', 'txt', 'yaml');
|
||||
|
||||
$oldPath = $this->basePath . 'content' . $oldPathWithoutType;
|
||||
$newPath = $this->basePath . 'content' . $newPathWithoutType;
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
public function moveElement($item, $folderPath, $index, $date = null)
|
||||
{
|
||||
$filetypes = array('md', 'txt', 'yaml');
|
||||
|
||||
# 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);
|
||||
|
||||
$newPath = $this->basePath . 'content' . $folderPath . DIRECTORY_SEPARATOR . $newOrder . '-' . $item->slug;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Models;
|
||||
|
||||
use Typemill\Models\WriteYaml;
|
||||
|
||||
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' OR $_SERVER['HTTP_CACHE_CONTROL'] == 'no-cache' ))
|
||||
{
|
||||
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);
|
||||
if($this->writeFile($folderName, $cacheFileName, $sCacheData))
|
||||
{
|
||||
if($requestFileName)
|
||||
{
|
||||
$this->writeFile($folderName, $requestFileName, time());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
public function getCachedStructure()
|
||||
{
|
||||
return $this->getCache('cache', 'structure.txt');
|
||||
}
|
||||
|
||||
/**
|
||||
* @todo Create a function to clear all cache files
|
||||
*/
|
||||
public function deleteCacheFiles($dir)
|
||||
{
|
||||
$iterator = new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS);
|
||||
$files = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::CHILD_FIRST);
|
||||
|
||||
$error = false;
|
||||
|
||||
foreach($files as $file)
|
||||
{
|
||||
if ($file->isDir())
|
||||
{
|
||||
if(!rmdir($file->getRealPath()))
|
||||
{
|
||||
$error = 'Could not delete some folders.';
|
||||
}
|
||||
}
|
||||
elseif($file->getExtension() !== 'css')
|
||||
{
|
||||
if(!unlink($file->getRealPath()) )
|
||||
{
|
||||
$error = 'Could not delete some files.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $error;
|
||||
}
|
||||
}
|
@ -1,379 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill\Models;
|
||||
|
||||
use Typemill\Extensions\ParsedownExtension;
|
||||
|
||||
class WriteMeta extends WriteYaml
|
||||
{
|
||||
# used by contentApiController (backend) and pageController (frontend) and TwigMetaExtension (list pages)
|
||||
public function getPageMeta($settings, $item)
|
||||
{
|
||||
$meta = $this->getYaml($settings['contentFolder'], $item->pathWithoutType . '.yaml');
|
||||
|
||||
if(!$meta)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
# compare with meta that are in use right now (e.g. changed theme, disabled plugin)
|
||||
$metascheme = $this->getYaml('cache', 'metatabs.yaml');
|
||||
|
||||
if($metascheme)
|
||||
{
|
||||
$meta = $this->whitelistMeta($meta,$metascheme);
|
||||
}
|
||||
|
||||
$meta = $this->addFileTimeToMeta($meta, $item, $settings);
|
||||
|
||||
return $meta;
|
||||
}
|
||||
|
||||
# cases are rare: updates from old version prior 1.3.4 or if content-files are added manually, e.g. by ftp
|
||||
public function getPageMetaDefaults($content, $settings, $item)
|
||||
{
|
||||
# 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);
|
||||
}
|
||||
|
||||
$title = false;
|
||||
|
||||
# delete markdown from title
|
||||
if(isset($content[0]))
|
||||
{
|
||||
$title = trim($content[0], "# ");
|
||||
}
|
||||
|
||||
$description = $this->generateDescription($content, $parsedown, $item);
|
||||
|
||||
# owner holds the edit-rights
|
||||
$owner = '';
|
||||
if(isset($_SESSION['user']))
|
||||
{
|
||||
$owner = $_SESSION['user'];
|
||||
}
|
||||
|
||||
$author = $settings['author'];
|
||||
|
||||
if(isset($_SESSION))
|
||||
{
|
||||
if(isset($_SESSION['firstname']) && $_SESSION['firstname'] != '' && isset($_SESSION['lastname']) && $_SESSION['lastname'] != '')
|
||||
{
|
||||
$author = $_SESSION['firstname'] . ' ' . $_SESSION['lastname'];
|
||||
}
|
||||
elseif(isset($_SESSION['user']))
|
||||
{
|
||||
$author = $_SESSION['user'];
|
||||
}
|
||||
}
|
||||
|
||||
# create new meta-file
|
||||
$meta = [
|
||||
'meta' => [
|
||||
'title' => $title,
|
||||
'description' => $description,
|
||||
'owner' => $owner,
|
||||
'author' => $author,
|
||||
'created' => date("Y-m-d"),
|
||||
'time' => date("H-i-s"),
|
||||
'navtitle' => $item->name,
|
||||
]
|
||||
];
|
||||
|
||||
$meta = $this->addFileTimeToMeta($meta, $item, $settings);
|
||||
|
||||
$this->updateYaml($settings['contentFolder'], $item->pathWithoutType . '.yaml', $meta);
|
||||
|
||||
return $meta;
|
||||
}
|
||||
|
||||
# used by MetaApiController. Do not set title or description in defaults if page is not published yet
|
||||
public function getPageMetaBlank($content, $settings, $item)
|
||||
{
|
||||
# owner holds the edit-rights
|
||||
$owner = '';
|
||||
if(isset($_SESSION['user']))
|
||||
{
|
||||
$owner = $_SESSION['user'];
|
||||
}
|
||||
|
||||
$author = $settings['author'];
|
||||
|
||||
if(isset($_SESSION))
|
||||
{
|
||||
if(isset($_SESSION['firstname']) && $_SESSION['firstname'] !='' && isset($_SESSION['lastname']) && $_SESSION['lastname'] != '')
|
||||
{
|
||||
$author = $_SESSION['firstname'] . ' ' . $_SESSION['lastname'];
|
||||
}
|
||||
elseif(isset($_SESSION['user']))
|
||||
{
|
||||
$author = $_SESSION['user'];
|
||||
}
|
||||
}
|
||||
|
||||
# create new meta-file
|
||||
$meta = [
|
||||
'meta' => [
|
||||
'title' => '',
|
||||
'description' => '',
|
||||
'owner' => $owner,
|
||||
'author' => $author,
|
||||
'created' => date("Y-m-d"),
|
||||
'time' => date("H-i-s"),
|
||||
'navtitle' => $item->name
|
||||
]
|
||||
];
|
||||
|
||||
$meta = $this->addFileTimeToMeta($meta, $item, $settings);
|
||||
|
||||
$this->updateYaml($settings['contentFolder'], $item->pathWithoutType . '.yaml', $meta);
|
||||
|
||||
return $meta;
|
||||
}
|
||||
|
||||
public function getNavtitle($url)
|
||||
{
|
||||
# get the extended structure where the navigation title is stored
|
||||
$extended = $this->getYaml('cache', 'structure-extended.yaml');
|
||||
|
||||
if(isset($extended[$url]['navtitle']))
|
||||
{
|
||||
return $extended[$url]['navtitle'];
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
# used by articleApiController and pageController to add title and description if an article is published
|
||||
public function completePageMeta($content, $settings, $item)
|
||||
{
|
||||
$meta = $this->getPageMeta($settings, $item);
|
||||
|
||||
if(!$meta)
|
||||
{
|
||||
return $this->getPageMetaDefaults($content, $settings, $item);
|
||||
}
|
||||
|
||||
$title = (isset($meta['meta']['title']) AND $meta['meta']['title'] !== '') ? true : false;
|
||||
$description = (isset($meta['meta']['description']) AND $meta['meta']['description'] !== '') ? true : false;
|
||||
|
||||
if($title && $description)
|
||||
{
|
||||
return $meta;
|
||||
}
|
||||
|
||||
# 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);
|
||||
}
|
||||
|
||||
# delete markdown from title
|
||||
if(!$title && isset($content[0]))
|
||||
{
|
||||
$meta['meta']['title'] = trim($content[0], "# ");
|
||||
}
|
||||
|
||||
if(!$description && isset($content[1]))
|
||||
{
|
||||
$meta['meta']['description'] = $this->generateDescription($content, $parsedown, $item);
|
||||
}
|
||||
|
||||
$this->updateYaml($settings['contentFolder'], $item->pathWithoutType . '.yaml', $meta);
|
||||
|
||||
return $meta;
|
||||
}
|
||||
|
||||
private function whitelistMeta($meta, $metascheme)
|
||||
{
|
||||
# we have only 2 dimensions, so no recursive needed
|
||||
foreach($meta as $tab => $values)
|
||||
{
|
||||
if(!isset($metascheme[$tab]))
|
||||
{
|
||||
unset($meta[$tab]);
|
||||
}
|
||||
foreach($values as $key => $value)
|
||||
{
|
||||
if(!isset($metascheme[$tab][$key]))
|
||||
{
|
||||
unset($meta[$tab][$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return $meta;
|
||||
}
|
||||
|
||||
private function addFileTimeToMeta($meta, $item, $settings)
|
||||
{
|
||||
$filePath = $settings['contentFolder'] . $item->path;
|
||||
$fileType = isset($item->fileType) ? $item->fileType : 'md';
|
||||
|
||||
# check if url is a folder.
|
||||
if($item->elementType == 'folder')
|
||||
{
|
||||
$filePath = $settings['contentFolder'] . $item->path . DIRECTORY_SEPARATOR . 'index.'. $fileType;
|
||||
}
|
||||
|
||||
# add the modified date for the file
|
||||
$meta['meta']['modified'] = file_exists($filePath) ? date("Y-m-d",filemtime($filePath)) : date("Y-m-d");
|
||||
|
||||
return $meta;
|
||||
}
|
||||
|
||||
public function generateDescription($content, $parsedown, $item)
|
||||
{
|
||||
$description = isset($content[1]) ? $content[1] : '';
|
||||
|
||||
# create description or abstract from content
|
||||
if($description !== '')
|
||||
{
|
||||
$firstLineArray = $parsedown->text($description);
|
||||
$description = strip_tags($parsedown->markup($firstLineArray, $item->urlAbs));
|
||||
|
||||
# if description is very short
|
||||
if(strlen($description) < 100 && isset($content[2]))
|
||||
{
|
||||
$secondLineArray = $parsedown->text($content[2]);
|
||||
$description .= ' ' . strip_tags($parsedown->markup($secondLineArray, $item->urlAbs));
|
||||
}
|
||||
|
||||
# if description is too long
|
||||
if(strlen($description) > 300)
|
||||
{
|
||||
$description = substr($description, 0, 300);
|
||||
$lastSpace = strrpos($description, ' ');
|
||||
$description = substr($description, 0, $lastSpace);
|
||||
}
|
||||
}
|
||||
return $description;
|
||||
}
|
||||
|
||||
public function transformPagesToPosts($folder)
|
||||
{
|
||||
$filetypes = array('md', 'txt', 'yaml');
|
||||
$result = true;
|
||||
|
||||
foreach($folder->folderContent as $page)
|
||||
{
|
||||
# create old filename without filetype
|
||||
$oldFile = $this->basePath . 'content' . $page->pathWithoutType;
|
||||
|
||||
# set default date
|
||||
$date = date('Y-m-d', time());
|
||||
$time = date('H-i', time());
|
||||
|
||||
$meta = $this->getYaml('content', $page->pathWithoutType . '.yaml');
|
||||
|
||||
if($meta)
|
||||
{
|
||||
# get dates from meta
|
||||
if(isset($meta['meta']['manualdate'])){ $date = $meta['meta']['manualdate']; }
|
||||
elseif(isset($meta['meta']['created'])){ $date = $meta['meta']['created']; }
|
||||
elseif(isset($meta['meta']['modified'])){ $date = $meta['meta']['modified']; }
|
||||
|
||||
# set time
|
||||
if(isset($meta['meta']['time']))
|
||||
{
|
||||
$time = $meta['meta']['time'];
|
||||
}
|
||||
}
|
||||
|
||||
$datetime = $date . '-' . $time;
|
||||
$datetime = implode(explode('-', $datetime));
|
||||
$datetime = substr($datetime,0,12);
|
||||
|
||||
# create new file-name without filetype
|
||||
$newFile = $this->basePath . 'content' . $folder->path . DIRECTORY_SEPARATOR . $datetime . '-' . $page->slug;
|
||||
|
||||
foreach($filetypes as $filetype)
|
||||
{
|
||||
$oldFilePath = $oldFile . '.' . $filetype;
|
||||
$newFilePath = $newFile . '.' . $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;
|
||||
}
|
||||
|
||||
public function transformPostsToPages($folder)
|
||||
{
|
||||
$filetypes = array('md', 'txt', 'yaml');
|
||||
$index = 0;
|
||||
$result = true;
|
||||
|
||||
foreach($folder->folderContent as $page)
|
||||
{
|
||||
# create old filename without filetype
|
||||
$oldFile = $this->basePath . 'content' . $page->pathWithoutType;
|
||||
|
||||
$order = $index;
|
||||
|
||||
if($index < 10)
|
||||
{
|
||||
$order = '0' . $index;
|
||||
}
|
||||
|
||||
# create new file-name without filetype
|
||||
$newFile = $this->basePath . 'content' . $folder->path . DIRECTORY_SEPARATOR . $order . '-' . $page->slug;
|
||||
|
||||
foreach($filetypes as $filetype)
|
||||
{
|
||||
$oldFilePath = $oldFile . '.' . $filetype;
|
||||
$newFilePath = $newFile . '.' . $filetype;
|
||||
|
||||
#check if file with filetype exists and rename
|
||||
if($oldFilePath != $newFilePath && file_exists($oldFilePath))
|
||||
{
|
||||
if(@rename($oldFilePath, $newFilePath))
|
||||
{
|
||||
$result = $result;
|
||||
}
|
||||
else
|
||||
{
|
||||
$result = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$index++;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function folderContainsFolders($folder)
|
||||
{
|
||||
foreach($folder->folderContent as $page)
|
||||
{
|
||||
if($page->elementType == 'folder')
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
@ -1,332 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill;
|
||||
|
||||
use \Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Typemill\Models\Fields;
|
||||
use Typemill\Models\WriteYaml;
|
||||
use Typemill\Models\Validation;
|
||||
use Typemill\Extensions\ParsedownExtension;
|
||||
|
||||
abstract class Plugin implements EventSubscriberInterface
|
||||
{
|
||||
protected $container;
|
||||
|
||||
protected $path;
|
||||
|
||||
protected $adminpath = false;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
*/
|
||||
|
||||
public function __construct($container)
|
||||
{
|
||||
$this->container = $container;
|
||||
|
||||
$this->path = trim($this->container['request']->getUri()->getPath(),"/");
|
||||
|
||||
if(substr($this->path, 0, 3) === "tm/")
|
||||
{
|
||||
$this->adminpath = true;
|
||||
}
|
||||
}
|
||||
|
||||
protected function isXhr()
|
||||
{
|
||||
if($this->container['request']->isXhr())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function getParams()
|
||||
{
|
||||
return $this->container['request']->getParams();
|
||||
}
|
||||
|
||||
protected function returnJson($data)
|
||||
{
|
||||
return $this->container['response']
|
||||
->withHeader("Content-Type", "application/json")
|
||||
->withStatus(200)
|
||||
->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
protected function returnJsonError($data)
|
||||
{
|
||||
return $this->container['response']
|
||||
->withHeader("Content-Type", "application/json")
|
||||
->withStatus(400)
|
||||
->write(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
protected function getSettings()
|
||||
{
|
||||
return $this->container->get('settings');
|
||||
}
|
||||
|
||||
protected function getPluginSettings($plugin)
|
||||
{
|
||||
return $this->container->get('settings')['plugins'][$plugin];
|
||||
}
|
||||
|
||||
protected function getRoute()
|
||||
{
|
||||
return $this->container['request']->getUri()->withUserInfo('');
|
||||
}
|
||||
|
||||
protected function getPath()
|
||||
{
|
||||
return $this->container['request']->getUri()->getPath();
|
||||
}
|
||||
|
||||
protected function getDispatcher()
|
||||
{
|
||||
return $this->container['dispatcher'];
|
||||
}
|
||||
|
||||
protected function getTwig()
|
||||
{
|
||||
return $this->container['view'];
|
||||
}
|
||||
|
||||
protected function addTwigGlobal($name, $class)
|
||||
{
|
||||
$this->container->view->getEnvironment()->addGlobal($name, $class);
|
||||
}
|
||||
|
||||
protected function addTwigFilter($name, $filter)
|
||||
{
|
||||
$filter = new \Twig_SimpleFilter($name, $filter);
|
||||
$this->container->view->getEnvironment()->addFilter($filter);
|
||||
}
|
||||
|
||||
protected function addTwigFunction($name, $function)
|
||||
{
|
||||
$function = new \Twig_SimpleFunction($name, $function);
|
||||
$this->container->view->getEnvironment()->addFunction($function);
|
||||
}
|
||||
|
||||
protected function addJS($JS)
|
||||
{
|
||||
$this->container->assets->addJS($JS);
|
||||
}
|
||||
|
||||
protected function addEditorJS($JS)
|
||||
{
|
||||
$this->container->assets->addEditorJS($JS);
|
||||
}
|
||||
|
||||
protected function addInlineJS($JS)
|
||||
{
|
||||
$this->container->assets->addInlineJS($JS);
|
||||
}
|
||||
|
||||
protected function addSvgSymbol($symbol)
|
||||
{
|
||||
$this->container->assets->addSvgSymbol($symbol);
|
||||
}
|
||||
|
||||
protected function addEditorInlineJS($JS)
|
||||
{
|
||||
$this->container->assets->addEditorInlineJS($JS);
|
||||
}
|
||||
|
||||
protected function addCSS($CSS)
|
||||
{
|
||||
$this->container->assets->addCSS($CSS);
|
||||
}
|
||||
|
||||
protected function addInlineCSS($CSS)
|
||||
{
|
||||
$this->container->assets->addInlineCSS($CSS);
|
||||
}
|
||||
|
||||
protected function addEditorCSS($CSS)
|
||||
{
|
||||
$this->container->assets->addEditorCSS($CSS);
|
||||
}
|
||||
|
||||
protected function getMeta()
|
||||
{
|
||||
return $this->container->assets->meta;
|
||||
}
|
||||
|
||||
public function addMeta($key,$meta)
|
||||
{
|
||||
$this->container->assets->addMeta($key, $meta);
|
||||
}
|
||||
|
||||
protected function activateAxios()
|
||||
{
|
||||
$this->container->assets->activateAxios();
|
||||
}
|
||||
|
||||
protected function activateVue()
|
||||
{
|
||||
$this->container->assets->activateVue();
|
||||
}
|
||||
|
||||
protected function activateTachyons()
|
||||
{
|
||||
$this->container->assets->activateTachyons();
|
||||
}
|
||||
|
||||
protected function markdownToHtml($markdown)
|
||||
{
|
||||
$parsedown = new ParsedownExtension();
|
||||
|
||||
$contentArray = $parsedown->text($markdown);
|
||||
$html = $parsedown->markup($contentArray);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
protected function getFormData($pluginName)
|
||||
{
|
||||
$flash = $this->container->flash->getMessages();
|
||||
|
||||
if(isset($flash['formdata']))
|
||||
{
|
||||
$yaml = new Models\WriteYaml();
|
||||
$formdata = $yaml->getYaml('settings', 'formdata.yaml');
|
||||
$yaml->updateYaml('settings', 'formdata.yaml', '');
|
||||
|
||||
if($flash['formdata'][0] == $pluginName && isset($formdata[$pluginName]))
|
||||
{
|
||||
return $formdata[$pluginName];
|
||||
}
|
||||
}
|
||||
elseif(isset($flash['publicform']) && $flash['publicform'][0] == 'bot')
|
||||
{
|
||||
return 'bot';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function generateForm($pluginName, $routename)
|
||||
{
|
||||
$fieldsModel = new Fields();
|
||||
|
||||
$settings = $this->getSettings();
|
||||
$form = false;
|
||||
|
||||
$pluginDefinitions = \Typemill\Settings::getObjectSettings('plugins', $pluginName);
|
||||
if(isset($settings['plugins'][$pluginName]['publicformdefinitions']) && $settings['plugins'][$pluginName]['publicformdefinitions'] != '')
|
||||
{
|
||||
$arrayFromYaml = \Symfony\Component\Yaml\Yaml::parse($settings['plugins'][$pluginName]['publicformdefinitions']);
|
||||
$pluginDefinitions['public']['fields'] = $arrayFromYaml;
|
||||
}
|
||||
|
||||
$buttonlabel = isset($settings['plugins'][$pluginName]['button_label']) ? $settings['plugins'][$pluginName]['button_label'] : false;
|
||||
$captchaoptions = isset($settings['plugins'][$pluginName]['captchaoptions']) ? $settings['plugins'][$pluginName]['captchaoptions'] : false;
|
||||
$recaptcha = isset($settings['plugins'][$pluginName]['recaptcha']) ? $settings['plugins'][$pluginName]['recaptcha_webkey'] : false;
|
||||
|
||||
if($captchaoptions == 'disabled')
|
||||
{
|
||||
# in case a captcha has failed on another page like login, the captcha-session must be deleted, otherwise it will not pass the security middleware
|
||||
unset($_SESSION['captcha']);
|
||||
}
|
||||
|
||||
$fieldsModel = new Fields();
|
||||
|
||||
if(isset($pluginDefinitions['public']['fields']))
|
||||
{
|
||||
# get all the fields and prefill them with the dafault-data, the user-data or old input data
|
||||
$fields = $fieldsModel->getFields($settings, 'plugins', $pluginName, $pluginDefinitions, 'public');
|
||||
|
||||
# get Twig Instance
|
||||
$twig = $this->getTwig();
|
||||
|
||||
# render each field and add it to the form
|
||||
$form = $twig->fetch('/partials/form.twig', [
|
||||
'routename' => $routename,
|
||||
'fields' => $fields,
|
||||
'itemName' => $pluginName,
|
||||
'object' => 'plugins',
|
||||
'buttonlabel' => $buttonlabel,
|
||||
'captchaoptions' => $captchaoptions,
|
||||
'recaptcha_webkey' => $recaptcha,
|
||||
]);
|
||||
}
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
protected function validateParams($params)
|
||||
{
|
||||
$pluginName = key($params);
|
||||
|
||||
if(isset($params[$pluginName]))
|
||||
{
|
||||
$userInput = $params[$pluginName];
|
||||
$settings = $this->getSettings();
|
||||
|
||||
# get settings and start validation
|
||||
$originalSettings = \Typemill\Settings::getObjectSettings('plugins', $pluginName);
|
||||
if(isset($settings['plugins'][$pluginName]['publicformdefinitions']) && $settings['plugins'][$pluginName]['publicformdefinitions'] != '')
|
||||
{
|
||||
$arrayFromYaml = \Symfony\Component\Yaml\Yaml::parse($settings['plugins'][$pluginName]['publicformdefinitions']);
|
||||
$originalSettings['public']['fields'] = $arrayFromYaml;
|
||||
}
|
||||
elseif(isset($originalSettings['settings']['publicformdefinitions']))
|
||||
{
|
||||
$arrayFromYaml = \Symfony\Component\Yaml\Yaml::parse($originalSettings['settings']['publicformdefinitions']);
|
||||
$originalSettings['public']['fields'] = $arrayFromYaml;
|
||||
}
|
||||
|
||||
$validate = new Validation();
|
||||
|
||||
if(isset($originalSettings['public']['fields']))
|
||||
{
|
||||
# flaten the multi-dimensional array with fieldsets to a one-dimensional array
|
||||
$originalFields = array();
|
||||
foreach($originalSettings['public']['fields'] as $fieldName => $fieldValue)
|
||||
{
|
||||
if(isset($fieldValue['fields']))
|
||||
{
|
||||
foreach($fieldValue['fields'] as $subFieldName => $subFieldValue)
|
||||
{
|
||||
$originalFields[$subFieldName] = $subFieldValue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$originalFields[$fieldName] = $fieldValue;
|
||||
}
|
||||
}
|
||||
|
||||
# take the user input data and iterate over all fields and values
|
||||
foreach($userInput as $fieldName => $fieldValue)
|
||||
{
|
||||
# get the corresponding field definition from original plugin settings
|
||||
$fieldDefinition = isset($originalFields[$fieldName]) ? $originalFields[$fieldName] : false;
|
||||
|
||||
if($fieldDefinition)
|
||||
{
|
||||
# validate user input for this field
|
||||
$validate->objectField($fieldName, $fieldValue, $pluginName, $fieldDefinition);
|
||||
}
|
||||
if(!$fieldDefinition && $fieldName != 'active')
|
||||
{
|
||||
$_SESSION['errors'][$pluginName][$fieldName] = array('This field is not defined!');
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($_SESSION['errors']))
|
||||
{
|
||||
$this->container->flash->addMessage('error', 'Please correct the errors');
|
||||
return false;
|
||||
}
|
||||
|
||||
return $params[$pluginName];
|
||||
}
|
||||
}
|
||||
|
||||
$this->container->flash->addMessage('error', 'The data from the form was invalid (missing or not defined)');
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,111 +0,0 @@
|
||||
<?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']))
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Typemill\Controllers\ControllerAuthorArticleApi;
|
||||
use Typemill\Controllers\ControllerAuthorBlockApi;
|
||||
use Typemill\Controllers\ControllerAuthorMetaApi;
|
||||
use Typemill\Controllers\ControllerAuthorMediaApi;
|
||||
use Typemill\Controllers\ControllerSettings;
|
||||
use Typemill\Middleware\RestrictApiAccess;
|
||||
|
||||
$app->get('/api/v1/themes', ControllerSettings::class . ':getThemeSettings')->setName('api.themes')->add(new RestrictApiAccess($container['router']));
|
||||
$app->delete('/api/v1/clearcache', ControllerSettings::class . ':clearCache')->setName('api.clearcache')->add(new RestrictApiAccess($container['router']));
|
||||
$app->get('/api/v1/users/getbynames', ControllerSettings::class . ':getUsersByNames')->setName('api.usersbynames')->add(new RestrictApiAccess($container['router']));
|
||||
$app->get('/api/v1/users/getbyemail', ControllerSettings::class . ':getUsersByEmail')->setName('api.usersbyemail')->add(new RestrictApiAccess($container['router']));
|
||||
$app->get('/api/v1/users/getbyrole', ControllerSettings::class . ':getUsersByRole')->setName('api.usersbyrole')->add(new RestrictApiAccess($container['router']));
|
||||
|
||||
$app->post('/api/v1/article/markdown', ControllerAuthorArticleApi::class . ':getArticleMarkdown')->setName('api.article.markdown')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/article/html', ControllerAuthorArticleApi::class . ':getArticleHtml')->setName('api.article.html')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/article/publish', ControllerAuthorArticleApi::class . ':publishArticle')->setName('api.article.publish')->add(new RestrictApiAccess($container['router']));
|
||||
$app->delete('/api/v1/article/unpublish', ControllerAuthorArticleApi::class . ':unpublishArticle')->setName('api.article.unpublish')->add(new RestrictApiAccess($container['router']));
|
||||
$app->delete('/api/v1/article/discard', ControllerAuthorArticleApi::class . ':discardArticleChanges')->setName('api.article.discard')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/article/rename', ControllerAuthorArticleApi::class . ':renameArticle')->setName('api.article.rename')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/article/sort', ControllerAuthorArticleApi::class . ':sortArticle')->setName('api.article.sort')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/article', ControllerAuthorArticleApi::class . ':createArticle')->setName('api.article.create')->add(new RestrictApiAccess($container['router']));
|
||||
$app->put('/api/v1/article', ControllerAuthorArticleApi::class . ':updateArticle')->setName('api.article.update')->add(new RestrictApiAccess($container['router']));
|
||||
$app->delete('/api/v1/article', ControllerAuthorArticleApi::class . ':deleteArticle')->setName('api.article.delete')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/baseitem', ControllerAuthorArticleApi::class . ':createBaseItem')->setName('api.baseitem.create')->add(new RestrictApiAccess($container['router']));
|
||||
$app->get('/api/v1/navigation', ControllerAuthorArticleApi::class . ':getNavigation')->setName('api.navigation.get')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/post', ControllerAuthorArticleApi::class . ':createPost')->setName('api.post.create')->add(new RestrictApiAccess($container['router']));
|
||||
|
||||
$app->get('/api/v1/metadefinitions', ControllerAuthorMetaApi::class . ':getMetaDefinitions')->setName('api.metadefinitions.get')->add(new RestrictApiAccess($container['router']));
|
||||
$app->get('/api/v1/article/metaobject', ControllerAuthorMetaApi::class . ':getArticleMetaobject')->setName('api.articlemetaobject.get')->add(new RestrictApiAccess($container['router']));
|
||||
$app->get('/api/v1/article/metadata', ControllerAuthorMetaApi::class . ':getArticleMeta')->setName('api.articlemeta.get')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/article/metadata', ControllerAuthorMetaApi::class . ':updateArticleMeta')->setName('api.articlemeta.update')->add(new RestrictApiAccess($container['router']));
|
||||
|
||||
$app->post('/api/v1/block', ControllerAuthorBlockApi::class . ':addBlock')->setName('api.block.add')->add(new RestrictApiAccess($container['router']));
|
||||
$app->put('/api/v1/block', ControllerAuthorBlockApi::class . ':updateBlock')->setName('api.block.update')->add(new RestrictApiAccess($container['router']));
|
||||
$app->delete('/api/v1/block', ControllerAuthorBlockApi::class . ':deleteBlock')->setName('api.block.delete')->add(new RestrictApiAccess($container['router']));
|
||||
$app->put('/api/v1/moveblock', ControllerAuthorBlockApi::class . ':moveBlock')->setName('api.block.move')->add(new RestrictApiAccess($container['router']));
|
||||
$app->get('/api/v1/shortcodedata', ControllerAuthorBlockApi::class . ':getShortcodeData')->setName('api.shortcodedata.get')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/video', ControllerAuthorMediaApi::class . ':saveVideoImage')->setName('api.video.save')->add(new RestrictApiAccess($container['router']));
|
||||
|
||||
$app->get('/api/v1/medialib/images', ControllerAuthorMediaApi::class . ':getMediaLibImages')->setName('api.medialibimg.get')->add(new RestrictApiAccess($container['router']));
|
||||
$app->get('/api/v1/medialib/files', ControllerAuthorMediaApi::class . ':getMediaLibFiles')->setName('api.medialibfiles.get')->add(new RestrictApiAccess($container['router']));
|
||||
$app->get('/api/v1/image', ControllerAuthorMediaApi::class . ':getImage')->setName('api.image.get')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/image', ControllerAuthorMediaApi::class . ':createImage')->setName('api.image.create')->add(new RestrictApiAccess($container['router']));
|
||||
$app->put('/api/v1/image', ControllerAuthorMediaApi::class . ':publishImage')->setName('api.image.publish')->add(new RestrictApiAccess($container['router']));
|
||||
$app->delete('/api/v1/image', ControllerAuthorMediaApi::class . ':deleteImage')->setName('api.image.delete')->add(new RestrictApiAccess($container['router']));
|
||||
$app->get('/api/v1/filerestrictions', ControllerAuthorMediaApi::class . ':getFileRestrictions')->setName('api.file.getrestrictions')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/filerestrictions', ControllerAuthorMediaApi::class . ':updateFileRestrictions')->setName('api.file.updaterestrictions')->add(new RestrictApiAccess($container['router']));
|
||||
$app->get('/api/v1/file', ControllerAuthorMediaApi::class . ':getFile')->setName('api.file.get')->add(new RestrictApiAccess($container['router']));
|
||||
$app->post('/api/v1/file', ControllerAuthorMediaApi::class . ':uploadFile')->setName('api.file.upload')->add(new RestrictApiAccess($container['router']));
|
||||
$app->put('/api/v1/file', ControllerAuthorMediaApi::class . ':publishFile')->setName('api.file.publish')->add(new RestrictApiAccess($container['router']));
|
||||
$app->delete('/api/v1/file', ControllerAuthorMediaApi::class . ':deleteFile')->setName('api.file.delete')->add(new RestrictApiAccess($container['router']));
|
@ -1,99 +0,0 @@
|
||||
<?php
|
||||
use Typemill\Controllers\ControllerAuthorEditor;
|
||||
use Typemill\Controllers\ControllerSettings;
|
||||
use Typemill\Controllers\ControllerDownload;
|
||||
use Typemill\Controllers\ControllerFrontendWebsite;
|
||||
use Typemill\Controllers\ControllerFrontendForms;
|
||||
use Typemill\Controllers\ControllerFrontendAuth;
|
||||
use Typemill\Controllers\ControllerFrontendSetup;
|
||||
use Typemill\Middleware\RedirectIfUnauthenticated;
|
||||
use Typemill\Middleware\RedirectIfAuthenticated;
|
||||
use Typemill\Middleware\RedirectIfNoAdmin;
|
||||
use Typemill\Middleware\accessMiddleware;
|
||||
|
||||
if($settings['settings']['setup'])
|
||||
{
|
||||
$app->get('/setup', ControllerFrontendSetup::class . ':show')->setName('setup.show');
|
||||
$app->post('/setup', ControllerFrontendSetup::class . ':create')->setName('setup.create');
|
||||
}
|
||||
else
|
||||
{
|
||||
$app->get('/setup', ControllerFrontendAuth::class . ':redirect');
|
||||
}
|
||||
if($settings['settings']['welcome'])
|
||||
{
|
||||
$app->get('/setup/welcome', ControllerFrontendSetup::class . ':welcome')->setName('setup.welcome')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
|
||||
}
|
||||
else
|
||||
{
|
||||
$app->get('/setup/welcome', ControllerFrontendAuth::class . ':redirect')->setName('setup.welcome');
|
||||
}
|
||||
|
||||
$app->post('/tm/formpost', ControllerFrontendForms::class . ':savePublicForm')->setName('form.save');
|
||||
|
||||
$app->get('/tm', ControllerFrontendAuth::class . ':redirect');
|
||||
$app->get('/tm/login', ControllerFrontendAuth::class . ':show')->setName('auth.show')->add(new RedirectIfAuthenticated($container['router'], $container['settings']));
|
||||
$app->post('/tm/login', ControllerFrontendAuth::class . ':login')->setName('auth.login')->add(new RedirectIfAuthenticated($container['router'], $container['settings']));
|
||||
$app->get('/tm/logout', ControllerFrontendAuth::class . ':logout')->setName('auth.logout')->add(new RedirectIfUnauthenticated($container['router'], $container['flash']));
|
||||
|
||||
if(isset($settings['settings']['recoverpw']) && $settings['settings']['recoverpw'])
|
||||
{
|
||||
$app->get('/tm/recoverpw', ControllerFrontendAuth::class . ':showrecoverpassword')->setName('auth.recoverpwshow')->add(new RedirectIfAuthenticated($container['router'], $container['settings']));
|
||||
$app->post('/tm/recoverpw', ControllerFrontendAuth::class . ':recoverpassword')->setName('auth.recoverpw')->add(new RedirectIfAuthenticated($container['router'], $container['settings']));
|
||||
$app->get('/tm/recoverpwnew', ControllerFrontendAuth::class . ':showrecoverpasswordnew')->setName('auth.recoverpwshownew')->add(new RedirectIfAuthenticated($container['router'], $container['settings']));
|
||||
$app->post('/tm/recoverpwnew', ControllerFrontendAuth::class . ':createrecoverpasswordnew')->setName('auth.recoverpwnew')->add(new RedirectIfAuthenticated($container['router'], $container['settings']));
|
||||
}
|
||||
|
||||
$app->get('/tm/settings', ControllerSettings::class . ':showSettings')->setName('settings.show')->add(new accessMiddleware($container['router'], $container['acl'], 'system', 'view'));
|
||||
$app->post('/tm/settings', ControllerSettings::class . ':saveSettings')->setName('settings.save')->add(new accessMiddleware($container['router'], $container['acl'], 'system', 'update'));
|
||||
|
||||
$app->get('/tm/themes', ControllerSettings::class . ':showThemes')->setName('themes.show')->add(new accessMiddleware($container['router'], $container['acl'], 'system', 'view'));
|
||||
$app->post('/tm/themes', ControllerSettings::class . ':saveThemes')->setName('themes.save')->add(new accessMiddleware($container['router'], $container['acl'], 'system', 'update'));
|
||||
|
||||
$app->get('/tm/plugins', ControllerSettings::class . ':showPlugins')->setName('plugins.show')->add(new accessMiddleware($container['router'], $container['acl'], 'system', 'view'));
|
||||
$app->post('/tm/plugins', ControllerSettings::class . ':savePlugins')->setName('plugins.save')->add(new accessMiddleware($container['router'], $container['acl'], 'system', 'update'));
|
||||
|
||||
$app->get('/tm/account', ControllerSettings::class . ':showAccount')->setName('user.account')->add(new accessMiddleware($container['router'], $container['acl'], 'user', 'view'));
|
||||
$app->get('/tm/user/new', ControllerSettings::class . ':newUser')->setName('user.new')->add(new accessMiddleware($container['router'], $container['acl'], 'user', 'create'));
|
||||
$app->post('/tm/user/create', ControllerSettings::class . ':createUser')->setName('user.create')->add(new accessMiddleware($container['router'], $container['acl'], 'user', 'create'));
|
||||
$app->post('/tm/user/update', ControllerSettings::class . ':updateUser')->setName('user.update')->add(new accessMiddleware($container['router'], $container['acl'], 'user', 'update'));
|
||||
$app->post('/tm/user/delete', ControllerSettings::class . ':deleteUser')->setName('user.delete')->add(new accessMiddleware($container['router'], $container['acl'], 'user', 'delete'));
|
||||
$app->get('/tm/user/{username}', ControllerSettings::class . ':showUser')->setName('user.show')->add(new accessMiddleware($container['router'], $container['acl'], 'user', 'view'));
|
||||
$app->get('/tm/users', ControllerSettings::class . ':listUser')->setName('user.list')->add(new accessMiddleware($container['router'], $container['acl'], 'userlist', 'view'));
|
||||
|
||||
$app->get('/tm/content/raw[/{params:.*}]', ControllerAuthorEditor::class . ':showContent')->setName('content.raw')->add(new accessMiddleware($container['router'], $container['acl'], 'content', 'view'));
|
||||
$app->get('/tm/content/visual[/{params:.*}]', ControllerAuthorEditor::class . ':showBlox')->setName('content.visual')->add(new accessMiddleware($container['router'], $container['acl'], 'content', 'view'));
|
||||
$app->get('/tm/content[/{params:.*}]', ControllerAuthorEditor::class . ':showEmpty')->setName('content.empty')->add(new accessMiddleware($container['router'], $container['acl'], 'content', 'view'));
|
||||
|
||||
$app->get('/media/files[/{params:.*}]', ControllerDownload::class . ':download')->setName('download.file');
|
||||
|
||||
foreach($routes as $pluginRoute)
|
||||
{
|
||||
$method = $pluginRoute['httpMethod'];
|
||||
$route = $pluginRoute['route'];
|
||||
$class = $pluginRoute['class'];
|
||||
$resource = isset($pluginRoute['resource']) ? $pluginRoute['resource'] : NULL;
|
||||
$privilege = isset($pluginRoute['privilege']) ? $pluginRoute['privilege'] : NULL;
|
||||
|
||||
if(isset($pluginRoute['name']))
|
||||
{
|
||||
$app->{$method}($route, $class)->setName($pluginRoute['name'])->add(new accessMiddleware($container['router'], $container['acl'], $resource, $privilege));
|
||||
}
|
||||
else
|
||||
{
|
||||
$app->{$method}($route, $class)->add(new accessMiddleware($container['router'], $container['acl'], $resource, $privilege));
|
||||
}
|
||||
}
|
||||
|
||||
if($settings['settings']['setup'])
|
||||
{
|
||||
$app->get('/[{params:.*}]', ControllerFrontendSetup::class . ':redirect');
|
||||
}
|
||||
elseif(isset($settings['settings']['access']) && $settings['settings']['access'] != '')
|
||||
{
|
||||
$app->get('/[{params:.*}]', ControllerFrontendWebsite::class . ':index')->setName('home')->add(new accessMiddleware($container['router'], $container['acl'], 'user', 'view'));
|
||||
}
|
||||
else
|
||||
{
|
||||
$app->get('/[{params:.*}]', ControllerFrontendWebsite::class . ':index')->setName('home');
|
||||
}
|
@ -1,268 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill;
|
||||
|
||||
use Laminas\Permissions\Acl\Acl;
|
||||
use Laminas\Permissions\Acl\Role\GenericRole as Role;
|
||||
use Laminas\Permissions\Acl\Resource\GenericResource as Resource;
|
||||
|
||||
class Settings
|
||||
{
|
||||
public static function loadSettings()
|
||||
{
|
||||
$defaultSettings = self::getDefaultSettings();
|
||||
$userSettings = self::getUserSettings();
|
||||
|
||||
$settings = $defaultSettings;
|
||||
|
||||
if($userSettings)
|
||||
{
|
||||
$settings = array_merge($defaultSettings, $userSettings);
|
||||
}
|
||||
|
||||
# no individual image sizes are allowed since 1.3.4
|
||||
# $settings['images'] = $defaultSettings['images'];
|
||||
# make sure that thumb sizes are still there.
|
||||
$images = array_merge($defaultSettings['images'], $settings['images']);
|
||||
$settings['images'] = $images;
|
||||
|
||||
# we have to check if the theme has been deleted
|
||||
$themefolder = $settings['rootPath'] . $settings['themeFolder'] . DIRECTORY_SEPARATOR;
|
||||
|
||||
# if there is no theme in settings or theme has been deleted
|
||||
if(!isset($settings['theme']) OR !file_exists($themefolder . $settings['theme']))
|
||||
{
|
||||
# scan theme folder and get the first theme
|
||||
$themes = array_filter(scandir($themefolder), function ($item) use($themefolder) {
|
||||
return is_dir($themefolder . $item) && strpos($item, '.') !== 0;
|
||||
});
|
||||
$firsttheme = reset($themes);
|
||||
|
||||
# if there is a theme with an index.twig-file
|
||||
if($firsttheme && file_exists($themefolder . $firsttheme . DIRECTORY_SEPARATOR . 'index.twig'))
|
||||
{
|
||||
$settings['theme'] = $firsttheme;
|
||||
}
|
||||
else
|
||||
{
|
||||
die('You need at least one theme with an index.twig-file in your theme-folder.');
|
||||
}
|
||||
}
|
||||
|
||||
# We know the used theme now so create the theme path
|
||||
$settings['themePath'] = $settings['rootPath'] . $settings['themeFolder'] . DIRECTORY_SEPARATOR . $settings['theme'];
|
||||
|
||||
# if there are no theme settings yet (e.g. no setup yet) use default theme settings
|
||||
if(!isset($settings['themes']))
|
||||
{
|
||||
$themeSettings = self::getObjectSettings('themes', $settings['theme']);
|
||||
$settings['themes'][$settings['theme']] = isset($themeSettings['settings']) ? $themeSettings['settings'] : false;
|
||||
}
|
||||
|
||||
return array('settings' => $settings);
|
||||
}
|
||||
|
||||
public static function getDefaultSettings()
|
||||
{
|
||||
$rootPath = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR;
|
||||
|
||||
return [
|
||||
'determineRouteBeforeAppMiddleware' => true,
|
||||
'title' => 'TYPEMILL',
|
||||
'author' => 'Unknown',
|
||||
'copyright' => 'Copyright',
|
||||
'language' => 'en',
|
||||
'langattr' => 'en',
|
||||
'startpage' => true,
|
||||
'rootPath' => $rootPath,
|
||||
'themeFolder' => 'themes',
|
||||
'themeBasePath' => $rootPath,
|
||||
'themePath' => '',
|
||||
'settingsPath' => $rootPath . 'settings',
|
||||
'userPath' => $rootPath . 'settings' . DIRECTORY_SEPARATOR . 'users',
|
||||
'authorPath' => __DIR__ . DIRECTORY_SEPARATOR . 'author' . DIRECTORY_SEPARATOR,
|
||||
'editor' => 'visual',
|
||||
'formats' => ['markdown', 'headline', 'ulist', 'olist', 'table', 'quote', 'notice', 'image', 'video', 'file', 'toc', 'hr', 'definition', 'code', 'shortcode'],
|
||||
'contentFolder' => 'content',
|
||||
'version' => '1.5.3.2',
|
||||
'setup' => true,
|
||||
'welcome' => true,
|
||||
'images' => ['live' => ['width' => 820], 'thumbs' => ['width' => 250, 'height' => 150]],
|
||||
];
|
||||
}
|
||||
|
||||
public static function getUserSettings()
|
||||
{
|
||||
$yaml = new Models\WriteYaml();
|
||||
|
||||
$userSettings = $yaml->getYaml('settings', 'settings.yaml');
|
||||
|
||||
return $userSettings;
|
||||
}
|
||||
|
||||
|
||||
public static function whichLanguage()
|
||||
{
|
||||
# Check which languages are available
|
||||
$langs = [];
|
||||
$path = __DIR__ . '/author/languages/*.yaml';
|
||||
|
||||
foreach (glob($path) as $filename)
|
||||
{
|
||||
$langs[] = basename($filename,'.yaml');
|
||||
}
|
||||
|
||||
# Detect browser language
|
||||
$accept_lang = isset($_SERVER['HTTP_ACCEPT_LANGUAGE']) ? substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2) : false;
|
||||
$lang = in_array($accept_lang, $langs) ? $accept_lang : 'en';
|
||||
|
||||
return $lang;
|
||||
}
|
||||
|
||||
public static function getObjectSettings($objectType, $objectName)
|
||||
{
|
||||
$yaml = new Models\WriteYaml();
|
||||
|
||||
$objectFolder = $objectType . DIRECTORY_SEPARATOR . $objectName;
|
||||
$objectFile = $objectName . '.yaml';
|
||||
$objectSettings = $yaml->getYaml($objectFolder, $objectFile);
|
||||
|
||||
return $objectSettings;
|
||||
}
|
||||
|
||||
public static function createSettings()
|
||||
{
|
||||
$yaml = new Models\WriteYaml();
|
||||
|
||||
$language = self::whichLanguage();
|
||||
|
||||
# create initial settings file with only setup false
|
||||
if($yaml->updateYaml('settings', 'settings.yaml', array('setup' => false, 'language' => $language)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static function updateSettings($settings)
|
||||
{
|
||||
# only allow if usersettings already exists (setup has been done)
|
||||
$userSettings = self::getUserSettings();
|
||||
|
||||
if($userSettings)
|
||||
{
|
||||
# whitelist settings that can be stored in usersettings (values are not relevant here, only keys)
|
||||
$allowedUserSettings = ['displayErrorDetails' => true,
|
||||
'title' => true,
|
||||
'copyright' => true,
|
||||
'language' => true,
|
||||
'langattr' => true,
|
||||
'startpage' => true,
|
||||
'author' => true,
|
||||
'year' => true,
|
||||
'access' => true,
|
||||
'pageaccess' => true,
|
||||
'hrdelimiter' => true,
|
||||
'restrictionnotice' => true,
|
||||
'wraprestrictionnotice' => true,
|
||||
'headlineanchors' => true,
|
||||
'theme' => true,
|
||||
'editor' => true,
|
||||
'formats' => true,
|
||||
'setup' => true,
|
||||
'welcome' => true,
|
||||
'images' => true,
|
||||
'live' => true,
|
||||
'width' => true,
|
||||
'height' => true,
|
||||
'plugins' => true,
|
||||
'themes' => true,
|
||||
'latestVersion' => true,
|
||||
'logo' => true,
|
||||
'favicon' => true,
|
||||
'twigcache' => true,
|
||||
'proxy' => true,
|
||||
'trustedproxies' => true,
|
||||
'headersoff' => true,
|
||||
'urlschemes' => true,
|
||||
'svg' => true,
|
||||
'recoverpw' => true,
|
||||
'recoversubject' => true,
|
||||
'recovermessage' => true,
|
||||
'recoverfrom' => true,
|
||||
'securitylog' => true,
|
||||
'oldslug' => true,
|
||||
'refreshcache' => true,
|
||||
'pingsitemap' => true,
|
||||
];
|
||||
|
||||
# cleanup the existing usersettings
|
||||
$userSettings = array_intersect_key($userSettings, $allowedUserSettings);
|
||||
|
||||
# cleanup the new settings passed as an argument
|
||||
$settings = array_intersect_key($settings, $allowedUserSettings);
|
||||
|
||||
# merge usersettings with new settings
|
||||
$settings = array_merge($userSettings, $settings);
|
||||
|
||||
# write settings to yaml
|
||||
$yaml = new Models\WriteYaml();
|
||||
$yaml->updateYaml('settings', 'settings.yaml', $settings);
|
||||
}
|
||||
}
|
||||
|
||||
public static function loadResources()
|
||||
{
|
||||
return ['content',
|
||||
'mycontent',
|
||||
'user',
|
||||
'userlist',
|
||||
'system'];
|
||||
}
|
||||
|
||||
public static function loadRolesAndPermissions()
|
||||
{
|
||||
$member['name'] = 'member';
|
||||
$member['inherits'] = NULL;
|
||||
$member['permissions'] = ['user' => ['view','update','delete']];
|
||||
|
||||
$author['name'] = 'author';
|
||||
$author['inherits'] = 'member';
|
||||
$author['permissions'] = ['mycontent' => ['view', 'create', 'update'],
|
||||
'content' => ['view']];
|
||||
|
||||
$editor['name'] = 'editor';
|
||||
$editor['inherits'] = 'author';
|
||||
$editor['permissions'] = [ 'mycontent' => ['delete', 'publish', 'unpublish'],
|
||||
'content' => ['create', 'update', 'delete', 'publish', 'unpublish']];
|
||||
|
||||
return ['member' => $member,'author' => $author, 'editor' => $editor];
|
||||
}
|
||||
|
||||
public static function createAcl($roles, $resources)
|
||||
{
|
||||
$acl = new Acl();
|
||||
|
||||
foreach($resources as $resource)
|
||||
{
|
||||
$acl->addResource(new Resource($resource));
|
||||
}
|
||||
|
||||
# add all other roles dynamically
|
||||
foreach($roles as $role)
|
||||
{
|
||||
$acl->addRole(new Role($role['name']), $role['inherits']);
|
||||
|
||||
foreach($role['permissions'] as $resource => $permissions)
|
||||
{
|
||||
$acl->allow($role['name'], $resource, $permissions);
|
||||
}
|
||||
}
|
||||
|
||||
# add administrator role
|
||||
$acl->addRole(new Role('administrator'));
|
||||
$acl->allow('administrator');
|
||||
|
||||
return $acl;
|
||||
}
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Typemill;
|
||||
|
||||
class Translations
|
||||
{
|
||||
public static function loadTranslations($environment)
|
||||
{
|
||||
$yaml = new Models\WriteYaml();
|
||||
$settings = $yaml->getYaml('settings', 'settings.yaml');
|
||||
|
||||
if(!isset($settings['language'])){
|
||||
$language = \Typemill\Settings::whichLanguage();
|
||||
} else {
|
||||
$language = $settings['language'];
|
||||
}
|
||||
|
||||
$theme = 'cyanine';
|
||||
if($settings !== NULL){
|
||||
if(is_array($settings)){
|
||||
if (array_key_exists('theme', $settings)) {
|
||||
$theme = $settings['theme'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// theme labels selected according to the environment: admin or user
|
||||
$theme_labels = [];
|
||||
$theme_language_folder = 'themes' . DIRECTORY_SEPARATOR . $theme . DIRECTORY_SEPARATOR . 'languages' . DIRECTORY_SEPARATOR . $environment . DIRECTORY_SEPARATOR;
|
||||
$theme_language_file = $language . '.yaml';
|
||||
if (file_exists($theme_language_folder . $theme_language_file))
|
||||
{
|
||||
$theme_labels = $yaml->getYaml($theme_language_folder, $theme_language_file);
|
||||
}
|
||||
|
||||
$system_labels = [];
|
||||
$plugins_labels = [];
|
||||
if($environment=='admin'){
|
||||
// system labels
|
||||
$system_language_folder ='system' . DIRECTORY_SEPARATOR . 'author' . DIRECTORY_SEPARATOR . 'languages' . DIRECTORY_SEPARATOR;
|
||||
$system_language_file = $language . '.yaml';
|
||||
if (file_exists($system_language_folder . $system_language_file))
|
||||
{
|
||||
$system_labels = $yaml->getYaml($system_language_folder, $system_language_file);
|
||||
}
|
||||
|
||||
// Next change, to provide labels for the admin and user environments.
|
||||
// There may be plugins that only work in the user environment,
|
||||
// only in the admin environment, or in both environments.
|
||||
$plugin_labels = [];
|
||||
if($settings !== NULL){
|
||||
if(is_array($settings)){
|
||||
if (array_key_exists('plugins', $settings)) {
|
||||
if($settings['plugins'] !== NULL) {
|
||||
foreach($settings['plugins'] as $plugin => $config){
|
||||
if($config['active']=='on'){
|
||||
$plugin_language_folder = 'plugins' . DIRECTORY_SEPARATOR . $plugin . DIRECTORY_SEPARATOR . 'languages' . DIRECTORY_SEPARATOR;
|
||||
$plugin_language_file = $language . '.yaml';
|
||||
if (file_exists($plugin_language_folder . $plugin_language_file)){
|
||||
$plugin_labels[$plugin] = $yaml->getYaml($plugin_language_folder, $plugin_language_file);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach($plugin_labels as $key => $value) {
|
||||
if(is_array($value)){
|
||||
$plugins_labels = array_merge($plugins_labels, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$labels = [];
|
||||
if(is_array($plugins_labels)){
|
||||
$labels = array_merge($labels, $plugins_labels);
|
||||
}
|
||||
if(is_array($system_labels)){
|
||||
$labels = array_merge($labels, $system_labels);
|
||||
}
|
||||
if(is_array($theme_labels)){
|
||||
$labels = array_merge($labels, $theme_labels);
|
||||
}
|
||||
|
||||
return $labels;
|
||||
}
|
||||
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
{% extends 'layouts/layoutAuth.twig' %}
|
||||
{% block title %}Login{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
<div class="setupWrapper">
|
||||
|
||||
<div class="setupContent">
|
||||
<p><svg class="icon baseline icon-bookmark-o"><use xlink:href="#icon-bookmark-o"></use></svg>{{ __('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>
|
||||
<div class="personal-mail">
|
||||
<label>Personal Mail</label>
|
||||
<input type="text" name="personal-honey-mail">
|
||||
</div>
|
||||
|
||||
{{ captcha(old) }}
|
||||
|
||||
</fieldset>
|
||||
|
||||
<div class="loginarea" id="loginarea">
|
||||
<input type="submit" value="{{ __('Login') }}" id="loginbutton" class="loginbutton" />
|
||||
{{ csrf_field() | raw }}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% if settings.recoverpw %}
|
||||
|
||||
<p><span class="left"><a href="{{ base_url() }}">{{ __('back to startpage') }}</a></span><span class="right"><a href="{{ base_url() }}/tm/recoverpw">{{ __('forgot password') }}</a></span></p>
|
||||
|
||||
{% else %}
|
||||
|
||||
<div class="setupContent">
|
||||
<p><a href="{{ base_url() }}">{{ __('back to startpage') }}</a></p>
|
||||
</div>
|
||||
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
<footer></footer>
|
||||
{% endblock %}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user