1
0
mirror of https://github.com/typemill/typemill.git synced 2025-07-17 12:31:13 +02:00

add backend form builder, add captcha and fix shortcodes

This commit is contained in:
trendschau
2023-08-24 23:14:29 +02:00
parent befcc65225
commit 022abe132b
22 changed files with 698 additions and 62 deletions

View File

@@ -28,7 +28,7 @@
"jbroadway/urlify": "1.1.3",
"laminas/laminas-permissions-acl": "^2.10",
"akrabat/proxy-detection-middleware": "^1.0.0",
"gregwar/captcha": "1.*"
"gregwar/captcha": "master"
},
"autoload": {
"psr-4": {

15
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "0cc1a21b272d67b53b9e5f448c2960ac",
"content-hash": "841b98a23a247d8faad15d86df8cbeba",
"packages": [
{
"name": "akrabat/proxy-detection-middleware",
@@ -223,16 +223,16 @@
},
{
"name": "gregwar/captcha",
"version": "v1.2.0",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/Gregwar/Captcha.git",
"reference": "6e5b61b66ac89885b505153f4ef9a74ffa5b3074"
"reference": "5b8323637b502d3dbcbe28a74633dad8aed63452"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Gregwar/Captcha/zipball/6e5b61b66ac89885b505153f4ef9a74ffa5b3074",
"reference": "6e5b61b66ac89885b505153f4ef9a74ffa5b3074",
"url": "https://api.github.com/repos/Gregwar/Captcha/zipball/5b8323637b502d3dbcbe28a74633dad8aed63452",
"reference": "5b8323637b502d3dbcbe28a74633dad8aed63452",
"shasum": ""
},
"require": {
@@ -244,6 +244,7 @@
"require-dev": {
"phpunit/phpunit": "^6.4"
},
"default-branch": true,
"type": "library",
"autoload": {
"psr-4": {
@@ -274,9 +275,9 @@
],
"support": {
"issues": "https://github.com/Gregwar/Captcha/issues",
"source": "https://github.com/Gregwar/Captcha/tree/v1.2.0"
"source": "https://github.com/Gregwar/Captcha/tree/master"
},
"time": "2023-03-24T22:12:41+00:00"
"time": "2023-05-08T15:17:31+00:00"
},
{
"name": "jbroadway/urlify",

View File

@@ -9,6 +9,8 @@
* DONE: License feature
* DONE: Enhance with plugins
[:contactform :]
----
## Visual Editor
@@ -93,20 +95,20 @@
## Open tasks
* DONE: Sitemap and ping
* SVG checker
* Version check
* DONE: Version check
* DONE: Proxy support
* DONE: SVG checker: https://github.com/TribalSystems/SVG-Sanitizer
* NEARLY DONE: Backend form builder
* Markdown secure rendering
* Responsive design
* Backend form builder
* Proxy support
* Image generation on the fly
* Captcha integration
* Clear cache
* Show security Log
* Fix error api systemnavi
* Reference feature
* Typemill Utilities
* Clear cache
* Show security Log
* User search only for +10 users
* Fix error api systemnavi
## Cleanups:

View File

@@ -1 +1 @@
["# ToDos Version 2","[TOC]","## System settings","* DONE: Migrate from backend to frontend with vue and api\n* DONE: Redesign\n* DONE: License feature\n* DONE: Enhance with plugins","----","## Visual Editor","* DONE: Refactor and redesign\n* DONE: Fix toc component in new block\n* DONE: Fix hr component in new block\n* DONE: finish shortcode component\n* DONE: Fix inline formats\n* DONE: fix lenght of page\n* DONE: Fix design of new block at the end (background color)\n* DONE: Move Block\n* DONE: Fix headline design\n* DONE: Fix save on two enter\n* DONE: fix quote design\n* DONE: Fix toc preview\n* DONE: disable enable \n* DONE: Add load sign (from navigation)\n* DONE: File is not published from tmp to media\/files if you save the block.\n* ToDo: Customfields not styled yet.\n* ToDo: Warn if open another block\n* ToDo: finish youtube component","## Raw Editor","* DONE: Refactor and redesign\n* DONE: Integrate highlighting","## Navigation","* DONE: Refactor and redesign\n* DONE: fix status in navigation\n* DONE: refresh navigation after changes\n* ToDo: fix error messages\n* ToDo: Wrong frontend navigation if unpublished pages","## Publish Controller","* DONE: Refactor and redesign\n* DONE: Create \n* DONE: publish\n* DONE: unpublish\n* DONE: discard\n* DONE: delete\n* DONE: save draft\n* DONE: switch to raw","## Meta Tabs","* DONE: Refactor and redesign\n* DONE: Enhance with plugins","## Medialib","* DONE: Refactor and redesign","## Posts","* DONE: Refactor and redesign","## Plugins","* Asset Class in progress","## Frontend","* DONE: Refactor\n* DONE: Test restrictions","## Other big tasks","* DONE: System setup\n* DONE: Recover Password","## Medium tasks","* DONE: Merge processAssets modell\n* DONE: Table of content duplicated for published pages\n* DONE: Session handling: csrf fail and session start error if restrictions are active\n* DONE: Image and files for meta","## Open tasks","* DONE: Sitemap and ping\n* DONE: Version check\n* DONE: Proxy support\n* DONE: SVG checker: https:\/\/github.com\/TribalSystems\/SVG-Sanitizer\n* Markdown secure rendering\n* Responsive design\n* Backend form builder\n* Image generation on the fly\n* Captcha integration\n* Fix error api systemnavi\n* Reference feature\n* Typemill Utilities\n* Clear cache\n* Show security Log\n* User search only for +10 users","## Cleanups:","* DONE: Events\n* Error messages\n* Translations","## Info: Select userroles","* Userroles for file restriction: in vue-blox-components loaded via api\n* Userroles for userfields: in php model user getUserFields()\n* Userroles for meta: in php controller apiAuthorMeta getMeta()\n* Plugins and themes: in php model extension getThemeDefinitions()","## Info: License Check","* On activation in apiControllerExtension. It checks the license in yaml.\n* In plugin php code with setPremiumLicense\n* In static plugins, it checks manual premium list and method setPremiumLicense and more "]
["# ToDos Version 2","[TOC]","## System settings","* DONE: Migrate from backend to frontend with vue and api\n* DONE: Redesign\n* DONE: License feature\n* DONE: Enhance with plugins","[:contactform :]","----","## Visual Editor","* DONE: Refactor and redesign\n* DONE: Fix toc component in new block\n* DONE: Fix hr component in new block\n* DONE: finish shortcode component\n* DONE: Fix inline formats\n* DONE: fix lenght of page\n* DONE: Fix design of new block at the end (background color)\n* DONE: Move Block\n* DONE: Fix headline design\n* DONE: Fix save on two enter\n* DONE: fix quote design\n* DONE: Fix toc preview\n* DONE: disable enable \n* DONE: Add load sign (from navigation)\n* DONE: File is not published from tmp to media\/files if you save the block.\n* ToDo: Customfields not styled yet.\n* ToDo: Warn if open another block\n* ToDo: finish youtube component","## Raw Editor","* DONE: Refactor and redesign\n* DONE: Integrate highlighting","## Navigation","* DONE: Refactor and redesign\n* DONE: fix status in navigation\n* DONE: refresh navigation after changes\n* ToDo: fix error messages\n* ToDo: Wrong frontend navigation if unpublished pages","## Publish Controller","* DONE: Refactor and redesign\n* DONE: Create \n* DONE: publish\n* DONE: unpublish\n* DONE: discard\n* DONE: delete\n* DONE: save draft\n* DONE: switch to raw","## Meta Tabs","* DONE: Refactor and redesign\n* DONE: Enhance with plugins","## Medialib","* DONE: Refactor and redesign","## Posts","* DONE: Refactor and redesign","## Plugins","* Asset Class in progress","## Frontend","* DONE: Refactor\n* DONE: Test restrictions","## Other big tasks","* DONE: System setup\n* DONE: Recover Password","## Medium tasks","* DONE: Merge processAssets modell\n* DONE: Table of content duplicated for published pages\n* DONE: Session handling: csrf fail and session start error if restrictions are active\n* DONE: Image and files for meta","## Open tasks","* DONE: Sitemap and ping\n* DONE: Version check\n* DONE: Proxy support\n* DONE: SVG checker: https:\/\/github.com\/TribalSystems\/SVG-Sanitizer\n* DONE: Backend form builder\n* Handle formdata centrally ???\n* Image generation on the fly\n* Markdown secure rendering\n* Responsive design\n* Captcha integration\n* Fix error api systemnavi\n* Reference feature\n* Typemill Utilities\n* Clear cache\n* Show security Log\n* User search only for +10 users","## Cleanups:","* DONE: Events\n* Error messages\n* Translations","## Info: Select userroles","* Userroles for file restriction: in vue-blox-components loaded via api\n* Userroles for userfields: in php model user getUserFields()\n* Userroles for meta: in php controller apiAuthorMeta getMeta()\n* Plugins and themes: in php model extension getThemeDefinitions()","## Info: License Check","* On activation in apiControllerExtension. It checks the license in yaml.\n* In plugin php code with setPremiumLicense\n* In static plugins, it checks manual premium list and method setPremiumLicense and more "]

View File

@@ -6,7 +6,7 @@ meta:
heroimagealt: null
owner: Sebastian
author: null
allowedrole: contributor
allowedrole: ''
alloweduser: null
manualdate: null
modified: '2023-06-19'

View File

@@ -736,6 +736,7 @@ class ParsedownExtension extends \ParsedownExtra
protected function blockShortcode($Line)
{
if ($this->dispatcher && preg_match('/^\[:.*:\]/', $Line['text'], $matches))
{
return $this->createShortcodeArray($matches,$block = true);

View File

@@ -59,7 +59,7 @@ class ControllerApiAuthorArticle extends Controller
}
# publish content
$content = new Content($urlinfo['baseurl']);
$content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher'));
$draftMarkdown = $content->getDraftMarkdown($item);
$publish = $content->publishMarkdown($item, $draftMarkdown);
if($publish !== true)
@@ -141,7 +141,7 @@ class ControllerApiAuthorArticle extends Controller
}
# publish content
$content = new Content($urlinfo['baseurl']);
$content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher'));
$draftMarkdown = $content->getDraftMarkdown($item);
$content->unpublishMarkdown($item, $draftMarkdown);
@@ -218,7 +218,7 @@ class ControllerApiAuthorArticle extends Controller
}
# save draft content
$content = new Content($urlinfo['baseurl']);
$content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher'));
$markdown = $params['title'] . PHP_EOL . PHP_EOL . $params['body'];
$markdownArray = $content->markdownTextToArray($markdown);
$content->saveDraftMarkdown($item, $markdownArray);
@@ -281,7 +281,7 @@ class ControllerApiAuthorArticle extends Controller
}
# save draft content
$content = new Content($urlinfo['baseurl']);
$content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher'));
$markdown = $params['title'] . PHP_EOL . PHP_EOL . $params['body'];
$markdownArray = $content->markdownTextToArray($markdown);
$content->publishMarkdown($item, $markdownArray);
@@ -361,7 +361,7 @@ class ControllerApiAuthorArticle extends Controller
}
# publish content
$content = new Content($urlinfo['baseurl']);
$content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher'));
$content->deleteDraft($item);
# refresh navigation and item
@@ -896,7 +896,7 @@ class ControllerApiAuthorArticle extends Controller
return $response->withHeader('Content-Type', 'application/json')->withStatus(404);
}
$content = new Content($urlinfo['baseurl']);
$content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher'));
# check if it is a folder and if the folder has published pages.
if($item->elementType == 'folder')

View File

@@ -50,7 +50,7 @@ class ControllerApiAuthorBlock extends Controller
return $response->withHeader('Content-Type', 'application/json')->withStatus(404);
}
$content = new Content($urlinfo['baseurl']);
$content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher'));
$draftMarkdown = $content->getDraftMarkdown($item);
# if it is a new content-block
@@ -154,7 +154,7 @@ class ControllerApiAuthorBlock extends Controller
return $response->withHeader('Content-Type', 'application/json')->withStatus(404);
}
$content = new Content($urlinfo['baseurl']);
$content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher'));
$draftMarkdown = $content->getDraftMarkdown($item);
@@ -247,7 +247,7 @@ class ControllerApiAuthorBlock extends Controller
return $response->withHeader('Content-Type', 'application/json')->withStatus(404);
}
$content = new Content($urlinfo['baseurl']);
$content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher'));
$draftMarkdown = $content->getDraftMarkdown($item);
@@ -353,7 +353,7 @@ class ControllerApiAuthorBlock extends Controller
return $response->withHeader('Content-Type', 'application/json')->withStatus(404);
}
$content = new Content($urlinfo['baseurl']);
$content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher'));
$draftMarkdown = $content->getDraftMarkdown($item);

View File

@@ -7,7 +7,6 @@ use Psr\Http\Message\ResponseInterface as Response;
use Slim\Routing\RouteContext;
use Typemill\Models\Navigation;
use Typemill\Models\Content;
use Typemill\Models\SvgSanitizer;
use Typemill\Events\OnPagetreeLoaded;
use Typemill\Events\OnItemLoaded;
use Typemill\Events\OnMarkdownLoaded;
@@ -17,10 +16,7 @@ use Typemill\Events\OnPageReady;
class ControllerWebAuthor extends Controller
{
public function showBlox(Request $request, Response $response, $args)
{
$svg = new SvgSanitizer();
{
# get url for requested page
$url = isset($args['route']) ? '/' . $args['route'] : '/';
$urlinfo = $this->c->get('urlinfo');
@@ -65,7 +61,7 @@ class ControllerWebAuthor extends Controller
$mainNavigation = $navigation->getMainNavigation($request->getAttribute('c_userrole'), $this->c->get('acl'), $urlinfo, $this->settings['editor']);
$content = new Content($urlinfo['baseurl']);
$content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher'));
$draftMarkdown = $content->getDraftMarkdown($item);
$draftMarkdown = $this->c->get('dispatcher')->dispatch(new OnMarkdownLoaded($draftMarkdown), 'onMarkdownLoaded')->getData();
@@ -134,7 +130,7 @@ class ControllerWebAuthor extends Controller
$mainNavigation = $navigation->getMainNavigation($request->getAttribute('c_userrole'), $this->c->get('acl'), $urlinfo, $this->settings['editor']);
$content = new Content($urlinfo['baseurl']);
$content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher'));
$draftMarkdown = $content->getDraftMarkdown($item);
$draftMarkdown = $this->c->get('dispatcher')->dispatch(new OnMarkdownLoaded($draftMarkdown), 'onMarkdownLoaded')->getData();

View File

@@ -90,7 +90,7 @@ class ControllerWebFrontend extends Controller
# GET THE CONTENT
$content = new Content($urlinfo['baseurl']);
$content = new Content($urlinfo['baseurl'], $this->settings, $this->c->get('dispatcher'));
$liveMarkdown = $content->getLiveMarkdown($item);
$liveMarkdown = $this->c->get('dispatcher')->dispatch(new OnMarkdownLoaded($liveMarkdown), 'onMarkdownLoaded')->getData();
$markdownArray = $content->markdownTextToArray($liveMarkdown);

View File

@@ -361,7 +361,7 @@ class ParsedownExtension extends \ParsedownExtra
}
$text = trim($Line['text'], '#');
$headline = Slug::createSlug($Line['text'], $this->settings);
$headline = Slug::createSlug($Line['text'], $this->settings['langattr']);
if ($this->strictMode and isset($text[0]) and $text[0] !== ' ')
{
@@ -815,7 +815,7 @@ class ParsedownExtension extends \ParsedownExtra
}
}
$html = $this->dispatcher->dispatch('onShortcodeFound', new OnShortcodeFound($shortcode))->getData();
$html = $this->dispatcher->dispatch(new OnShortcodeFound($shortcode), 'onShortcodeFound')->getData();
# if no shortcode has been processed, add the original string
if(is_array($html) OR is_object($html))

View File

@@ -0,0 +1,46 @@
<?php
namespace Typemill\Extensions;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
use Gregwar\Captcha\CaptchaBuilder;
class TwigCaptchaExtension extends AbstractExtension
{
public function getFunctions()
{
return [
new TwigFunction('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;
}
}
}

View File

@@ -9,10 +9,10 @@ class Content
{
private $storage;
public function __construct($baseurl = NULL)
public function __construct($baseurl = NULL, $settings = NULL, $dispatcher = NULL)
{
$this->storage = new StorageWrapper('\Typemill\Models\Storage');
$this->parsedown = new ParsedownExtension($baseurl);
$this->parsedown = new ParsedownExtension($baseurl, $settings, $dispatcher);
}
public function getDraftMarkdown($item)

View File

@@ -70,8 +70,8 @@ class Extension
'description' => 'You can overwrite the theme-css with your own css here.'
];
# add image preview file
$themeSettings['preview'] = 'http://localhost/typemill/themes/' . $themeName . '/' . $themeName . '.png';
# add image preview file
$themeSettings['preview'] = '/themes/' . $themeName . '/' . $themeName . '.png';
return $themeSettings;
}

View File

@@ -0,0 +1,289 @@
<?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;
}
}

View File

@@ -0,0 +1,138 @@
<?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;
}
}

View File

@@ -106,7 +106,7 @@ class Settings
return false;
}
public function getObjectSettings($objectType, $objectName)
{
$objectSettings = $this->storage->getYaml($objectType, $objectName, $objectName . '.yaml');

View File

@@ -4,10 +4,10 @@ namespace Typemill;
use \Symfony\Component\EventDispatcher\EventSubscriberInterface;
use DI\Container;
# use Typemill\Models\Fields;
use Typemill\Models\StorageWrapper;
use Typemill\Models\Extension;
use Typemill\Models\Validation;
use Typemill\Models\Fields;
use Typemill\Extensions\ParsedownExtension;
abstract class Plugin implements EventSubscriberInterface
@@ -22,11 +22,7 @@ abstract class Plugin implements EventSubscriberInterface
public function __construct(Container $container)
{
/*
echo '<pre>';
echo '<h1>FIRST</h1>';
print_r($container);
*/
$this->container = $container;
$this->urlinfo = $this->container->get('urlinfo');
$this->route = $this->urlinfo['route'];
@@ -281,23 +277,27 @@ abstract class Plugin implements EventSubscriberInterface
return false;
}
protected function generateForm($pluginName, $routename)
protected function generateForm($routename, $pluginname = NULL)
{
$fieldsModel = new Fields();
$settings = $this->getSettings();
$form = false;
$pluginDefinitions = \Typemill\Settings::getObjectSettings('plugins', $pluginName);
if(isset($settings['plugins'][$pluginName]['publicformdefinitions']) && $settings['plugins'][$pluginName]['publicformdefinitions'] != '')
$fieldsModel = new Fields();
$extensionModel = new Extension();
$pluginSettings = $this->getPluginSettings();
$pluginName = $this->getPluginName($pluginname);
$pluginDefinitions = $extensionModel->getPluginDefinition($pluginName);
# add field-definitions entered into author interface
if(isset($pluginSettings['publicformdefinitions']) && $pluginSettings['publicformdefinitions'] != '')
{
$arrayFromYaml = \Symfony\Component\Yaml\Yaml::parse($settings['plugins'][$pluginName]['publicformdefinitions']);
$arrayFromYaml = \Symfony\Component\Yaml\Yaml::parse($pluginSettings['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;
$buttonlabel = isset($pluginSettings['button_label']) ? $pluginSettings['button_label'] : false;
$captchaoptions = isset($pluginSettings['captchaoptions']) ? $pluginSettings['captchaoptions'] : false;
$recaptcha = isset($pluginSettings['recaptcha']) ? $pluginSettings['recaptcha_webkey'] : false;
if($captchaoptions == 'disabled')
{
@@ -308,7 +308,9 @@ abstract class Plugin implements EventSubscriberInterface
$fieldsModel = new Fields();
if(isset($pluginDefinitions['public']['fields']))
{
{
$settings = $this->container->get('settings');
# 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');
@@ -326,7 +328,7 @@ abstract class Plugin implements EventSubscriberInterface
'recaptcha_webkey' => $recaptcha,
]);
}
return $form;
}

View File

@@ -2293,7 +2293,7 @@ bloxeditor.component('shortcode-component', {
{
params:
{
'url': data.urlinfo.route,
'url': data.urlinfo.route,
}
})
.then(function (response)
@@ -2359,7 +2359,7 @@ bloxeditor.component('shortcode-component', {
this.compmarkdown = '[:' + shortcodename + attributes + ' :]';
this.$emit('updatedMarkdown', this.compmarkdown);
this.$emit('updateMarkdownEvent', this.compmarkdown);
},
selectsearch: function(item,attribute)
{

View File

@@ -0,0 +1,103 @@
<div class="{{ class ? class : 'cardField' }}{{ errors[itemName][field.name] ? ' error' : '' }}{{field.fieldsize ? ' ' ~ field.fieldsize : ''}}">
<label for="{{ itemName }}[{{ field.name }}]">{{ translate( field.getLabel() ) }}
{% if field.getAttribute('required') %}<strong><abbr title="{{ translate('required') }}">*</abbr></strong>{% endif %}
{% if field.help %}<div class="help">?<span class="tooltip">{{ translate(field.help|slice(0,100)) }}</span></div>{% endif %}
</label>
{% if field.type == 'image' %}
<div class="img-upload flex flex-wrap item-start">
<div class="w-50">
<div class="w6 h6 bg-black-40 dtc v-mid bg-chess">
<img src="{{ base_url }}/{{ settings[object][itemName][field.name] }}" class="function-img-src mw6 max-h6 dt center">
</div>
</div>
<div class="w-50 ph3 lh-copy f6 relative">
<div class="relative dib w-100">
<input class="function-img-file absolute o-0 w-100 top-0 z-1 pointer" type="file" id="{{ itemName }}[{{ field.name }}]" name="{{ itemName }}[{{ field.name }}]" accept="image/*" />
<div class="relative w-100 bn br1 bg-tm-green white pa3 ma0 tc"><svg class="icon icon-upload baseline"><use xlink:href="#icon-upload"></use></svg> {{ translate('Upload an image') }}</div>
</div>
<div class="dib w-100 mt3">
<label>{{ translate('Image URL') }}</label>
<div class="flex">
<button class="function-delete-img w-10 bg-tm-gray bn hover-bg-tm-red hover-white">x</button>
<input class="function-img-url w-90 mbfix" type="text" name="{{ itemName }}[{{ field.name }}]" value="{{ settings[object][itemName][field.name] }}" readonly>
</div>
</div>
{% if errors[itemName][field.name] %}
<div class="error f6">{{ errors[itemName][field.name] | first }}</div>
{% endif %}
{% if field.description %}<div class="description pv3">{{ translate(field.description) }}</div>{% endif %}
</div>
</div>
{% else %}
{% if field.type == 'textarea' %}
<textarea id="{{ itemName }}[{{ field.name }}]" name="{{ itemName }}[{{ field.name }}]"{{field.getAttributeValues() }}{{ field.getAttributes() }}>{{ field.getContent() }}</textarea>
{% elseif (field.type == 'paragraph') and (field.getContent() != '') %}
{{ markdown(field.getContent()) }}
{% elseif field.type == 'checkbox' %}
<label class="control-group">{{ markdown( field.getCheckboxLabel() ) }}
<input type="checkbox" id="{{ itemName}}[{{ field.name }}]" name="{{ itemName}}[{{ field.name }}]"{{ field.getAttributeValues() }}{{ field.getAttributes() }}>
<span class="checkmark"></span>
</label>
{% elseif field.type == 'checkboxlist' %}
{% set options = field.getOptions() %}
{% for value,label in options %}
<label class="control-group">{{ markdown( label ) }}
<input type="checkbox" id="{{ itemName }}[{{ field.name }}][{{value}}]" name="{{ itemName }}[{{ field.name }}][{{value}}]" {{ settings[object][itemName][field.name][value] ? ' checked' : '' }}>
<span class="checkmark"></span>
</label>
{% endfor %}
{% elseif field.type == 'select' %}
{% set options = field.getOptions() %}
<select id="{{ itemName }}[{{ field.name }}]" name="{{ itemName }}[{{ field.name }}]"{{ field.getAttributeValues() }}{{ field.getAttributes() }}>
{% for value,label in options %}
<option value="{{ value }}" {{ (value == field.getAttributeValue('value')) ? ' selected' : '' }}>{{ translate(label) }}</option>
{% endfor %}
</select>
{% elseif field.type == 'radio' %}
{% set options = field.getOptions() %}
{% for value,label in options %}
<label class="control-group">{{ markdown(label) }}
<input type="radio" id="{{ itemName }}[{{ field.name }}]" name="{{ itemName }}[{{ field.name }}]" value="{{ value }}" {{ (value == settings[object][itemName][field.name]) ? ' checked' : '' }}>
<span class="radiomark"></span>
</label>
{% endfor %}
{% else %}
<input id="{{itemName}}[{{ field.name }}]" name="{{itemName}}[{{ field.name }}]" type="{{ field.type }}"{{ field.getAttributeValues() }}{{ field.getAttributes() }}>
{% endif %}
{% if field.description %}<div class="description">{{ translate(field.description) }}</div>{% endif %}
{% if errors[itemName][field.name] %}
<span class="error">{{ errors[itemName][field.name] | first }}</span>
{% endif %}
{% endif %}
</div>

View File

@@ -0,0 +1,56 @@
{% if recaptcha_webkey %}
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
{% endif %}
<form method="POST" action="{{ url_for(routename) }}" enctype="multipart/form-data">
<fieldset class="card{{ errors[itemName] ? ' errors' : '' }}">
{% for field in fields %}
{% if field.type == 'fieldset' %}
<fieldset class="subfield">
<legend>{{ field.legend }}</legend>
{% for field in field.fields %}
{% include '/partials/fields.twig' with {'itemName' : itemName, 'object' : object } %}
{% endfor %}
</fieldset>
{% else %}
{% include '/partials/fields.twig' with {'itemName' : itemName, 'object' : object } %}
{% endif %}
{% endfor %}
<div class="personal-mail">
<label>Personal Mail</label>
<input type="text" name="personal-honey-mail">
</div>
{% if captchaoptions == 'disabled' %}
{% elseif captchaoptions == 'aftererror' %}
{{ captcha(old) }}
{% else %}
{{ captcha(true) }}
{% endif %}
{% if recaptcha_webkey %}
<p><div class="g-recaptcha" data-sitekey="{{ recaptcha_webkey }}"></div></p>
{% endif %}
<input type="submit" value="{{ buttonlabel ? buttonlabel : 'send' }}" />
<style>.personal-mail{display:none}</style>
</fieldset>
</form>

View File

@@ -32,6 +32,7 @@ use Typemill\Extensions\TwigUrlExtension;
use Typemill\Extensions\TwigUserExtension;
use Typemill\Extensions\TwigLanguageExtension;
use Typemill\Extensions\TwigMarkdownExtension;
use Typemill\Extensions\TwigCaptchaExtension;
$timer = [];
$timer['start'] = microtime(true);
@@ -301,6 +302,7 @@ $container->set('view', function() use ($settings, $csrf, $urlinfo, $translation
$twig->addExtension(new TwigUrlExtension($urlinfo));
$twig->addExtension(new TwigLanguageExtension( $translations ));
$twig->addExtension(new TwigMarkdownExtension());
$twig->addExtension(new TwigCaptchaExtension());
# start csrf only if session is active
/*