diff --git a/cache/stats.txt b/cache/stats.txt new file mode 100644 index 0000000..0d91248 --- /dev/null +++ b/cache/stats.txt @@ -0,0 +1 @@ +a:2:{s:31:"http://localhost/typemill/setup";i:1517561841;s:25:"http://localhost/typemill";i:1517562535;} \ No newline at end of file diff --git a/composer.json b/composer.json index 6cf8533..008b968 100644 --- a/composer.json +++ b/composer.json @@ -13,11 +13,13 @@ "twig/twig": "~1.18", "slim/twig-view": "~2.3", "slim/flash": "~0.4", + "Slim/csrf": "~0.8", "symfony/yaml": "~2.8", "symfony/event-dispatcher": "~3.3", "erusev/parsedown": "~1.4", "erusev/parsedown-extra": "dev-master", - "jbroadway/urlify": "~1.1" + "jbroadway/urlify": "~1.1", + "vlucas/valitron": "dev-master" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 9f0f86b..b9ec5ec 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "f4e4110212e9000bba2745d405bae5b2", + "content-hash": "1ad663bcab9db15e71762a8ac4872c03", "packages": [ { "name": "container-interop/container-interop", @@ -224,17 +224,65 @@ "time": "2017-01-19T11:35:12+00:00" }, { - "name": "pimple/pimple", - "version": "v3.2.2", + "name": "paragonie/random_compat", + "version": "v2.0.11", "source": { "type": "git", - "url": "https://github.com/silexphp/Pimple.git", - "reference": "4d45fb62d96418396ec58ba76e6f065bca16e10a" + "url": "https://github.com/paragonie/random_compat.git", + "reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/silexphp/Pimple/zipball/4d45fb62d96418396ec58ba76e6f065bca16e10a", - "reference": "4d45fb62d96418396ec58ba76e6f065bca16e10a", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/5da4d3c796c275c55f057af5a643ae297d96b4d8", + "reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "autoload": { + "files": [ + "lib/random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "pseudorandom", + "random" + ], + "time": "2017-09-27T21:40:39+00:00" + }, + { + "name": "pimple/pimple", + "version": "v3.2.3", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/9e403941ef9d65d20cba7d54e29fe906db42cf32", + "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32", "shasum": "" }, "require": { @@ -271,7 +319,7 @@ "container", "dependency injection" ], - "time": "2017-07-23T07:32:15+00:00" + "time": "2018-01-21T07:42:36+00:00" }, { "name": "psr/container", @@ -372,6 +420,56 @@ ], "time": "2016-08-06T14:39:51+00:00" }, + { + "name": "slim/csrf", + "version": "0.8.2", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Slim-Csrf.git", + "reference": "e416320bc0eecdf409aefaad2889737da095ff9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Slim-Csrf/zipball/e416320bc0eecdf409aefaad2889737da095ff9a", + "reference": "e416320bc0eecdf409aefaad2889737da095ff9a", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "^1.1|^2.0", + "php": ">=5.5.0", + "psr/http-message": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0", + "slim/slim": "~3.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Slim\\Csrf\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "http://joshlockhart.com" + } + ], + "description": "Slim Framework 3 CSRF protection middleware", + "homepage": "http://slimframework.com", + "keywords": [ + "csrf", + "framework", + "middleware", + "slim" + ], + "time": "2017-10-14T07:59:05+00:00" + }, { "name": "slim/flash", "version": "0.4.0", @@ -543,16 +641,16 @@ }, { "name": "symfony/event-dispatcher", - "version": "v3.3.13", + "version": "v3.4.4", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "271d8c27c3ec5ecee6e2ac06016232e249d638d9" + "reference": "26b87b6bca8f8f797331a30b76fdae5342dc26ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/271d8c27c3ec5ecee6e2ac06016232e249d638d9", - "reference": "271d8c27c3ec5ecee6e2ac06016232e249d638d9", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/26b87b6bca8f8f797331a30b76fdae5342dc26ca", + "reference": "26b87b6bca8f8f797331a30b76fdae5342dc26ca", "shasum": "" }, "require": { @@ -563,10 +661,10 @@ }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~2.8|~3.0", - "symfony/dependency-injection": "~3.3", - "symfony/expression-language": "~2.8|~3.0", - "symfony/stopwatch": "~2.8|~3.0" + "symfony/config": "~2.8|~3.0|~4.0", + "symfony/dependency-injection": "~3.3|~4.0", + "symfony/expression-language": "~2.8|~3.0|~4.0", + "symfony/stopwatch": "~2.8|~3.0|~4.0" }, "suggest": { "symfony/dependency-injection": "", @@ -575,7 +673,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "3.4-dev" } }, "autoload": { @@ -602,20 +700,20 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2017-11-05T15:47:03+00:00" + "time": "2018-01-03T07:37:34+00:00" }, { "name": "symfony/yaml", - "version": "v2.8.31", + "version": "v2.8.34", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "d819bf267e901727141fe828ae888486fd21236e" + "reference": "be720fcfae4614df204190d57795351059946a77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/d819bf267e901727141fe828ae888486fd21236e", - "reference": "d819bf267e901727141fe828ae888486fd21236e", + "url": "https://api.github.com/repos/symfony/yaml/zipball/be720fcfae4614df204190d57795351059946a77", + "reference": "be720fcfae4614df204190d57795351059946a77", "shasum": "" }, "require": { @@ -651,7 +749,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2017-11-05T15:25:56+00:00" + "time": "2018-01-03T07:36:31+00:00" }, { "name": "twig/twig", @@ -717,13 +815,60 @@ "templating" ], "time": "2017-09-27T18:06:46+00:00" + }, + { + "name": "vlucas/valitron", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/vlucas/valitron.git", + "reference": "1712b04f16db5f243ddfa5ce26a9b51eb1948a36" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/vlucas/valitron/zipball/1712b04f16db5f243ddfa5ce26a9b51eb1948a36", + "reference": "1712b04f16db5f243ddfa5ce26a9b51eb1948a36", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.8.35" + }, + "type": "library", + "autoload": { + "psr-0": { + "Valitron": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "authors": [ + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "http://www.vancelucas.com" + } + ], + "description": "Simple, elegant, stand-alone validation library with NO dependencies", + "homepage": "http://github.com/vlucas/valitron", + "keywords": [ + "valid", + "validation", + "validator" + ], + "time": "2017-12-20T08:03:40+00:00" } ], "packages-dev": [], "aliases": [], "minimum-stability": "stable", "stability-flags": { - "erusev/parsedown-extra": 20 + "erusev/parsedown-extra": 20, + "vlucas/valitron": 20 }, "prefer-stable": false, "prefer-lowest": false, diff --git a/content/4_info/01-release-notes.md b/content/4_info/01-release-notes.md index 83028ef..b5d3f9a 100644 --- a/content/4_info/01-release-notes.md +++ b/content/4_info/01-release-notes.md @@ -2,6 +2,24 @@ This is the version history with some release notes. +## Version 1.1.0 (05.02.2018) + +Version 1.1.0 introduces plugins to typemill. And because of the GDPR, the first plugin is an implementation of the famous cookieconsent. So heads up, you can publish GDPR-compliant websites with typemill now! + +All plugins are in the "plugin"-folder, and I can't wait, that this folder will be stuffed with cool extensions. + +To update the system, please delete, replace and upload + +* the "system" folder and +* the "theme" folder. +* the "plugin" folder (new). + +Then backup and delete your settings.yaml in the settings-folder. + +Now visit your startpage and click on the new setup-button. The button will direct you to the new setup page, where you can configure the basic system and the new cookieconsent-plugin. + +Plugins are easy to use for writers, but a plugin system is pretty complex to implement for a developer, so there is a lot of new code. For now, there is no documentation for developers, but it will follow, soon. + ## Version 1.0.5 (30.11.2017) - Improvement: Character encoding for the navigation has improved. You can try to use other characters than english for your file names now, but there is no garanty for it. If the characters do not work in the navigation, please use english characters only. diff --git a/settings/settings.yaml.example b/settings/settings.yaml.example index 8e72ef5..5bf82aa 100644 --- a/settings/settings.yaml.example +++ b/settings/settings.yaml.example @@ -1,6 +1,6 @@ title: MyWebsite author: 'Your Name' copyright: © -year: '2017' +year: '2018' theme: typemill startpage: true \ No newline at end of file diff --git a/system/Assets.php b/system/Assets.php index 0860540..2d49c4c 100644 --- a/system/Assets.php +++ b/system/Assets.php @@ -15,7 +15,7 @@ class Assets $this->inlineCSS = array(); } - public function addCSS(string $CSS) + public function addCSS($CSS) { $CSSfile = $this->getFileUrl($CSS); @@ -30,7 +30,7 @@ class Assets $this->inlineCSS[] = ''; } - public function addJS(string $JS) + public function addJS($JS) { $JSfile = $this->getFileUrl($JS); @@ -52,7 +52,7 @@ class Assets public function renderJS() { - return implode('
', $this->JS) . implode('
', $this->inlineJS); + return implode('
', $this->JS) . '
' . implode('
', $this->inlineJS); } /** @@ -60,8 +60,8 @@ class Assets * @params $path string * @return string or false */ - public function getFileUrl(string $path) - { + public function getFileUrl($path) + { $internalFile = __DIR__ . '/../plugins' . $path; if(file_exists($internalFile)) diff --git a/system/Controllers/SetupController.php b/system/Controllers/SetupController.php index bd037be..4894728 100644 --- a/system/Controllers/SetupController.php +++ b/system/Controllers/SetupController.php @@ -3,105 +3,178 @@ namespace Typemill\Controllers; use \Symfony\Component\Yaml\Yaml; +use Typemill\Models\Field; +use Typemill\Models\Validation; class SetupController extends Controller { public function setup($request, $response, $args) - { - $themes = $this->getThemes(); - $copyright = $this->getCopyright(); - $uri = $request->getUri(); - $base_url = $uri->getBaseUrl(); - $errors = false; + { + $settings = $this->c->get('settings'); + $themes = $this->getThemes(); + $copyright = $this->getCopyright(); - /* Check, if setting folder is writable */ - if(!is_writable($this->c->get('settings')['settingsPath'])){ $errors['folder'] = 'Your settings folder is not writable.'; } + $plugins = array(); + $fields = array(); + + /* iterate through the plugins in the stored user settings */ + foreach($settings['plugins'] as $pluginName => $pluginUserSettings) + { + /* add plugin to plugin Data */ + $plugins[$pluginName] = Null; + + /* Check if the user has deleted a plugin. Then delete it in the settings and store the updated settings. */ + if(!is_dir($settings['rootPath'] . 'plugins' . DIRECTORY_SEPARATOR . $pluginName)) + { + /* remove the plugin settings and store updated settings */ + \Typemill\Settings::removePluginSettings($pluginName); + continue; + } + + /* load the original plugin definitions from the plugin folder (author, version and stuff) */ + $pluginOriginalSettings = \Typemill\Settings::getPluginSettings($pluginName); + if($pluginOriginalSettings) + { + /* store them as default plugin data with plugin author, plugin year, default settings and field-definitions */ + $plugins[$pluginName] = $pluginOriginalSettings; + } + + /* overwrite the original plugin settings with the stored user settings, if they exist */ + if($pluginUserSettings) + { + $plugins[$pluginName]['settings'] = $pluginUserSettings; + } + + /* check, if the plugin has been disabled in the form-session-data */ + /* TODO: Works only, if there is at least one plugin with settings */ + if(isset($_SESSION['old']) && !isset($_SESSION['old'][$pluginName]['active'])) + { + $plugins[$pluginName]['settings']['active'] = false; + } + + /* if the plugin defines forms and fields, so that the user can edit the plugin settings in the frontend */ + if(isset($pluginOriginalSettings['forms'])) + { + $fields = array(); - $data = array( - 'themes' => $themes, - 'copyright' => $copyright, - 'inputs' => false, - 'errors' => $errors, - 'base_url' => $base_url - ); - $this->c->view->render($response, '/setup.twig', $data); + /* then iterate through the fields */ + foreach($pluginOriginalSettings['forms']['fields'] as $fieldName => $fieldConfigs) + { + /* and create a new field object with the field name and the field configurations. */ + $field = new Field($fieldName, $fieldConfigs); + + /* now you have the configurations of the field. Time to set the values */ + + /* At first, get the value for the field from the stored user settings */ + // $userValue = isset($pluginUserSettings[$fieldName]) ? $pluginUserSettings[$fieldName] : NULL; + $userValue = isset($plugins[$pluginName]['settings'][$fieldName]) ? $plugins[$pluginName]['settings'][$fieldName] : NULL; + + /* Then overwrite the value, if there are old input values for the field in the session */ + $userValue = isset($_SESSION['old'][$pluginName][$fieldName]) ? $_SESSION['old'][$pluginName][$fieldName] : $userValue; + + if($field->getType() == "textarea") + { + if($userValue) + { + $field->setContent($userValue); + } + } + elseIf($field->getType() != "checkbox") + { + $field->setAttributeValue('value', $userValue); + } + + /* add the field to the field-List with the plugin-name as key */ + $fields[] = $field; + } + /* overwrite original plugin form definitions with enhanced form objects */ + $plugins[$pluginName]['forms']['fields'] = $fields; + } + } + $this->c->view->render($response, '/setup.twig', array('settings' => $settings, 'themes' => $themes,'copyright' => $copyright,'plugins' => $plugins)); } - + public function save($request, $response, $args) { if($request->isPost()) { - $params = $request->getParams(); - - $copyright = $this->getCopyright(); - $themes = $this->getThemes(); - $errors = array(); - $uri = $request->getUri(); - $base_url = $uri->getBaseUrl(); - - /* Validate Title */ - if(!isset($params['title'])){ $errors['title'] = 'Please add a title. '; } - if(strlen($params['title']) < 2){ $errors['title'] = 'Title is too short (< 2). '; } - if(strlen($params['title']) > 20){ $errors['title'] = 'Title is too long (> 20). '; } - - /* Validate Author */ - if(isset($params['author']) && !empty($params['author'])) + $settings = $this->c->get('settings'); + $pluginSettings = array(); + $params = $request->getParams(); + $validate = new Validation(); + + /* extract the settings for the basic application and validate them */ + $appSettings = isset($params['settings']) ? $params['settings'] : false; + if($appSettings) { - if(strlen($params['author']) < 2){ $errors['author'] = 'Text is too short (< 2). '; } - if(strlen($params['author']) > 40){ $errors['author'] .= 'Text is too long (> 40). '; } - if(preg_match('/[\(\)\[\]\{\}\?\*\$\"\'\|<>=!;@#%§]/', $params['author'])){ $errors['author'] .= 'Only special chars like a,b a-b a_b a&b are allowed.'; } - } - - /* Validate Year */ - if(!isset($params['year'])){ $errors['year'] = 'Please add a year, e.g. 2017.'; } - if(!preg_match('/^(\d{4})$/', $params['year'])){ $errors['year'] = 'Use four digits for the year like 2017.'; } - - /* Validate Copyright */ - if(isset($params['copyright']) AND !in_array($params['copyright'], $copyright )){ $errors['copyright'] = 'Please select a valid copyright.'; } - - /* Validate Theme */ - if(!isset($params['theme']) AND !in_array($params['theme'], $themes)){ $errors['theme'] = 'Please select a valid theme.'; } - - /* Validate Startpage */ - if(isset($params['startpage'])){ $params['startpage'] = true; }else{ $params['startpage'] = false; } - - /* Validate Folder Writable */ - if(!is_writable($this->c->get('settings')['settingsPath'])){ $errors['folder'] = 'Your settings folder is not writable.'; } - - /* Prevent Title From Hacking */ - $params['title'] = htmlentities(stripslashes($params['title'])); - - if(!empty($errors)) - { - $data = array( - 'themes' => $themes, - 'copyright' => $copyright, - 'errors' => $errors, - 'inputs' => $params, - 'base_url' => $base_url - - ); - $this->c->view->render($response, '/setup.twig', $data); - } - else - { - $file = $this->c->get('settings')['settingsPath'] . DIRECTORY_SEPARATOR . 'settings.yaml'; - $fh = fopen($file, 'w'); - $yaml = Yaml::dump($params); + $copyright = $this->getCopyright(); + $themes = $this->getThemes(); + $appSettings['startpage'] = isset($appSettings['startpage']) ? true : false; - file_put_contents($file, $yaml); - - $data = array( - 'inputs' => $params, - 'base_url' => $base_url - - ); - - $this->c->view->render($response, '/welcome.twig', $data); + $validate->settings($appSettings, $themes, $copyright, 'settings'); } + + /* use the stored user settings and iterate over all original plugin settings, so we do not forget any... */ + foreach($settings['plugins'] as $pluginName => $pluginUserSettings) + { + /* if there are no input-data for this plugin, then use the stored plugin settings */ + if(!isset($params[$pluginName])) + { + $pluginSettings[$pluginName] = $pluginUserSettings; + } + else + { + /* now fetch the original plugin settings from the plugin folder to get the field definitions */ + $pluginOriginalSettings = \Typemill\settings::getPluginSettings($pluginName); + + if($pluginOriginalSettings) + { + /* take the user input data and iterate over all fields and values */ + foreach($params[$pluginName] as $fieldName => $fieldValue) + { + /* get the corresponding field definition from original plugin settings */ + $fieldDefinition = isset($pluginOriginalSettings['forms']['fields'][$fieldName]) ? $pluginOriginalSettings['forms']['fields'][$fieldName] : false; + if($fieldDefinition) + { + /* validate user input for this field */ + $validate->pluginField($fieldName, $fieldValue, $pluginName, $fieldDefinition); + } + } + } + + /* use the input data */ + $pluginSettings[$pluginName] = $params[$pluginName]; + } + + /* deactivate the plugin, if there is no active flag */ + if(!isset($params[$pluginName]['active'])) + { + $pluginSettings[$pluginName]['active'] = false; + } + } + + if(!is_writable($this->c->get('settings')['settingsPath'])) + { + $_SESSION['errors']['folder'] = 'Your settings-folder is not writable'; + } + + if(isset($_SESSION['errors'])) + { + return $response->withRedirect($this->c->router->pathFor('setup')); + } + + /* if everything is valid, add plugin settings to base settings again */ + $appSettings['plugins'] = $pluginSettings; + + /* store updated settings */ + \Typemill\Settings::updateSettings($appSettings); + + unset($_SESSION['old']); + + $this->c->view->render($response, '/welcome.twig', $appSettings); } } - + private function getCopyright() { return array( diff --git a/system/Extensions/TwigCsrfExtension.php b/system/Extensions/TwigCsrfExtension.php new file mode 100644 index 0000000..0418815 --- /dev/null +++ b/system/Extensions/TwigCsrfExtension.php @@ -0,0 +1,31 @@ +guard = $guard; + } + + public function getFunctions() + { + return [ + new \Twig_SimpleFunction('csrf_field', array($this, 'csrfField' )) + ]; + } + + public function csrfField() + { + return ' + + + '; + } +} \ No newline at end of file diff --git a/system/Middleware/OldInputMiddleware.php b/system/Middleware/OldInputMiddleware.php new file mode 100644 index 0000000..0461445 --- /dev/null +++ b/system/Middleware/OldInputMiddleware.php @@ -0,0 +1,32 @@ +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; + } +} \ No newline at end of file diff --git a/system/Middleware/ValidationErrorsMiddleware.php b/system/Middleware/ValidationErrorsMiddleware.php new file mode 100644 index 0000000..a23bfe4 --- /dev/null +++ b/system/Middleware/ValidationErrorsMiddleware.php @@ -0,0 +1,30 @@ +view = $view; + } + + public function __invoke(Request $request, Response $response, $next) + { + if(isset($_SESSION['errors'])) + { + $this->view->getEnvironment()->addGlobal('errors', $_SESSION['errors']); + + unset($_SESSION['errors']); + } + + $response = $next($request, $response); + return $response; + } +} \ No newline at end of file diff --git a/system/Models/Field.php b/system/Models/Field.php new file mode 100644 index 0000000..108859d --- /dev/null +++ b/system/Models/Field.php @@ -0,0 +1,272 @@ +setName($fieldName); + + if(isset($fieldConfigs['type'])) + { + $this->setType($fieldConfigs['type']); + } + + if(isset($fieldConfigs['label'])) + { + $this->setLabel($fieldConfigs['label']); + } + + if(isset($fieldConfigs['options'])) + { + $this->setOptions($fieldConfigs['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; + } + + private function setLabel($label) + { + $this->label = $label; + } + + public function getLabel() + { + return $this->label; + } + + 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; + } + + /* 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) && array_key_exists($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; + } + + private function getAttributeValue($key) + { + if(isset($this->attributeValues[$key])) + { + return $this->attributeValues[$key]; + } + + return false; + } + + + private 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; + } +} \ No newline at end of file diff --git a/system/Models/Validation.php b/system/Models/Validation.php new file mode 100644 index 0000000..d450228 --- /dev/null +++ b/system/Models/Validation.php @@ -0,0 +1,290 @@ +rule('required', ['username', 'password'])->message("notwendig"); + $v->rule('alphaNum', 'username')->message("ungültig"); + $v->rule('lengthBetween', 'password', 5, 20)->message("Länge 5 - 20"); + $v->rule('lengthBetween', 'username', 5, 20)->message("Länge 5 - 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 validateSignup(array $params) + { + $v = new Validator($params); + $v->rule('required', ['signup_username', 'signup_email', 'signup_password'])->message("notwendig"); + $v->rule('alphaNum', 'signup_username')->message("ungültig"); + $v->rule('lengthBetween', 'signup_password', 5, 20)->message("Länge 5 - 20"); + $v->rule('lengthBetween', 'signup_username', 5, 20)->message("Länge 5 - 20"); + $v->rule('userAvailable', 'signup_username')->message("vergeben"); + $v->rule('email', 'signup_email')->message("ungültig"); + $v->rule('emailAvailable', 'signup_email')->message("vergeben"); + + return $this->validationResult($v); + } + + public function validateReset(array $params) + { + $v = new Validator($params); + $v->rule('required', 'email')->message("notwendig"); + $v->rule('email', 'email')->message("ungültig"); + $v->rule('emailKnown', 'email')->message("unbekannt"); + + 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 validatePassword(array $params) + { + $v = new Validator($params); + $v->rule('required', ['password', 'password_old']); + $v->rule('lengthBetween', 'password', 5, 50); + $v->rule('checkPassword', 'password_old'); + + return $this->validationResult($v); + } + + /** + * validation for password reset + * + * @param array $params with form data. + * @return obj $v the validation object passed to a result method. + */ + + public function validateResetPassword(array $params) + { + $v = new Validator($params); + $v->rule('required', ['password', 'username']); + $v->rule(['lengthBetween' => [['password', 5, 50], ['username', 3,20]]]); + + return $this->validationResult($v); + } + + /** + * validation for basic settings input + * + * @param array $params with form data. + * @return obj $v the validation object passed to a result method. + */ + + public function settings(array $params, array $themes, array $copyright, $name = false) + { + $v = new Validator($params); + + $v->rule('required', ['title', 'author', 'copyright', 'year', 'theme']); + $v->rule('lengthBetween', 'title', 2, 20); + $v->rule('lengthBetween', 'author', 2, 40); + $v->rule('regex', 'title', '/^[\pL0-9_ \-]*$/u'); + $v->rule('regex', 'author', '/^[\pL_ \-]*$/u'); + $v->rule('integer', 'year'); + $v->rule('length', 'year', 4); + $v->rule('in', 'copyright', $copyright); + $v->rule('in', 'theme', $themes); + + return $this->validationResult($v, $name); + } + + public function pluginField($fieldName, $fieldValue, $pluginName, $fieldDefinitions) + { + $v = new Validator(array($fieldName => $fieldValue)); + + + if(isset($fieldDefinitions['required'])) + { + $v->rule('required', $fieldName); + } + + switch($fieldDefinitions['type']) + { + case "select": + case "radio": + case "checkboxlist": + $v->rule('in', $fieldName, $fieldDefinitions['options']); + break; + case "color": + $v->rule('regex', $fieldName, '/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/'); + break; + case "email": + $v->rule('email', $fieldName); + break; + case "date": + $v->rule('date', $fieldName); + break; + case "checkbox": + $v->rule('accepted', $fieldName); + break; + case "url": + $v->rule('lengthMax', $fieldName, 200); + $v->rule('url', $fieldName); + break; + case "text": + $v->rule('lengthMax', $fieldName, 200); + $v->rule('regex', $fieldName, '/^[\pL0-9_ \-]*$/u'); + break; + case "textarea": + $v->rule('lengthMax', $fieldName, 1000); + $v->rule('noHTML', $fieldName); + // $v->rule('regex', $fieldName, '/<[^<]+>/'); + break; + default: + $v->rule('lengthMax', $fieldName, 1000); + $v->rule('regex', $fieldName, '/^[\pL0-9_ \-]*$/u'); + } + + return $this->validationResult($v, $pluginName); + } + + /** + * validation for election input + * + * @param array $params with form data. + * @return obj $v the validation object passed to a result method. + */ + + public function validateShowroom(array $params) + { + $v = new Validator($params); + $v->rule('required', ['title']); + $v->rule('lengthBetween', 'title', 2, 50); + $v->rule('regex', 'title', '/^[^-_\-\s][0-9A-Za-zÄäÜüÖöß_ \-]+$/'); + $v->rule('integer', 'election' ); + $v->rule('email', 'email'); + $v->rule('length', 'invite', 40); + $v->rule('alphaNum', 'invite'); + $v->rule('required', ['group_id', 'politician_id', 'ressort_id']); + $v->rule('integer', ['group_id', 'politician_id', 'ressort_id']); + + return $this->validationResult($v); + } + + + public function validateGroup(array $params) + { +// $v->rule('date', 'deadline'); +// $v->rule('dateAfter', 'deadline', new \DateTime('now')); +// $v->rule('dateBefore', 'deadline', new \DateTime($params['election_date'])); + + return $this->validationResult($v); + } + + /** + * result for validation + * + * @param obj $v the validation object. + * @return bool + */ + + public function validationResult($v, $name = false) + { + if($v->validate()) + { + return true; + } + else + { + if($name) + { + if(isset($_SESSION['errors'][$name])) + { + foreach ($v->errors() as $key => $val) + { + $_SESSION['errors'][$name][$key] = $val; + break; + } + } + else + { + $_SESSION['errors'][$name] = $v->errors(); + } + } + else + { + $_SESSION['errors'] = $v->errors(); + } + return false; + } + } +} \ No newline at end of file diff --git a/system/Models/VersionCheck.php b/system/Models/VersionCheck.php index aeb9859..5fa957e 100644 --- a/system/Models/VersionCheck.php +++ b/system/Models/VersionCheck.php @@ -15,16 +15,11 @@ class VersionCheck $context = stream_context_create($opts); - try { - $version = file_get_contents('http://typemill.net/tma1/checkversion', false, $context); - - if ($version) - { - $version = json_decode($version); - return $version->version; - } - } catch (Exception $e) { + if(false === ($version = @file_get_contents('http://typemill.net/api/v1/checkversion', false, $context))) + { return false; } + $version = json_decode($version); + return $version->version; } } \ No newline at end of file diff --git a/system/Models/Write.php b/system/Models/Write.php index 355fa86..b1918ad 100644 --- a/system/Models/Write.php +++ b/system/Models/Write.php @@ -31,7 +31,7 @@ class Write } protected function checkFile($folder, $file) - { + { if(!file_exists($this->basePath . $folder . DIRECTORY_SEPARATOR . $file)) { return false; diff --git a/system/Models/WriteYaml.php b/system/Models/WriteYaml.php index 1ad1f42..c6d3047 100644 --- a/system/Models/WriteYaml.php +++ b/system/Models/WriteYaml.php @@ -12,6 +12,7 @@ class WriteYaml extends Write public function getYaml($folderName, $yamlFileName) { $yaml = $this->getFile($folderName, $yamlFileName); + if($yaml) { return \Symfony\Component\Yaml\Yaml::parse($yaml); @@ -27,7 +28,7 @@ class WriteYaml extends Write */ public function updateYaml($folderName, $yamlFileName, $contentArray) { - $yaml = \Symfony\Component\Yaml\Yaml::dump($contentArray); + $yaml = \Symfony\Component\Yaml\Yaml::dump($contentArray,6); $this->writeFile($folderName, $yamlFileName, $yaml); } } \ No newline at end of file diff --git a/system/Plugin.php b/system/Plugin.php index 3f4e7dc..8cabd6c 100644 --- a/system/Plugin.php +++ b/system/Plugin.php @@ -33,6 +33,11 @@ abstract class Plugin implements EventSubscriberInterface return $this->$dispatcher; } + protected function getTwig() + { + return $this->container['view']; + } + protected function addTwigGlobal($name, $class) { $this->container->view->getEnvironment()->addGlobal($name, $class); @@ -47,7 +52,7 @@ abstract class Plugin implements EventSubscriberInterface protected function addTwigFunction($name, $function) { $function = new \Twig_SimpleFunction($name, $function); - $this->container->view->getEnvironment()->addFunction($function); + $this->container->view->getEnvironment()->addFunction($function); } protected function addJS($JS) diff --git a/system/Plugins.php b/system/Plugins.php index fe3d0e4..9594760 100644 --- a/system/Plugins.php +++ b/system/Plugins.php @@ -8,16 +8,16 @@ class Plugins { $pluginFolder = $this->scanPluginFolder(); $classNames = array(); - + /* iterate over plugin folders */ foreach($pluginFolder as $plugin) { - $className = DIRECTORY_SEPARATOR . 'Plugins' . DIRECTORY_SEPARATOR . $plugin . DIRECTORY_SEPARATOR . $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[] = $className; + $classNames[] = array('className' => $className, 'name' => $plugin); } } return $classNames; diff --git a/system/Routes/web.php b/system/Routes/web.php index 02cea58..c2fdc39 100644 --- a/system/Routes/web.php +++ b/system/Routes/web.php @@ -3,7 +3,7 @@ use Typemill\Controllers\PageController; use Typemill\Controllers\SetupController; -if(!isset($userSettings)) +if($settings['settings']['setup']) { $app->get('/setup', SetupController::class . ':setup')->setName('setup'); $app->post('/setup', SetupController::class . ':save')->setName('save'); diff --git a/system/author/css/color-picker.min.css b/system/author/css/color-picker.min.css new file mode 100644 index 0000000..21a7958 --- /dev/null +++ b/system/author/css/color-picker.min.css @@ -0,0 +1 @@ +.color-picker,.color-picker *,.color-picker :after,.color-picker :before,.color-picker:after,.color-picker:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.color-picker{position:absolute;top:0;left:0;z-index:9999}.color-picker-control{border:1px solid #000;-webkit-box-shadow:1px 5px 10px rgba(0,0,0,.5);-moz-box-shadow:1px 5px 10px rgba(0,0,0,.5);box-shadow:1px 5px 10px rgba(0,0,0,.5)}.color-picker-control *,.color-picker-control :after,.color-picker-control :before{border-color:inherit}.color-picker-control:after{content:" ";display:table;clear:both}.color-picker i{font:inherit}.color-picker-h{position:relative;width:20px;height:150px;float:right;border-left:1px solid;border-left-color:inherit;cursor:ns-resize;background:url(color-picker-h.png) 50% 50% no-repeat;background-image:-webkit-linear-gradient(to top,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red 100%);background-image:-moz-linear-gradient(to top,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red 100%);background-image:linear-gradient(to top,red 0,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red 100%);-webkit-background-size:100% 100%;-moz-background-size:100% 100%;background-size:100% 100%;overflow:hidden}.color-picker-h i,.color-picker-h i:before{left:0;display:block;position:absolute;right:0}.color-picker-h i{top:-3px;z-index:3;height:6px}.color-picker-h i:before{content:"";top:0;bottom:0;border:3px solid;border-color:inherit;border-top-color:transparent;border-bottom-color:transparent}.color-picker-sv{position:relative;width:150px;height:150px;float:left;background:url(color-picker-sv.png) 50% 50% no-repeat;background-image:-webkit-linear-gradient(to top,#000,rgba(0,0,0,0)),linear-gradient(to right,#fff,rgba(255,255,255,0));background-image:-moz-linear-gradient(to top,#000,rgba(0,0,0,0)),linear-gradient(to right,#fff,rgba(255,255,255,0));background-image:linear-gradient(to top,#000,rgba(0,0,0,0)),linear-gradient(to right,#fff,rgba(255,255,255,0));-webkit-background-size:100% 100%;-moz-background-size:100% 100%;background-size:100% 100%;cursor:crosshair}.color-picker-sv i{position:absolute;top:-4px;right:-4px;z-index:3;display:block;width:8px;height:8px}.color-picker-sv i:after,.color-picker-sv i:before{content:"";position:absolute;top:0;right:0;bottom:0;left:0;display:block;border:1px solid;border-color:inherit;-webkit-border-radius:100%;-moz-border-radius:100%;border-radius:100%}.color-picker-sv i:before{top:-1px;right:-1px;bottom:-1px;left:-1px;border-color:#fff}.color-picker-h,.color-picker-sv{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent} \ No newline at end of file diff --git a/system/author/css/style.css b/system/author/css/style.css index b590ddd..0f3176f 100644 --- a/system/author/css/style.css +++ b/system/author/css/style.css @@ -2,7 +2,7 @@ * HELPERS * **********************/ -a, a:link, a:visited, a:focus, a:hover, a:active, button, .button, input{ +a, a:link, a:visited, a:focus, a:hover, a:active, button, .button, input, .control-group{ -webkit-transition: all 0.2s ease; -moz-transition: all 0.2s ease; -o-transition: all 0.2s ease; @@ -14,27 +14,70 @@ a, a:link, a:visited, a:focus, a:hover, a:active, button, .button, input{ * COLORS * **********************/ -body{ background: #f9f8f6; color: #444; } -.card{ background: #FFF; } -.card header{ background: #e0494c; color: #f9f8f6; } -.onoffswitch-label { border: 0px solid #DDD; } -.onoffswitch-inner:before { background: #8ce196; color: #f9f8f6; } -.onoffswitch-inner:after { background: #e0474c; color: #f9f8f6; } -.onoffswitch-switch { background: #f9f8f6; border: 1px solid #999; } -input, select{ border: 1px solid #ddd; background: #fff; } -select:focus, input:focus{ border: 1px solid #FFF; outline: none; box-shadow: 0 0 2px #e0474c; } -input[type="submit"]{ border: 2px solid #FFF; background: #e0474c; color: #f9f8f6; } -input[type="submit"]:hover{ background: #FFF; color: #444; } -a.button{ border: 2px solid #e0474c; background: #e0474c; color: #f9f8f6; } -a.button:hover{ border: 2px solid #e0474c; background: #FFF; color: #444; } +body, .pluginHead header{ background: #f9f8f6; color: #444; } +.errors .pluginHead header{ background: #e0474c; color: #fff; } +.onoffswitch-label { border: 1px solid #999; } +.onoffswitch-inner:before, +.onoffswitch-inner:after { color: #FFFFFF; } +.onoffswitch-inner:before { background: #70c1b3; color: #f9f8f6; } +.onoffswitch-inner:after { background: #f9f8f6; color: #999999; } +.onoffswitch-switch { border: 1px solid #999; background: #f9f8f6; } +input, select, textarea, +.large img{ border: 1px solid #ddd; background: #fff; } +section, .welcome{ background: #FFFFFF; box-shadow: 0 0 2px #ddd; } +select:focus, input:focus, +textarea:focus{ border: 1px solid #FFF; outline: none; box-shadow: 0 0 2px #000; } +button[type="button"].active{ background: #70c1b3; color: #f9f8f6; border-top: 1px solid #70c1b3; } +button[type="button"].active:hover{ background: #66b0a3; border-top: 1px solid #66b0a3; } +button[type="button"]{ background: #f9f8f6; color: #444; border-top: 1px solid #ddd; } +input[type="submit"], +a.button,a.button:link, +a.button:visited{ border: 2px solid #e0474c; background: #e0474c; color: #f9f8f6; } +input[type="submit"]:hover, +a.button:focus,a.button:hover, +a.button:active{ border: 2px solid #cc4146; background: #cc4146; } .error label{ color: #e0474c; } -.error input, .error select{ border: 1px solid #e0474c; } -.error small, .error p{ color: #e0474c; } +.error input, .error select, +.error textarea{ border: 1px solid #e0474c; } +.error, .error small, .error p{ color: #e0474c; } +.pluginInner{ border: 1px solid #ddd; background: #FFF; } +.color-box{ border: 1px solid #ddd; } +label .help, .label .help{ color: #FFF; background: #70c1b3; } +.help .tooltip { color: #fff; background-color: #70c1b3; } +.help .tooltip:after{ border-color: transparent transparent transparent #70c1b3; } + +.checkmark,.radiomark { border: 2px solid #ccc; background-color: #fff;} +.control-group:hover input ~ .checkmark, +.control-group:hover input ~ .radiomark { background-color: #ccc; } +.control-group input:checked ~ .checkmark, +.control-group input:checked ~ .radiomark { border: 2px solid #70c1b3; background-color: #70c1b3; } +.control-group .checkmark:after { border: solid #fff; } +.control-group .radiomark:after { background: #fff; } +.control-group input:disabled ~ .checkmark, +.control-group input:disabled ~ .radiomark { border: 2px solid #eee; background-color: #eee; } +.pluginInfo a,.pluginInfo a:link, +.pluginInfo a:visited{ color: #70c1b3; text-decoration: none;} +.pluginInfo a:focus,.pluginInfo a:hover, +.pluginInfo a:active{ color: #70c1b3; text-decoration: underline;} + /******************** * FONTS * ********************/ +html,body{ + padding: 0; + margin:0; +} + +body{ + padding: 20px; + font-size: 16px; + font-family: Helvetica, Calibri, Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + header, nav, h1, h2, h3, h4, h5, h6{ font-family: arial, sans-serif; } @@ -44,75 +87,109 @@ header, nav, h1, h2, h3, h4, h5, h6{ ********************/ h1, h2, h3, h4, h5, h6{ font-weight: 700; } -h1{ font-size: 1em; margin: 0.6em 0 0.6em; } -h2{ font-size: 1em; margin: 0.6em 0 0.6em; } -h3{ font-size: 1em; margin: 0.6em 0 0.6em; } +h1{ font-size: 1.8em; margin: 1em 0 0.8em; } +h2{ font-size: 1.6em; margin: 1em 0 0.8em; } +h3{ font-size: 1.1em; margin: 0.6em 0 0.6em; } -html,body{ - padding: 0; - margin:0; -} -body{ - padding: 20px; - font-size: 18px; -} -.card{ - display: block; - margin: auto; - max-width: 400px; - box-shadow: 0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12); -} -.card img{ - width: 100%; -} -.card header{ - display: inlin-block; - width: 100%; - padding: 20px; +section{ + margin-bottom: 40px; + padding: 20px 20px 40px; box-sizing: border-box; } -.card header h1{ +header{ + padding: 0 20px; +} +header h1, header h2{ display: inline-block; } -form{} +header input[type="submit"]{ + margin-top: 1.5em; + margin-right: 10px; + float: right; + width: 150px; + box-sizing:border-box; + border: 0px; +} +.formWrapper, .welcome{ + display: block; + position: relative; + max-width: 900px; + margin: auto; +} +.welcome{ + padding: 30px 50px; + box-sizing: border-box; +} +.welcome p{ + font-size: 1.1em; + line-height: 1.5em; + padding: 0px; +} +.welcome a.button{ + margin: 40px auto; + display: block; + width: 200px; + text-align: center; + padding: 10px; + border-radius: 3px; + text-decoration: none; +} + fieldset{ - padding: 30px 20px; + display: block; + position: relative; + width: 100%; margin: 0; box-sizing: border-box; border: none; + padding: 0; +} +.large, .medium, .small{ + padding: 18px 20px; + box-sizing:border-box; + vertical-align: top; +} +form .large, form .medium, form .small{ + width: 100%; + display: block; +} +.large img{ + width: 100%; } label,.label{ - display: block; - font-size: 0.65em; + display: inline-block; + width: 100%; + font-size: 0.75em; font-weight: 300; - padding: 15px 0 0; + padding: 0px; line-height: 1.5em; text-transform: uppercase; } -.field{ - margin: 5px 0; - box-sizing: border-box; +label .help, .label .help{ + float:right; + width: 20px; + height: 20px; + line-height: 20px; + font-weight: bold; + border-radius: 50%; + text-align: center; + cursor: pointer; } -.size100{ - width: 100%; -} -.size25, .size50, .size75{ - display: inline-block; - vertical-align: top; -} -.size75{ width: 74%; } -.size50{ width: 49%; } -.size25{ width: 24%; } -input, select{ - display: inline-block; +input, select, textarea, .onoffswitch{ margin-top: 5px; +} +input, select, textarea{ + display: inline-block; box-shadow: none; width: 100%; - font-size: 0.8em; + font-size: 1em; box-sizing: border-box; min-height: 42px; } -input{ +textarea{ + min-height: 84px; +} +input, textarea{ padding: 10px; } select{ @@ -120,15 +197,109 @@ select{ } input[type="submit"]{ border-radius: 3px; - float: right; - margin-top: 0px; - width: 150px; } -input[type="submit"]:hover{ +button,input[type="submit"]:hover{ cursor: pointer; } -/* Select Button, see https://codepen.io/vkjgr/pen/VYMeXp */ +fieldset.plugin{ + width: 100%; + display: inline-block; + padding: 15px 20px; + box-sizing: border-box; + vertical-align: top; +} +.pluginInner{ + box-sizing: border-box; +} +.pluginHead header, .pluginHead p, .pluginHead ul{ + padding: 15px 20px; +} +.pluginHead header{ + width: 100%; + display: inline-block; + box-sizing: border-box; +} +.pluginHead legend{ + font-size: 1.2em; + font-weight: 700; + float:left; +} +.pluginHead .pluginActive{ + float: right; +} +.pluginHead header label{ + display: inline-block; + width: auto; + line-height: 20px; + margin: 0; + padding: 0 25px 0 0; +} +.pluginHead header .control-group .checkmark{ + right:-5px; + left: auto; +} +.pluginHead header input{ + width: auto; + min-height: 0px; + padding: 0; + margin: 0; + vertical-align: middle; +} +.pluginHead button[type="button"]{ + width: 100%; + text-align: center; + padding: 15px 0; + border-left: 0px; + border-right: 0px; + border-bottom: 0px; + margin-top: 10px; +} +ul.pluginInfo{ + font-size: 0.8em; + padding: 0 20px; +} +.pluginInfo li{ + display: inline-block; + box-sizing: border-box; + vertical-align: top; + padding: 0px 5px 0 0; + margin: 0px 5px 0 0; + color: grey; + border-right: 1px solid grey; +} +.pluginInfo li:last-child{ + border-right: 0px; +} +.pluginFields{ + max-height: 0; + transition: max-height 0.5s ease-out; + overflow: hidden; + padding: 0px 20px; + border: 0px; +} +.pluginFields.open{ + max-height: 1800px; + transition: max-height 0.5s ease-in; + overflow: hidden; + border: 1px; +} +.pluginFields label{ + margin-top: 12px; +} +.pluginFields .pluginField:first-child{ + margin-top: 50px; +} +.pluginFields input, .pluginFields select, .pluginFields textarea, .pluginFields .onoffswitch{ + margin-bottom: 12px; +} + + + +/* +** SELECT BUTTON +** https://codepen.io/vkjgr/pen/VYMeXp +*/ select { /* reset */ -webkit-appearance: none; @@ -147,22 +318,30 @@ select { 5px 5px, 5px 5px, 2.8em 2.8em; - background-repeat: no-repeat; + background-repeat: no-repeat; + cursor: pointer; +} +span.error{ + font-size: 0.8em; + line-height: 1em; +} +.pluginInner span.error{ + display: block; + margin-top: -8px; + margin-bottom: 8px; } -/* On Off Switch */ +/* +** ON OFF SWITCH +** https://proto.io/freebies/onoff/ +*/ .onoffswitch { - margin-top: 5px; - display: inline-block; - position: relative; - width: 100%; - -webkit-user-select:none; + position: relative; + width: 90px; + -webkit-user-select:none; -moz-user-select:none; -ms-user-select:none; } -.onoffswitch label{ - padding: 0; -} .onoffswitch-checkbox { display: none; } @@ -170,32 +349,33 @@ select { display: block; overflow: hidden; cursor: pointer; + border-radius: 20px; } .onoffswitch-inner { display: block; - width: 200%; + width: 200%; margin-left: -100%; transition: margin 0.3s ease-in 0s; } .onoffswitch-inner:before, .onoffswitch-inner:after { display: block; float: left; - width: 50%; - padding: 10px 0; - font-size: 1.5em; - line-height: 1.9em; + width: 50%; + height: 38px; + padding: 0; + line-height: 38px; + font-size: 14px; font-family: Trebuchet, Arial, sans-serif; font-weight: bold; box-sizing: border-box; - min-height: 42px; } .onoffswitch-inner:before { content: "ON"; - padding-left: 20px; + padding-left: 10px; } .onoffswitch-inner:after { content: "OFF"; - padding-right: 20px; + padding-right: 10px; text-align: right; } .onoffswitch-switch { @@ -205,7 +385,7 @@ select { position: absolute; top: 0; bottom: 0; - right: 60%; + right: 56px; border-radius: 20px; transition: all 0.3s ease-in 0s; } @@ -215,17 +395,170 @@ select { .onoffswitch-checkbox:checked + .onoffswitch-label .onoffswitch-switch { right: 0px; } -.welcome{ - padding: 20px; - font-size: 0.9em; -} -a.button{ - display: block; + +/* +** COLOR PICKER +*/ +.color-box{ width: 100%; - padding: 10px; - text-align: center; - margin: 20px 0; - border-radius: 3px; - box-sizing: border-box; - text-decoration: none; -} \ No newline at end of file + height: 36px; + margin-top: 5px; + margin-bottom: 12px; + display: inline-block; +} +.color-box:hover{ + cursor: pointer; +} +.color-picker-control{ + width: 172px; +} +.color-picker-control input{ + margin-top: 0px; +} + +/* +** CHECKBOX / RADIO +*/ + +/* Hide the browser's default checkbox */ +.control-group input { + position: absolute; + opacity: 0; + cursor: pointer; + width: 0; + height: 0; + padding: 0; + margin: 0; + font-size: 0.1em; + min-height: 0px; +} + +/* Customize the label (the container) */ +.control-group { + text-transform: none; + display: block; + position: relative; + padding-left: 30px; + margin-bottom: 7px; + margin-top: 5px; + cursor: pointer; + font-size: 1em; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} +.checkmark { + position: absolute; + top: 0; + left: 0; + height: 16px; + width: 16px; + border-radius: 2px; +} +/* Create the checkmark/indicator (hidden when not checked) */ +.checkmark:after { + content: ""; + position: absolute; + display: none; +} +/* Show the checkmark when checked */ +.control-group input:checked ~ .checkmark:after { + display: block; +} + +/* Style the checkmark/indicator */ +.control-group .checkmark:after { + left: 5px; + top: 0px; + width: 5px; + height: 10px; + border-width: 0 2px 2px 0; + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); +} +/* Create a custom radio button */ +.radiomark { + position: absolute; + top: 0; + left: 0; + height: 16px; + width: 16px; + border-radius: 50%; +} +/* Create the indicator (the dot/circle - hidden when not checked) */ +.radiomark:after { + content: ""; + position: absolute; + display: none; +} +/* Show the indicator (dot/circle) when checked */ +.control-group input:checked ~ .radiomark:after { + display: block; +} +/* Style the indicator (dot/circle) */ +.control-group .radiomark:after { + top: 5px; + left: 5px; + width: 6px; + height: 6px; + border-radius: 50%; +} + +/* TOOLTIP */ +/* Tooltip container */ +.help { + position: relative; + display: inline-block; + border-bottom: 1px dotted black; /* If you want dots under the hoverable text */ +} + +/* Tooltip text */ +.help .tooltip { + visibility: hidden; + width: 200px; + top: -5px; + right: 25px; + text-align: center; + padding: 5px 0; + border-radius: 6px; + + /* Position the tooltip text - see examples below! */ + position: absolute; + z-index: 1; + opacity: 0; + transition: opacity .6s; + + text-transform: none; +} +.help .tooltip:after{ + content: ""; + position: absolute; + top: 15px; + left: 100%; + margin-top: -5px; + border-width: 5px; + border-style: solid; +} +/* Show the tooltip text when you mouse over the tooltip container */ +.help:hover .tooltip { + visibility: visible; + opacity: 1; +} + +@media only screen and (min-width: 600px) { + form .medium{ + width: 49.5%; + display: inline-block; + } + form .small{ + width: 33.3%; + display: inline-block; + } +} +@media only screen and (min-width: 750px) { + fieldset.plugin{ + width: 49.5%; + } +} diff --git a/system/author/js/author.js b/system/author/js/author.js new file mode 100644 index 0000000..408b8d3 --- /dev/null +++ b/system/author/js/author.js @@ -0,0 +1,221 @@ +(function () +{ + + /********************************** + ** Global HttpRequest-Function ** + ** for AJAX-Requests ** + **********************************/ + + function prepareHttpRequest() + { + var httpRequest; + if (window.XMLHttpRequest){ // Mozilla, Safari, ... + httpRequest = new XMLHttpRequest(); + } + else if (window.ActiveXObject){ // IE + try{ + httpRequest = new ActiveXObject("Msxml2.XMLHTTP"); + } + catch (e){ + try{ + httpRequest = new ActiveXObject("Microsoft.XMLHTTP"); + } + catch (e) {} + } + } + if (!httpRequest){ + alert('Giving up :( Cannot create an XMLHTTP instance'); + return false; + } + return httpRequest; + } + + function sendJson(callback, getPost, url, jsonData) + { + var httpRequest = prepareHttpRequest(); + httpRequest.open(getPost, url, true); + httpRequest.onreadystatechange = function(e) + { + if (this.readyState == 4) + { + if(this.status == 200) + { + if(httpRequest.responseText && callback) + { + callback(httpRequest.response); + } + } + else + { + console.log('connection error, status is not 200'); + } + } + }; + + // if you use application/json, make sure you collect the data in php + // with file_get_contents('php://input') instead of $_POST + httpRequest.setRequestHeader('Content-Type', 'application/json'); + // httpRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + httpRequest.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + + httpRequest.send(JSON.stringify(jsonData)); + } + + + /* change the theme if choosen in selectbox */ + var themeSwitch = document.getElementById("themeSwitch"); + if(themeSwitch) + { + var themePrev = document.getElementById("themePrev"), + themePath = themePrev.src.split("themes")[0]; + + themeSwitch.addEventListener('change', function() + { + themePrev.src = themePath + 'themes/' + themeSwitch.value + '/' + themeSwitch.value + '-large.jpg'; + }); + + } + + var pluginVersions = document.getElementsByClassName("fc-plugin-version"); + if(pluginVersions) + { + var query = 'plugins='; + for (var i = 0, len = pluginVersions.length; i < len; i++) + { + query += pluginVersions[i].id + ','; + } + + var getPost = 'GET'; + url = 'http://typemill.net/api/v1/checkversion?' + query; + + sendJson(function(response) + { + if(response !== 'error') + { + var versions = JSON.parse(response); + if(versions.plugins) + { + updatePluginVersions(versions.plugins); + } + } + else + { + return false; + } + }, getPost, url, false); + } + + function updatePluginVersions(pluginVersions) + { + for (var key in pluginVersions) + { + if (pluginVersions.hasOwnProperty(key)) + { + pluginElement = document.getElementById(key); + if(pluginVersions[key] && pluginElement && cmpVersions(pluginVersions[key], pluginElement.innerHTML) > 0) + { + pluginElement.innerHTML = "update to " + pluginVersions[key]; + } + } + } + } + + /* credit: https://stackoverflow.com/questions/6832596/how-to-compare-software-version-number-using-js-only-number */ + function cmpVersions (a, b) + { + var i, diff; + var regExStrip0 = /(\.0+)+$/; + var segmentsA = a.replace(regExStrip0, '').split('.'); + var segmentsB = b.replace(regExStrip0, '').split('.'); + var l = Math.min(segmentsA.length, segmentsB.length); + + for (i = 0; i < l; i++) + { + diff = parseInt(segmentsA[i], 10) - parseInt(segmentsB[i], 10); + if (diff) + { + return diff; + } + } + return segmentsA.length - segmentsB.length; + } + + /* activate/deactivate plugin and open/close settings */ + var plugins = document.getElementsByClassName("plugin"); + if(plugins) + { + for (var i = 0, len = plugins.length; i < len; i++) + { + plugins[i].addEventListener("click", function(e) + { + if(e.target.classList.contains("fc-active")) + { + this.getElementsByClassName("fc-settings")[0].classList.toggle("active"); + } + if(e.target.classList.contains("fc-settings")) + { + this.getElementsByClassName("pluginFields")[0].classList.toggle("open"); + } + }); + } + } + + /* add color picker for color fields */ + var target = document.querySelectorAll('input[type=color]'); + // set hooks for each target element + for (var i = 0, len = target.length; i < len; ++i) + { + var thisTarget = target[i]; + + (function(thisTarget){ + + /* hide the input field and show color box instead */ + var box = document.createElement('div'); + + box.className = 'color-box'; + box.style.backgroundColor = thisTarget.value; + box.setAttribute('data-color', thisTarget.value); + thisTarget.parentNode.insertBefore(box, thisTarget); + thisTarget.type = 'hidden'; + + var picker = new CP(box), + code = document.createElement('input'); + + picker.target.onclick = function(e) + { + e.preventDefault(); + }; + + code.className = 'color-code'; + code.pattern = '^#[A-Fa-f0-9]{6}$'; + code.type = 'text'; + + picker.on("enter", function() { + code.value = '#' + CP._HSV2HEX(this.get()); + }); + + + picker.on("change", function(color) { + thisTarget.value = '#' + color; + this.target.style.backgroundColor = '#' + color; + code.value = '#' + color; + }); + + picker.picker.firstChild.appendChild(code); + + function update() { + if (this.value.length) { + picker.set(this.value); + picker.trigger("change", [this.value.slice(1)]); + } + } + + code.oncut = update; + code.onpaste = update; + code.onkeyup = update; + code.oninput = update; + + + })(thisTarget); + } +})(); \ No newline at end of file diff --git a/system/author/js/color-picker.min.js b/system/author/js/color-picker.min.js new file mode 100644 index 0000000..336fedb --- /dev/null +++ b/system/author/js/color-picker.min.js @@ -0,0 +1,9 @@ +/*! + * ========================================================== + * COLOR PICKER PLUGIN 1.3.5 + * ========================================================== + * Author: Taufik Nurrohman + * License: MIT + * ---------------------------------------------------------- + */ +!function(e,t,n){function a(e){return"undefined"!=typeof e}function l(e){return"string"==typeof e}function h(e){return"object"==typeof e}function p(e){return Object.keys(e).length}function v(e,t,n){return t>e?t:e>n?n:e}function d(e,t){return parseInt(e,t||10)}function g(e){return Math.round(e)}function y(e){var i,f,o,u,s,c,a,l,t=+e[0],n=+e[1],r=+e[2];switch(u=Math.floor(6*t),s=6*t-u,c=r*(1-n),a=r*(1-s*n),l=r*(1-(1-s)*n),u=u||0,a=a||0,l=l||0,u%6){case 0:i=r,f=l,o=c;break;case 1:i=a,f=r,o=c;break;case 2:i=c,f=r,o=l;break;case 3:i=c,f=a,o=r;break;case 4:i=l,f=c,o=r;break;case 5:i=r,f=c,o=a}return[g(255*i),g(255*f),g(255*o)]}function x(e){return m(y(e))}function b(e){var u,t=+e[0],n=+e[1],r=+e[2],i=Math.max(t,n,r),f=Math.min(t,n,r),o=i-f,s=0===i?0:o/i,c=i/255;switch(i){case f:u=0;break;case t:u=n-r+o*(r>n?6:0),u/=6*o;break;case n:u=r-t+2*o,u/=6*o;break;case r:u=t-n+4*o,u/=6*o}return[u,s,c]}function m(e){var t=+e[2]|+e[1]<<8|+e[0]<<16;return t="000000"+t.toString(16),t.slice(-6)}function k(e){return b(H(e))}function H(e){return 3===e.length&&(e=e.replace(/./g,"$&$&")),[d(e[0]+e[1],16),d(e[2]+e[3],16),d(e[4]+e[5],16)]}function w(e){return[+e[0]/360,+e[1]/100,+e[2]/100]}function E(e){return[g(360*+e[0]),g(100*+e[1]),g(100*+e[2])]}function X(e){return[+e[0]/255,+e[1]/255,+e[2]/255]}function S(e){if(h(e))return e;var t=/\s*rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)\s*$/i.exec(e),n=/\s*hsv\s*\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*\)\s*$/i.exec(e),r="#"===e[0]&&e.match(/^#([\da-f]{3}|[\da-f]{6})$/i);return r?k(e.slice(1)):n?w([+n[1],+n[2],+n[3]]):t?b([+t[1],+t[2],+t[3]]):[0,1,1]}var r="__instance__",i="firstChild",f="scrollLeft",o="scrollTop",u="offsetLeft",s="offsetTop",c=setTimeout;!function(e){e.version="1.3.5",e[r]={},e.each=function(t,n){return c(function(){var i,n=e[r];for(i in n)t(n[i],i,n)},0===n?0:n||1),e},e.parse=S,e._HSV2RGB=y,e._HSV2HEX=x,e._RGB2HSV=b,e._HEX2HSV=k,e._HEX2RGB=function(e){return X(H(e))},e.HSV2RGB=function(e){return y(w(e))},e.HSV2HEX=function(e){return x(w(e))},e.RGB2HSV=function(e){return E(b(e))},e.RGB2HEX=m,e.HEX2HSV=function(e){return E(k(e))},e.HEX2RGB=H}(e[n]=function(d,g){function G(e,t,n){e=e.split(/\s+/);for(var r=0,i=e.length;i>r;++r)t.addEventListener(e[r],n,!1)}function R(e,t,n){e=e.split(/\s+/);for(var r=0,i=e.length;i>r;++r)t.removeEventListener(e[r],n)}function M(e,t){var n=t.touches?t.touches[0].pageX:t.pageX,r=t.touches?t.touches[0].pageY:t.pageY,i=C(e);return{x:n-i.l,y:r-i.t}}function C(t){if(t===e)var n=e.pageXOffset||m[f],r=e.pageYOffset||m[o];else for(var n=t[u],r=t[s];t=t.offsetParent;)n+=t[u],r+=t[s];return{l:n,t:r}}function L(e,t){for(;(e=e.parentElement)&&e!==t;);return e}function $(e){e&&e.preventDefault()}function T(t){return t===e?{w:e.innerWidth,h:e.innerHeight}:{w:t.offsetWidth,h:t.offsetHeight}}function j(e){return w||(a(e)?e:!1)}function O(e){w=e}function Y(e,t,n){return a(e)?a(t)?(a(E[e])||(E[e]={}),a(n)||(n=p(E[e])),E[e][n]=t,k):E[e]:E}function N(e,t){return a(e)?a(t)?(delete E[e][t],k):(E[e]={},k):(E={},k)}function P(e,t,n){if(!a(E[e]))return k;if(a(n))a(E[e][n])&&E[e][n].apply(k,t);else for(var r in E[e])E[e][r].apply(k,t);return k}function ie(e,t){e&&"h"!==e||P("change:h",t),e&&"sv"!==e||P("change:sv",t),P("change",t)}function fe(){return X.parentNode}function oe(n,r){function h(e){var t=e.target,n=t===d||L(t,d)===d;n?oe():k.exit(),P(n?"enter":"exit",[k])}function p(e){var n=(y(z),y([z[0],1,1]));D.style.backgroundColor="rgb("+n.join(",")+")",O(z),$(e)}function m(e){var t=v(M(A,e).y,0,o);z[0]=(o-t)/o,I.style.top=t-c/2+"px",p(e)}function H(e){var t=M(D,e),n=v(t.x,0,u),r=v(t.y,0,s);z[1]=1-(u-n)/u,z[2]=(s-r)/s,q.style.right=u-n-a/2+"px",q.style.top=r-l/2+"px",p(e)}function w(e){K&&(m(e),ne=x(z),F||(P("drag:h",[ne,k]),P("drag",[ne,k]),ie("h",[ne,k]))),Q&&(H(e),ne=x(z),J||(P("drag:sv",[ne,k]),P("drag",[ne,k]),ie("sv",[ne,k]))),F=0,J=0}function E(e){var t=e.target,n=K?"h":"sv",r=[x(z),k],i=t===d||L(t,d)===d,f=t===X||L(t,X)===X;i||f?f&&(P("stop:"+n,r),P("stop",r),ie(n,r)):fe()&&g!==!1&&(k.exit(),P("exit",[k]),ie(0,r)),K=0,Q=0}function C(e){F=1,K=1,w(e),$(e),P("start:h",[ne,k]),P("start",[ne,k]),ie("h",[ne,k])}function Y(e){J=1,Q=1,w(e),$(e),P("start:sv",[ne,k]),P("start",[ne,k]),ie("sv",[ne,k])}n||((r||b).appendChild(X),k.visible=!0),ee=T(X).w,te=T(X).h;var i=T(D),f=T(q),o=T(A).h,u=i.w,s=i.h,c=T(I).h,a=f.w,l=f.h;n?(X.style.left=X.style.top="-9999px",g!==!1&&G(g,d,h),k.create=function(){return oe(1),P("create",[k]),k},k.destroy=function(){return g!==!1&&R(g,d,h),k.exit(),O(!1),P("destroy",[k]),k}):ue(),re=function(){z=j(z),p(),I.style.top=o-c/2-o*+z[0]+"px",q.style.right=u-a/2-u*+z[1]+"px",q.style.top=s-l/2-s*+z[2]+"px"},k.exit=function(n){return fe()&&(fe().removeChild(X),k.visible=!1),R(S,A,C),R(S,D,Y),R(_,t,w),R(V,t,E),R(B,e,ue),k},re(),n||(G(S,A,C),G(S,D,Y),G(_,t,w),G(V,t,E),G(B,e,ue))}function ue(){return k.fit()}var b=t.body,m=t.documentElement,k=this,H=e[n],w=!1,E={},X=t.createElement("div"),S="touchstart mousedown",_="touchmove mousemove",V="touchend mouseup",B="orientationchange resize";if(!(k instanceof H))return new H(d,g);H[r][d.id||d.name||p(H[r])]=k,a(g)||(g=S),O(H.parse(d.getAttribute("data-color")||d.value||[0,1,1])),X.className="color-picker",X.innerHTML='
';var re,W=X[i].children,z=j([0,1,1]),A=W[0],D=W[1],I=A[i],q=D[i],F=0,J=0,K=0,Q=0,U=0,Z=0,ee=0,te=0,ne=x(z);return oe(1),c(function(){var e=[x(z),k];P("create",e),ie(0,e)},0),k.fit=function(t){var n=T(e),r=T(m),i=r.h>n.h,f=C(e),u=(C(m),i?r.w:n.w+f.l),s=i?n.h+f.t:Math.max(r.h,n.h),c=C(d);return U=c.l,Z=c.t+T(d).h,h(t)?(a(t[0])&&(U=t[0]),a(t[1])&&(Z=t[1])):(U+ee>u&&(U=u-ee),Z+te>s&&(Z=s-te)),X.style.left=U+"px",X.style.top=Z+"px",P("fit",[k]),k},k.set=function(e){return a(e)?(l(e)&&(e=H.parse(e)),O(e),re(),k):j()},k.get=function(e){return j(e)},k.target=d,k.picker=X,k.visible=!1,k.on=Y,k.off=N,k.trigger=P,k.hooks=E,k.enter=function(e){return oe(0,e)},k})}(window,document,"CP"); \ No newline at end of file diff --git a/system/author/layout.twig b/system/author/layout.twig index 93c69de..e7adccf 100644 --- a/system/author/layout.twig +++ b/system/author/layout.twig @@ -9,10 +9,13 @@ +
{% block content %}{% endblock %}
+ + \ No newline at end of file diff --git a/system/author/setup.twig b/system/author/setup.twig index 3c74eaa..8b02400 100644 --- a/system/author/setup.twig +++ b/system/author/setup.twig @@ -1,74 +1,186 @@ {% extends '/layout.twig' %} - {% block title %}Setup{% endblock %} +{% set startpage = old.settings.startpage ? old.settings.startpage : settings.startpage %} {% block content %} -
+ +
+
+ +
+
-

Setup

- +

Settings

+
+
- {% if errors.folder %} -
-

{{ errors.folder }}

-
- {% endif %} -
- - - {% if errors.title %}{{ errors.title }}{% endif %} -
-
- - - {% if errors.author %}{{ errors.author }}{% endif %} -
-
- - + {% if errors.settings.title %} + {{ errors.settings.title | first }} + {% endif %} +
+ + + {% if errors.settings.author %} + {{ errors.settings.author | first }} + {% endif %} +
+ + - {% if errors.copyright %}{{ errors.copyright }}{% endif %} -
-
- - - {% if errors.year %}{{ errors.year }}{% endif %} -
-
- - + {% if errors.settings.year %} + {{ errors.settings.year | first }} + {% endif %} +
+ + - {% if errors.theme %}{{ errors.theme }}{% endif %} -
-
Startpage -
- - -
+ {% if errors.settings.theme %} + {{ errors.settings.theme | first }} + {% endif %} +
+ Startpage + +
+ +
+
+ +
+ +
+
+

Plugins

+ +
+ + {% for pluginName,plugin in plugins %} + +
+
+
+ {{ pluginName }} +
+ +
+
+ +

{{ plugin.description ? plugin.description : 'No description' }}

+ +
    +
  • {{ plugin.version ? plugin.version : 'Unknown' }}
  • + {{ plugin.licence ? plugin.licence : 'Unkown' }}
  • + by {{ plugin.author ? plugin.author : 'Unknown' }}
  • {% if plugin.homepage %}
  • + Web
  • {% endif %} +
+
+ {% for field in plugin.forms.fields %} + +
+ + + {% if field.type == 'textarea' %} + + + + {% elseif field.type == 'checkbox' %} + + + + {% elseif field.type == 'checkboxlist' %} + + {% set options = field.getOptions() %} + + {% for value,label in options %} + + + + {% endfor %} + + {% elseif field.type == 'select' %} + + {% set options = field.getOptions() %} + + + + {% elseif field.type == 'radio' %} + + {% set options = field.getOptions() %} + + {% for value,label in options %} + + + + {% endfor %} + + {% else %} + + + + {% endif %} + + {% if errors[pluginName][field.name] %} + {{ errors[pluginName][field.name] | first }} + {% endif %} + +
+ {% endfor %} +
+ +
+
+ + {% endfor %} + +
+ + + + {{ csrf_field() | raw }} +
- +
- + {% endblock %} \ No newline at end of file diff --git a/system/author/welcome.twig b/system/author/welcome.twig index 6544faa..349b569 100644 --- a/system/author/welcome.twig +++ b/system/author/welcome.twig @@ -3,15 +3,16 @@ {% block title %}Setup Welcome{% endblock %} {% block content %} -
-
-

Congratulations!

-
-
-

Hello {{ author }}!

-

Your settings are stored in your settings folder now. If you want to change the settings, simply open the file "settings.yaml" and edit them.

-

Not sure how to start? Simply create some content for your new website or visit the homepage and read the documentation...

- Homepage -
+ +
+ +

Congratulations

+ +

Hello {{ author }}!

+

Your settings are stored in your settings folder now. If you want to change the settings, simply open the file "settings.yaml" in the folder "settings" and edit the setting-data.

+

Not sure how to start? Simply create some content for your new website or visit the homepage and read the documentation...

+ Homepage +
+ {% endblock %} \ No newline at end of file diff --git a/system/settings.php b/system/settings.php index f19a7a6..7788d6f 100644 --- a/system/settings.php +++ b/system/settings.php @@ -7,13 +7,16 @@ class Settings public static function loadSettings() { $settings = self::getDefaultSettings(); - $userSettings = self::getUserSettings($settings['settingsPath']); + $userSettings = self::getUserSettings(); if($userSettings) { $settings = array_merge($settings, $userSettings); + $settings['setup'] = false; } + $settings['themePath'] = $settings['rootPath'] . $settings['themeFolder'] . DIRECTORY_SEPARATOR . $settings['theme']; + return array('settings' => $settings); } @@ -36,21 +39,87 @@ class Settings 'settingsPath' => $rootPath . 'settings', 'authorPath' => __DIR__ . DIRECTORY_SEPARATOR . 'author' . DIRECTORY_SEPARATOR, 'contentFolder' => 'content', - 'version' => '1.0.5' + 'version' => '1.1.0', + 'setup' => true ]; } - private function getUserSettings($settingsPath) + private function getUserSettings() { - if(file_exists($settingsPath . DIRECTORY_SEPARATOR . 'settings.yaml')) - { - $yaml = new \Symfony\Component\Yaml\Parser(); + $yaml = new Models\WriteYaml(); + + $userSettings = $yaml->getYaml('settings', 'settings.yaml'); + + return $userSettings; + } + + public static function getPluginSettings($pluginName) + { + $yaml = new Models\WriteYaml(); + + $pluginFolder = 'plugins' . DIRECTORY_SEPARATOR . $pluginName; + $pluginFile = $pluginName . '.yaml'; + $pluginSettings = $yaml->getYaml($pluginFolder, $pluginFile); - try { - $userSettings = $yaml->parse( file_get_contents($settingsPath . DIRECTORY_SEPARATOR . 'settings.yaml' ) ); - } catch (ParseException $e) { - printf("Unable to parse the YAML string: %s", $e->getMessage()); + return $pluginSettings; + } + + public static function getThemeSettings($themeName) + { + $yaml = new Models\WriteYaml(); + + $themeFolder = 'themes' . DIRECTORY_SEPARATOR . $themeName; + $themeFile = $themeName . '.yaml'; + $themeSettings = $yaml->getYaml($themeFolder, $themeFile); + + return $themeSettings; + } + + public static function updateSettings($settings) + { + $yaml = new Models\WriteYaml(); + + /* write settings to yaml */ + $yaml->updateYaml('settings', 'settings.yaml', $settings); + } + + public static function removePluginSettings($pluginName) + { + $userSettings = self::getUserSettings(); + + if($userSettings && isset($userSettings['plugins'][$pluginName])) + { + $yaml = new Models\WriteYaml(); + + /* delete the plugin from settings */ + unset($userSettings['plugins'][$pluginName]); + + /* write settings to yaml */ + $yaml->updateYaml('settings', 'settings.yaml', $userSettings); + } + + return $userSettings; + } + + public static function addPluginSettings($pluginName) + { + $userSettings = self::getUserSettings(); + + if($userSettings) + { + $yaml = new Models\WriteYaml(); + + $pluginSettings = self::getPluginSettings($pluginName); + if($pluginSettings['settings']) + { + $userSettings['plugins'][$pluginName] = $pluginSettings['settings']; } + + $userSettings['plugins'][$pluginName]['active'] = true; + + /* write settings to yaml */ + $yaml->updateYaml('settings', 'settings.yaml', $userSettings); + return $userSettings; } return false; diff --git a/system/system.php b/system/system.php index cb77cc5..c6a133c 100644 --- a/system/system.php +++ b/system/system.php @@ -20,7 +20,6 @@ $dispatcher = new \Symfony\Component\EventDispatcher\EventDispatcher(); ************************/ $settings = Typemill\settings::loadSettings(); -$settings = $dispatcher->dispatch('onSettingsLoaded', new LoadSettingsEvent($settings))->getData(); /************************ * INITIATE SLIM * @@ -34,23 +33,69 @@ $app = new \Slim\App($settings); $container = $app->getContainer(); +/************************ +* ADD CSRF PROTECTION * +************************/ + +$container['csrf'] = function ($c) { + $guard = new \Slim\Csrf\Guard(); + $guard->setPersistentTokenMode(true); + + return $guard; +}; + /************************ * LOAD PLUGINS * ************************/ -$plugins = new Typemill\Plugins(); -$pluginClassNames = $plugins->load(); -$routes = $middleware = array(); +$plugins = new Typemill\Plugins(); +$pluginNames = $plugins->load(); +$pluginSettings['plugins'] = array(); -foreach($pluginClassNames as $pluginClassName) +$routes = $middleware = array(); + +foreach($pluginNames as $pluginName) { - $routes = $plugins->getNewRoutes($pluginClassName, $routes); - $middleware = $plugins->getNewMiddleware($pluginClassName, $middleware); - - $dispatcher->addSubscriber(new $pluginClassName($container)); + $className = $pluginName['className']; + $name = $pluginName['name']; + + /* if plugin is not in user settings yet */ + if(!isset($settings['settings']['plugins'][$name])) + { + /* then read the plugin default settings and write them to the users setting.yaml */ + $updateSettingsYaml = Typemill\settings::addPluginSettings($name); + + /* if default settings are written successfully to user settings, update the pluginSettings */ + if($updateSettingsYaml) + { + $pluginSettings['plugins'][$name] = $updateSettingsYaml; + } + /* if not, then settingsYaml does not exist, so set plugin to false for further use */ + else + { + $pluginSettings['plugins'][$name] = false; + } + /* get settings from di-container and update them with the new plugin Settings */ + $DIsettings = $container->get('settings'); + $DIsettings->replace($pluginSettings); + } + + /* if the plugin is activated, add routes/middleware and add plugin as event subscriber */ + if(isset($settings['settings']['plugins'][$name]['active']) && $settings['settings']['plugins'][$name]['active'] != false) + { + $routes = $plugins->getNewRoutes($className, $routes); + $middleware = $plugins->getNewMiddleware($className, $middleware); + + $dispatcher->addSubscriber(new $className($container)); + } } -$dispatcher->dispatch('onPluginsLoaded', new LoadPluginsEvent($pluginClassNames)); +/* dispatch the event onPluginsLoaded */ +$dispatcher->dispatch('onPluginsLoaded', new LoadPluginsEvent($pluginNames)); + +/* dispatch settings event and get all setting-updates from plugins */ +/* TODO, how to update the settings with a plugin? You cannot replace the full settings in the container, so you have to add settings in the container directly */ +$dispatcher->dispatch('onSettingsLoaded', new LoadSettingsEvent($settings))->getData(); /****************************** * ADD DISPATCHER TO CONTAINER * @@ -61,6 +106,15 @@ $container['dispatcher'] = function($container) use ($dispatcher) return $dispatcher; }; +/******************************** +* ADD ASSET-FUNCTION FOR TWIG * +********************************/ + +$container['assets'] = function($c) +{ + return new \Typemill\Assets($c['request']->getUri()->getBaseUrl()); +}; + /****************************** * ADD FLASH MESSAGES FOR TIWG * ******************************/ @@ -70,39 +124,35 @@ $container['flash'] = function () return new \Slim\Flash\Messages(); }; -/******************************** -* ADD ASSET-FUNCTION FOR TWIG * -********************************/ - -$container['assets'] = function($c) -{ - return new \Typemill\Assets($c['request']->getUri()->getBaseUrl()); -}; - /************************ * LOAD TWIG VIEW * ************************/ -$container['view'] = function ($container) use ($settings) +$container['view'] = function ($container) { - $path = array($settings['settings']['themePath'], $settings['settings']['authorPath']); + $path = array($container->get('settings')['themePath'], $container->get('settings')['authorPath']); $view = new \Slim\Views\Twig( $path, [ 'cache' => false, - 'autoescape' => false + 'autoescape' => false, + 'debug' => true ]); // Instantiate and add Slim specific extension $basePath = rtrim(str_ireplace('index.php', '', $container['request']->getUri()->getBasePath()), '/'); $view->addExtension(new Slim\Views\TwigExtension($container['router'], $basePath)); - $view['baseUrl'] = $container['request']->getUri()->getBaseUrl(); + $view->addExtension(new Twig_Extension_Debug()); + $view->addExtension(new Typemill\Extensions\TwigCsrfExtension($container['csrf'])); + + /* use {{ base_url() }} in twig templates */ + $view['base_url'] = $container['request']->getUri()->getBaseUrl(); /* add flash messages to all views */ $view->getEnvironment()->addGlobal('flash', $container->flash); /* add asset-function to all views */ $view->getEnvironment()->addGlobal('assets', $container->assets); - + return $view; }; @@ -131,6 +181,10 @@ foreach($middleware as $pluginMiddleware) } } +$app->add(new \Typemill\Middleware\ValidationErrorsMiddleware($container['view'])); +$app->add(new \Typemill\Middleware\OldInputMiddleware($container['view'])); +$app->add($container->get('csrf')); + /************************ * ADD ROUTES * ************************/ diff --git a/system/vendor/composer/autoload_files.php b/system/vendor/composer/autoload_files.php index 2756ddb..8816ca1 100644 --- a/system/vendor/composer/autoload_files.php +++ b/system/vendor/composer/autoload_files.php @@ -7,4 +7,5 @@ $baseDir = dirname(dirname($vendorDir)); return array( '253c157292f75eb38082b5acb06f3f01' => $vendorDir . '/nikic/fast-route/src/functions.php', + '5255c38a0faeba867671b61dfda6d864' => $vendorDir . '/paragonie/random_compat/lib/random.php', ); diff --git a/system/vendor/composer/autoload_namespaces.php b/system/vendor/composer/autoload_namespaces.php index 8ccf941..e44a58f 100644 --- a/system/vendor/composer/autoload_namespaces.php +++ b/system/vendor/composer/autoload_namespaces.php @@ -6,6 +6,7 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname(dirname($vendorDir)); return array( + 'Valitron' => array($vendorDir . '/vlucas/valitron/src'), 'URLify' => array($vendorDir . '/jbroadway/urlify'), 'Twig_' => array($vendorDir . '/twig/twig/lib'), 'Pimple' => array($vendorDir . '/pimple/pimple/src'), diff --git a/system/vendor/composer/autoload_psr4.php b/system/vendor/composer/autoload_psr4.php index 5aeb42e..2351a6e 100644 --- a/system/vendor/composer/autoload_psr4.php +++ b/system/vendor/composer/autoload_psr4.php @@ -12,6 +12,7 @@ return array( 'Symfony\\Component\\EventDispatcher\\' => array($vendorDir . '/symfony/event-dispatcher'), 'Slim\\Views\\' => array($vendorDir . '/slim/twig-view/src'), 'Slim\\Flash\\' => array($vendorDir . '/slim/flash/src'), + 'Slim\\Csrf\\' => array($vendorDir . '/slim/csrf/src'), 'Slim\\' => array($vendorDir . '/slim/slim/Slim'), 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src'), 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), diff --git a/system/vendor/composer/autoload_static.php b/system/vendor/composer/autoload_static.php index da3be67..3680cb5 100644 --- a/system/vendor/composer/autoload_static.php +++ b/system/vendor/composer/autoload_static.php @@ -8,6 +8,7 @@ class ComposerStaticInit836351be733ecbf4741aea17e1973480 { public static $files = array ( '253c157292f75eb38082b5acb06f3f01' => __DIR__ . '/..' . '/nikic/fast-route/src/functions.php', + '5255c38a0faeba867671b61dfda6d864' => __DIR__ . '/..' . '/paragonie/random_compat/lib/random.php', ); public static $prefixLengthsPsr4 = array ( @@ -22,6 +23,7 @@ class ComposerStaticInit836351be733ecbf4741aea17e1973480 'Symfony\\Component\\EventDispatcher\\' => 34, 'Slim\\Views\\' => 11, 'Slim\\Flash\\' => 11, + 'Slim\\Csrf\\' => 10, 'Slim\\' => 5, ), 'P' => @@ -65,6 +67,10 @@ class ComposerStaticInit836351be733ecbf4741aea17e1973480 array ( 0 => __DIR__ . '/..' . '/slim/flash/src', ), + 'Slim\\Csrf\\' => + array ( + 0 => __DIR__ . '/..' . '/slim/csrf/src', + ), 'Slim\\' => array ( 0 => __DIR__ . '/..' . '/slim/slim/Slim', @@ -92,6 +98,13 @@ class ComposerStaticInit836351be733ecbf4741aea17e1973480 ); public static $prefixesPsr0 = array ( + 'V' => + array ( + 'Valitron' => + array ( + 0 => __DIR__ . '/..' . '/vlucas/valitron/src', + ), + ), 'U' => array ( 'URLify' => diff --git a/system/vendor/composer/installed.json b/system/vendor/composer/installed.json index f1981ce..98ccece 100644 --- a/system/vendor/composer/installed.json +++ b/system/vendor/composer/installed.json @@ -180,58 +180,6 @@ "description": "Promoting the interoperability of container objects (DIC, SL, etc.)", "homepage": "https://github.com/container-interop/container-interop" }, - { - "name": "pimple/pimple", - "version": "v3.2.2", - "version_normalized": "3.2.2.0", - "source": { - "type": "git", - "url": "https://github.com/silexphp/Pimple.git", - "reference": "4d45fb62d96418396ec58ba76e6f065bca16e10a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/silexphp/Pimple/zipball/4d45fb62d96418396ec58ba76e6f065bca16e10a", - "reference": "4d45fb62d96418396ec58ba76e6f065bca16e10a", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "psr/container": "^1.0" - }, - "require-dev": { - "symfony/phpunit-bridge": "^3.2" - }, - "time": "2017-07-23T07:32:15+00:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2.x-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-0": { - "Pimple": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - } - ], - "description": "Pimple, a simple Dependency Injection Container", - "homepage": "http://pimple.sensiolabs.org", - "keywords": [ - "container", - "dependency injection" - ] - }, { "name": "twig/twig", "version": "v1.35.0", @@ -624,24 +572,174 @@ ] }, { - "name": "symfony/yaml", - "version": "v2.8.31", - "version_normalized": "2.8.31.0", + "name": "vlucas/valitron", + "version": "dev-master", + "version_normalized": "9999999-dev", "source": { "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "d819bf267e901727141fe828ae888486fd21236e" + "url": "https://github.com/vlucas/valitron.git", + "reference": "1712b04f16db5f243ddfa5ce26a9b51eb1948a36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/d819bf267e901727141fe828ae888486fd21236e", - "reference": "d819bf267e901727141fe828ae888486fd21236e", + "url": "https://api.github.com/repos/vlucas/valitron/zipball/1712b04f16db5f243ddfa5ce26a9b51eb1948a36", + "reference": "1712b04f16db5f243ddfa5ce26a9b51eb1948a36", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.8.35" + }, + "time": "2017-12-20T08:03:40+00:00", + "type": "library", + "installation-source": "source", + "autoload": { + "psr-0": { + "Valitron": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD" + ], + "authors": [ + { + "name": "Vance Lucas", + "email": "vance@vancelucas.com", + "homepage": "http://www.vancelucas.com" + } + ], + "description": "Simple, elegant, stand-alone validation library with NO dependencies", + "homepage": "http://github.com/vlucas/valitron", + "keywords": [ + "valid", + "validation", + "validator" + ] + }, + { + "name": "paragonie/random_compat", + "version": "v2.0.11", + "version_normalized": "2.0.11.0", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/5da4d3c796c275c55f057af5a643ae297d96b4d8", + "reference": "5da4d3c796c275c55f057af5a643ae297d96b4d8", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "time": "2017-09-27T21:40:39+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "lib/random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "pseudorandom", + "random" + ] + }, + { + "name": "slim/csrf", + "version": "0.8.2", + "version_normalized": "0.8.2.0", + "source": { + "type": "git", + "url": "https://github.com/slimphp/Slim-Csrf.git", + "reference": "e416320bc0eecdf409aefaad2889737da095ff9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/slimphp/Slim-Csrf/zipball/e416320bc0eecdf409aefaad2889737da095ff9a", + "reference": "e416320bc0eecdf409aefaad2889737da095ff9a", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "^1.1|^2.0", + "php": ">=5.5.0", + "psr/http-message": "^1.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.0", + "slim/slim": "~3.0" + }, + "time": "2017-10-14T07:59:05+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Slim\\Csrf\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "http://joshlockhart.com" + } + ], + "description": "Slim Framework 3 CSRF protection middleware", + "homepage": "http://slimframework.com", + "keywords": [ + "csrf", + "framework", + "middleware", + "slim" + ] + }, + { + "name": "symfony/yaml", + "version": "v2.8.34", + "version_normalized": "2.8.34.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "be720fcfae4614df204190d57795351059946a77" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/be720fcfae4614df204190d57795351059946a77", + "reference": "be720fcfae4614df204190d57795351059946a77", "shasum": "" }, "require": { "php": ">=5.3.9" }, - "time": "2017-11-05T15:25:56+00:00", + "time": "2018-01-03T07:36:31+00:00", "type": "library", "extra": { "branch-alias": { @@ -675,19 +773,18 @@ "homepage": "https://symfony.com" }, { -<<<<<<< HEAD "name": "symfony/event-dispatcher", - "version": "v3.3.13", - "version_normalized": "3.3.13.0", + "version": "v3.4.4", + "version_normalized": "3.4.4.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "271d8c27c3ec5ecee6e2ac06016232e249d638d9" + "reference": "26b87b6bca8f8f797331a30b76fdae5342dc26ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/271d8c27c3ec5ecee6e2ac06016232e249d638d9", - "reference": "271d8c27c3ec5ecee6e2ac06016232e249d638d9", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/26b87b6bca8f8f797331a30b76fdae5342dc26ca", + "reference": "26b87b6bca8f8f797331a30b76fdae5342dc26ca", "shasum": "" }, "require": { @@ -698,20 +795,20 @@ }, "require-dev": { "psr/log": "~1.0", - "symfony/config": "~2.8|~3.0", - "symfony/dependency-injection": "~3.3", - "symfony/expression-language": "~2.8|~3.0", - "symfony/stopwatch": "~2.8|~3.0" + "symfony/config": "~2.8|~3.0|~4.0", + "symfony/dependency-injection": "~3.3|~4.0", + "symfony/expression-language": "~2.8|~3.0|~4.0", + "symfony/stopwatch": "~2.8|~3.0|~4.0" }, "suggest": { "symfony/dependency-injection": "", "symfony/http-kernel": "" }, - "time": "2017-11-05T15:47:03+00:00", + "time": "2018-01-03T07:37:34+00:00", "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "3.4-dev" } }, "installation-source": "dist", @@ -722,35 +819,6 @@ "exclude-from-classmap": [ "/Tests/" ] -======= - "name": "erusev/parsedown", - "version": "1.6.4", - "version_normalized": "1.6.4.0", - "source": { - "type": "git", - "url": "https://github.com/erusev/parsedown.git", - "reference": "fbe3fe878f4fe69048bb8a52783a09802004f548" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/fbe3fe878f4fe69048bb8a52783a09802004f548", - "reference": "fbe3fe878f4fe69048bb8a52783a09802004f548", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35" - }, - "time": "2017-11-14T20:44:03+00:00", - "type": "library", - "installation-source": "source", - "autoload": { - "psr-0": { - "Parsedown": "" - } ->>>>>>> master }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -758,7 +826,6 @@ ], "authors": [ { -<<<<<<< HEAD "name": "Fabien Potencier", "email": "fabien@symfony.com" }, @@ -769,18 +836,57 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com" -======= - "name": "Emanuil Rusev", - "email": "hello@erusev.com", - "homepage": "http://erusev.com" + }, + { + "name": "pimple/pimple", + "version": "v3.2.3", + "version_normalized": "3.2.3.0", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/9e403941ef9d65d20cba7d54e29fe906db42cf32", + "reference": "9e403941ef9d65d20cba7d54e29fe906db42cf32", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/container": "^1.0" + }, + "require-dev": { + "symfony/phpunit-bridge": "^3.2" + }, + "time": "2018-01-21T07:42:36+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-0": { + "Pimple": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" } ], - "description": "Parser for Markdown.", - "homepage": "http://parsedown.org", + "description": "Pimple, a simple Dependency Injection Container", + "homepage": "http://pimple.sensiolabs.org", "keywords": [ - "markdown", - "parser" + "container", + "dependency injection" ] ->>>>>>> master } ] diff --git a/system/vendor/paragonie/random_compat/LICENSE b/system/vendor/paragonie/random_compat/LICENSE new file mode 100644 index 0000000..45c7017 --- /dev/null +++ b/system/vendor/paragonie/random_compat/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Paragon Initiative Enterprises + +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. + diff --git a/system/vendor/paragonie/random_compat/build-phar.sh b/system/vendor/paragonie/random_compat/build-phar.sh new file mode 100644 index 0000000..b4a5ba3 --- /dev/null +++ b/system/vendor/paragonie/random_compat/build-phar.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +basedir=$( dirname $( readlink -f ${BASH_SOURCE[0]} ) ) + +php -dphar.readonly=0 "$basedir/other/build_phar.php" $* \ No newline at end of file diff --git a/system/vendor/paragonie/random_compat/composer.json b/system/vendor/paragonie/random_compat/composer.json new file mode 100644 index 0000000..1c5978c --- /dev/null +++ b/system/vendor/paragonie/random_compat/composer.json @@ -0,0 +1,37 @@ +{ + "name": "paragonie/random_compat", + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "random", + "pseudorandom" + ], + "license": "MIT", + "type": "library", + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "support": { + "issues": "https://github.com/paragonie/random_compat/issues", + "email": "info@paragonie.com", + "source": "https://github.com/paragonie/random_compat" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "autoload": { + "files": [ + "lib/random.php" + ] + } +} diff --git a/system/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey b/system/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey new file mode 100644 index 0000000..eb50ebf --- /dev/null +++ b/system/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEEd+wCqJDrx5B4OldM0dQE0ZMX+lx1ZWm +pui0SUqD4G29L3NGsz9UhJ/0HjBdbnkhIK5xviT0X5vtjacF6ajgcCArbTB+ds+p ++h7Q084NuSuIpNb6YPfoUFgC/CL9kAoc +-----END PUBLIC KEY----- diff --git a/system/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc b/system/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc new file mode 100644 index 0000000..6a1d7f3 --- /dev/null +++ b/system/vendor/paragonie/random_compat/dist/random_compat.phar.pubkey.asc @@ -0,0 +1,11 @@ +-----BEGIN PGP SIGNATURE----- +Version: GnuPG v2.0.22 (MingW32) + +iQEcBAABAgAGBQJWtW1hAAoJEGuXocKCZATaJf0H+wbZGgskK1dcRTsuVJl9IWip +QwGw/qIKI280SD6/ckoUMxKDCJiFuPR14zmqnS36k7N5UNPnpdTJTS8T11jttSpg +1LCmgpbEIpgaTah+cELDqFCav99fS+bEiAL5lWDAHBTE/XPjGVCqeehyPYref4IW +NDBIEsvnHPHPLsn6X5jq4+Yj5oUixgxaMPiR+bcO4Sh+RzOVB6i2D0upWfRXBFXA +NNnsg9/zjvoC7ZW73y9uSH+dPJTt/Vgfeiv52/v41XliyzbUyLalf02GNPY+9goV +JHG1ulEEBJOCiUD9cE1PUIJwHA/HqyhHIvV350YoEFiHl8iSwm7SiZu5kPjaq74= +=B6+8 +-----END PGP SIGNATURE----- diff --git a/system/vendor/paragonie/random_compat/lib/byte_safe_strings.php b/system/vendor/paragonie/random_compat/lib/byte_safe_strings.php new file mode 100644 index 0000000..3de86b2 --- /dev/null +++ b/system/vendor/paragonie/random_compat/lib/byte_safe_strings.php @@ -0,0 +1,181 @@ + RandomCompat_strlen($binary_string)) { + return ''; + } + + return (string) mb_substr($binary_string, $start, $length, '8bit'); + } + + } else { + + /** + * substr() implementation that isn't brittle to mbstring.func_overload + * + * This version just uses the default substr() + * + * @param string $binary_string + * @param int $start + * @param int $length (optional) + * + * @throws TypeError + * + * @return string + */ + function RandomCompat_substr($binary_string, $start, $length = null) + { + if (!is_string($binary_string)) { + throw new TypeError( + 'RandomCompat_substr(): First argument should be a string' + ); + } + + if (!is_int($start)) { + throw new TypeError( + 'RandomCompat_substr(): Second argument should be an integer' + ); + } + + if ($length !== null) { + if (!is_int($length)) { + throw new TypeError( + 'RandomCompat_substr(): Third argument should be an integer, or omitted' + ); + } + + return (string) substr($binary_string, $start, $length); + } + + return (string) substr($binary_string, $start); + } + } +} diff --git a/system/vendor/paragonie/random_compat/lib/cast_to_int.php b/system/vendor/paragonie/random_compat/lib/cast_to_int.php new file mode 100644 index 0000000..9a4fab9 --- /dev/null +++ b/system/vendor/paragonie/random_compat/lib/cast_to_int.php @@ -0,0 +1,75 @@ + operators might accidentally let a float + * through. + * + * @param int|float $number The number we want to convert to an int + * @param bool $fail_open Set to true to not throw an exception + * + * @return float|int + * @psalm-suppress InvalidReturnType + * + * @throws TypeError + */ + function RandomCompat_intval($number, $fail_open = false) + { + if (is_int($number) || is_float($number)) { + $number += 0; + } elseif (is_numeric($number)) { + $number += 0; + } + + if ( + is_float($number) + && + $number > ~PHP_INT_MAX + && + $number < PHP_INT_MAX + ) { + $number = (int) $number; + } + + if (is_int($number)) { + return (int) $number; + } elseif (!$fail_open) { + throw new TypeError( + 'Expected an integer.' + ); + } + return $number; + } +} diff --git a/system/vendor/paragonie/random_compat/lib/error_polyfill.php b/system/vendor/paragonie/random_compat/lib/error_polyfill.php new file mode 100644 index 0000000..6a91990 --- /dev/null +++ b/system/vendor/paragonie/random_compat/lib/error_polyfill.php @@ -0,0 +1,49 @@ += 70000) { + return; +} + +if (!defined('RANDOM_COMPAT_READ_BUFFER')) { + define('RANDOM_COMPAT_READ_BUFFER', 8); +} + +$RandomCompatDIR = dirname(__FILE__); + +require_once $RandomCompatDIR . '/byte_safe_strings.php'; +require_once $RandomCompatDIR . '/cast_to_int.php'; +require_once $RandomCompatDIR . '/error_polyfill.php'; + +if (!is_callable('random_bytes')) { + /** + * PHP 5.2.0 - 5.6.x way to implement random_bytes() + * + * We use conditional statements here to define the function in accordance + * to the operating environment. It's a micro-optimization. + * + * In order of preference: + * 1. Use libsodium if available. + * 2. fread() /dev/urandom if available (never on Windows) + * 3. mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM) + * 4. COM('CAPICOM.Utilities.1')->GetRandom() + * + * See RATIONALE.md for our reasoning behind this particular order + */ + if (extension_loaded('libsodium')) { + // See random_bytes_libsodium.php + if (PHP_VERSION_ID >= 50300 && is_callable('\\Sodium\\randombytes_buf')) { + require_once $RandomCompatDIR . '/random_bytes_libsodium.php'; + } elseif (method_exists('Sodium', 'randombytes_buf')) { + require_once $RandomCompatDIR . '/random_bytes_libsodium_legacy.php'; + } + } + + /** + * Reading directly from /dev/urandom: + */ + if (DIRECTORY_SEPARATOR === '/') { + // DIRECTORY_SEPARATOR === '/' on Unix-like OSes -- this is a fast + // way to exclude Windows. + $RandomCompatUrandom = true; + $RandomCompat_basedir = ini_get('open_basedir'); + + if (!empty($RandomCompat_basedir)) { + $RandomCompat_open_basedir = explode( + PATH_SEPARATOR, + strtolower($RandomCompat_basedir) + ); + $RandomCompatUrandom = (array() !== array_intersect( + array('/dev', '/dev/', '/dev/urandom'), + $RandomCompat_open_basedir + )); + $RandomCompat_open_basedir = null; + } + + if ( + !is_callable('random_bytes') + && + $RandomCompatUrandom + && + @is_readable('/dev/urandom') + ) { + // Error suppression on is_readable() in case of an open_basedir + // or safe_mode failure. All we care about is whether or not we + // can read it at this point. If the PHP environment is going to + // panic over trying to see if the file can be read in the first + // place, that is not helpful to us here. + + // See random_bytes_dev_urandom.php + require_once $RandomCompatDIR . '/random_bytes_dev_urandom.php'; + } + // Unset variables after use + $RandomCompat_basedir = null; + } else { + $RandomCompatUrandom = false; + } + + /** + * mcrypt_create_iv() + * + * We only want to use mcypt_create_iv() if: + * + * - random_bytes() hasn't already been defined + * - the mcrypt extensions is loaded + * - One of these two conditions is true: + * - We're on Windows (DIRECTORY_SEPARATOR !== '/') + * - We're not on Windows and /dev/urandom is readabale + * (i.e. we're not in a chroot jail) + * - Special case: + * - If we're not on Windows, but the PHP version is between + * 5.6.10 and 5.6.12, we don't want to use mcrypt. It will + * hang indefinitely. This is bad. + * - If we're on Windows, we want to use PHP >= 5.3.7 or else + * we get insufficient entropy errors. + */ + if ( + !is_callable('random_bytes') + && + // Windows on PHP < 5.3.7 is broken, but non-Windows is not known to be. + (DIRECTORY_SEPARATOR === '/' || PHP_VERSION_ID >= 50307) + && + // Prevent this code from hanging indefinitely on non-Windows; + // see https://bugs.php.net/bug.php?id=69833 + ( + DIRECTORY_SEPARATOR !== '/' || + (PHP_VERSION_ID <= 50609 || PHP_VERSION_ID >= 50613) + ) + && + extension_loaded('mcrypt') + ) { + // See random_bytes_mcrypt.php + require_once $RandomCompatDIR . '/random_bytes_mcrypt.php'; + } + $RandomCompatUrandom = null; + + /** + * This is a Windows-specific fallback, for when the mcrypt extension + * isn't loaded. + */ + if ( + !is_callable('random_bytes') + && + extension_loaded('com_dotnet') + && + class_exists('COM') + ) { + $RandomCompat_disabled_classes = preg_split( + '#\s*,\s*#', + strtolower(ini_get('disable_classes')) + ); + + if (!in_array('com', $RandomCompat_disabled_classes)) { + try { + $RandomCompatCOMtest = new COM('CAPICOM.Utilities.1'); + if (method_exists($RandomCompatCOMtest, 'GetRandom')) { + // See random_bytes_com_dotnet.php + require_once $RandomCompatDIR . '/random_bytes_com_dotnet.php'; + } + } catch (com_exception $e) { + // Don't try to use it. + } + } + $RandomCompat_disabled_classes = null; + $RandomCompatCOMtest = null; + } + + /** + * throw new Exception + */ + if (!is_callable('random_bytes')) { + /** + * We don't have any more options, so let's throw an exception right now + * and hope the developer won't let it fail silently. + * + * @param mixed $length + * @return void + * @throws Exception + */ + function random_bytes($length) + { + unset($length); // Suppress "variable not used" warnings. + throw new Exception( + 'There is no suitable CSPRNG installed on your system' + ); + } + } +} + +if (!is_callable('random_int')) { + require_once $RandomCompatDIR . '/random_int.php'; +} + +$RandomCompatDIR = null; diff --git a/system/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php b/system/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php new file mode 100644 index 0000000..fc1926e --- /dev/null +++ b/system/vendor/paragonie/random_compat/lib/random_bytes_com_dotnet.php @@ -0,0 +1,88 @@ +GetRandom($bytes, 0)); + if (RandomCompat_strlen($buf) >= $bytes) { + /** + * Return our random entropy buffer here: + */ + return RandomCompat_substr($buf, 0, $bytes); + } + ++$execCount; + } while ($execCount < $bytes); + + /** + * If we reach here, PHP has failed us. + */ + throw new Exception( + 'Could not gather sufficient random data' + ); + } +} \ No newline at end of file diff --git a/system/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php b/system/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php new file mode 100644 index 0000000..df5b915 --- /dev/null +++ b/system/vendor/paragonie/random_compat/lib/random_bytes_dev_urandom.php @@ -0,0 +1,167 @@ + 0); + + /** + * Is our result valid? + */ + if (is_string($buf)) { + if (RandomCompat_strlen($buf) === $bytes) { + /** + * Return our random entropy buffer here: + */ + return $buf; + } + } + } + + /** + * If we reach here, PHP has failed us. + */ + throw new Exception( + 'Error reading from source device' + ); + } +} diff --git a/system/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php b/system/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php new file mode 100644 index 0000000..4af1a24 --- /dev/null +++ b/system/vendor/paragonie/random_compat/lib/random_bytes_libsodium.php @@ -0,0 +1,88 @@ + 2147483647) { + $buf = ''; + for ($i = 0; $i < $bytes; $i += 1073741824) { + $n = ($bytes - $i) > 1073741824 + ? 1073741824 + : $bytes - $i; + $buf .= \Sodium\randombytes_buf($n); + } + } else { + $buf = \Sodium\randombytes_buf($bytes); + } + + if ($buf !== false) { + if (RandomCompat_strlen($buf) === $bytes) { + return $buf; + } + } + + /** + * If we reach here, PHP has failed us. + */ + throw new Exception( + 'Could not gather sufficient random data' + ); + } +} diff --git a/system/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php b/system/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php new file mode 100644 index 0000000..705af52 --- /dev/null +++ b/system/vendor/paragonie/random_compat/lib/random_bytes_libsodium_legacy.php @@ -0,0 +1,92 @@ + 2147483647) { + for ($i = 0; $i < $bytes; $i += 1073741824) { + $n = ($bytes - $i) > 1073741824 + ? 1073741824 + : $bytes - $i; + $buf .= Sodium::randombytes_buf((int) $n); + } + } else { + $buf .= Sodium::randombytes_buf((int) $bytes); + } + + if (is_string($buf)) { + if (RandomCompat_strlen($buf) === $bytes) { + return $buf; + } + } + + /** + * If we reach here, PHP has failed us. + */ + throw new Exception( + 'Could not gather sufficient random data' + ); + } +} diff --git a/system/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php b/system/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php new file mode 100644 index 0000000..aac9c01 --- /dev/null +++ b/system/vendor/paragonie/random_compat/lib/random_bytes_mcrypt.php @@ -0,0 +1,77 @@ + operators might accidentally let a float + * through. + */ + + try { + $min = RandomCompat_intval($min); + } catch (TypeError $ex) { + throw new TypeError( + 'random_int(): $min must be an integer' + ); + } + + try { + $max = RandomCompat_intval($max); + } catch (TypeError $ex) { + throw new TypeError( + 'random_int(): $max must be an integer' + ); + } + + /** + * Now that we've verified our weak typing system has given us an integer, + * let's validate the logic then we can move forward with generating random + * integers along a given range. + */ + if ($min > $max) { + throw new Error( + 'Minimum value must be less than or equal to the maximum value' + ); + } + + if ($max === $min) { + return (int) $min; + } + + /** + * Initialize variables to 0 + * + * We want to store: + * $bytes => the number of random bytes we need + * $mask => an integer bitmask (for use with the &) operator + * so we can minimize the number of discards + */ + $attempts = $bits = $bytes = $mask = $valueShift = 0; + + /** + * At this point, $range is a positive number greater than 0. It might + * overflow, however, if $max - $min > PHP_INT_MAX. PHP will cast it to + * a float and we will lose some precision. + */ + $range = $max - $min; + + /** + * Test for integer overflow: + */ + if (!is_int($range)) { + + /** + * Still safely calculate wider ranges. + * Provided by @CodesInChaos, @oittaa + * + * @ref https://gist.github.com/CodesInChaos/03f9ea0b58e8b2b8d435 + * + * We use ~0 as a mask in this case because it generates all 1s + * + * @ref https://eval.in/400356 (32-bit) + * @ref http://3v4l.org/XX9r5 (64-bit) + */ + $bytes = PHP_INT_SIZE; + $mask = ~0; + + } else { + + /** + * $bits is effectively ceil(log($range, 2)) without dealing with + * type juggling + */ + while ($range > 0) { + if ($bits % 8 === 0) { + ++$bytes; + } + ++$bits; + $range >>= 1; + $mask = $mask << 1 | 1; + } + $valueShift = $min; + } + + $val = 0; + /** + * Now that we have our parameters set up, let's begin generating + * random integers until one falls between $min and $max + */ + do { + /** + * The rejection probability is at most 0.5, so this corresponds + * to a failure probability of 2^-128 for a working RNG + */ + if ($attempts > 128) { + throw new Exception( + 'random_int: RNG is broken - too many rejections' + ); + } + + /** + * Let's grab the necessary number of random bytes + */ + $randomByteString = random_bytes($bytes); + + /** + * Let's turn $randomByteString into an integer + * + * This uses bitwise operators (<< and |) to build an integer + * out of the values extracted from ord() + * + * Example: [9F] | [6D] | [32] | [0C] => + * 159 + 27904 + 3276800 + 201326592 => + * 204631455 + */ + $val &= 0; + for ($i = 0; $i < $bytes; ++$i) { + $val |= ord($randomByteString[$i]) << ($i * 8); + } + + /** + * Apply mask + */ + $val &= $mask; + $val += $valueShift; + + ++$attempts; + /** + * If $val overflows to a floating point number, + * ... or is larger than $max, + * ... or smaller than $min, + * then try again. + */ + } while (!is_int($val) || $val > $max || $val < $min); + + return (int) $val; + } +} diff --git a/system/vendor/paragonie/random_compat/other/build_phar.php b/system/vendor/paragonie/random_compat/other/build_phar.php new file mode 100644 index 0000000..70ef4b2 --- /dev/null +++ b/system/vendor/paragonie/random_compat/other/build_phar.php @@ -0,0 +1,57 @@ +buildFromDirectory(dirname(__DIR__).'/lib'); +rename( + dirname(__DIR__).'/lib/index.php', + dirname(__DIR__).'/lib/random.php' +); + +/** + * If we pass an (optional) path to a private key as a second argument, we will + * sign the Phar with OpenSSL. + * + * If you leave this out, it will produce an unsigned .phar! + */ +if ($argc > 1) { + if (!@is_readable($argv[1])) { + echo 'Could not read the private key file:', $argv[1], "\n"; + exit(255); + } + $pkeyFile = file_get_contents($argv[1]); + + $private = openssl_get_privatekey($pkeyFile); + if ($private !== false) { + $pkey = ''; + openssl_pkey_export($private, $pkey); + $phar->setSignatureAlgorithm(Phar::OPENSSL, $pkey); + + /** + * Save the corresponding public key to the file + */ + if (!@is_readable($dist.'/random_compat.phar.pubkey')) { + $details = openssl_pkey_get_details($private); + file_put_contents( + $dist.'/random_compat.phar.pubkey', + $details['key'] + ); + } + } else { + echo 'An error occurred reading the private key from OpenSSL.', "\n"; + exit(255); + } +} diff --git a/system/vendor/paragonie/random_compat/psalm-autoload.php b/system/vendor/paragonie/random_compat/psalm-autoload.php new file mode 100644 index 0000000..d71d1b8 --- /dev/null +++ b/system/vendor/paragonie/random_compat/psalm-autoload.php @@ -0,0 +1,9 @@ + + + + + + + + + + + + diff --git a/system/vendor/pimple/pimple/CHANGELOG b/system/vendor/pimple/pimple/CHANGELOG index f277b96..ba56760 100644 --- a/system/vendor/pimple/pimple/CHANGELOG +++ b/system/vendor/pimple/pimple/CHANGELOG @@ -1,3 +1,7 @@ +* 3.2.3 (2017-XX-XX) + + * n/a + * 3.2.2 (2017-07-23) * reverted extending a protected closure throws an exception (deprecated it instead) diff --git a/system/vendor/pimple/pimple/README.rst b/system/vendor/pimple/pimple/README.rst index d27d8aa..a03b6d3 100644 --- a/system/vendor/pimple/pimple/README.rst +++ b/system/vendor/pimple/pimple/README.rst @@ -108,7 +108,7 @@ If you change the ``session_storage`` service definition like below: }; You can now easily change the cookie name by overriding the -``session_storage_class`` parameter instead of redefining the service +``cookie_name`` parameter instead of redefining the service definition. Protecting Parameters diff --git a/system/vendor/pimple/pimple/ext/pimple/php_pimple.h b/system/vendor/pimple/pimple/ext/pimple/php_pimple.h index 258f3ee..eed7c17 100644 --- a/system/vendor/pimple/pimple/ext/pimple/php_pimple.h +++ b/system/vendor/pimple/pimple/ext/pimple/php_pimple.h @@ -41,7 +41,7 @@ extern zend_module_entry pimple_module_entry; #include "TSRM.h" #endif -#define PIMPLE_VERSION "3.2.2-DEV" +#define PIMPLE_VERSION "3.2.3-DEV" #define PIMPLE_NS "Pimple" #define PSR_CONTAINER_NS "Psr\\Container" diff --git a/system/vendor/pimple/pimple/src/Pimple/Container.php b/system/vendor/pimple/pimple/src/Pimple/Container.php index e761964..707b92b 100644 --- a/system/vendor/pimple/pimple/src/Pimple/Container.php +++ b/system/vendor/pimple/pimple/src/Pimple/Container.php @@ -103,9 +103,9 @@ class Container implements \ArrayAccess if ( isset($this->raw[$id]) - || !is_object($this->values[$id]) + || !\is_object($this->values[$id]) || isset($this->protected[$this->values[$id]]) - || !method_exists($this->values[$id], '__invoke') + || !\method_exists($this->values[$id], '__invoke') ) { return $this->values[$id]; } @@ -143,7 +143,7 @@ class Container implements \ArrayAccess public function offsetUnset($id) { if (isset($this->keys[$id])) { - if (is_object($this->values[$id])) { + if (\is_object($this->values[$id])) { unset($this->factories[$this->values[$id]], $this->protected[$this->values[$id]]); } @@ -162,7 +162,7 @@ class Container implements \ArrayAccess */ public function factory($callable) { - if (!method_exists($callable, '__invoke')) { + if (!\method_exists($callable, '__invoke')) { throw new ExpectedInvokableException('Service definition is not a Closure or invokable object.'); } @@ -184,7 +184,7 @@ class Container implements \ArrayAccess */ public function protect($callable) { - if (!method_exists($callable, '__invoke')) { + if (!\method_exists($callable, '__invoke')) { throw new ExpectedInvokableException('Callable is not a Closure or invokable object.'); } @@ -241,15 +241,15 @@ class Container implements \ArrayAccess throw new FrozenServiceException($id); } - if (!is_object($this->values[$id]) || !method_exists($this->values[$id], '__invoke')) { + if (!\is_object($this->values[$id]) || !\method_exists($this->values[$id], '__invoke')) { throw new InvalidServiceIdentifierException($id); } if (isset($this->protected[$this->values[$id]])) { - @trigger_error(sprintf('How Pimple behaves when extending protected closures will be fixed in Pimple 4. Are you sure "%s" should be protected?', $id), E_USER_DEPRECATED); + @\trigger_error(\sprintf('How Pimple behaves when extending protected closures will be fixed in Pimple 4. Are you sure "%s" should be protected?', $id), \E_USER_DEPRECATED); } - if (!is_object($callable) || !method_exists($callable, '__invoke')) { + if (!\is_object($callable) || !\method_exists($callable, '__invoke')) { throw new ExpectedInvokableException('Extension service definition is not a Closure or invokable object.'); } @@ -274,7 +274,7 @@ class Container implements \ArrayAccess */ public function keys() { - return array_keys($this->values); + return \array_keys($this->values); } /** diff --git a/system/vendor/pimple/pimple/src/Pimple/Exception/FrozenServiceException.php b/system/vendor/pimple/pimple/src/Pimple/Exception/FrozenServiceException.php index 64b0265..e4d2f6d 100644 --- a/system/vendor/pimple/pimple/src/Pimple/Exception/FrozenServiceException.php +++ b/system/vendor/pimple/pimple/src/Pimple/Exception/FrozenServiceException.php @@ -40,6 +40,6 @@ class FrozenServiceException extends \RuntimeException implements ContainerExcep */ public function __construct($id) { - parent::__construct(sprintf('Cannot override frozen service "%s".', $id)); + parent::__construct(\sprintf('Cannot override frozen service "%s".', $id)); } } diff --git a/system/vendor/pimple/pimple/src/Pimple/Exception/InvalidServiceIdentifierException.php b/system/vendor/pimple/pimple/src/Pimple/Exception/InvalidServiceIdentifierException.php index 9df9c66..91e82f9 100644 --- a/system/vendor/pimple/pimple/src/Pimple/Exception/InvalidServiceIdentifierException.php +++ b/system/vendor/pimple/pimple/src/Pimple/Exception/InvalidServiceIdentifierException.php @@ -40,6 +40,6 @@ class InvalidServiceIdentifierException extends \InvalidArgumentException implem */ public function __construct($id) { - parent::__construct(sprintf('Identifier "%s" does not contain an object definition.', $id)); + parent::__construct(\sprintf('Identifier "%s" does not contain an object definition.', $id)); } } diff --git a/system/vendor/pimple/pimple/src/Pimple/Exception/UnknownIdentifierException.php b/system/vendor/pimple/pimple/src/Pimple/Exception/UnknownIdentifierException.php index 2841318..fb6b626 100644 --- a/system/vendor/pimple/pimple/src/Pimple/Exception/UnknownIdentifierException.php +++ b/system/vendor/pimple/pimple/src/Pimple/Exception/UnknownIdentifierException.php @@ -40,6 +40,6 @@ class UnknownIdentifierException extends \InvalidArgumentException implements No */ public function __construct($id) { - parent::__construct(sprintf('Identifier "%s" is not defined.', $id)); + parent::__construct(\sprintf('Identifier "%s" is not defined.', $id)); } } diff --git a/system/vendor/pimple/pimple/src/Pimple/Psr11/ServiceLocator.php b/system/vendor/pimple/pimple/src/Pimple/Psr11/ServiceLocator.php index 61e4984..3361c6f 100644 --- a/system/vendor/pimple/pimple/src/Pimple/Psr11/ServiceLocator.php +++ b/system/vendor/pimple/pimple/src/Pimple/Psr11/ServiceLocator.php @@ -49,7 +49,7 @@ class ServiceLocator implements ContainerInterface $this->container = $container; foreach ($ids as $key => $id) { - $this->aliases[is_int($key) ? $id : $key] = $id; + $this->aliases[\is_int($key) ? $id : $key] = $id; } } diff --git a/system/vendor/pimple/pimple/src/Pimple/ServiceIterator.php b/system/vendor/pimple/pimple/src/Pimple/ServiceIterator.php index 744271d..5cde518 100644 --- a/system/vendor/pimple/pimple/src/Pimple/ServiceIterator.php +++ b/system/vendor/pimple/pimple/src/Pimple/ServiceIterator.php @@ -44,26 +44,26 @@ final class ServiceIterator implements \Iterator public function rewind() { - reset($this->ids); + \reset($this->ids); } public function current() { - return $this->container[current($this->ids)]; + return $this->container[\current($this->ids)]; } public function key() { - return current($this->ids); + return \current($this->ids); } public function next() { - next($this->ids); + \next($this->ids); } public function valid() { - return null !== key($this->ids); + return null !== \key($this->ids); } } diff --git a/system/vendor/slim/csrf/CHANGELOG.md b/system/vendor/slim/csrf/CHANGELOG.md new file mode 100644 index 0000000..c577ad7 --- /dev/null +++ b/system/vendor/slim/csrf/CHANGELOG.md @@ -0,0 +1,22 @@ +# Change Log + +## 2016-08-14 + +Now supports "persistence mode", to persist a single CSRF name/value pair throughout the life of a user's session. Added the following methods: + +- `protected getLastKeyPair` - gets the most recently generated key/value pair from storage. +- `protected loadLastKeyPair` - gets the most recently generated key/value pair from storage, and assign it to `$this->keyPair`. +- `public setPersistentTokenMode` +- `public getPersistentTokenMode` + +Note that if CSRF token validation fails, then the token should be renewed regardless of the persistence setting. + +The methods `getTokenName` and `getTokenValue` now return `null` if `$this->keyPair` has not yet been set. + +### Tests added: + +- `testPersistenceModeTrueBetweenRequestsArray` - Token should persist between requests after initial creation, when stored in an array. +- `testPersistenceModeTrueBetweenRequestsArrayAccess` - Token should persist between requests after initial creation, when stored in an ArrayObject. +- `testPersistenceModeFalseBetweenRequestsArray` - Token should be changed between requests, when stored in an array. +- `testPersistenceModeFalseBetweenRequestsArrayAccess` - Token should be changed between requests, when stored in an ArrayObject. +- `testUpdateAfterInvalidTokenWithPersistenceModeTrue` - New token should be generated after an invalid request, even if persistence mode is enabled. \ No newline at end of file diff --git a/system/vendor/slim/csrf/LICENSE.md b/system/vendor/slim/csrf/LICENSE.md new file mode 100644 index 0000000..130902a --- /dev/null +++ b/system/vendor/slim/csrf/LICENSE.md @@ -0,0 +1,19 @@ +Copyright (c) 2012-2015 Josh Lockhart + +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. diff --git a/system/vendor/slim/csrf/composer.json b/system/vendor/slim/csrf/composer.json new file mode 100644 index 0000000..087dd14 --- /dev/null +++ b/system/vendor/slim/csrf/composer.json @@ -0,0 +1,34 @@ +{ + "name": "slim/csrf", + "type": "library", + "description": "Slim Framework 3 CSRF protection middleware", + "keywords": ["slim","framework","middleware","csrf"], + "homepage": "http://slimframework.com", + "license": "MIT", + "authors": [ + { + "name": "Josh Lockhart", + "email": "hello@joshlockhart.com", + "homepage": "http://joshlockhart.com" + } + ], + "require": { + "php": ">=5.5.0", + "psr/http-message": "^1.0", + "paragonie/random_compat": "^1.1|^2.0" + }, + "require-dev": { + "slim/slim": "~3.0", + "phpunit/phpunit": "^4.0" + }, + "autoload": { + "psr-4": { + "Slim\\Csrf\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Slim\\Csrf\\Tests\\": "tests" + } + } +} diff --git a/system/vendor/slim/csrf/src/Guard.php b/system/vendor/slim/csrf/src/Guard.php new file mode 100644 index 0000000..43cb9a9 --- /dev/null +++ b/system/vendor/slim/csrf/src/Guard.php @@ -0,0 +1,474 @@ +prefix = rtrim($prefix, '_'); + if ($strength < 16) { + throw new RuntimeException('CSRF middleware failed. Minimum strength is 16.'); + } + $this->strength = $strength; + $this->storage = &$storage; + + $this->setFailureCallable($failureCallable); + $this->setStorageLimit($storageLimit); + + $this->setPersistentTokenMode($persistentTokenMode); + + $this->keyPair = null; + } + + /** + * Retrieve token name key + * + * @return string + */ + public function getTokenNameKey() + { + return $this->prefix . '_name'; + } + + /** + * Retrieve token value key + * + * @return string + */ + public function getTokenValueKey() + { + return $this->prefix . '_value'; + } + + /** + * Invoke middleware + * + * @param ServerRequestInterface $request PSR7 request object + * @param ResponseInterface $response PSR7 response object + * @param callable $next Next middleware callable + * + * @return ResponseInterface PSR7 response object + */ + public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next) + { + $this->validateStorage(); + + // Validate POST, PUT, DELETE, PATCH requests + if (in_array($request->getMethod(), ['POST', 'PUT', 'DELETE', 'PATCH'])) { + $body = $request->getParsedBody(); + $body = $body ? (array)$body : []; + $name = isset($body[$this->prefix . '_name']) ? $body[$this->prefix . '_name'] : false; + $value = isset($body[$this->prefix . '_value']) ? $body[$this->prefix . '_value'] : false; + if (!$name || !$value || !$this->validateToken($name, $value)) { + // Need to regenerate a new token, as the validateToken removed the current one. + $request = $this->generateNewToken($request); + + $failureCallable = $this->getFailureCallable(); + return $failureCallable($request, $response, $next); + } + } + + // Generate new CSRF token if persistentTokenMode is false, or if a valid keyPair has not yet been stored + if (!$this->persistentTokenMode || !$this->loadLastKeyPair()) { + $request = $this->generateNewToken($request); + } elseif ($this->persistentTokenMode) { + $pair = $this->loadLastKeyPair() ? $this->keyPair : $this->generateToken(); + $request = $this->attachRequestAttributes($request, $pair); + } + + // Enforce the storage limit + $this->enforceStorageLimit(); + + return $next($request, $response); + } + + /** + * @param $prefix + * @param $storage + * @return mixed + */ + public function validateStorage() + { + if (is_array($this->storage)) { + return $this->storage; + } + + if ($this->storage instanceof ArrayAccess) { + return $this->storage; + } + + if (!isset($_SESSION)) { + throw new RuntimeException('CSRF middleware failed. Session not found.'); + } + if (!array_key_exists($this->prefix, $_SESSION)) { + $_SESSION[$this->prefix] = []; + } + $this->storage = &$_SESSION[$this->prefix]; + return $this->storage; + } + + /** + * Generates a new CSRF token + * + * @return array + */ + public function generateToken() + { + // Generate new CSRF token + $name = uniqid($this->prefix); + $value = $this->createToken(); + $this->saveToStorage($name, $value); + + $this->keyPair = [ + $this->prefix . '_name' => $name, + $this->prefix . '_value' => $value + ]; + + return $this->keyPair; + } + + /** + * Generates a new CSRF token and attaches it to the Request Object + * + * @param ServerRequestInterface $request PSR7 response object. + * + * @return ServerRequestInterface PSR7 response object. + */ + public function generateNewToken(ServerRequestInterface $request) + { + + $pair = $this->generateToken(); + + $request = $this->attachRequestAttributes($request, $pair); + + return $request; + } + + /** + * Validate CSRF token from current request + * against token value stored in $_SESSION + * + * @param string $name CSRF name + * @param string $value CSRF token value + * + * @return bool + */ + public function validateToken($name, $value) + { + $token = $this->getFromStorage($name); + if (function_exists('hash_equals')) { + $result = ($token !== false && hash_equals($token, $value)); + } else { + $result = ($token !== false && $token === $value); + } + + // If we're not in persistent token mode, delete the token. + if (!$this->persistentTokenMode) { + $this->removeFromStorage($name); + } + + return $result; + } + + /** + * Create CSRF token value + * + * @return string + */ + protected function createToken() + { + return bin2hex(random_bytes($this->strength)); + } + + /** + * Save token to storage + * + * @param string $name CSRF token name + * @param string $value CSRF token value + */ + protected function saveToStorage($name, $value) + { + $this->storage[$name] = $value; + } + + /** + * Get token from storage + * + * @param string $name CSRF token name + * + * @return string|bool CSRF token value or `false` if not present + */ + protected function getFromStorage($name) + { + return isset($this->storage[$name]) ? $this->storage[$name] : false; + } + + /** + * Get the most recent key pair from storage. + * + * @return string[]|null Array containing name and value if found, null otherwise + */ + protected function getLastKeyPair() + { + // Use count, since empty ArrayAccess objects can still return false for `empty` + if (count($this->storage) < 1) { + return null; + } + + foreach ($this->storage as $name => $value) { + continue; + } + + $keyPair = [ + $this->prefix . '_name' => $name, + $this->prefix . '_value' => $value + ]; + + return $keyPair; + } + + /** + * Load the most recent key pair in storage. + * + * @return bool `true` if there was a key pair to load in storage, false otherwise. + */ + protected function loadLastKeyPair() + { + $this->keyPair = $this->getLastKeyPair(); + + if ($this->keyPair) { + return true; + } + + return false; + } + + /** + * Remove token from storage + * + * @param string $name CSRF token name + */ + protected function removeFromStorage($name) + { + $this->storage[$name] = ' '; + unset($this->storage[$name]); + } + + /** + * Remove the oldest tokens from the storage array so that there + * are never more than storageLimit tokens in the array. + * + * This is required as a token is generated every request and so + * most will never be used. + */ + protected function enforceStorageLimit() + { + if ($this->storageLimit < 1) { + return; + } + + // $storage must be an array or implement Countable and Traversable + if (!is_array($this->storage) + && !($this->storage instanceof Countable && $this->storage instanceof Traversable) + ) { + return; + } + + if (is_array($this->storage)) { + while (count($this->storage) > $this->storageLimit) { + array_shift($this->storage); + } + } else { + // array_shift() doesn't work for ArrayAccess, so we need an iterator in order to use rewind() + // and key(), so that we can then unset + $iterator = $this->storage; + if ($this->storage instanceof \IteratorAggregate) { + $iterator = $this->storage->getIterator(); + } + while (count($this->storage) > $this->storageLimit) { + $iterator->rewind(); + unset($this->storage[$iterator->key()]); + } + } + } + + /** + * @param ServerRequestInterface $request + * @param $pair + * @return static + */ + protected function attachRequestAttributes(ServerRequestInterface $request, $pair) + { + return $request->withAttribute($this->prefix . '_name', $pair[$this->prefix . '_name']) + ->withAttribute($this->prefix . '_value', $pair[$this->prefix . '_value']); + } + + /** + * Getter for failureCallable + * + * @return callable|\Closure + */ + public function getFailureCallable() + { + if (is_null($this->failureCallable)) { + $this->failureCallable = function (ServerRequestInterface $request, ResponseInterface $response, $next) { + $body = new \Slim\Http\Body(fopen('php://temp', 'r+')); + $body->write('Failed CSRF check!'); + return $response->withStatus(400)->withHeader('Content-type', 'text/plain')->withBody($body); + }; + } + return $this->failureCallable; + } + + /** + * Setter for failureCallable + * + * @param mixed $failureCallable Value to set + * @return $this + */ + public function setFailureCallable($failureCallable) + { + $this->failureCallable = $failureCallable; + return $this; + } + + /** + * Setter for persistentTokenMode + * + * @param bool $persistentTokenMode True to use the same token throughout the session (unless there is a validation error), + * false to get a new token with each request. + * @return $this + */ + public function setPersistentTokenMode($persistentTokenMode) + { + $this->persistentTokenMode = $persistentTokenMode; + return $this; + } + + /** + * Setter for storageLimit + * + * @param integer $storageLimit Value to set + * @return $this + */ + public function setStorageLimit($storageLimit) + { + $this->storageLimit = (int)$storageLimit; + return $this; + } + + /** + * Getter for persistentTokenMode + * + * @return bool + */ + public function getPersistentTokenMode() + { + return $this->persistentTokenMode; + } + + /** + * @return string + */ + public function getTokenName() + { + return isset($this->keyPair[$this->getTokenNameKey()]) ? $this->keyPair[$this->getTokenNameKey()] : null; + } + + /** + * @return string + */ + public function getTokenValue() + { + return isset($this->keyPair[$this->getTokenValueKey()]) ? $this->keyPair[$this->getTokenValueKey()] : null; + } +} diff --git a/system/vendor/symfony/event-dispatcher/CHANGELOG.md b/system/vendor/symfony/event-dispatcher/CHANGELOG.md index 736bd84..c6aa538 100644 --- a/system/vendor/symfony/event-dispatcher/CHANGELOG.md +++ b/system/vendor/symfony/event-dispatcher/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +3.4.0 +----- + + * Implementing `TraceableEventDispatcherInterface` without the `reset()` method has been deprecated. + 3.3.0 ----- diff --git a/system/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php b/system/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php index 313db4a..81a1ff7 100644 --- a/system/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php +++ b/system/vendor/symfony/event-dispatcher/ContainerAwareEventDispatcher.php @@ -46,7 +46,7 @@ class ContainerAwareEventDispatcher extends EventDispatcher $class = get_parent_class($class); } if (__CLASS__ !== $class) { - @trigger_error(sprintf('The %s class is deprecated since version 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), E_USER_DEPRECATED); + @trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), E_USER_DEPRECATED); } } @@ -64,7 +64,7 @@ class ContainerAwareEventDispatcher extends EventDispatcher */ public function addListenerService($eventName, $callback, $priority = 0) { - @trigger_error(sprintf('The %s class is deprecated since version 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), E_USER_DEPRECATED); + @trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), E_USER_DEPRECATED); if (!is_array($callback) || 2 !== count($callback)) { throw new \InvalidArgumentException('Expected an array("service", "method") argument'); @@ -78,7 +78,7 @@ class ContainerAwareEventDispatcher extends EventDispatcher $this->lazyLoad($eventName); if (isset($this->listenerIds[$eventName])) { - foreach ($this->listenerIds[$eventName] as $i => list($serviceId, $method, $priority)) { + foreach ($this->listenerIds[$eventName] as $i => list($serviceId, $method)) { $key = $serviceId.'.'.$method; if (isset($this->listeners[$eventName][$key]) && $listener === array($this->listeners[$eventName][$key], $method)) { unset($this->listeners[$eventName][$key]); @@ -146,7 +146,7 @@ class ContainerAwareEventDispatcher extends EventDispatcher */ public function addSubscriberService($serviceId, $class) { - @trigger_error(sprintf('The %s class is deprecated since version 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), E_USER_DEPRECATED); + @trigger_error(sprintf('The %s class is deprecated since Symfony 3.3 and will be removed in 4.0. Use EventDispatcher with closure factories instead.', __CLASS__), E_USER_DEPRECATED); foreach ($class::getSubscribedEvents() as $eventName => $params) { if (is_string($params)) { @@ -163,7 +163,7 @@ class ContainerAwareEventDispatcher extends EventDispatcher public function getContainer() { - @trigger_error('The '.__METHOD__.'() method is deprecated since version 3.3 as its class will be removed in 4.0. Inject the container or the services you need in your listeners/subscribers instead.', E_USER_DEPRECATED); + @trigger_error('The '.__METHOD__.'() method is deprecated since Symfony 3.3 as its class will be removed in 4.0. Inject the container or the services you need in your listeners/subscribers instead.', E_USER_DEPRECATED); return $this->container; } diff --git a/system/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php b/system/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php index a24f6ec..9b5c689 100644 --- a/system/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php +++ b/system/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcher.php @@ -207,6 +207,11 @@ class TraceableEventDispatcher implements TraceableEventDispatcherInterface return $notCalled; } + public function reset() + { + $this->called = array(); + } + /** * Proxies all method calls to the original event dispatcher. * diff --git a/system/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php b/system/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php index 5483e81..f021275 100644 --- a/system/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php +++ b/system/vendor/symfony/event-dispatcher/Debug/TraceableEventDispatcherInterface.php @@ -15,6 +15,8 @@ use Symfony\Component\EventDispatcher\EventDispatcherInterface; /** * @author Fabien Potencier + * + * @method reset() Resets the trace. */ interface TraceableEventDispatcherInterface extends EventDispatcherInterface { diff --git a/system/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php b/system/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php index 0b5f562..9f9c09c 100644 --- a/system/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php +++ b/system/vendor/symfony/event-dispatcher/DependencyInjection/RegisterListenersPass.php @@ -28,6 +28,9 @@ class RegisterListenersPass implements CompilerPassInterface protected $listenerTag; protected $subscriberTag; + private $hotPathEvents = array(); + private $hotPathTagName; + /** * @param string $dispatcherService Service name of the event dispatcher in processed container * @param string $listenerTag Tag name used for listener @@ -40,6 +43,14 @@ class RegisterListenersPass implements CompilerPassInterface $this->subscriberTag = $subscriberTag; } + public function setHotPathEvents(array $hotPathEvents, $tagName = 'container.hot_path') + { + $this->hotPathEvents = array_flip($hotPathEvents); + $this->hotPathTagName = $tagName; + + return $this; + } + public function process(ContainerBuilder $container) { if (!$container->hasDefinition($this->dispatcherService) && !$container->hasAlias($this->dispatcherService)) { @@ -65,6 +76,10 @@ class RegisterListenersPass implements CompilerPassInterface } $definition->addMethodCall('addListener', array($event['event'], array(new ServiceClosureArgument(new Reference($id)), $event['method']), $priority)); + + if (isset($this->hotPathEvents[$event['event']])) { + $container->getDefinition($id)->addTag($this->hotPathTagName); + } } } @@ -91,6 +106,10 @@ class RegisterListenersPass implements CompilerPassInterface foreach ($extractingDispatcher->listeners as $args) { $args[1] = array(new ServiceClosureArgument(new Reference($id)), $args[1]); $definition->addMethodCall('addListener', $args); + + if (isset($this->hotPathEvents[$args[0]])) { + $container->getDefinition($id)->addTag('container.hot_path'); + } } $extractingDispatcher->listeners = array(); } diff --git a/system/vendor/symfony/event-dispatcher/EventDispatcher.php b/system/vendor/symfony/event-dispatcher/EventDispatcher.php index 4630b01..bc79a95 100644 --- a/system/vendor/symfony/event-dispatcher/EventDispatcher.php +++ b/system/vendor/symfony/event-dispatcher/EventDispatcher.php @@ -209,7 +209,7 @@ class EventDispatcher implements EventDispatcherInterface if ($event->isPropagationStopped()) { break; } - call_user_func($listener, $event, $eventName, $this); + \call_user_func($listener, $event, $eventName, $this); } } @@ -225,7 +225,7 @@ class EventDispatcher implements EventDispatcherInterface foreach ($this->listeners[$eventName] as $priority => $listeners) { foreach ($listeners as $k => $listener) { - if (is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) { + if (\is_array($listener) && isset($listener[0]) && $listener[0] instanceof \Closure) { $listener[0] = $listener[0](); $this->listeners[$eventName][$priority][$k] = $listener; } diff --git a/system/vendor/symfony/event-dispatcher/LICENSE b/system/vendor/symfony/event-dispatcher/LICENSE index 17d16a1..21d7fb9 100644 --- a/system/vendor/symfony/event-dispatcher/LICENSE +++ b/system/vendor/symfony/event-dispatcher/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2017 Fabien Potencier +Copyright (c) 2004-2018 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/system/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php b/system/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php index 1805561..9d5eecc 100644 --- a/system/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php +++ b/system/vendor/symfony/event-dispatcher/Tests/ContainerAwareEventDispatcherTest.php @@ -143,7 +143,7 @@ class ContainerAwareEventDispatcherTest extends AbstractEventDispatcherTest $listeners = $dispatcher->getListeners(); - $this->assertTrue(isset($listeners['onEvent'])); + $this->assertArrayHasKey('onEvent', $listeners); $this->assertCount(1, $dispatcher->getListeners('onEvent')); } diff --git a/system/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php b/system/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php index a1cf670..53a3421 100644 --- a/system/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php +++ b/system/vendor/symfony/event-dispatcher/Tests/Debug/TraceableEventDispatcherTest.php @@ -124,6 +124,21 @@ class TraceableEventDispatcherTest extends TestCase $this->assertEquals(array(), $tdispatcher->getNotCalledListeners()); } + public function testClearCalledListeners() + { + $tdispatcher = new TraceableEventDispatcher(new EventDispatcher(), new Stopwatch()); + $tdispatcher->addListener('foo', function () {}, 5); + + $tdispatcher->dispatch('foo'); + $tdispatcher->reset(); + + $listeners = $tdispatcher->getNotCalledListeners(); + $this->assertArrayHasKey('stub', $listeners['foo.closure']); + unset($listeners['foo.closure']['stub']); + $this->assertEquals(array(), $tdispatcher->getCalledListeners()); + $this->assertEquals(array('foo.closure' => array('event' => 'foo', 'pretty' => 'closure', 'priority' => 5)), $listeners); + } + public function testGetCalledListenersNested() { $tdispatcher = null; diff --git a/system/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php b/system/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php index d46d8c5..dbb1aa5 100644 --- a/system/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php +++ b/system/vendor/symfony/event-dispatcher/Tests/DependencyInjection/RegisterListenersPassTest.php @@ -141,6 +141,18 @@ class RegisterListenersPassTest extends TestCase $this->assertEquals($expectedCalls, $definition->getMethodCalls()); } + public function testHotPathEvents() + { + $container = new ContainerBuilder(); + + $container->register('foo', SubscriberService::class)->addTag('kernel.event_subscriber', array()); + $container->register('event_dispatcher', 'stdClass'); + + (new RegisterListenersPass())->setHotPathEvents(array('event'))->process($container); + + $this->assertTrue($container->getDefinition('foo')->hasTag('container.hot_path')); + } + /** * @expectedException \InvalidArgumentException * @expectedExceptionMessage You have requested a non-existent parameter "subscriber.class" diff --git a/system/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php b/system/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php index c84d3ac..9cf68c9 100644 --- a/system/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php +++ b/system/vendor/symfony/event-dispatcher/Tests/GenericEventTest.php @@ -114,8 +114,8 @@ class GenericEventTest extends TestCase public function testOffsetIsset() { - $this->assertTrue(isset($this->event['name'])); - $this->assertFalse(isset($this->event['nameNotExist'])); + $this->assertArrayHasKey('name', $this->event); + $this->assertArrayNotHasKey('nameNotExist', $this->event); } public function testHasArgument() diff --git a/system/vendor/symfony/event-dispatcher/composer.json b/system/vendor/symfony/event-dispatcher/composer.json index 994e8ca..75b881b 100644 --- a/system/vendor/symfony/event-dispatcher/composer.json +++ b/system/vendor/symfony/event-dispatcher/composer.json @@ -19,10 +19,10 @@ "php": "^5.5.9|>=7.0.8" }, "require-dev": { - "symfony/dependency-injection": "~3.3", - "symfony/expression-language": "~2.8|~3.0", - "symfony/config": "~2.8|~3.0", - "symfony/stopwatch": "~2.8|~3.0", + "symfony/dependency-injection": "~3.3|~4.0", + "symfony/expression-language": "~2.8|~3.0|~4.0", + "symfony/config": "~2.8|~3.0|~4.0", + "symfony/stopwatch": "~2.8|~3.0|~4.0", "psr/log": "~1.0" }, "conflict": { @@ -41,7 +41,7 @@ "minimum-stability": "dev", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "3.4-dev" } } } diff --git a/system/vendor/symfony/yaml/Inline.php b/system/vendor/symfony/yaml/Inline.php index a10c0e4..a4367fb 100644 --- a/system/vendor/symfony/yaml/Inline.php +++ b/system/vendor/symfony/yaml/Inline.php @@ -375,6 +375,7 @@ class Inline $output = array(); $len = strlen($mapping); ++$i; + $allowOverwrite = false; // {foo: bar, bar:foo, ...} while ($i < $len) { @@ -394,6 +395,10 @@ class Inline // key $key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false); + if ('<<' === $key) { + $allowOverwrite = true; + } + // value $done = false; @@ -405,7 +410,12 @@ class Inline // Spec: Keys MUST be unique; first one wins. // Parser cannot abort this mapping earlier, since lines // are processed sequentially. - if (!isset($output[$key])) { + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + foreach ($value as $parsedValue) { + $output += $parsedValue; + } + } elseif ($allowOverwrite || !isset($output[$key])) { $output[$key] = $value; } $done = true; @@ -416,7 +426,10 @@ class Inline // Spec: Keys MUST be unique; first one wins. // Parser cannot abort this mapping earlier, since lines // are processed sequentially. - if (!isset($output[$key])) { + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + $output += $value; + } elseif ($allowOverwrite || !isset($output[$key])) { $output[$key] = $value; } $done = true; @@ -429,7 +442,10 @@ class Inline // Spec: Keys MUST be unique; first one wins. // Parser cannot abort this mapping earlier, since lines // are processed sequentially. - if (!isset($output[$key])) { + // But overwriting is allowed when a merge node is used in current block. + if ('<<' === $key) { + $output += $value; + } elseif ($allowOverwrite || !isset($output[$key])) { $output[$key] = $value; } $done = true; diff --git a/system/vendor/symfony/yaml/LICENSE b/system/vendor/symfony/yaml/LICENSE index 17d16a1..21d7fb9 100644 --- a/system/vendor/symfony/yaml/LICENSE +++ b/system/vendor/symfony/yaml/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2004-2017 Fabien Potencier +Copyright (c) 2004-2018 Fabien Potencier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/system/vendor/symfony/yaml/Tests/Fixtures/sfMergeKey.yml b/system/vendor/symfony/yaml/Tests/Fixtures/sfMergeKey.yml index 59f6125..499446c 100644 --- a/system/vendor/symfony/yaml/Tests/Fixtures/sfMergeKey.yml +++ b/system/vendor/symfony/yaml/Tests/Fixtures/sfMergeKey.yml @@ -22,6 +22,7 @@ yaml: | foo: bar foo: ignore bar: foo + bar_inline: {a: before, d: other, <<: *foo, b: new, x: Oren, c: { foo: bar, foo: ignore, bar: foo}} duplicate: foo: bar foo: ignore @@ -46,15 +47,20 @@ yaml: | p: 12345 z: <<: *nestedref + head_inline: &head_inline { <<: [ *foo , *dong , *foo2 ] } + recursive_inline: { <<: *head_inline, c: { <<: *foo2 } } php: | array( 'foo' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'e' => 'notnull'), 'bar' => array('a' => 'before', 'd' => 'other', 'e' => null, 'b' => 'new', 'c' => array('foo' => 'bar', 'bar' => 'foo'), 'x' => 'Oren'), + 'bar_inline' => array('a' => 'before', 'd' => 'other', 'b' => 'new', 'c' => array('foo' => 'bar', 'bar' => 'foo'), 'e' => 'notnull', 'x' => 'Oren'), 'duplicate' => array('foo' => 'bar'), 'foo2' => array('a' => 'Ballmer'), 'ding' => array('fi', 'fei', 'fo', 'fam'), 'check' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'e' => 'notnull', 'fi', 'fei', 'fo', 'fam', 'isit' => 'tested'), 'head' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'e' => 'notnull', 'fi', 'fei', 'fo', 'fam'), 'taz' => array('a' => 'Steve', 'w' => array('p' => 1234)), - 'nested' => array('a' => 'Steve', 'w' => array('p' => 12345), 'd' => 'Doug', 'z' => array('p' => 12345)) + 'nested' => array('a' => 'Steve', 'w' => array('p' => 12345), 'd' => 'Doug', 'z' => array('p' => 12345)), + 'head_inline' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'e' => 'notnull', 'fi', 'fei', 'fo', 'fam'), + 'recursive_inline' => array('a' => 'Steve', 'b' => 'Clark', 'c' => array('a' => 'Ballmer'), 'e' => 'notnull', 'fi', 'fei', 'fo', 'fam'), ) diff --git a/system/vendor/symfony/yaml/Tests/ParserTest.php b/system/vendor/symfony/yaml/Tests/ParserTest.php index 9cafc92..c990423 100644 --- a/system/vendor/symfony/yaml/Tests/ParserTest.php +++ b/system/vendor/symfony/yaml/Tests/ParserTest.php @@ -1280,6 +1280,18 @@ YAML; $this->assertSame($expected, $this->parser->parse($yaml)); } + + /** + * @expectedException \Symfony\Component\Yaml\Exception\ParseException + * @expectedExceptionMessage Reference "foo" does not exist + */ + public function testEvalRefException() + { + $yaml = <<parser->parse($yaml); + } } class B diff --git a/system/vendor/symfony/yaml/Yaml.php b/system/vendor/symfony/yaml/Yaml.php index 7a57943..595fbef 100644 --- a/system/vendor/symfony/yaml/Yaml.php +++ b/system/vendor/symfony/yaml/Yaml.php @@ -33,7 +33,7 @@ class Yaml * you must validate the input before calling this method. Passing a file * as an input is a deprecated feature and will be removed in 3.0. * - * Note: the ability to pass file names to the Yaml::parse method is deprecated since version 2.2 and will be removed in 3.0. Pass the YAML contents of the file instead. + * Note: the ability to pass file names to the Yaml::parse method is deprecated since Symfony 2.2 and will be removed in 3.0. Pass the YAML contents of the file instead. * * @param string $input Path to a YAML file or a string containing YAML * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise @@ -49,7 +49,7 @@ class Yaml // if input is a file, process it $file = ''; if (false === strpos($input, "\n") && is_file($input)) { - @trigger_error('The ability to pass file names to the '.__METHOD__.' method is deprecated since version 2.2 and will be removed in 3.0. Pass the YAML contents of the file instead.', E_USER_DEPRECATED); + @trigger_error('The ability to pass file names to the '.__METHOD__.' method is deprecated since Symfony 2.2 and will be removed in 3.0. Pass the YAML contents of the file instead.', E_USER_DEPRECATED); if (false === is_readable($input)) { throw new ParseException(sprintf('Unable to parse "%s" as the file is not readable.', $input)); diff --git a/system/vendor/vlucas/valitron b/system/vendor/vlucas/valitron new file mode 160000 index 0000000..1712b04 --- /dev/null +++ b/system/vendor/vlucas/valitron @@ -0,0 +1 @@ +Subproject commit 1712b04f16db5f243ddfa5ce26a9b51eb1948a36 diff --git a/tests/plugin.feature b/tests/plugin.feature new file mode 100644 index 0000000..484b602 --- /dev/null +++ b/tests/plugin.feature @@ -0,0 +1,28 @@ +Feature: Handle Plugins + In order to use plugins + As an user + I want to access the setup page + + Scenario: Setups a plugin + Given + And + When + Then + + Scenario: User adds a plugin + Given + And + When + Then + + Scenario: User removes a plugin + Given + And + When + Then + + Scenario: User edits plugin settings again + Given + And + When + Then diff --git a/tests/setup.feature b/tests/setup.feature new file mode 100644 index 0000000..f5aaf7f --- /dev/null +++ b/tests/setup.feature @@ -0,0 +1,27 @@ +Feature: Setup the system + In order to setup the system + As an user + I want to access the setup page + + Scenario: User setups system for first time. + Given I am on '/typemill' + And in the settings the 'setup' is true + When I go to '/typemill/setup' + Then some testable outcome is achieved + + Scenario: System has been setup already and user tries to reach setup page again. + Given I am on '/typemill' + And in the settings the 'setup' is false + When I go to '/typemill/setup' + Then I can read 'page not found' + + Scenario: System saves valid settings. + Given I am on '/typemill/setup' + And I enter '' to '' + When I enter + Then I am on '/typemill/welcome' + + Scenario: System saves invalid settings. + Given I am on '/typemill/setup' + When I enter + Then I am on '/typemill/setup' \ No newline at end of file diff --git a/themes/typemill/cover.twig b/themes/typemill/cover.twig index b8ef2ea..d326240 100644 --- a/themes/typemill/cover.twig +++ b/themes/typemill/cover.twig @@ -9,7 +9,11 @@ {{ content }} Start - + + {% if settings.setup %} + Setup + {% endif %} +
{% endblock %} \ No newline at end of file diff --git a/themes/typemill/css/style.css b/themes/typemill/css/style.css index b9e0adc..a85e22e 100644 --- a/themes/typemill/css/style.css +++ b/themes/typemill/css/style.css @@ -54,10 +54,10 @@ pre{ border-left: 4px solid #e0474c; } ********************/ header, nav, h1, h2, h3, h4, h5, h6, article .paging, article .breadcrumb, .cover .lead a, a.readMore{ - font-family: Calibri, "Helvetica Neue", Arial, sans-serif; + font-family: Calibri, Helvetica, Arial, sans-serif; } article{ - font-family: Calibri, "Helvetica Neue", Arial, sans-serif; + font-family: Calibri, Helvetica, Arial, sans-serif; } pre,code{ font-family: monospace; @@ -83,6 +83,20 @@ html,body{ padding: 0; margin:0; } +html{ + /* Adjust font size */ + font-size: 100%; + -webkit-text-size-adjust: 100%; + /* Font varient */ + font-variant-ligatures: none; + -webkit-font-variant-ligatures: none; + /* Smoothing */ + text-rendering: optimizeLegibility; + -moz-osx-font-smoothing: grayscale; + font-smoothing: antialiased; + -webkit-font-smoothing: antialiased; + text-shadow: rgba(0, 0, 0, .01) 0 0 1px; +} body{ padding: 20px; font-size: 18px; @@ -446,6 +460,10 @@ li.h2, li.h3, li.h4, li.h5, li.h6 padding-left: 25px; } +.control-set{ + margin: 10px 0; +} + /************************ * GitHub Ribbon * ************************/ diff --git a/themes/typemill/typemill.yaml b/themes/typemill/typemill.yaml new file mode 100644 index 0000000..98acc2d --- /dev/null +++ b/themes/typemill/typemill.yaml @@ -0,0 +1,6 @@ +name: Typemill Theme +version: 1.0.3 +description: The Standard Theme For Typemill +author: Sebastian Schürmanns +homepage: http://typemill.net +licence: MIT \ No newline at end of file diff --git a/typemill-1.0.5.zip b/typemill-1.0.5.zip deleted file mode 100644 index f3004f0..0000000 Binary files a/typemill-1.0.5.zip and /dev/null differ