diff --git a/CHANGELOG.md b/CHANGELOG.md index f78c7f6a..f5428abe 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,25 @@ + +# [0.9.16](https://github.com/flextype/flextype/compare/v0.9.15...v0.9.16) (2020-01-14) + +### Features + +* **parsers** add commonmark instead of parsedown ([#540](https://github.com/flextype/flextype/issues/540)) +* **shortcodes** add new shortcode - media_files_fetch + + example: + + ``` + [media_files_fetch id="entries/home/foo.txt" field="title" default="Bar"] + ``` + +### Bug Fixes + +* **bootstrap** fix include path for dependencies. + +### Refactoring + +* **core** general code refactoring and improvements. + # [0.9.15](https://github.com/flextype/flextype/compare/v0.9.14...v0.9.15) (2020-01-03) diff --git a/README.md b/README.md index c8dc1892..52fd1122 100755 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ However, the amount of effort needed to maintain and develop new features for th You can support it's ongoing development by being a project backer or a sponsor: * [Become a backer or sponsor on Patreon](https://www.patreon.com/awilum). -* [One-time donation via PayPal, QIWI, Sberbank, Yandex](https://flextype.org/en/one-time-donation) +* [One-time donation via PayPal, QIWI, Sberbank](https://flextype.org/en/one-time-donation) * [Visit our Sponsors & Backers page](https://flextype.org/en/sponsors) ### PLATFORM CONTRIBUTIONS diff --git a/composer.json b/composer.json index 8d9dc155..ec99f4df 100755 --- a/composer.json +++ b/composer.json @@ -41,19 +41,18 @@ "league/event": "^2.2.0", "league/glide": "^1.7.0", - "phpfastcache/phpfastcache": "^8.0.3", + "phpfastcache/phpfastcache": "^8.0.4", "respect/validation": "^2.1.0", - "monolog/monolog": "^2.1.1", + "monolog/monolog": "^2.2.0", "cocur/slugify": "^4.0.0", "ramsey/uuid": "^4.1.1", - "symfony/yaml": "^5.2.0", - "symfony/finder": "^5.2.0", + "symfony/yaml": "^5.2.1", + "symfony/finder": "^5.2.1", "bnf/slim3-psr15": "^1.1.1", - "erusev/parsedown": "^1.7.3", - "erusev/parsedown-extra": "^0.7.1", + "league/commonmark": "^1.5.7", "thunderer/shortcode": "^0.7.4", @@ -77,10 +76,10 @@ ] }, "require-dev": { - "doctrine/coding-standard": "8.1.0", - "pestphp/pest": "^0.3.3", - "phpstan/phpstan": "^0.12.42", - "symfony/var-dumper": "^5.1.5", + "doctrine/coding-standard": "8.2.0", + "pestphp/pest": "^1.0.0", + "phpstan/phpstan": "^0.12.66", + "symfony/var-dumper": "^5.2.1", "victorjonsson/markdowndocs": "^1.3.8" } } diff --git a/phpstan.neon b/phpstan.neon index aa8255cc..9e85587b 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,5 @@ parameters: - level: 0 + level: 3 reportUnmatchedIgnoredErrors: false paths: - src diff --git a/src/flextype/Foundation/Flextype.php b/src/flextype/Foundation/Flextype.php index 7be6ee4f..fd570cec 100644 --- a/src/flextype/Foundation/Flextype.php +++ b/src/flextype/Foundation/Flextype.php @@ -22,7 +22,7 @@ final class Flextype extends App /** * Flextype version */ - public const VERSION = '0.9.15'; + public const VERSION = '0.9.16'; /** * The Flextype's instance is stored in a static field. This field is an diff --git a/src/flextype/Foundation/Media/MediaFiles.php b/src/flextype/Foundation/Media/MediaFiles.php index 5f59c021..06183f78 100644 --- a/src/flextype/Foundation/Media/MediaFiles.php +++ b/src/flextype/Foundation/Media/MediaFiles.php @@ -85,8 +85,8 @@ class MediaFiles $acceptFileTypes = flextype('registry')->get('flextype.settings.media.accept_file_types'); $maxFileSize = flextype('registry')->get('flextype.settings.media.max_file_size'); $safeNames = flextype('registry')->get('flextype.settings.media.safe_names'); - $maxImageWidth = flextype('registry')->get('flextype.settings.media.max_image_width'); - $maxImageHeight = flextype('registry')->get('flextype.settings.media.max_image_height'); + $maxImageWidth = flextype('registry')->get('flextype.settings.media.images.max_image_width'); + $maxImageHeight = flextype('registry')->get('flextype.settings.media.images.max_image_height'); $exact = false; $chmod = 0644; @@ -149,29 +149,30 @@ class MediaFiles chmod($filename, $chmod); if (in_array(mime_content_type($filename), ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'])) { + // open an image file $img = Image::make($filename); // now you are able to resize the instance - if (flextype('registry')->get('flextype.settings.media.image_width') > 0 && flextype('registry')->get('flextype.settings.media.image_height') > 0) { - $img->resize(flextype('registry')->get('flextype.settings.media.image_width'), flextype('registry')->get('flextype.settings.media.image_height'), static function ($constraint): void { + if (flextype('registry')->get('flextype.settings.media.images.image_width') > 0 && flextype('registry')->get('flextype.settings.media.images.image_height') > 0) { + $img->resize(flextype('registry')->get('flextype.settings.media.images.image_width'), flextype('registry')->get('flextype.settings.media.images.image_height'), static function ($constraint): void { $constraint->aspectRatio(); $constraint->upsize(); }); - } elseif (flextype('registry')->get('flextype.settings.media.image_width') > 0) { - $img->resize(flextype('registry')->get('flextype.settings.media.image_width'), null, static function ($constraint): void { + } elseif (flextype('registry')->get('flextype.settings.media.images.image_width') > 0) { + $img->resize(flextype('registry')->get('flextype.settings.media.images.image_width'), null, static function ($constraint): void { $constraint->aspectRatio(); $constraint->upsize(); }); - } elseif (flextype('registry')->get('flextype.settings.media.image_height') > 0) { - $img->resize(null, flextype('registry')->get('flextype.settings.media.image_height'), static function ($constraint): void { + } elseif (flextype('registry')->get('flextype.settings.media.images.image_height') > 0) { + $img->resize(null, flextype('registry')->get('flextype.settings.media.images.image_height'), static function ($constraint): void { $constraint->aspectRatio(); $constraint->upsize(); }); } // finally we save the image as a new file - $img->save($filename, flextype('registry')->get('flextype.settings.media.image_quality')); + $img->save($filename, flextype('registry')->get('flextype.settings.media.images.image_quality')); // destroy $img->destroy(); @@ -220,7 +221,7 @@ class MediaFiles * @param string $id The path to file. * @param array $options Options array. * - * @return self Returns instance of The Arrays class. + * @return Arrays Returns instance of The Arrays class. * * @access public */ @@ -233,7 +234,8 @@ class MediaFiles isset($options['collection']) && strings($options['collection'])->isTrue() ) { - $result = []; + + $result = []; foreach (filesystem()->find()->files()->in(flextype('media')->folders()->meta()->getDirectoryMetaLocation($id)) as $file) { $basename = $file->getBasename('.' . $file->getExtension()); diff --git a/src/flextype/Foundation/Media/MediaFolders.php b/src/flextype/Foundation/Media/MediaFolders.php index 22c8e698..1535031f 100644 --- a/src/flextype/Foundation/Media/MediaFolders.php +++ b/src/flextype/Foundation/Media/MediaFolders.php @@ -39,7 +39,7 @@ class MediaFolders * @param string $id The path to folder. * @param array $options Options array. * - * @return self Returns instance of The Arrays class. + * @return Arrays Returns instance of The Arrays class. * * @access public */ diff --git a/src/flextype/Foundation/Plugins.php b/src/flextype/Foundation/Plugins.php index 972b9c26..99a38eef 100755 --- a/src/flextype/Foundation/Plugins.php +++ b/src/flextype/Foundation/Plugins.php @@ -190,7 +190,7 @@ class Plugins * * @param array $pluginsList Plugins list * - * @access private + * @access public */ public function getPluginsDictionary(array $pluginsList, string $locale): array { @@ -218,7 +218,7 @@ class Plugins * * @param array $pluginsList Plugins list * - * @access private + * @access public */ public function getPluginsCacheID(array $pluginsList): string { diff --git a/src/flextype/Support/Parsers/Markdown.php b/src/flextype/Support/Parsers/Markdown.php index 958651b5..490f4e9b 100644 --- a/src/flextype/Support/Parsers/Markdown.php +++ b/src/flextype/Support/Parsers/Markdown.php @@ -10,7 +10,10 @@ declare(strict_types=1); namespace Flextype\Support\Parsers; use Exception; -use ParsedownExtra; +use League\CommonMark\CommonMarkConverter; +use League\CommonMark\Environment; +use League\CommonMark\Extension\Attributes\AttributesExtension; +use League\CommonMark\Extension\Table\TableExtension; use function flextype; use function strings; @@ -27,9 +30,14 @@ final class Markdown private static $instances = []; /** - * Markdown facade + * Markdown Environment */ - private $markdownFacade = null; + private $environment = null; + + /** + * Markdown Converter + */ + private $converter = null; /** * Markdown should not be cloneable. @@ -49,31 +57,34 @@ final class Markdown /** * Markdown construct - * - * @param */ protected function __construct() { - $this->markdownFacade = new ParsedownExtra(); - $this->markdownFacade->setBreaksEnabled(flextype('registry')->get('flextype.settings.parsers.markdown.auto_line_breaks')); - $this->markdownFacade->setUrlsLinked(flextype('registry')->get('flextype.settings.parsers.markdown.auto_url_links')); - $this->markdownFacade->setMarkupEscaped(flextype('registry')->get('flextype.settings.parsers.markdown.escape_markup')); + $config = flextype('registry')->get('flextype.settings.parsers.markdown'); + $this->environment = Environment::createCommonMarkEnvironment(); + $this->environment->addExtension(new AttributesExtension()); + $this->environment->addExtension(new TableExtension()); + $this->converter = new CommonMarkConverter($config, $this->environment); } /** - * Markdown facade - * - * @param + * Markdown Environment */ - public function facade(): ParsedownExtra + public function environment(): Environment { - return $this->markdownFacade; + return $this->environment; + } + + /** + * Markdown Converter + */ + public function converter(): CommonMarkConverter + { + return $this->converter; } /** * Returns Markdown Instance - * - * @param */ public static function getInstance(): Markdown { @@ -93,7 +104,7 @@ final class Markdown * * @return mixed The MARKDOWN converted to a PHP value */ - public function parse(string $input, bool $cache = true): string + public function parse(string $input, bool $cache = true) { if ($cache === true && flextype('registry')->get('flextype.settings.cache.enabled') === true) { $key = $this->getCacheID($input); @@ -102,13 +113,13 @@ final class Markdown return $dataFromCache; } - $data = $this->facade()->text($input); + $data = $this->converter()->convertToHtml($input); flextype('cache')->set($key, $data); return $data; } - return $this->facade()->text($input); + return $this->converter()->convertToHtml($input); } /** diff --git a/src/flextype/Support/Parsers/Shortcodes/MediaShortcode.php b/src/flextype/Support/Parsers/Shortcodes/MediaShortcode.php new file mode 100644 index 00000000..0375212f --- /dev/null +++ b/src/flextype/Support/Parsers/Shortcodes/MediaShortcode.php @@ -0,0 +1,17 @@ +get('flextype.settings.parsers.shortcode.shortcodes.media.enabled')) { + flextype('parsers')->shortcode()->addHandler('media_files_fetch', static function (ShortcodeInterface $s) { + return arrays(flextype('media')->files()->fetch($s->getParameter('id')))->get($s->getParameter('field'), $s->getParameter('default')); + }); +} diff --git a/src/flextype/Support/Serializers/Json.php b/src/flextype/Support/Serializers/Json.php index 3944b8f1..4f43e9ce 100644 --- a/src/flextype/Support/Serializers/Json.php +++ b/src/flextype/Support/Serializers/Json.php @@ -39,7 +39,7 @@ class Json * * @return mixed A JSON string representing the original PHP value */ - public function encode($input, int $options = 0, int $depth = 512): string + public function encode($input, int $options = 0, int $depth = 512) { $options = ($options & self::ESCAPE_UNICODE ? 0 : JSON_UNESCAPED_UNICODE) | JSON_UNESCAPED_SLASHES @@ -66,7 +66,7 @@ class Json * * @return mixed The JSON converted to a PHP value * - * @throws ParseException If the JSON is not valid + * @throws RuntimeException If the JSON is not valid */ public function decode(string $input, bool $cache = true, bool $assoc = true, int $depth = 512, int $flags = 0) { diff --git a/src/flextype/Support/Serializers/Yaml.php b/src/flextype/Support/Serializers/Yaml.php index 0b3ca84d..6280885f 100644 --- a/src/flextype/Support/Serializers/Yaml.php +++ b/src/flextype/Support/Serializers/Yaml.php @@ -80,9 +80,9 @@ class Yaml * * @return mixed The YAML converted to a PHP value * - * @throws ParseException If the YAML is not valid + * @throws RuntimeException If the YAML is not valid */ - public function decode(string $input, bool $cache = true, int $flags = 0): array + public function decode(string $input, bool $cache = true, int $flags = 0) { $decode = function (string $input, int $flags = 0) { // Try native PECL YAML PHP extension first if available. diff --git a/src/flextype/bootstrap.php b/src/flextype/bootstrap.php index 61c0d870..c39e2fa7 100755 --- a/src/flextype/bootstrap.php +++ b/src/flextype/bootstrap.php @@ -103,7 +103,7 @@ if ($registry->get('flextype.settings.errors.display')) { /** * Include Dependencies */ -include_once 'dependencies.php'; +include_once ROOT_DIR . '/src/flextype/dependencies.php'; /** * Set session options before you start the session diff --git a/src/flextype/dependencies.php b/src/flextype/dependencies.php index 25980428..f79e0060 100644 --- a/src/flextype/dependencies.php +++ b/src/flextype/dependencies.php @@ -220,7 +220,7 @@ flextype()->container()['serializers'] = fn() => new Serializers(); */ flextype()->container()['images'] = static function () { // Get images settings - $imagesSettings = ['driver' => flextype('registry')->get('flextype.settings.image.driver')]; + $imagesSettings = ['driver' => flextype('registry')->get('flextype.settings.media.image.driver')]; // Set source filesystem $source = new Flysystem( diff --git a/src/flextype/flextype.yaml b/src/flextype/flextype.yaml index cd432d16..64889bc4 100644 --- a/src/flextype/flextype.yaml +++ b/src/flextype/flextype.yaml @@ -1,5 +1,5 @@ name: Flextype -version: 0.9.15 +version: 0.9.16 description: Hybrid Content Management System author: name: Sergey Romanenko diff --git a/src/flextype/settings.yaml b/src/flextype/settings.yaml index 13a94a3f..c10e20e8 100644 --- a/src/flextype/settings.yaml +++ b/src/flextype/settings.yaml @@ -333,28 +333,29 @@ slugify: lowercase_after_regexp: false strip_tags: false -# Image -# -# - driver: gd or imagick -image: - driver: gd - # Markdown # -# - markdown.auto_line_breaks: Enable automatic line breaks -# - markdown.auto_url_links: Enable automatic HTML links -# - markdown.escape_markup: Escape markup tags into entities -# # Shortcode # # - shortcodes: Flextype Shortcodes to load. parsers: markdown: - auto_line_breaks: false - auto_url_links: false - escape_markup: false + renderer: + block_separator: "\n" + inner_separator: "\n" + soft_break: "\n" + enable_em: true + enable_strong: true + use_asterisk: true + use_underscore: true + unordered_list_markers: ['-', '*', '+'] + html_input: 'allow' + allow_unsafe_links: false + max_nesting_level: INF shortcode: shortcodes: + media: + enabled: true entries: enabled: true raw: @@ -409,11 +410,13 @@ media: accept_file_types: 'gif, jpg, jpeg, png, ico, zip, tgz, txt, md, doc, docx, pdf, epub, xls, xlsx, ppt, pptx, mp3, ogg, wav, m4a, mp4, m4v, ogv, wmv, avi, webm, svg' max_file_size: 8000000 safe_names: true - image_width: 1600 - image_height: 0 - image_quality: 70 - max_image_width: null - max_image_height: null + images: + driver: gd + image_width: 1600 + image_height: 0 + image_quality: 70 + max_image_width: null + max_image_height: null # Session # diff --git a/tests/Foundation/Entries/Fields/ParsersFieldTest.php b/tests/Foundation/Entries/Fields/ParsersFieldTest.php index b7d92fcf..d8d44702 100644 --- a/tests/Foundation/Entries/Fields/ParsersFieldTest.php +++ b/tests/Foundation/Entries/Fields/ParsersFieldTest.php @@ -11,8 +11,8 @@ afterEach(function (): void { }); test('test ParsersField', function () { - flextype('entries')->create('foo', ['content' => '#Foo', 'parsers' => ['markdown' => ['enabled' => true, 'fields' => ['content']]]]); - $this->assertEquals('

Foo

', flextype('entries')->fetch('foo')['content']); + flextype('entries')->create('foo', ['content' => '# Foo', 'parsers' => ['markdown' => ['enabled' => true, 'fields' => ['content']]]]); + $this->assertEquals(trim('

Foo

'), trim(flextype('entries')->fetch('foo')['content'])); flextype('entries')->create('bar', ['content' => '[registry_get name="Bar" default="Zed"]', 'parsers' => ['shortcode' => ['enabled' => true, 'fields' => ['content']]]]); $this->assertEquals('Zed', flextype('entries')->fetch('bar')['content']); diff --git a/tests/Support/Parsers/Shortcodes/MediaShortcodeTest.php b/tests/Support/Parsers/Shortcodes/MediaShortcodeTest.php new file mode 100644 index 00000000..6e4c1b79 --- /dev/null +++ b/tests/Support/Parsers/Shortcodes/MediaShortcodeTest.php @@ -0,0 +1,23 @@ +directory(PATH['project'] . '/media')->create(0755, true); + filesystem()->directory(PATH['project'] . '/media/.meta')->create(0755, true); +}); + +afterEach(function (): void { + filesystem()->directory(PATH['project'] . '/media/.meta')->delete(); + filesystem()->directory(PATH['project'] . '/media')->delete(); +}); + +test('test media_files_fetch shortcode', function () { + filesystem()->file(PATH['project'] . '/media/foo.txt')->put('foo'); + filesystem()->file(PATH['project'] . '/media/.meta/foo.txt.yaml')->put(flextype('serializers')->yaml()->encode(['title' => 'Foo', 'description' => '', 'type' => 'text/plain', 'filesize' => 3, 'uploaded_on' => 1603090370, 'exif' => []])); + filesystem()->file(PATH['project'] . '/media/bar.txt')->put('foo'); + filesystem()->file(PATH['project'] . '/media/.meta/bar.txt.yaml')->put(flextype('serializers')->yaml()->encode(['title' => 'Bar', 'description' => '', 'type' => 'text/plain', 'filesize' => 3, 'uploaded_on' => 1603090370, 'exif' => []])); + + $this->assertEquals('Foo', flextype('parsers')->shortcode()->process('[media_files_fetch id="foo.txt" field="title"]')); + $this->assertEquals('Bar', flextype('parsers')->shortcode()->process('[media_files_fetch id="foo.txt" field="foo" default="Bar"]')); +});