From 68bb5b0e24d0d97d336c4d25ac1350002b0db7fe Mon Sep 17 00:00:00 2001 From: Patrick Ward Date: Fri, 23 Oct 2015 12:41:54 -0400 Subject: [PATCH] Initial shortcode implementation --- Plugin.php | 90 +++++++++++++++++++ README.md | 62 ++++++++++++- classes/Shortcode.php | 153 +++++++++++++++++++++++++++++++ classes/ShortcodeFacade.php | 17 ++++ classes/ShortcodeServiceProvider.php | 35 ++++++++ composer.json | 26 ++++++ composer.lock | 169 +++++++++++++++++++++++++++++++++++ models/Settings.php | 17 ++++ models/settings/fields.yaml | 10 +++ tests/ShortcodeTest.php | 91 +++++++++++++++++++ 10 files changed, 669 insertions(+), 1 deletion(-) create mode 100644 Plugin.php create mode 100644 classes/Shortcode.php create mode 100644 classes/ShortcodeFacade.php create mode 100644 classes/ShortcodeServiceProvider.php create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 models/Settings.php create mode 100644 models/settings/fields.yaml create mode 100644 tests/ShortcodeTest.php diff --git a/Plugin.php b/Plugin.php new file mode 100644 index 0000000..a1f2d93 --- /dev/null +++ b/Plugin.php @@ -0,0 +1,90 @@ + 'Shortcode', + 'description' => 'Shortcode integration for OctoberCMS.', + 'author' => 'Sensory 5', + 'icon' => 'icon-code' + ]; + } + + /** + * Register service provider, Twig extensions, and alias facade. + */ + public function boot() + { + // Service provider + App::register('\Sensory5\Shortcode\Classes\ShortcodeServiceProvider'); + + // Register alias + $alias = AliasLoader::getInstance(); + $alias->alias('Shortcode', '\Sensory5\Shortcode\Classes\ShortcodeFacade'); + + // Enable shortcodes on all pages if requested + if (Settings::get('enable_on_render', false)) + { + Event::listen('cms.page.render', function($controller, $content) { + + return \Shortcode::parse($content); + + }); + } + + } + + /** + * Register twig filters + */ + public function registerMarkupTags() + { + + return [ + 'filters' => [ 'shortcode' => ['\Shortcode', 'parse'] ], + 'functions' => ['shortcode' => ['\Shortcode', 'parse'] ] + ]; + + } + + /** + * Register backend settings + */ + public function registerSettings() + { + + return [ + 'settings' => [ + 'label' => 'Shortcode Settings', + 'description' => 'Manage shortcode settings', + 'category' => 'Shortcodes', + 'icon' => 'icon-code', + 'class' => 'Sensory5\Shortcode\Models\Settings', + 'order' => 600, + 'keywords' => 'shortcode shortcodes' + ] + ]; + + } + +} diff --git a/README.md b/README.md index 7011f9a..4373188 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,62 @@ -# oc-shortcode-plugin +# Shortcodes for October CMS + Adds the ability to use shortcodes within October CMS + +### Add new shortcodes + +Add the shortcode in the `boot` function of a plugin. + +Then, use the provided facade to add new shortcodes in your own plugins: + + \Shortcode::add('code', function(ShortcodeInterface $s) { + $text = "

Shortcode Name: {$s->getName()}

"; + $text .= "

Shortcode Parameter: {$s->getParameter('param', 'Not Found')}

"; + $text .= "

Shortcode Content: {$s->getContent()}

"; + return $text; + }); + +The above registration would enable the following shortcode to be used: + + [code param="Parameter Value"] Inside Content [/code] + +### Use the shortcode within a page or blog post: + +The usual shortcode syntax is supported via the [Thunderer\Shortcode](https://github.com/thunderer/Shortcode) project. + + [code] + [code argument="value"] + [code novalue argument=simple other="complex value"] + [code]content[/code] + [code argument="value"]content[/code] + +### Enable shortcodes on all pages + +To enable shortcodes on all page rendering, go to **Shortcode Settings** in the admin settings panel and check "Enable Shortcodes on all page rendering". + +### Using filters and functions + +If you don't want to enable shortcodes on all page rendering, you can use the built-in filter or function. + +To use the `shortcode` filter within a blog post or page, add the `shortcode` filter before any `raw` filtering. + +
{{ post.content_html|shortcode|raw }}
+ +To use the `shortcode` function within a layout and against the October CMS `page` tag, change the `page` tag from a token to a function: + + {% page %} + +Becomes: + + {{ shortcode(page()) }} + + -- or -- + + {{ page()|shortcode }} + +### Strip Shortcodes + +You can strip shortcodes from text using the `Shortcode::strip` function. For example, when using with the `Sensory5\BlogExtension` plugin, you can strip shortcodes from the summary using the following code: + + Event::listen('sensory5.blog.summary', function($content) { + return Str::limit(Html::strip(\Shortcode::strip($content)), 600); + }); diff --git a/classes/Shortcode.php b/classes/Shortcode.php new file mode 100644 index 0000000..91dc955 --- /dev/null +++ b/classes/Shortcode.php @@ -0,0 +1,153 @@ +handlers = new HandlerContainer(); + } + + /** + * Get the names for all registered shortcodes. + * + * @return array + */ + public function getNames() + { + return $this->handlers->getNames(); + } + + /** + * Add a new shortcode to the handler container. + * + * @param string $name + * @param mixed $callback + */ + public function add($name, $callback) + { + $this->handlers->add($name, $callback); + } + + /** + * Remove the specified shortcode name from the handler. + * + * @param string $name + */ + public function remove($name) + { + if ($this->exists($name)) { + $this->handlers->remove($name); + } + + return $this; + } + + /** + * Remove all registered shortcodes + * + * @return self + */ + public function destroyAll() + { + $this->handlers = new HandlerContainer(); + + return $this; + } + + /** + * Strip any shortcodes from the content. + * + * @param string $content + * + * @return string + */ + public function strip($content) + { + $handlers = new HandlerContainer(); + $handlers->setDefault(function(ShortcodeInterface $s) { return $s->getContent(); }); + $processor = new Processor(new RegexParser(), $handlers); + + return $processor->process($content); + } + + /** + * Get count from all shortcodes. + * + * @return int + */ + public function count() + { + return count($this->handlers->getNames()); + } + + /** + * Return true is the given name exist in shortcodes array. + * + * @param string $name + * + * @return bool + */ + public function exists($name) + { + return $this->handlers->has($name); + } + + /** + * Return true is the given content contains the named shortcode. + * + * @param string $content + * @param string $name + * + * @return bool + */ + public function contains($content, $name) + { + $hasShortcode = false; + + $handlers = new HandlerContainer(); + $handlers->setDefault(function(ShortcodeInterface $s) use($name, &$hasShortcode) { + if($s->getName() === $name) { + $hasShortcode = true; + } + }); + $processor = new Processor(new RegexParser(), $handlers); + $processor->process($content); + + return $hasShortcode; + } + + /** + * Parse content and replace parts of it using registered handlers + * + * @param $content + * + * @return string + */ + public function parse($content) + { + $processor = new Processor(new RegexParser(), $this->handlers); + + return $processor->process($content); + } +} + diff --git a/classes/ShortcodeFacade.php b/classes/ShortcodeFacade.php new file mode 100644 index 0000000..2c44795 --- /dev/null +++ b/classes/ShortcodeFacade.php @@ -0,0 +1,17 @@ +app['shortcode'] = $this->app->share(function ($app) { + return new Shortcode(); + }); + } + + /** + * Get the services provided by the provider. + * + * @return array + */ + public function provides() + { + return array('shortcode'); + } + +} + diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..01189e9 --- /dev/null +++ b/composer.json @@ -0,0 +1,26 @@ +{ + "name": "sensory5/oc-shortcode-plugin", + "description": "Adds the ability to use shortcodes within October CMS", + "type": "october-plugin", + "extra": { + "installer-name": "shortcode" + }, + "license": "MIT", + "authors": [ + { + "name": "Patrick Ward", + "email": "patrick@sensory5.com" + } + ], + "minimum-stability": "dev", + "require": { + "php": ">=5.4.0", + "composer/installers": "~1.0", + "thunderer/shortcode": "dev-master" + }, + "autoload": { + "psr-4": { + "Sensory5\\Shortcode\\": "" + } + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..a7efe24 --- /dev/null +++ b/composer.lock @@ -0,0 +1,169 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "1af5d57fe99292fd0a02e59af2fe9fd5", + "packages": [ + { + "name": "composer/installers", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/composer/installers.git", + "reference": "e420b539e8d7b38b7c6f3f99dccc0386bd3dfe41" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/installers/zipball/e420b539e8d7b38b7c6f3f99dccc0386bd3dfe41", + "reference": "e420b539e8d7b38b7c6f3f99dccc0386bd3dfe41", + "shasum": "" + }, + "replace": { + "roundcube/plugin-installer": "*", + "shama/baton": "*" + }, + "require-dev": { + "composer/composer": "1.0.*@dev", + "phpunit/phpunit": "4.1.*" + }, + "type": "composer-installer", + "extra": { + "class": "Composer\\Installers\\Installer", + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "Composer\\Installers\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Kyle Robinson Young", + "email": "kyle@dontkry.com", + "homepage": "https://github.com/shama" + } + ], + "description": "A multi-framework Composer library installer", + "homepage": "http://composer.github.com/installers/", + "keywords": [ + "Craft", + "Dolibarr", + "Hurad", + "MODX Evo", + "OXID", + "SMF", + "Thelia", + "WolfCMS", + "agl", + "aimeos", + "annotatecms", + "bitrix", + "cakephp", + "chef", + "codeigniter", + "concrete5", + "croogo", + "dokuwiki", + "drupal", + "elgg", + "fuelphp", + "grav", + "installer", + "joomla", + "kohana", + "laravel", + "lithium", + "magento", + "mako", + "mediawiki", + "modulework", + "moodle", + "phpbb", + "piwik", + "ppi", + "puppet", + "roundcube", + "shopware", + "silverstripe", + "symfony", + "typo3", + "wordpress", + "zend", + "zikula" + ], + "time": "2015-06-13 15:30:38" + }, + { + "name": "thunderer/shortcode", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/thunderer/Shortcode.git", + "reference": "8a57fc61058e616279e758d0437fb5c525fdf837" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thunderer/Shortcode/zipball/8a57fc61058e616279e758d0437fb5c525fdf837", + "reference": "8a57fc61058e616279e758d0437fb5c525fdf837", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.1" + }, + "suggest": { + "ext-dom": "if you want to use XML serializer", + "ext-json": "if you want to use JSON serializer", + "symfony/yaml": "if you want to use YAML serializer" + }, + "type": "library", + "autoload": { + "psr-4": { + "Thunder\\Shortcode\\": "src/", + "Thunder\\Shortcode\\Tests\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tomasz Kowalczyk", + "email": "tomasz@kowalczyk.cc" + } + ], + "description": "Advanced shortcode (BBCode) parser and engine for PHP", + "keywords": [ + "bbcode", + "engine", + "library", + "parser", + "shortcode" + ], + "time": "2015-10-19 19:17:59" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": { + "thunderer/shortcode": 20 + }, + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.4.0" + }, + "platform-dev": [] +} diff --git a/models/Settings.php b/models/Settings.php new file mode 100644 index 0000000..416543f --- /dev/null +++ b/models/Settings.php @@ -0,0 +1,17 @@ +assertSame($expect, $this->getShortcode()->parse($text)); + } + + public function provideTexts() + { + return [ + ['[name]', 'name'], + ['[content]', ''], + ['[content]thunder[/content]', 'thunder'], + ['[content][name][/content]', 'name'], + ['[nc][name][/nc]', 'nc: name'], + ]; + } + + public function testCount() + { + $this->assertSame(3, $this->getShortcode()->count()); + } + + public function testAll() + { + $this->assertSame(['name', 'content', 'nc'], $this->getShortcode()->all()); + } + + public function testUnregister() + { + $this->assertSame('[name]', $this->getShortcode()->unregister('name')->parse('[name]')); + } + + public function testDestroy() + { + $this->assertSame('[name]', $this->getShortcode()->destroy()->parse('[name]')); + } + + public function testStrip() + { + $this->assertSame('', $this->getShortcode()->strip('[name]')); + $this->assertSame('x y', $this->getShortcode()->strip('x [name]y')); + $this->assertSame('x a a y', $this->getShortcode()->strip('x [name] a [content /] a [/name] y')); + } + + public function testExists() + { + $shortcode = $this->getShortcode(); + + $this->assertTrue($shortcode->exists('name')); + $this->assertTrue($shortcode->exists('content')); + $this->assertTrue($shortcode->exists('nc')); + $this->assertFalse($shortcode->exists('invalid')); + } + + public function testContains() + { + $shortcode = $this->getShortcode(); + + $this->assertTrue($shortcode->contains('[name]', 'name')); + $this->assertFalse($shortcode->contains('[x]', 'name')); + } + + private function getShortcode() + { + $shortcode = new Shortcode(); + + $shortcode->register('name', function(ShortcodeInterface $s) { + return $s->getName(); + }); + $shortcode->register('content', function(ShortcodeInterface $s) { + return $s->getContent(); + }); + $shortcode->register('nc', function(ShortcodeInterface $s) { + return $s->getName().': '.$s->getContent(); + }); + + return $shortcode; + } +}