├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── assets └── shortcode-core-1.png ├── blueprints.yaml ├── classes ├── Shortcode.php ├── ShortcodeManager.php ├── ShortcodeObject.php ├── plugin │ ├── Shortcode.php │ ├── ShortcodeManager.php │ ├── ShortcodeObject.php │ └── ShortcodeTwigVar.php └── shortcodes │ ├── AlignShortcode.php │ ├── ColorShortcode.php │ ├── ColumnsShortcode.php │ ├── DetailsShortcode.php │ ├── DivShortcode.php │ ├── FigureShortcode.php │ ├── FontAwesomeShortcode.php │ ├── HShortcode.php │ ├── LanguageShortcode.php │ ├── LoremShortcode.php │ ├── MarkShortcode.php │ ├── NoticeShortcode.php │ ├── RawShortcode.php │ ├── SafeEmailShortcode.php │ ├── SectionShortcode.php │ ├── Shortcode.php │ ├── ShortcodeObject.php │ ├── SizeShortcode.php │ ├── SpanShortcode.php │ └── UnderlineShortcode.php ├── cli └── ShortcodesCommand.php ├── composer.json ├── composer.lock ├── css └── shortcode-notice.css ├── nextgen-editor ├── .browserslistrc ├── .editorconfig ├── .env ├── .eslintrc.js ├── .gitignore ├── README.md ├── babel.config.js ├── dist │ ├── css │ │ └── app.css │ └── js │ │ ├── app.js │ │ └── app.js.map ├── package.json ├── shortcodes │ ├── align │ │ └── align.js │ ├── color │ │ └── color.js │ ├── columns │ │ └── columns.js │ ├── details │ │ ├── details.css │ │ └── details.js │ ├── div │ │ └── div.js │ ├── figure │ │ └── figure.js │ ├── fontawesome │ │ └── fontawesome.js │ ├── headers │ │ ├── headers.css │ │ └── headers.js │ ├── language │ │ └── language.js │ ├── lorem │ │ └── lorem.js │ ├── mark │ │ ├── mark.css │ │ └── mark.js │ ├── notice │ │ ├── notice.css │ │ └── notice.js │ ├── raw │ │ └── raw.js │ ├── safe-email │ │ └── safe-email.js │ ├── section │ │ └── section.js │ ├── shortcode-core.js │ ├── size │ │ └── size.js │ ├── span │ │ └── span.js │ └── u │ │ └── u.js ├── src │ ├── collapse.js │ ├── command.js │ ├── converters.js │ ├── events.js │ ├── init.js │ ├── main.css │ ├── main.js │ ├── postsave.js │ ├── prerender.js │ ├── remove.js │ ├── render.js │ ├── save.js │ ├── settings.js │ └── uncollapse.js ├── vue.config.js └── yarn.lock ├── shortcode-core.php ├── shortcode-core.yaml ├── templates └── shortcodes │ └── notice.html.twig └── vendor ├── autoload.php ├── composer ├── ClassLoader.php ├── InstalledVersions.php ├── LICENSE ├── autoload_classmap.php ├── autoload_namespaces.php ├── autoload_psr4.php ├── autoload_real.php ├── autoload_static.php ├── installed.json ├── installed.php └── platform_check.php └── thunderer └── shortcode ├── .github └── workflows │ ├── test-old.yaml │ └── test.yaml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── composer.json ├── docker-compose.yaml ├── docker ├── php-5.x │ └── Dockerfile └── php │ ├── Dockerfile │ └── php.ini ├── infection.json ├── phpunit.xml.dist ├── psalm.xml ├── src ├── Event │ ├── FilterShortcodesEvent.php │ └── ReplaceShortcodesEvent.php ├── EventContainer │ ├── EventContainer.php │ └── EventContainerInterface.php ├── EventHandler │ ├── FilterRawEventHandler.php │ └── ReplaceJoinEventHandler.php ├── Events.php ├── Handler │ ├── ContentHandler.php │ ├── DeclareHandler.php │ ├── EmailHandler.php │ ├── NameHandler.php │ ├── NullHandler.php │ ├── PlaceholderHandler.php │ ├── RawHandler.php │ ├── SerializerHandler.php │ ├── UrlHandler.php │ └── WrapHandler.php ├── HandlerContainer │ ├── HandlerContainer.php │ ├── HandlerContainerInterface.php │ └── ImmutableHandlerContainer.php ├── Parser │ ├── ParserInterface.php │ ├── RegexParser.php │ ├── RegularParser.php │ └── WordpressParser.php ├── Processor │ ├── Processor.php │ ├── ProcessorContext.php │ └── ProcessorInterface.php ├── Serializer │ ├── JsonSerializer.php │ ├── SerializerInterface.php │ ├── TextSerializer.php │ ├── XmlSerializer.php │ └── YamlSerializer.php ├── Shortcode │ ├── AbstractShortcode.php │ ├── ParsedShortcode.php │ ├── ParsedShortcodeInterface.php │ ├── ProcessedShortcode.php │ ├── ReplacedShortcode.php │ ├── Shortcode.php │ └── ShortcodeInterface.php ├── ShortcodeFacade.php ├── Syntax │ ├── CommonSyntax.php │ ├── Syntax.php │ ├── SyntaxBuilder.php │ └── SyntaxInterface.php └── Utility │ └── RegexBuilderUtility.php └── tests ├── AbstractTestCase.php ├── EventsTest.php ├── FacadeTest.php ├── Fake └── ReverseShortcode.php ├── HandlerContainerTest.php ├── ParserTest.php ├── ProcessorTest.php ├── SerializerTest.php ├── ShortcodeTest.php └── SyntaxTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /vendor/.DS_Store 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Grav 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /assets/shortcode-core-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/getgrav/grav-plugin-shortcode-core/cb60184db9ac09e511f27a394f415a15706483ca/assets/shortcode-core-1.png -------------------------------------------------------------------------------- /blueprints.yaml: -------------------------------------------------------------------------------- 1 | name: Shortcode Core 2 | slug: shortcode-core 3 | type: plugin 4 | version: 5.2.1 5 | description: "This plugin provides the core functionality for shortcode plugins" 6 | icon: code 7 | author: 8 | name: Team Grav 9 | email: devs@getgrav.org 10 | url: http://getgrav.org 11 | homepage: https://github.com/getgrav/grav-plugin-shortcode-core 12 | demo: http://learn.getgrav.org 13 | keywords: gui, plugin, tabs, twig 14 | bugs: https://github.com/getgrav/grav-plugin-shortcode-core/issues 15 | license: MIT 16 | 17 | dependencies: 18 | - { name: grav, version: '>=1.6.4' } 19 | 20 | form: 21 | validation: strict 22 | fields: 23 | enabled: 24 | type: toggle 25 | label: Plugin Enabled 26 | highlight: 1 27 | default: 1 28 | options: 29 | 1: Enabled 30 | 0: Disabled 31 | validate: 32 | type: bool 33 | 34 | active: 35 | type: toggle 36 | label: Activated 37 | help: Site-Wide activation 38 | highlight: 1 39 | default: 1 40 | options: 41 | 1: Enabled 42 | 0: Disabled 43 | validate: 44 | type: bool 45 | 46 | active_admin: 47 | type: toggle 48 | label: Activated in Admin 49 | highlight: 1 50 | default: 1 51 | options: 52 | 1: Enabled 53 | 0: Disabled 54 | validate: 55 | type: bool 56 | 57 | admin_pages_only: 58 | type: toggle 59 | label: Admin Real-Pages Only 60 | help: When activate, only process real-pages 61 | highlight: 1 62 | default: 1 63 | options: 64 | 1: Enabled 65 | 0: Disabled 66 | validate: 67 | type: bool 68 | 69 | parser: 70 | type: select 71 | size: medium 72 | classes: fancy 73 | label: Processor 74 | help: Which built-in processor to use. WordPress (fastest), Regular (customizable), Regex (solid) 75 | options: 76 | wordpress: WordpressParser 77 | regex: RegexParser 78 | regular: RegularParser 79 | 80 | custom_shortcodes: 81 | type: text 82 | label: Custom Shortcodes 83 | help: The path to a location where you store custom shortcodes. 84 | placeholder: '/user/custom/shortcodes' 85 | size: large 86 | 87 | css.notice_enabled: 88 | type: toggle 89 | label: Enable Notice Shortcode CSS 90 | help: Enable the default notice CSS by default. Disable if you want to use your own custom CSS. 91 | highlight: 1 92 | default: 1 93 | options: 94 | 1: Enabled 95 | 0: Disabled 96 | validate: 97 | type: bool 98 | 99 | fontawesome.load: 100 | type: toggle 101 | label: Load Fontawesome Library 102 | help: Used by the `safe-email` shortcode if your theme doesn't already load it 103 | highlight: 1 104 | default: 1 105 | options: 106 | 1: Enabled 107 | 0: Disabled 108 | validate: 109 | type: bool 110 | 111 | fontawesome.url: 112 | type: text 113 | label: Fontawesome URL 114 | help: You can change the location of fontawesome by changing this URL 115 | size: large 116 | 117 | fontawesome.v5: 118 | type: toggle 119 | label: Use Fontawesome Version 5 120 | help: Allows usage of the 'fab', 'fas' and other new font families of Fontawesome 5. 121 | highlight: 0 122 | default: 0 123 | options: 124 | 1: Enabled 125 | 0: Disabled 126 | validate: 127 | type: bool 128 | -------------------------------------------------------------------------------- /classes/Shortcode.php: -------------------------------------------------------------------------------- 1 | obj_name = $name; 13 | $this->obj_object = $object; 14 | } 15 | 16 | public function __toString() 17 | { 18 | return $this->obj_object; 19 | } 20 | 21 | public function name() 22 | { 23 | return $this->obj_name; 24 | } 25 | 26 | public function object() 27 | { 28 | return $this->obj_object; 29 | } 30 | } 31 | 32 | // Make sure we also autoload the deprecated class. 33 | class_exists(\Grav\Plugin\Shortcodes\ShortcodeObject::class); 34 | -------------------------------------------------------------------------------- /classes/plugin/ShortcodeTwigVar.php: -------------------------------------------------------------------------------- 1 | getObjects(); 14 | 15 | if ($objects) { 16 | return $objects[$name] ?? []; 17 | } 18 | 19 | $page_meta = Grav::instance()['page']->getContentMeta('shortcodeMeta'); 20 | if (isset($page_meta['shortcode'])) { 21 | $objects = (array) $page_meta['shortcode']; 22 | return $objects[$name] ?? []; 23 | } 24 | 25 | return []; 26 | } 27 | } -------------------------------------------------------------------------------- /classes/shortcodes/AlignShortcode.php: -------------------------------------------------------------------------------- 1 | shortcode->getHandlers()->add('center', static function(ShortcodeInterface $sc) { 11 | return '
' . $sc->getContent() . '
'; 12 | }); 13 | 14 | $this->shortcode->getHandlers()->add('left', static function(ShortcodeInterface $sc) { 15 | return '
' . $sc->getContent() . '
'; 16 | }); 17 | 18 | $this->shortcode->getHandlers()->add('right', static function(ShortcodeInterface $sc) { 19 | return '
' . $sc->getContent() . '
'; 20 | }); 21 | 22 | $this->shortcode->getHandlers()->add('justify', static function(ShortcodeInterface $sc) { 23 | return '
' . $sc->getContent() . '
'; 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /classes/shortcodes/ColorShortcode.php: -------------------------------------------------------------------------------- 1 | shortcode->getHandlers()->add('color', function(ShortcodeInterface $sc) { 11 | $color = $sc->getParameter('color', $this->getBbCode($sc)); 12 | 13 | return '' . $sc->getContent() . ''; 14 | }); 15 | } 16 | } -------------------------------------------------------------------------------- /classes/shortcodes/ColumnsShortcode.php: -------------------------------------------------------------------------------- 1 | shortcode->getHandlers()->add('columns', static function(ShortcodeInterface $sc) { 11 | $column_count = (int)$sc->getParameter('count', 2); 12 | $column_width = $sc->getParameter('width', 'auto'); 13 | $column_gap = $sc->getParameter('gap', 'normal'); 14 | $column_rule = $sc->getParameter('rule', false); 15 | 16 | $css_style = 'columns:' . $column_count . ' ' . $column_width . ';-moz-columns:' . $column_count . ' ' . $column_width . ';'; 17 | $css_style .= 'column-gap:' . $column_gap . ';-moz-column-gap:' . $column_gap . ';'; 18 | 19 | if ($column_rule) { 20 | $css_style .= 'column-rule:' . $column_rule . ';-moz-column-rule:' . $column_rule . ';'; 21 | } 22 | 23 | return '
' . $sc->getContent() . '
'; 24 | }); 25 | 26 | } 27 | } -------------------------------------------------------------------------------- /classes/shortcodes/DetailsShortcode.php: -------------------------------------------------------------------------------- 1 | shortcode->getHandlers()->add('details', function(ShortcodeInterface $sc) { 11 | // Get summary/title 12 | $summary = $sc->getParameter('summary', $this->getBbCode($sc)); 13 | $summaryHTML = $summary ? '' . $summary . '' : ''; 14 | 15 | // Get classes for details 16 | $class = $sc->getParameter('class', $this->getBbCode($sc)); 17 | $classHTML = (isset($class) and $class !== $summary) ? 'class="' . $class . '"' : ''; 18 | 19 | // Get content 20 | $content = $sc->getContent(); 21 | 22 | // Return the details/summary block 23 | return '
' . $summaryHTML . $content . '
'; 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /classes/shortcodes/DivShortcode.php: -------------------------------------------------------------------------------- 1 | shortcode->getHandlers()->add('div', static function(ShortcodeInterface $sc) { 11 | $id = $sc->getParameter('id'); 12 | $class = $sc->getParameter('class'); 13 | $style = $sc->getParameter('style'); 14 | 15 | $id_output = $id ? ' id="' . $id . '" ': ''; 16 | $class_output = $class ? ' class="' . $class . '"' : ''; 17 | $style_output = $style ? ' style="' . $style . '"' : ''; 18 | 19 | return '
' . $sc->getContent() . '
'; 20 | }); 21 | } 22 | } -------------------------------------------------------------------------------- /classes/shortcodes/FigureShortcode.php: -------------------------------------------------------------------------------- 1 | shortcode->getHandlers()->add('figure', function(ShortcodeInterface $sc) { 12 | $id = $sc->getParameter('id'); 13 | $class = $sc->getParameter('class'); 14 | $caption = $sc->getParameter('caption'); 15 | $page = $this->grav['page']; 16 | 17 | // Process any markdown on caption 18 | $caption = Utils::processMarkdown($caption, false, $page); 19 | 20 | $id_output = $id ? 'id="' . $id . '" ': ''; 21 | $class_output = $class ? 'class="' . $class . '"' : ''; 22 | $caption_output = $caption ? '
' . $caption . '
' : ''; 23 | 24 | return '
'.$sc->getContent(). $caption_output . '
'; 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /classes/shortcodes/FontAwesomeShortcode.php: -------------------------------------------------------------------------------- 1 | shortcode->getHandlers()->add('fa', function(ShortcodeInterface $sc) { 12 | // Load assets if required 13 | if ($this->config->get('plugins.shortcode-core.fontawesome.load', false)) { 14 | $this->shortcode->addAssets('css', $this->config->get('plugins.shortcode-core.fontawesome.url')); 15 | } 16 | if ($this->config->get('plugins.shortcode-core.fontawesome.v5', false)) { 17 | $v5classes = ['fab', 'fal', 'fas', 'far', 'fad']; 18 | } else { 19 | $v5classes = []; 20 | } 21 | 22 | // Get shortcode content and parameters 23 | $str = $sc->getContent(); 24 | $icon = $sc->getParameter('icon', $sc->getParameter('fa', $this->getBbCode($sc))); 25 | 26 | if (!Utils::startsWith($icon, 'fa-')) { 27 | $icon = 'fa-'.$icon; 28 | } 29 | 30 | if ($icon) { 31 | $fa_class = 'fa'; 32 | $extras = explode(',', $sc->getParameter('extras', '')); 33 | 34 | foreach($extras as $extra) { 35 | if(!empty($extra)) { 36 | if(in_array($extra, $v5classes, true)) { 37 | $fa_class = $extra; 38 | continue; 39 | } 40 | if(!Utils::startsWith($extra, 'fa-')) { 41 | $extra = 'fa-' . $extra; 42 | } 43 | $icon .= ' ' . $extra; 44 | } 45 | } 46 | 47 | return '' . $str . ''; 48 | } 49 | 50 | return ''; 51 | }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /classes/shortcodes/HShortcode.php: -------------------------------------------------------------------------------- 1 | shortcode->getHandlers()->add('h1', function(ShortcodeInterface $sc) { 11 | return $this->header(1, $sc); 12 | }); 13 | 14 | $this->shortcode->getHandlers()->add('h2', function(ShortcodeInterface $sc) { 15 | return $this->header(2, $sc); 16 | }); 17 | 18 | $this->shortcode->getHandlers()->add('h3', function(ShortcodeInterface $sc) { 19 | return $this->header(3, $sc); 20 | }); 21 | 22 | $this->shortcode->getHandlers()->add('h4', function(ShortcodeInterface $sc) { 23 | return $this->header(4, $sc); 24 | }); 25 | 26 | $this->shortcode->getHandlers()->add('h5', function(ShortcodeInterface $sc) { 27 | return $this->header(5, $sc); 28 | }); 29 | 30 | $this->shortcode->getHandlers()->add('h6', function(ShortcodeInterface $sc) { 31 | return $this->header(6, $sc); 32 | }); 33 | 34 | 35 | } 36 | 37 | protected function header($level, ShortcodeInterface $sc) 38 | { 39 | $id = $sc->getParameter('id'); 40 | $class = $sc->getParameter('class'); 41 | $tag = 'h' . $level; 42 | 43 | $id_output = $id ? ' id="' . $id . '" ': ''; 44 | $class_output = $class ? ' class="' . $class . '"' : ''; 45 | 46 | return "<{$tag}{$id_output}{$class_output}>{$sc->getContent()}"; 47 | } 48 | } -------------------------------------------------------------------------------- /classes/shortcodes/LanguageShortcode.php: -------------------------------------------------------------------------------- 1 | shortcode->getHandlers()->add('lang', function(ShortcodeInterface $sc) { 13 | $lang = $this->getBbCode($sc); 14 | 15 | if ($lang) { 16 | $list = explode(',', $lang); 17 | array_walk($list, 'trim'); 18 | 19 | /** @var Language $language */ 20 | $language = $this->grav['language']; 21 | $current = $language->getLanguage(); 22 | 23 | if (in_array($current, $list)) { 24 | return $sc->getContent(); 25 | } 26 | } 27 | 28 | return ''; 29 | }); 30 | } 31 | } -------------------------------------------------------------------------------- /classes/shortcodes/MarkShortcode.php: -------------------------------------------------------------------------------- 1 | shortcode->getHandlers()->add('mark', function(ShortcodeInterface $sc) { 11 | $style = $sc->getParameter('style', $this->getBbCode($sc)); 12 | $class = $sc->getParameter('class', 'default'); 13 | 14 | $css_class = 'class="mark-class-' . $class . '"'; 15 | 16 | if ($style === 'block') { 17 | $css_style = 'style="display:block;"'; 18 | $content = trim($sc->getContent(), "\n"); 19 | } else { 20 | $css_style = ''; 21 | $content = $sc->getContent(); 22 | } 23 | 24 | return "{$content}"; 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /classes/shortcodes/NoticeShortcode.php: -------------------------------------------------------------------------------- 1 | shortcode->getHandlers()->add('notice', function(ShortcodeInterface $sc) { 11 | $css_enabled = $this->grav['config']->get('plugins.shortcode-core.css.notice_enabled', true); 12 | if ($css_enabled) { 13 | $this->shortcode->addAssets('css', 'plugin://shortcode-core/css/shortcode-notice.css'); 14 | } 15 | 16 | $output = $this->twig->processTemplate('shortcodes/notice.html.twig', [ 17 | 'type' => $sc->getParameter('notice', $this->getBbCode($sc)) ?: 'info', 18 | 'content' => $sc->getContent(), 19 | ]); 20 | 21 | return $output; 22 | }); 23 | } 24 | } -------------------------------------------------------------------------------- /classes/shortcodes/RawShortcode.php: -------------------------------------------------------------------------------- 1 | shortcode->getHandlers()->add('raw', static function(ShortcodeInterface $sc) { 13 | return trim($sc->getContent()); 14 | }); 15 | 16 | $this->shortcode->getEvents()->addListener(Events::FILTER_SHORTCODES, new FilterRawEventHandler(['raw'])); 17 | } 18 | } -------------------------------------------------------------------------------- /classes/shortcodes/SafeEmailShortcode.php: -------------------------------------------------------------------------------- 1 | shortcode->getHandlers()->add('safe-email', function(ShortcodeInterface $sc) { 11 | // Load assets if required 12 | if ($this->config->get('plugins.shortcode-core.fontawesome.load', false)) { 13 | $this->shortcode->addAssets('css', $this->config->get('plugins.shortcode-core.fontawesome.url')); 14 | } 15 | 16 | // Get shortcode content and parameters 17 | $addr_str = $sc->getContent(); 18 | $icon = $sc->getParameter('icon', false); 19 | $icon_base = "fa fa-"; 20 | $autolink = $sc->getParameter('autolink', false); 21 | $subject = $sc->getParameter('subject', false); 22 | 23 | // Add subject, if any, to the link target. 24 | $link_str = $addr_str; 25 | if ($subject) { 26 | $subject = html_entity_decode($subject); 27 | $link_str .= '?subject=' . rawurlencode($subject); 28 | } 29 | 30 | // Encode display text and link target 31 | $email_disp = static::encodeText($addr_str); 32 | $email_link = static::encodeText($link_str); 33 | 34 | // Handle autolinking 35 | if ($autolink) { 36 | $output = '' . $email_disp . ''; 37 | } else { 38 | $output = $email_disp; 39 | } 40 | 41 | // Handle icon option 42 | if ($icon) { 43 | if ($this->config->get('plugins.shortcode-core.fontawesome.v5', false)) { 44 | if (preg_match("/^(?Pfa[srlbd]) fa-(?.+)/", $icon, $icon_parts)) { 45 | $icon_base = $icon_parts["weight"] . " fa-"; 46 | $icon = $icon_parts["icon"]; 47 | } 48 | } 49 | 50 | $output = ' ' . $output; 51 | } 52 | 53 | return $output; 54 | }); 55 | } 56 | 57 | /** 58 | * encodes text as numeric HTML entities 59 | * @param string $text the text to encode 60 | * @return string the encoded text 61 | */ 62 | private static function encodeText($text) 63 | { 64 | $encoded = ''; 65 | $str_len = strlen($text); 66 | 67 | for ($i = 0; $i < $str_len; $i++) { 68 | $encoded .= '&#' . ord($text[$i]). ';'; 69 | } 70 | 71 | return $encoded; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /classes/shortcodes/SectionShortcode.php: -------------------------------------------------------------------------------- 1 | shortcode->getHandlers()->add('section', function(ShortcodeInterface $sc) { 11 | $name = $sc->getParameter('name'); 12 | $page = $sc->getParameter('page'); 13 | $content = $sc->getContent(); 14 | 15 | if (empty($content) && isset($page)) { 16 | if ($target = $this->grav['pages']->find($page)) { 17 | if ($shortcodeObject = $target->contentMeta()['shortcodeMeta']['shortcode'][$sc->getName()][$name] ?? false) { 18 | return (string) $shortcodeObject; 19 | } 20 | } 21 | } 22 | 23 | $object = new \Grav\Plugin\ShortcodeCore\ShortcodeObject($name, $sc->getContent()); 24 | $this->shortcode->addObject($sc->getName(), $object); 25 | }); 26 | 27 | } 28 | } -------------------------------------------------------------------------------- /classes/shortcodes/Shortcode.php: -------------------------------------------------------------------------------- 1 | grav = Grav::instance(); 31 | $this->shortcode = $this->grav['shortcode']; 32 | $this->config = $this->grav['config']; 33 | $this->twig = $this->grav['twig']; 34 | } 35 | 36 | /** 37 | * Initialize shortcode handler 38 | */ 39 | public function init() 40 | { 41 | user_error(__METHOD__ . '() method will be abstract in the future, please override it!', E_USER_DEPRECATED); 42 | 43 | // FIXME: This code had to be put back because of some plugins do not properly initialize themselves. 44 | $this->shortcode->getHandlers()->add('u', static function(ShortcodeInterface $shortcode) { 45 | return $shortcode->getContent(); 46 | }); 47 | } 48 | 49 | /** 50 | * Returns the name of the class if required 51 | * 52 | * @return string the name of the class 53 | */ 54 | public function getName() 55 | { 56 | return get_class($this); 57 | } 58 | 59 | /** 60 | * @return string 61 | */ 62 | public function getParser() 63 | { 64 | return $this->config->get('plugins.shortcode-core.parser'); 65 | } 66 | 67 | /** 68 | * @param ShortcodeInterface $sc 69 | * @param string|null $default 70 | * @return string|null 71 | */ 72 | public function getBbCode(ShortcodeInterface $sc, $default = null) 73 | { 74 | $code = $default; 75 | 76 | if ($this->getParser() === 'wordpress') { 77 | $params = $sc->getParameters(); 78 | if (is_array($params)) { 79 | $keys = array_keys($params); 80 | $code = trim(array_shift($keys), '='); 81 | } 82 | } else { 83 | $code = $sc->getBbCode(); 84 | } 85 | 86 | return $code; 87 | } 88 | } 89 | 90 | // Make sure we also autoload the deprecated class. 91 | class_exists(\Grav\Plugin\ShortcodeCore\Shortcode::class); 92 | -------------------------------------------------------------------------------- /classes/shortcodes/ShortcodeObject.php: -------------------------------------------------------------------------------- 1 | shortcode->getHandlers()->add('size', function(ShortcodeInterface $sc) { 11 | $size = $sc->getParameter('size', $this->getBbCode($sc)); 12 | if (is_numeric($size)) { 13 | $size .= 'px'; 14 | } 15 | 16 | return '' . $sc->getContent() . ''; 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /classes/shortcodes/SpanShortcode.php: -------------------------------------------------------------------------------- 1 | shortcode->getHandlers()->add('span', static function(ShortcodeInterface $sc) { 11 | $id = $sc->getParameter('id'); 12 | $class = $sc->getParameter('class'); 13 | $style = $sc->getParameter('style'); 14 | 15 | $id_output = $id ? 'id="' . $id . '" ': ''; 16 | $class_output = $class ? 'class="' . $class . '"' : ''; 17 | $style_output = $style ? 'style="' . $style . '"' : ''; 18 | 19 | return '' . $sc->getContent() . ''; 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /classes/shortcodes/UnderlineShortcode.php: -------------------------------------------------------------------------------- 1 | shortcode->getHandlers()->add('u', static function(ShortcodeInterface $sc) { 11 | return '' . $sc->getContent() . ''; 12 | }); 13 | } 14 | } -------------------------------------------------------------------------------- /cli/ShortcodesCommand.php: -------------------------------------------------------------------------------- 1 | setName('display') 28 | ->setDescription('Display a list the available shortcodes that are registered'); 29 | } 30 | 31 | /** 32 | * @return int|null|void 33 | */ 34 | protected function serve() 35 | { 36 | $io = new SymfonyStyle($this->input, $this->output); 37 | $this->initializePlugins(); 38 | $this->initializeThemes(); 39 | 40 | $shortcodes = Grav::instance()['shortcode']; 41 | 42 | $io->title('Available Shortcodes'); 43 | $io->section('Regular Handlers:'); 44 | foreach ($shortcodes->getHandlers()->getNames() as $name) { 45 | $io->writeln($name); 46 | } 47 | $io->section('Raw Handlers:'); 48 | foreach ($shortcodes->getRawHandlers()->getNames() as $name) { 49 | $io->writeln($name); 50 | } 51 | 52 | $io->newLine(); 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "getgrav/shortcode-core", 3 | "type": "grav-plugin", 4 | "description": "Shortcode Core plugin for Grav CMS", 5 | "keywords": ["shortcode"], 6 | "homepage": "https://github.com/getgrav/grav-plugin-shortcode-core/", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Team Grav", 11 | "email": "devs@getgrav.org", 12 | "homepage": "http://getgrav.org", 13 | "role": "Developer" 14 | } 15 | ], 16 | "require": { 17 | "php": ">=7.1.3", 18 | "thunderer/shortcode": "~0.7" 19 | }, 20 | "autoload": { 21 | "psr-4": { 22 | "Grav\\Plugin\\ShortcodeCore\\": "classes/plugin", 23 | "Grav\\Plugin\\Shortcodes\\": "classes/shortcodes" 24 | }, 25 | "classmap": ["shortcode-core.php"] 26 | }, 27 | "config": { 28 | "platform": { 29 | "php": "7.1.3" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", 5 | "This file is @generated automatically" 6 | ], 7 | "content-hash": "95ac1934b2d5e35cff7d71f6744b2666", 8 | "packages": [ 9 | { 10 | "name": "thunderer/shortcode", 11 | "version": "v0.7.6", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/thunderer/Shortcode.git", 15 | "reference": "9f81424b4909007483d93c5defc0917d8a58debd" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/thunderer/Shortcode/zipball/9f81424b4909007483d93c5defc0917d8a58debd", 20 | "reference": "9f81424b4909007483d93c5defc0917d8a58debd", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": ">=5.3" 25 | }, 26 | "require-dev": { 27 | "phpunit/phpunit": ">=4.1", 28 | "symfony/yaml": ">=2.0" 29 | }, 30 | "suggest": { 31 | "ext-dom": "if you want to use XML serializer", 32 | "ext-json": "if you want to use JSON serializer", 33 | "symfony/yaml": "if you want to use YAML serializer" 34 | }, 35 | "type": "library", 36 | "autoload": { 37 | "psr-4": { 38 | "Thunder\\Shortcode\\": "src/" 39 | } 40 | }, 41 | "notification-url": "https://packagist.org/downloads/", 42 | "license": [ 43 | "MIT" 44 | ], 45 | "authors": [ 46 | { 47 | "name": "Tomasz Kowalczyk", 48 | "email": "tomasz@kowalczyk.cc" 49 | } 50 | ], 51 | "description": "Advanced shortcode (BBCode) parser and engine for PHP", 52 | "keywords": [ 53 | "bbcode", 54 | "engine", 55 | "library", 56 | "parser", 57 | "shortcode" 58 | ], 59 | "support": { 60 | "issues": "https://github.com/thunderer/Shortcode/issues", 61 | "source": "https://github.com/thunderer/Shortcode/tree/v0.7.6" 62 | }, 63 | "time": "2024-12-15T21:57:40+00:00" 64 | } 65 | ], 66 | "packages-dev": [], 67 | "aliases": [], 68 | "minimum-stability": "stable", 69 | "stability-flags": {}, 70 | "prefer-stable": false, 71 | "prefer-lowest": false, 72 | "platform": { 73 | "php": ">=7.1.3" 74 | }, 75 | "platform-dev": {}, 76 | "platform-overrides": { 77 | "php": "7.1.3" 78 | }, 79 | "plugin-api-version": "2.6.0" 80 | } 81 | -------------------------------------------------------------------------------- /css/shortcode-notice.css: -------------------------------------------------------------------------------- 1 | .sc-notice { 2 | margin: 30px 0; 3 | position: relative; 4 | } 5 | 6 | .sc-notice > div { 7 | padding: 5px 20px; 8 | display: block; 9 | margin-top: 0rem; 10 | margin-bottom: 0rem; 11 | color: #666; 12 | } 13 | 14 | .sc-notice > div:before { 15 | position: absolute; 16 | top: 2px; 17 | color: #fff; 18 | font-family: FontAwesome; 19 | content: ''; 20 | left: 10px; 21 | } 22 | 23 | .sc-notice > div:first-child:after { 24 | position: absolute; 25 | top: 2px; 26 | color: #fff; 27 | left: 30px; 28 | } 29 | 30 | .sc-notice.info > div:first-child { 31 | border-top: 30px solid #F0B37E; 32 | background: #FFF2DB; 33 | } 34 | 35 | .sc-notice.info > div:first-child:after { 36 | content: 'Info'; 37 | } 38 | 39 | .sc-notice.warning > div:first-child { 40 | border-top: 30px solid #DF6F6C; 41 | background: #FAE2E2; 42 | } 43 | 44 | .sc-notice.warning > div:first-child:after { 45 | content: 'Warning'; 46 | } 47 | 48 | .sc-notice.note > div:first-child { 49 | border-top: 30px solid #6AB0DE; 50 | background: #E7F2FA; 51 | } 52 | 53 | .sc-notice.note > div:first-child:after { 54 | content: 'Note'; 55 | } 56 | 57 | .sc-notice.tip > div:first-child { 58 | border-top: 30px solid #77C577; 59 | background: #E6F9E6; 60 | } 61 | 62 | .sc-notice.tip > div:first-child:after { 63 | content: 'Tip'; 64 | } 65 | -------------------------------------------------------------------------------- /nextgen-editor/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | -------------------------------------------------------------------------------- /nextgen-editor/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | end_of_line = lf 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | max_line_length = 100 8 | -------------------------------------------------------------------------------- /nextgen-editor/.env: -------------------------------------------------------------------------------- 1 | DEV_HOST=localhost 2 | DEV_PORT=2001 3 | -------------------------------------------------------------------------------- /nextgen-editor/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | }, 6 | extends: [ 7 | 'plugin:vue/recommended', 8 | '@vue/airbnb', 9 | ], 10 | parserOptions: { 11 | parser: 'babel-eslint', 12 | }, 13 | rules: { 14 | 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', 15 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', 16 | 'import/extensions': 'off', 17 | 'import/no-unresolved': 'off', 18 | 'import/no-extraneous-dependencies': ['error', { devDependencies: true }], 19 | 'no-restricted-syntax': ['off', 'ForOfStatement'], 20 | 'no-param-reassign': ['error', { props: false }], 21 | 'class-methods-use-this': 'off', 22 | 'object-curly-newline': 'off', 23 | 'no-nested-ternary': 'off', 24 | 'no-await-in-loop': 'off', 25 | 'max-len': 'off', 26 | }, 27 | }; 28 | -------------------------------------------------------------------------------- /nextgen-editor/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | 4 | # local env files 5 | .env.local 6 | .env.*.local 7 | 8 | # Log files 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # Editor directories and files 14 | .idea 15 | .vscode 16 | *.suo 17 | *.ntvs* 18 | *.njsproj 19 | *.sln 20 | *.sw? 21 | -------------------------------------------------------------------------------- /nextgen-editor/README.md: -------------------------------------------------------------------------------- 1 | # app 2 | 3 | ## Project setup 4 | ``` 5 | yarn install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | yarn serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | yarn build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | yarn lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /nextgen-editor/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/cli-plugin-babel/preset', 4 | ], 5 | }; 6 | -------------------------------------------------------------------------------- /nextgen-editor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextgen-editor", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vue-cli-service serve", 7 | "serve": "vue-cli-service serve", 8 | "build": "vue-cli-service build", 9 | "lint": "vue-cli-service lint" 10 | }, 11 | "devDependencies": { 12 | "@vue/cli-plugin-babel": "~4.5.13", 13 | "@vue/cli-plugin-eslint": "~4.5.13", 14 | "@vue/cli-service": "~4.5.13", 15 | "@vue/eslint-config-airbnb": "^5.3.0", 16 | "babel-eslint": "^10.1.0", 17 | "directory-named-webpack-plugin": "^4.0.1", 18 | "eslint": "^7.32.0", 19 | "eslint-plugin-import": "^2.24.2", 20 | "eslint-plugin-vue": "^7.18.0", 21 | "node-sass": "^6.0.1", 22 | "sass-loader": "^12.1.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /nextgen-editor/shortcodes/align/align.js: -------------------------------------------------------------------------------- 1 | window.nextgenEditor.addHook('hookInit', () => { 2 | window.nextgenEditor.addButtonGroup('shortcode-core-align', { 3 | icon: '', 4 | label: 'SC Align', 5 | }); 6 | }); 7 | 8 | window.nextgenEditor.addShortcode('left', { 9 | type: 'block', 10 | plugin: 'shortcode-core', 11 | title: 'Align Left', 12 | button: { 13 | group: 'shortcode-core-align', 14 | label: 'Align Left', 15 | }, 16 | content() { 17 | return '
{{content_editable}}
'; 18 | }, 19 | }); 20 | 21 | window.nextgenEditor.addShortcode('center', { 22 | type: 'block', 23 | title: 'Align Center', 24 | button: { 25 | group: 'shortcode-core-align', 26 | label: 'Align Center', 27 | }, 28 | content() { 29 | return '
{{content_editable}}
'; 30 | }, 31 | }); 32 | 33 | window.nextgenEditor.addShortcode('right', { 34 | type: 'block', 35 | title: 'Align Right', 36 | button: { 37 | group: 'shortcode-core-align', 38 | label: 'Align Right', 39 | }, 40 | content() { 41 | return '
{{content_editable}}
'; 42 | }, 43 | }); 44 | 45 | window.nextgenEditor.addShortcode('justify', { 46 | type: 'block', 47 | title: 'Justify', 48 | button: { 49 | group: 'shortcode-core-align', 50 | label: 'Justify', 51 | }, 52 | content() { 53 | return '
{{content_editable}}
'; 54 | }, 55 | }); 56 | -------------------------------------------------------------------------------- /nextgen-editor/shortcodes/color/color.js: -------------------------------------------------------------------------------- 1 | window.nextgenEditor.addShortcode('color', { 2 | type: 'inline', 3 | plugin: 'shortcode-core', 4 | title: 'Color', 5 | button: { 6 | group: 'shortcode-core', 7 | label: 'Color', 8 | icon: '', 9 | }, 10 | attributes: { 11 | color: { 12 | type: String, 13 | title: 'Color', 14 | bbcode: true, 15 | widget: 'input-text', 16 | default: '', 17 | }, 18 | }, 19 | content({ attributes }) { 20 | return `{{content_editable}}`; 21 | }, 22 | }); 23 | -------------------------------------------------------------------------------- /nextgen-editor/shortcodes/columns/columns.js: -------------------------------------------------------------------------------- 1 | window.nextgenEditor.addShortcode('columns', { 2 | type: 'block', 3 | plugin: 'shortcode-core', 4 | title: 'Columns', 5 | button: { 6 | group: 'shortcode-core', 7 | label: 'Columns', 8 | icon: '', 9 | }, 10 | attributes: { 11 | count: { 12 | type: Number, 13 | title: 'Count', 14 | widget: { 15 | type: 'input-number', 16 | min: 1, 17 | max: 12, 18 | }, 19 | default: 2, 20 | }, 21 | width: { 22 | type: String, 23 | title: 'Width', 24 | widget: 'input-text', 25 | default: 'auto', 26 | }, 27 | gap: { 28 | type: String, 29 | title: 'Gap', 30 | widget: 'input-text', 31 | default: 'normal', 32 | }, 33 | rule: { 34 | type: String, 35 | title: 'Rule', 36 | widget: 'input-text', 37 | default: '', 38 | }, 39 | }, 40 | titlebar({ attributes }) { 41 | return `columns: ${attributes.count}`; 42 | }, 43 | content({ attributes }) { 44 | const styles = [] 45 | .concat([ 46 | `columns:${attributes.count} ${attributes.width}`, 47 | `-moz-columns:${attributes.count} ${attributes.width}`, 48 | `column-gap:${attributes.gap}`, 49 | `-moz-column-gap:${attributes.gap}`, 50 | attributes.rule ? `column-rule:${attributes.rule}` : null, 51 | attributes.rule ? `-moz-column-rule:${attributes.rule}` : null, 52 | ]) 53 | .filter((item) => !!item) 54 | .join(';'); 55 | 56 | return `
{{content_editable}}
`; 57 | }, 58 | }); 59 | -------------------------------------------------------------------------------- /nextgen-editor/shortcodes/details/details.css: -------------------------------------------------------------------------------- 1 | shortcode-block[name="details"] summary { 2 | margin: 8px 8px; 3 | } 4 | 5 | shortcode-block[name="details"] summary > p { 6 | display: inline; 7 | margin: 0; 8 | } 9 | -------------------------------------------------------------------------------- /nextgen-editor/shortcodes/details/details.js: -------------------------------------------------------------------------------- 1 | window.nextgenEditor.addShortcode('details', { 2 | type: 'block', 3 | plugin: 'shortcode-core', 4 | title: 'Details', 5 | button: { 6 | group: 'shortcode-core', 7 | label: 'Details', 8 | icon: '', 9 | }, 10 | attributes: { 11 | summary: { 12 | type: String, 13 | title: 'Summary', 14 | bbcode: true, 15 | widget: 'input-text', 16 | default: '', 17 | }, 18 | class: { 19 | type: String, 20 | title: 'Class', 21 | widget: 'input-text', 22 | default: '', 23 | }, 24 | }, 25 | titlebar({ attributes }) { 26 | return attributes.class 27 | ? `class: ${attributes.class}` 28 | : ''; 29 | }, 30 | content({ attributes }) { 31 | let output = ''; 32 | 33 | output += `
`; 34 | 35 | if (attributes.summary) { 36 | output += `${attributes.summary}`; 37 | } 38 | 39 | output += '{{content_editable}}'; 40 | output += '
'; 41 | 42 | return output; 43 | }, 44 | preserve: { 45 | block: [ 46 | 'details', 47 | 'summary', 48 | ], 49 | }, 50 | }); 51 | -------------------------------------------------------------------------------- /nextgen-editor/shortcodes/div/div.js: -------------------------------------------------------------------------------- 1 | window.nextgenEditor.addShortcode('div', { 2 | type: 'block', 3 | plugin: 'shortcode-core', 4 | title: 'Div', 5 | button: { 6 | group: 'shortcode-core', 7 | label: 'Div', 8 | icon: '', 9 | }, 10 | attributes: { 11 | id: { 12 | type: String, 13 | title: 'ID', 14 | widget: 'input-text', 15 | default: '', 16 | }, 17 | class: { 18 | type: String, 19 | title: 'Class', 20 | widget: 'input-text', 21 | default: '', 22 | }, 23 | style: { 24 | type: String, 25 | title: 'Style', 26 | widget: 'input-text', 27 | default: '', 28 | }, 29 | }, 30 | titlebar({ attributes }) { 31 | return [] 32 | .concat([ 33 | attributes.id ? `id: ${attributes.id}` : null, 34 | attributes.class ? `class: ${attributes.class}` : null, 35 | attributes.style ? `style: ${attributes.style}` : null, 36 | ]) 37 | .filter((item) => !!item) 38 | .join(', '); 39 | }, 40 | content({ attributes }) { 41 | const id = attributes.id || ''; 42 | const cclass = attributes.class || ''; 43 | const style = attributes.style || ''; 44 | 45 | return `
{{content_editable}}
`; 46 | }, 47 | }); 48 | -------------------------------------------------------------------------------- /nextgen-editor/shortcodes/figure/figure.js: -------------------------------------------------------------------------------- 1 | window.nextgenEditor.addShortcode('figure', { 2 | type: 'block', 3 | plugin: 'shortcode-core', 4 | title: 'Figure', 5 | button: { 6 | group: 'shortcode-core', 7 | label: 'Figure', 8 | icon: '', 9 | }, 10 | attributes: { 11 | id: { 12 | type: String, 13 | title: 'ID', 14 | widget: 'input-text', 15 | default: '', 16 | }, 17 | class: { 18 | type: String, 19 | title: 'Class', 20 | widget: 'input-text', 21 | default: '', 22 | }, 23 | caption: { 24 | type: String, 25 | title: 'Caption', 26 | widget: 'input-text', 27 | default: '', 28 | }, 29 | }, 30 | titlebar({attributes }) { 31 | return [] 32 | .concat([ 33 | attributes.id ? `id: ${attributes.id}` : null, 34 | attributes.class ? `class: ${attributes.class}` : null, 35 | ]) 36 | .filter((item) => !!item) 37 | .join(', '); 38 | }, 39 | content({ attributes }) { 40 | const id = attributes.id || ''; 41 | const cclass = attributes.class || ''; 42 | const caption = attributes.caption || ''; 43 | 44 | return `
{{content_editable}}
${caption}
`; 45 | }, 46 | }); 47 | -------------------------------------------------------------------------------- /nextgen-editor/shortcodes/fontawesome/fontawesome.js: -------------------------------------------------------------------------------- 1 | window.nextgenEditor.addShortcode('fa', { 2 | type: 'inline', 3 | plugin: 'shortcode-core', 4 | title: 'Font Awesome', 5 | wrapOnInsert: false, 6 | button: { 7 | group: 'shortcode-core', 8 | label: 'Font Awesome', 9 | icon: '', 10 | }, 11 | attributes: { 12 | icon: { 13 | type: String, 14 | title: 'Icon', 15 | bbcode: true, 16 | widget: 'input-text', 17 | default: 'grav', 18 | }, 19 | extras: { 20 | type: String, 21 | title: 'Extras', 22 | widget: 'input-text', 23 | default: '', 24 | }, 25 | }, 26 | content({ attributes }) { 27 | let faclass = 'fa'; 28 | 29 | let icon = attributes.icon && !attributes.icon.startsWith('fa-') 30 | ? `fa-${attributes.icon}` 31 | : attributes.icon; 32 | 33 | if (attributes.extras) { 34 | attributes.extras.split(',').forEach((extra) => { 35 | if (extra) { 36 | if (['fab', 'fal', 'fas', 'far', 'fad'].includes(extra)) { 37 | faclass = extra; 38 | return; 39 | } 40 | 41 | icon += !extra.startsWith('fa-') 42 | ? ` fa-${extra}` 43 | : ` ${extra}`; 44 | } 45 | }); 46 | } 47 | 48 | return ``; 49 | }, 50 | }); 51 | -------------------------------------------------------------------------------- /nextgen-editor/shortcodes/headers/headers.css: -------------------------------------------------------------------------------- 1 | shortcode-block[name="h1"] .sc-content, 2 | shortcode-block[name="h2"] .sc-content, 3 | shortcode-block[name="h3"] .sc-content, 4 | shortcode-block[name="h4"] .sc-content, 5 | shortcode-block[name="h5"] .sc-content, 6 | shortcode-block[name="h6"] .sc-content { 7 | font-weight: 700; 8 | } 9 | 10 | shortcode-block[name="h1"] .sc-content { 11 | font-size: 30px; 12 | } 13 | 14 | shortcode-block[name="h2"] .sc-content { 15 | font-size: 24px; 16 | } 17 | 18 | shortcode-block[name="h3"] .sc-content { 19 | font-size: 20px; 20 | } 21 | 22 | shortcode-block[name="h4"] .sc-content { 23 | font-size: 18px; 24 | } 25 | 26 | shortcode-block[name="h5"] .sc-content { 27 | font-size: 16px; 28 | } 29 | 30 | shortcode-block[name="h6"] .sc-content { 31 | font-size: 14px; 32 | } 33 | -------------------------------------------------------------------------------- /nextgen-editor/shortcodes/headers/headers.js: -------------------------------------------------------------------------------- 1 | window.nextgenEditor.addHook('hookInit', () => { 2 | window.nextgenEditor.addButtonGroup('shortcode-core-headers', { 3 | icon: '', 4 | label: 'SC Headers', 5 | }); 6 | }); 7 | 8 | for (let i = 1; i <= 6; i += 1) { 9 | window.nextgenEditor.addShortcode(`h${i}`, { 10 | type: 'block', 11 | plugin: 'shortcode-core', 12 | title: `H${i}`, 13 | button: { 14 | group: 'shortcode-core-headers', 15 | label: `H${i}`, 16 | }, 17 | attributes: { 18 | id: { 19 | type: String, 20 | title: 'ID', 21 | widget: 'input-text', 22 | default: '', 23 | }, 24 | class: { 25 | type: String, 26 | title: 'Class', 27 | widget: 'input-text', 28 | default: '', 29 | }, 30 | }, 31 | titlebar({ attributes }) { 32 | return [] 33 | .concat([ 34 | attributes.id ? `id: ${attributes.id}` : null, 35 | attributes.class ? `class: ${attributes.class}` : null, 36 | ]) 37 | .filter((item) => !!item) 38 | .join(', '); 39 | }, 40 | content({ attributes }) { 41 | const id = attributes.id || ''; 42 | const cclass = attributes.class || ''; 43 | 44 | return `
{{content_editable}}
`; 45 | }, 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /nextgen-editor/shortcodes/language/language.js: -------------------------------------------------------------------------------- 1 | window.nextgenEditor.addShortcode('lang', { 2 | type: 'block', 3 | plugin: 'shortcode-core', 4 | title: 'Language', 5 | button: { 6 | group: 'shortcode-core', 7 | label: 'Language', 8 | icon: '', 9 | }, 10 | attributes: { 11 | lang: { 12 | type: String, 13 | title: 'Language', 14 | bbcode: true, 15 | widget: 'input-text', 16 | default: 'en', 17 | }, 18 | }, 19 | titlebar({ attributes }) { 20 | return `language: ${attributes.lang}`; 21 | }, 22 | content() { 23 | return '{{content_editable}}'; 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /nextgen-editor/shortcodes/lorem/lorem.js: -------------------------------------------------------------------------------- 1 | const sentence = 'Lorem ipsum dolor sit amet consectetur adipiscing elit, molestie tortor cubilia eu facilisi ex varius, convallis pretium dapibus fusce porta ligula.'; 2 | const words = [].concat(...Array(1000).fill(sentence.toLowerCase().replace(/[.|,]/g, '').split(' '))); 3 | const paragraph = Array(2).fill(sentence).join(' '); 4 | 5 | window.nextgenEditor.addShortcode('lorem', { 6 | type: 'block', 7 | plugin: 'shortcode-core', 8 | title: 'Lorem', 9 | wrapOnInsert: false, 10 | button: { 11 | group: 'shortcode-core', 12 | label: 'Lorem', 13 | icon: '', 14 | }, 15 | attributes: { 16 | p: { 17 | type: Number, 18 | title: 'Paragraphs', 19 | bbcode: true, 20 | widget: { 21 | type: 'input-number', 22 | min: 0, 23 | max: 10, 24 | }, 25 | default: 2, 26 | }, 27 | tag: { 28 | type: String, 29 | title: 'Tag', 30 | widget: 'input-text', 31 | default: 'p', 32 | }, 33 | s: { 34 | type: Number, 35 | title: 'Sentences', 36 | widget: 'input-number', 37 | default: 0, 38 | }, 39 | w: { 40 | type: Number, 41 | title: 'Words', 42 | widget: 'input-number', 43 | default: 0, 44 | }, 45 | }, 46 | titlebar({ attributes }) { 47 | if (attributes.w) { 48 | return `words: ${attributes.w}`; 49 | } 50 | 51 | if (attributes.s) { 52 | return `sentences: ${attributes.s}`; 53 | } 54 | 55 | if (attributes.p) { 56 | return `paragraphs: ${attributes.p}`; 57 | } 58 | 59 | return ''; 60 | }, 61 | content({ attributes }) { 62 | let output = ''; 63 | 64 | output += '
'; 65 | 66 | if (attributes.w) { 67 | output += words.slice(0, attributes.w).join(' '); 68 | } else if (attributes.s) { 69 | output += Array(attributes.s).fill(sentence).join(' '); 70 | } else if (attributes.p) { 71 | [...Array(attributes.p)].forEach(() => { 72 | output += `

${paragraph}

`; 73 | }); 74 | } 75 | 76 | output += '
'; 77 | 78 | return output; 79 | }, 80 | }); 81 | -------------------------------------------------------------------------------- /nextgen-editor/shortcodes/mark/mark.css: -------------------------------------------------------------------------------- 1 | shortcode-inline[name="mark"] > .sc-content > span { 2 | background: #ffe9b3; 3 | border-bottom: .05rem solid #ffd367; 4 | border-radius: .1rem; 5 | color: #50596c; 6 | padding: .05rem .1rem 0; 7 | } -------------------------------------------------------------------------------- /nextgen-editor/shortcodes/mark/mark.js: -------------------------------------------------------------------------------- 1 | window.nextgenEditor.addShortcode('mark', { 2 | type: 'inline', 3 | plugin: 'shortcode-core', 4 | title: 'Mark', 5 | button: { 6 | group: 'shortcode-core', 7 | label: 'Mark', 8 | icon: '', 9 | }, 10 | attributes: { 11 | style: { 12 | type: String, 13 | title: 'Style', 14 | bbcode: true, 15 | widget: { 16 | type: 'radios', 17 | values: [ 18 | { value: 'inline', label: 'Inline' }, 19 | { value: 'block', label: 'Block' }, 20 | ], 21 | }, 22 | default: 'inline', 23 | }, 24 | class: { 25 | type: String, 26 | title: 'Class', 27 | widget: 'input-text', 28 | default: '', 29 | }, 30 | }, 31 | titlebar({ attributes }) { 32 | return [] 33 | .concat([ 34 | attributes.style ? `style: ${attributes.style}` : null, 35 | attributes.class ? `class: ${attributes.class}` : null, 36 | ]) 37 | .filter((item) => !!item) 38 | .join(', '); 39 | }, 40 | content({ attributes }) { 41 | const style = attributes.style === 'block' 42 | ? 'display:block' 43 | : ''; 44 | 45 | const cclass = `mark-class-${attributes.class}`; 46 | 47 | return `{{content_editable}}`; 48 | }, 49 | }); 50 | -------------------------------------------------------------------------------- /nextgen-editor/shortcodes/notice/notice.css: -------------------------------------------------------------------------------- 1 | shortcode-block[name="notice"] .sc-notice { 2 | color: #666; 3 | font-size: 16px; 4 | line-height: 24px; 5 | margin: 8px 8px; 6 | position: relative; 7 | } 8 | 9 | shortcode-block[name="notice"] .sc-notice .sc-notice-wrapper { 10 | padding: 4px 12px; 11 | } 12 | 13 | shortcode-block[name="notice"] .sc-notice .sc-notice-wrapper:before { 14 | color: rgb(255, 255, 255); 15 | content: ""; 16 | font-family: FontAwesome; 17 | left: 10px; 18 | position: absolute; 19 | top: 2px; 20 | } 21 | 22 | shortcode-block[name="notice"] .sc-notice .sc-notice-wrapper:after { 23 | color: #fff; 24 | left: 30px; 25 | position: absolute; 26 | top: 3px; 27 | } 28 | 29 | shortcode-block[name="notice"] .sc-notice-info .sc-notice-wrapper { 30 | border-top: 30px solid #F0B37E; 31 | background: #FFF2DB; 32 | } 33 | 34 | shortcode-block[name="notice"] .sc-notice-warning .sc-notice-wrapper { 35 | border-top: 30px solid #DF6F6C; 36 | background: #FAE2E2; 37 | } 38 | 39 | shortcode-block[name="notice"] .sc-notice-note .sc-notice-wrapper { 40 | border-top: 30px solid #6AB0DE; 41 | background: #E7F2FA; 42 | } 43 | 44 | shortcode-block[name="notice"] .sc-notice-tip .sc-notice-wrapper { 45 | border-top: 30px solid #77C577; 46 | background: #E6F9E6; 47 | } 48 | 49 | shortcode-block[name="notice"] .sc-notice-info .sc-notice-wrapper:after { 50 | content: 'Info'; 51 | } 52 | 53 | shortcode-block[name="notice"] .sc-notice-warning .sc-notice-wrapper:after { 54 | content: 'Warning'; 55 | } 56 | 57 | shortcode-block[name="notice"] .sc-notice-note .sc-notice-wrapper:after { 58 | content: 'Note'; 59 | } 60 | 61 | shortcode-block[name="notice"] .sc-notice-tip .sc-notice-wrapper:after { 62 | content: 'Tip'; 63 | } 64 | -------------------------------------------------------------------------------- /nextgen-editor/shortcodes/notice/notice.js: -------------------------------------------------------------------------------- 1 | window.nextgenEditor.addShortcode('notice', { 2 | type: 'block', 3 | plugin: 'shortcode-core', 4 | title: 'Notice', 5 | button: { 6 | group: 'shortcode-core', 7 | label: 'Notice', 8 | icon: '', 9 | }, 10 | attributes: { 11 | notice: { 12 | type: String, 13 | title: 'Type', 14 | bbcode: true, 15 | widget: { 16 | type: 'radios', 17 | values: [ 18 | { value: 'info', label: 'Info' }, 19 | { value: 'warning', label: 'Warning' }, 20 | { value: 'note', label: 'Note' }, 21 | { value: 'tip', label: 'Tip' }, 22 | ], 23 | }, 24 | default: 'info', 25 | }, 26 | }, 27 | titlebar({ attributes }) { 28 | const notice = attributes.notice 29 | ? this.attributes.notice.widget.values.find((item) => item.value === attributes.notice) 30 | : ''; 31 | 32 | const type = notice 33 | ? notice.label 34 | : ''; 35 | 36 | return `type: ${type}`; 37 | }, 38 | content({ attributes }) { 39 | return `
{{content_editable}}
`; 40 | }, 41 | }); 42 | -------------------------------------------------------------------------------- /nextgen-editor/shortcodes/raw/raw.js: -------------------------------------------------------------------------------- 1 | window.nextgenEditor.addShortcode('raw', { 2 | type: 'block', 3 | plugin: 'shortcode-core', 4 | title: 'Raw', 5 | button: { 6 | group: 'shortcode-core', 7 | label: 'Raw', 8 | icon: '', 9 | }, 10 | content() { 11 | return '{{content_editable}}'; 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /nextgen-editor/shortcodes/safe-email/safe-email.js: -------------------------------------------------------------------------------- 1 | window.nextgenEditor.addShortcode('safe-email', { 2 | type: 'inline', 3 | plugin: 'shortcode-core', 4 | title: 'Safe Email', 5 | button: { 6 | group: 'shortcode-core', 7 | label: 'Safe Email', 8 | icon: '', 9 | }, 10 | attributes: { 11 | subject: { 12 | type: String, 13 | title: 'Subject', 14 | widget: 'input-text', 15 | default: '', 16 | }, 17 | icon: { 18 | type: String, 19 | title: 'Icon', 20 | bbcode: true, 21 | widget: 'input-text', 22 | default: 'grav', 23 | }, 24 | autolink: { 25 | type: String, 26 | title: 'Autolink', 27 | widget: { 28 | type: 'checkbox', 29 | valueType: String, 30 | label: 'Yes', 31 | }, 32 | default: 'false', 33 | }, 34 | }, 35 | content({ attributes }) { 36 | let output = ''; 37 | 38 | if (attributes.autolink === 'true') { 39 | output += ''; 40 | } 41 | 42 | if (attributes.icon) { 43 | output += ``; 44 | } 45 | 46 | output += '{{content_editable}}'; 47 | 48 | if (attributes.autolink === 'true') { 49 | output += ''; 50 | } 51 | 52 | return output; 53 | }, 54 | }); 55 | -------------------------------------------------------------------------------- /nextgen-editor/shortcodes/section/section.js: -------------------------------------------------------------------------------- 1 | window.nextgenEditor.addShortcode('section', { 2 | type: 'block', 3 | plugin: 'shortcode-core', 4 | title: 'Section', 5 | button: { 6 | group: 'shortcode-core', 7 | label: 'Section', 8 | icon: '', 9 | }, 10 | attributes: { 11 | name: { 12 | type: String, 13 | title: 'Name', 14 | widget: 'input-text', 15 | default: '', 16 | }, 17 | }, 18 | titlebar({ attributes }) { 19 | return attributes.name 20 | ? `name: ${attributes.name}` 21 | : ''; 22 | }, 23 | content() { 24 | return '{{content_editable}}'; 25 | }, 26 | }); 27 | -------------------------------------------------------------------------------- /nextgen-editor/shortcodes/shortcode-core.js: -------------------------------------------------------------------------------- 1 | window.nextgenEditor.addHook('hookInit', () => { 2 | window.nextgenEditor.addShortcodePlugin('shortcode-core', { 3 | title: 'Shortcode Core', 4 | }); 5 | 6 | window.nextgenEditor.addButtonGroup('shortcode-core', { 7 | icon: '', 8 | label: 'Shortcode Core', 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /nextgen-editor/shortcodes/size/size.js: -------------------------------------------------------------------------------- 1 | window.nextgenEditor.addShortcode('size', { 2 | type: 'inline', 3 | plugin: 'shortcode-core', 4 | title: 'Font Size', 5 | button: { 6 | group: 'shortcode-core', 7 | label: 'Font Size', 8 | icon: '', 9 | }, 10 | attributes: { 11 | size: { 12 | type: String, 13 | title: 'Size', 14 | bbcode: true, 15 | widget: 'input-text', 16 | default: '14', 17 | }, 18 | }, 19 | content({ attributes }) { 20 | const size = !Number.isNaN(+attributes.size) 21 | ? `${attributes.size}px` 22 | : attributes.size; 23 | 24 | return `{{content_editable}}`; 25 | }, 26 | }); 27 | -------------------------------------------------------------------------------- /nextgen-editor/shortcodes/span/span.js: -------------------------------------------------------------------------------- 1 | window.nextgenEditor.addShortcode('span', { 2 | type: 'inline', 3 | plugin: 'shortcode-core', 4 | title: 'Span', 5 | button: { 6 | group: 'shortcode-core', 7 | label: 'Span', 8 | icon: '', 9 | }, 10 | attributes: { 11 | id: { 12 | type: String, 13 | title: 'ID', 14 | widget: 'input-text', 15 | default: '', 16 | }, 17 | class: { 18 | type: String, 19 | title: 'Class', 20 | widget: 'input-text', 21 | default: '', 22 | }, 23 | style: { 24 | type: String, 25 | title: 'Style', 26 | widget: 'input-text', 27 | default: '', 28 | }, 29 | }, 30 | content({ attributes }) { 31 | const id = attributes.id || ''; 32 | const cclass = attributes.class || ''; 33 | const style = attributes.style || ''; 34 | 35 | return `{{content_editable}}`; 36 | }, 37 | }); 38 | -------------------------------------------------------------------------------- /nextgen-editor/shortcodes/u/u.js: -------------------------------------------------------------------------------- 1 | window.nextgenEditor.addShortcode('u', { 2 | type: 'inline', 3 | plugin: 'shortcode-core', 4 | title: 'Underline', 5 | button: { 6 | group: 'shortcode-core', 7 | label: 'Underline', 8 | icon: '', 9 | }, 10 | content() { 11 | return '{{content_editable}}'; 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /nextgen-editor/src/collapse.js: -------------------------------------------------------------------------------- 1 | export default function collapse(input) { 2 | let output = input; 3 | 4 | output = output.replace(/
((((?!(<\/figure>)).)|\n)*)<\/figure>/gm, '$1'); 5 | 6 | const domOutput = new DOMParser().parseFromString(output, 'text/html'); 7 | 8 | [...domOutput.querySelectorAll('shortcode-block, shortcode-inline')].forEach((domShortcode) => { 9 | domShortcode.setAttribute('sc-rendered', false); 10 | }); 11 | 12 | let domShortcode = domOutput.querySelector('shortcode-block[sc-rendered], shortcode-inline[sc-rendered]'); 13 | 14 | while (domShortcode) { 15 | const name = domShortcode.getAttribute('name'); 16 | const shortcode = window.nextgenEditor.shortcodes[name]; 17 | 18 | domShortcode.removeAttribute('class'); 19 | domShortcode.removeAttribute('sc-rendered'); 20 | 21 | const domInnerContent = domShortcode.querySelector(`shortcode-${shortcode.type}-editable, shortcode-${shortcode.type}-readonly`); 22 | domShortcode.innerHTML = (domInnerContent && domInnerContent.innerHTML) || ''; 23 | 24 | domShortcode = domOutput.querySelector('shortcode-block[sc-rendered], shortcode-inline[sc-rendered]'); 25 | } 26 | 27 | output = domOutput.body.innerHTML; 28 | 29 | return output; 30 | } 31 | -------------------------------------------------------------------------------- /nextgen-editor/src/events.js: -------------------------------------------------------------------------------- 1 | import displaySettings from './settings'; 2 | 3 | window.scDisplaySettings = function scDisplaySettings() { 4 | const domShortcode = this.closest('shortcode-block, shortcode-inline'); 5 | 6 | if (domShortcode) { 7 | displaySettings(domShortcode); 8 | } 9 | }; 10 | 11 | window.scBlockAddChildFromParent = function scBlockAddChildFromParent() { 12 | const { editors } = window.nextgenEditor; 13 | 14 | const domShortcode = this.parentNode; 15 | const editor = (editors.filter((instance) => instance.ui.view.element.contains(domShortcode)) || []).shift(); 16 | 17 | const name = domShortcode.getAttribute('name'); 18 | const shortcode = window.nextgenEditor.shortcodes[name]; 19 | 20 | if (editor) { 21 | const viewShortcode = editor.editing.view.domConverter.mapDomToView(domShortcode); 22 | const modelShortcode = editor.editing.mapper.toModelElement(viewShortcode); 23 | 24 | const domShortcodeBlockReadOnly = domShortcode.querySelector('shortcode-block-readonly'); 25 | const viewShortcodeBlockReadOnly = editor.editing.view.domConverter.mapDomToView(domShortcodeBlockReadOnly); 26 | const modelShortcodeBlockReadOnly = editor.editing.mapper.toModelElement(viewShortcodeBlockReadOnly); 27 | 28 | editor.model.change((modelWriter) => { 29 | const insertPosition = modelWriter.createPositionAt(modelShortcodeBlockReadOnly, 0); 30 | editor.execute(`shortcode_${shortcode.child.name}`, { insertPosition, modelParentShortcode: modelShortcode }); 31 | 32 | domShortcode.querySelector('.sc-add-child').classList.remove('sc-visible'); 33 | }); 34 | } 35 | }; 36 | 37 | window.scBlockAddChild = function scBlockAddChild(event, where) { 38 | const { editors } = window.nextgenEditor; 39 | 40 | const domShortcode = this.parentNode; 41 | const editor = (editors.filter((instance) => instance.ui.view.element.contains(domShortcode)) || []).shift(); 42 | 43 | const name = domShortcode.getAttribute('name'); 44 | const shortcode = window.nextgenEditor.shortcodes[name]; 45 | 46 | if (editor) { 47 | const viewShortcode = editor.editing.view.domConverter.mapDomToView(domShortcode); 48 | const modelShortcode = editor.editing.mapper.toModelElement(viewShortcode); 49 | 50 | editor.model.change((modelWriter) => { 51 | let modelParentShortcode = modelShortcode.parent; 52 | const insertPosition = modelWriter.createPositionAt(modelShortcode, where); 53 | 54 | while (modelParentShortcode && modelParentShortcode.name !== 'shortcode-block') { 55 | modelParentShortcode = modelParentShortcode.parent; 56 | } 57 | 58 | if (modelParentShortcode) { 59 | editor.execute(`shortcode_${shortcode.name}`, { insertPosition, modelParentShortcode }); 60 | } 61 | }); 62 | } 63 | }; 64 | 65 | window.scBlockMoveChild = function scBlockMove(event, where) { 66 | const { editors } = window.nextgenEditor; 67 | 68 | const domShortcode = this.parentNode; 69 | const editor = (editors.filter((instance) => instance.ui.view.element.contains(domShortcode)) || []).shift(); 70 | 71 | if (editor) { 72 | const viewShortcode = editor.editing.view.domConverter.mapDomToView(domShortcode); 73 | const modelShortcode = editor.editing.mapper.toModelElement(viewShortcode); 74 | 75 | const domSiblingShortcode = where === 'up' 76 | ? domShortcode.previousSibling 77 | : domShortcode.nextSibling; 78 | 79 | const viewSiblingShortcode = editor.editing.view.domConverter.mapDomToView(domSiblingShortcode); 80 | const modelSiblingShortcode = editor.editing.mapper.toModelElement(viewSiblingShortcode); 81 | 82 | editor.model.change((modelWriter) => { 83 | modelWriter.move(modelWriter.createRangeOn(modelShortcode), modelSiblingShortcode, where === 'up' ? 'before' : 'after'); 84 | }); 85 | } 86 | }; 87 | -------------------------------------------------------------------------------- /nextgen-editor/src/init.js: -------------------------------------------------------------------------------- 1 | window.nextgenEditor.addHook('hookInit', () => { 2 | Object.values(window.nextgenEditor.shortcodes).forEach((shortcode) => { 3 | shortcode.attributes = shortcode.attributes || {}; 4 | 5 | if (!shortcode.button) { 6 | shortcode.button = { label: shortcode.title }; 7 | } 8 | 9 | Object.values(shortcode.attributes).forEach((attribute) => { 10 | if (attribute.default === undefined) { 11 | attribute.default = ''; 12 | } 13 | if (typeof attribute.default !== 'object') { 14 | attribute.default = { value: attribute.default }; 15 | } 16 | if (attribute.shorthand === undefined) { 17 | attribute.shorthand = true; 18 | } 19 | }); 20 | 21 | if (shortcode.type === 'block' && !shortcode.titlebar) { 22 | shortcode.titlebar = () => ''; 23 | } 24 | if (!shortcode.content) { 25 | shortcode.content = () => ''; 26 | } 27 | 28 | if (shortcode.preserve) { 29 | if (shortcode.preserve.block) { 30 | window.nextgenEditor.addVariable('preserveBlockTags', shortcode.preserve.block); 31 | } 32 | 33 | if (shortcode.preserve.inline) { 34 | window.nextgenEditor.addVariable('preserveInlineTags', shortcode.preserve.inline); 35 | } 36 | } 37 | 38 | if (!shortcode.parent) { 39 | window.nextgenEditor.addButton(`shortcode_${shortcode.name}`, { 40 | command: `shortcode_${shortcode.name}`, 41 | ...shortcode.button, 42 | }); 43 | } 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /nextgen-editor/src/main.js: -------------------------------------------------------------------------------- 1 | import './command'; 2 | import './converters'; 3 | import './events'; 4 | import './init'; 5 | import './prerender'; 6 | import './postsave'; 7 | import './remove'; 8 | import './render'; 9 | import './save'; 10 | import './main.css'; 11 | -------------------------------------------------------------------------------- /nextgen-editor/src/postsave.js: -------------------------------------------------------------------------------- 1 | window.nextgenEditor.addHook('hookHTMLtoMarkdown', { 2 | weight: 50, 3 | handler(options, editor, input) { 4 | let output = input; 5 | 6 | const realNames = Object.values(window.nextgenEditor.shortcodes).map((shortcode) => shortcode.realName) 7 | .filter((value, index, self) => self.indexOf(value) === index); 8 | 9 | const openingRegexp = realNames 10 | .map((name) => `(\\[${name}[^\\]]*\\])`).join('|'); 11 | 12 | const hashMap = {}; 13 | let shortcodeCounter = 1; 14 | 15 | while (shortcodeCounter > 0) { 16 | shortcodeCounter = 0; 17 | 18 | // eslint-disable-next-line no-loop-func 19 | Object.values(window.nextgenEditor.shortcodes).forEach((shortcode) => { 20 | const regexp = `(?\\[${shortcode.realName}[^\\]]*\\])(?(((?!(${openingRegexp}|(\\[\\/${shortcode.realName}\\]))).)|\\n)*)(?\\[\\/${shortcode.realName}\\])`; 21 | 22 | output = output.replace(new RegExp(regexp, 'g'), (...matches) => { 23 | shortcodeCounter += 1; 24 | 25 | const hash = Math.random().toString(36).slice(2); 26 | hashMap[hash] = { shortcode, matches }; 27 | 28 | if (shortcode.child) { 29 | const childName = shortcode.child.realName; 30 | 31 | Object.keys(hashMap).forEach((childHash) => { 32 | const childShortcode = hashMap[childHash].shortcode; 33 | 34 | if (childShortcode === shortcode.child && childShortcode.name !== `${shortcode.realName}_${childName}` && matches[0].includes(childHash)) { 35 | hashMap[childHash].shortcode = window.nextgenEditor.shortcodes[`${shortcode.realName}_${childName}`]; 36 | } 37 | }); 38 | } 39 | 40 | return hash; 41 | }); 42 | }); 43 | } 44 | 45 | shortcodeCounter = 1; 46 | 47 | while (shortcodeCounter > 0) { 48 | shortcodeCounter = 0; 49 | 50 | // eslint-disable-next-line no-loop-func 51 | Object.keys(hashMap).forEach((hash) => { 52 | if (!output.includes(hash)) { 53 | return; 54 | } 55 | 56 | shortcodeCounter += 1; 57 | 58 | const { shortcode, matches } = hashMap[hash]; 59 | const groups = matches.pop(); 60 | 61 | if (shortcode.type === 'block') { 62 | let content = groups.content.replace(/^\n/, '').replace(/\n$/, ''); 63 | 64 | if (shortcode.child) { 65 | content = content.trim().split('\n').filter((line) => !!line).join('\n'); 66 | content = `\n${content}\n`; 67 | } 68 | 69 | if (shortcode.parent) { 70 | content = `\n${content}\n`; 71 | } 72 | 73 | output = output.replace(hash, `${groups.opening}${content}${groups.closing}`); 74 | } 75 | 76 | if (shortcode.type === 'inline') { 77 | output = output.replace(hash, matches[0]); 78 | } 79 | }); 80 | } 81 | 82 | /* 83 | Object.values(window.nextgenEditor.shortcodes).forEach((shortcode) => { 84 | const regexp = `(?\\[${shortcode.realName}[^\\]]*\\])\n(?(((?!(${openingRegexp}|(\\[\\/${shortcode.realName}\\]))).))*)\n(?\\[\\/${shortcode.realName}\\])`; 85 | 86 | output = output.replace(new RegExp(regexp, 'g'), (...matches) => { 87 | const groups = matches.pop(); 88 | return `${groups.opening}${groups.content}${groups.closing}`; 89 | }); 90 | }); 91 | */ 92 | 93 | return output; 94 | }, 95 | }); 96 | -------------------------------------------------------------------------------- /nextgen-editor/src/prerender.js: -------------------------------------------------------------------------------- 1 | window.nextgenEditor.addHook('hookMarkdowntoHTML', { 2 | weight: -50, 3 | handler(options, input) { 4 | let output = input; 5 | 6 | const realNames = Object.values(window.nextgenEditor.shortcodes).map((shortcode) => shortcode.realName) 7 | .filter((value, index, self) => self.indexOf(value) === index); 8 | 9 | const openingRegexp = realNames 10 | .map((name) => `(\\[${name}[^\\]]*\\])`).join('|'); 11 | 12 | realNames.forEach((name) => { 13 | const regexp = `\\[${name}(?(=| +).+?(?=/]))?\\/\\]`; 14 | 15 | output = output.replace(new RegExp(regexp, 'g'), (...matches) => { 16 | const groups = matches.pop(); 17 | 18 | const attributes = groups.attributes.trim() 19 | ? `${groups.attributes}` 20 | : ''; 21 | 22 | return `[${name}${attributes}][/${name}]`; 23 | }); 24 | }); 25 | 26 | const hashMap = {}; 27 | let shortcodeCounter = 1; 28 | 29 | while (shortcodeCounter > 0) { 30 | shortcodeCounter = 0; 31 | 32 | // eslint-disable-next-line no-loop-func 33 | Object.values(window.nextgenEditor.shortcodes).forEach((shortcode) => { 34 | const regexp = `(? *)\\[${shortcode.realName}(?(=| +)[^\\]]*)?\\](?(((?!(${openingRegexp}|(\\[\\/${shortcode.realName}\\]))).)|\\n)*)\\[\\/${shortcode.realName}\\](? *)`; 35 | 36 | output = output.replace(new RegExp(regexp, 'g'), (...matches) => { 37 | shortcodeCounter += 1; 38 | 39 | const hash = Math.random().toString(36).slice(2); 40 | hashMap[hash] = { shortcode, matches }; 41 | 42 | if (shortcode.child) { 43 | const childName = shortcode.child.realName; 44 | 45 | Object.keys(hashMap).forEach((childHash) => { 46 | const childShortcode = hashMap[childHash].shortcode; 47 | 48 | if (childShortcode === shortcode.child && childShortcode.name !== `${shortcode.realName}_${childName}` && matches[0].includes(childHash)) { 49 | hashMap[childHash].shortcode = window.nextgenEditor.shortcodes[`${shortcode.realName}_${childName}`]; 50 | } 51 | }); 52 | } 53 | 54 | return hash; 55 | }); 56 | }); 57 | } 58 | 59 | shortcodeCounter = 1; 60 | 61 | while (shortcodeCounter > 0) { 62 | shortcodeCounter = 0; 63 | 64 | // eslint-disable-next-line no-loop-func 65 | Object.keys(hashMap).forEach((hash) => { 66 | if (!output.includes(hash)) { 67 | return; 68 | } 69 | 70 | shortcodeCounter += 1; 71 | 72 | const { shortcode, matches } = hashMap[hash]; 73 | const groups = matches.pop(); 74 | 75 | const spacesBefore = groups.spaces_before.replace(/ /g, ' '); 76 | const spacesAfter = groups.spaces_after.replace(/ /g, ' '); 77 | 78 | if (shortcode.type === 'block') { 79 | let content = groups.content.trim(); 80 | 81 | if (groups.spaces_before.length) { 82 | content = content.replace(new RegExp(`^( ){${groups.spaces_before.length}}`, 'gm'), ''); 83 | } 84 | 85 | const replacement = `\n\n[${shortcode.name}${groups.attributes || ''}]\n\n${content}\n\n[/${shortcode.name}]\n\n`; 86 | 87 | output = output.replace(new RegExp(`(\\n)?(\\n)?${hash}(\\n)?(\\n)?`), replacement); 88 | } 89 | 90 | if (shortcode.type === 'inline') { 91 | output = output.replace(hash, `${spacesBefore}[${shortcode.name}${groups.attributes || ''}]${groups.content}[/${shortcode.name}]${spacesAfter}`); 92 | } 93 | }); 94 | } 95 | 96 | output = output.replace(/^\n\n/, '').replace(/\n\n$/, ''); 97 | 98 | return output; 99 | }, 100 | }); 101 | -------------------------------------------------------------------------------- /nextgen-editor/src/remove.js: -------------------------------------------------------------------------------- 1 | window.nextgenEditor.addPlugin('GravShortcodeCoreRemove', { 2 | init() { 3 | const deleteBackwardCommand = this.editor.commands.get('delete'); 4 | const deleteForwardCommand = this.editor.commands.get('forwardDelete'); 5 | 6 | const preDelete = (event) => { 7 | const selectedElement = this.editor.model.document.selection.getSelectedElement(); 8 | 9 | if (selectedElement && selectedElement.name === 'shortcode-block') { 10 | const name = selectedElement.getAttribute('name'); 11 | const shortcode = window.nextgenEditor.shortcodes[name]; 12 | 13 | if (shortcode.parent) { 14 | const viewShortcode = this.editor.editing.mapper.toViewElement(selectedElement); 15 | const domShortcode = this.editor.editing.view.domConverter.mapViewToDom(viewShortcode); 16 | const domParentShortcode = domShortcode.closest(`shortcode-block[name="${shortcode.parent.name}"]`); 17 | 18 | event.childShortcodeDeleted = true; 19 | event.modelShortcodeBlockReadOnly = selectedElement.parent; 20 | event.domParentShortcode = domParentShortcode; 21 | } 22 | } 23 | }; 24 | 25 | const postDelete = (event) => { 26 | if (event.childShortcodeDeleted) { 27 | const { domParentShortcode, modelShortcodeBlockReadOnly } = event; 28 | 29 | const children = [...modelShortcodeBlockReadOnly.getChildren()]; 30 | const scChildren = children.filter((child) => child.name === 'shortcode-block'); 31 | const otherChildren = children.filter((child) => child.name !== 'shortcode-block'); 32 | 33 | setTimeout(() => { 34 | this.editor.model.change((modelWriter) => { 35 | otherChildren.forEach((modelChild) => { 36 | if (modelChild.name === 'paragraph' && modelChild.childCount === 0) { 37 | modelWriter.remove(modelChild); 38 | } 39 | }); 40 | }); 41 | }); 42 | 43 | if (!scChildren.length) { 44 | domParentShortcode.querySelector('shortcode-block > .sc-add-child').classList.add('sc-visible'); 45 | } 46 | } 47 | }; 48 | 49 | deleteBackwardCommand.on('execute', preDelete, { priority: 'highest' }); 50 | deleteForwardCommand.on('execute', preDelete, { priority: 'highest' }); 51 | 52 | deleteBackwardCommand.on('execute', postDelete, { priority: 'lowest' }); 53 | deleteForwardCommand.on('execute', postDelete, { priority: 'lowest' }); 54 | }, 55 | }); 56 | -------------------------------------------------------------------------------- /nextgen-editor/src/render.js: -------------------------------------------------------------------------------- 1 | import uncollapse from './uncollapse'; 2 | 3 | window.nextgenEditor.addHook('hookMarkdowntoHTML', { 4 | weight: 50, 5 | handler(options, input) { 6 | let output = input; 7 | 8 | let shortcodeCounter = 1; 9 | const openingRegexp = Object.keys(window.nextgenEditor.shortcodes).map((name) => `(\\[${name}[^\\]]*\\])`).join('|'); 10 | 11 | while (shortcodeCounter > 0) { 12 | shortcodeCounter = 0; 13 | 14 | // eslint-disable-next-line no-loop-func 15 | Object.values(window.nextgenEditor.shortcodes).forEach((shortcode) => { 16 | const regexp = `(?

)?\\[${shortcode.name}(?(=| +)[^\\]]*)?\\](<\\/p>)?(?(((?!(${openingRegexp}|(\\[\\/${shortcode.name}\\]))).)|\\n)*)\\[\\/${shortcode.name}\\](?<\\/p>)?`; 17 | 18 | output = output.replace(new RegExp(regexp, 'g'), (...matches) => { 19 | shortcodeCounter += 1; 20 | 21 | const groups = matches.pop(); 22 | 23 | let content = shortcode.type === 'block' 24 | ? groups.content.replace(/

$/, '') 25 | : groups.content; 26 | 27 | const bbcode = Object.keys(shortcode.attributes).reduce((acc, attrName) => acc || (shortcode.attributes[attrName].bbcode && shortcode.attributes[attrName].shorthand && attrName), ''); 28 | const innerHTMLAttribute = Object.keys(shortcode.attributes).reduce((acc, attrName) => acc || (shortcode.attributes[attrName].innerHTML && attrName), ''); 29 | 30 | let attrGroup = bbcode && groups.attributes && groups.attributes.startsWith('=') 31 | ? `${bbcode}${groups.attributes}` 32 | : groups.attributes || ''; 33 | 34 | if (innerHTMLAttribute) { 35 | const innerHTML = shortcode.type === 'block' 36 | ? content.replace(/^

/, '').replace(/<\/p>$/, '').replace(/^ $/, '') 37 | : content.replace(/^ $/, ''); 38 | 39 | attrGroup = attrGroup 40 | ? `${attrGroup} ${innerHTMLAttribute}="${innerHTML}"` 41 | : `${innerHTMLAttribute}="${innerHTML}"`; 42 | 43 | content = ''; 44 | } 45 | 46 | const domAttributes = new DOMParser().parseFromString(`

`, 'text/html').body.firstChild.attributes; 47 | 48 | const attributes = Object.keys(shortcode.attributes).reduce((acc, attrName) => { 49 | const attribute = shortcode.attributes[attrName]; 50 | 51 | let attrValue = domAttributes.getNamedItem(attrName) 52 | ? domAttributes.getNamedItem(attrName).value 53 | : attribute.default.value; 54 | 55 | if (attribute.type === Boolean && domAttributes.getNamedItem(attrName)) { 56 | attrValue = domAttributes.getNamedItem(attrName) !== 'false'; 57 | } 58 | 59 | if (attribute.type === Number) { 60 | attrValue = +attrValue; 61 | } 62 | 63 | acc[attrName] = attrValue; 64 | 65 | return acc; 66 | }, {}); 67 | 68 | let replacement = ''; 69 | 70 | const attributesEncoded = encodeURIComponent(JSON.stringify(attributes)); 71 | 72 | if (shortcode.type === 'block') { 73 | replacement += ``; 74 | replacement += content; 75 | replacement += ''; 76 | } 77 | 78 | if (shortcode.type === 'inline') { 79 | replacement += groups.p1 || ''; 80 | replacement += ``; 81 | replacement += content; 82 | replacement += ''; 83 | replacement += groups.p2 || ''; 84 | } 85 | 86 | return replacement; 87 | }); 88 | }); 89 | } 90 | 91 | output = uncollapse(output); 92 | 93 | return output; 94 | }, 95 | }); 96 | -------------------------------------------------------------------------------- /nextgen-editor/src/save.js: -------------------------------------------------------------------------------- 1 | import collapse from './collapse'; 2 | 3 | window.nextgenEditor.addHook('hookHTMLtoMarkdown', { 4 | weight: -50, 5 | handler(options, editor, input) { 6 | let output = input; 7 | 8 | output = collapse(output); 9 | 10 | const domOutput = new DOMParser().parseFromString(output, 'text/html'); 11 | 12 | let domShortcode = domOutput.querySelector('shortcode-block, shortcode-inline'); 13 | 14 | while (domShortcode) { 15 | const name = domShortcode.getAttribute('name'); 16 | const shortcode = window.nextgenEditor.shortcodes[name]; 17 | const attributes = JSON.parse(decodeURIComponent(domShortcode.getAttribute('attributes'))); 18 | 19 | const innerHTMLAttribute = Object.keys(shortcode.attributes).reduce((acc, attrName) => acc || (shortcode.attributes[attrName].innerHTML && attrName), ''); 20 | 21 | const attrLine = Object.keys(shortcode.attributes).reduce((acc, attrName) => { 22 | const attribute = shortcode.attributes[attrName]; 23 | 24 | if (attribute.type === Boolean) { 25 | return attributes[attrName] 26 | ? `${acc} ${attrName}` 27 | : acc; 28 | } 29 | 30 | if (attributes[attrName] === attribute.default.value && !attribute.default.preserve) { 31 | return acc; 32 | } 33 | 34 | if (attribute.bbcode && attribute.shorthand) { 35 | return `="${attributes[attrName]}"${acc}`; 36 | } 37 | 38 | if (attribute.innerHTML) { 39 | return acc; 40 | } 41 | 42 | return `${acc} ${attrName}="${attributes[attrName]}"`; 43 | }, ''); 44 | 45 | if (shortcode.type === 'block') { 46 | if (domShortcode.innerHTML === '

 

') { 47 | domShortcode.innerHTML = ''; 48 | } 49 | 50 | if (innerHTMLAttribute) { 51 | domShortcode.outerHTML = `

[${shortcode.realName}${attrLine}]${attributes[innerHTMLAttribute]}[/${shortcode.realName}]

`; 52 | } else if (domShortcode.innerHTML) { 53 | domShortcode.outerHTML = `

[${shortcode.realName}${attrLine}]

${domShortcode.innerHTML}

[/${shortcode.realName}]

`; 54 | } else { 55 | domShortcode.outerHTML = `

[${shortcode.realName}${attrLine} /]

`; 56 | } 57 | } 58 | 59 | if (shortcode.type === 'inline') { 60 | if (domShortcode.innerHTML === ' ') { 61 | domShortcode.innerHTML = ''; 62 | } 63 | 64 | if (innerHTMLAttribute) { 65 | domShortcode.outerHTML = `[${shortcode.realName}${attrLine}]${attributes[innerHTMLAttribute]}[/${shortcode.realName}]`; 66 | } else if (domShortcode.innerHTML) { 67 | domShortcode.outerHTML = `[${shortcode.realName}${attrLine}]${domShortcode.innerHTML}[/${shortcode.realName}]`; 68 | } else { 69 | domShortcode.outerHTML = `[${shortcode.realName}${attrLine} /]`; 70 | } 71 | } 72 | 73 | domShortcode = domOutput.querySelector('shortcode-block, shortcode-inline'); 74 | } 75 | 76 | output = domOutput.body.innerHTML; 77 | 78 | return output; 79 | }, 80 | }); 81 | -------------------------------------------------------------------------------- /nextgen-editor/vue.config.js: -------------------------------------------------------------------------------- 1 | const DirectoryNamedWebpackPlugin = require('directory-named-webpack-plugin'); 2 | 3 | module.exports = { 4 | filenameHashing: false, 5 | publicPath: process.env.NODE_ENV === 'development' 6 | ? `http://${process.env.DEV_HOST}:${process.env.DEV_PORT}/` 7 | : '/', 8 | configureWebpack: { 9 | resolve: { 10 | plugins: [ 11 | new DirectoryNamedWebpackPlugin(), 12 | ], 13 | }, 14 | optimization: { 15 | splitChunks: false, 16 | }, 17 | }, 18 | chainWebpack: (webpackConfig) => { 19 | webpackConfig.plugins.delete('html'); 20 | webpackConfig.plugins.delete('preload'); 21 | webpackConfig.plugins.delete('prefetch'); 22 | }, 23 | devServer: { 24 | host: process.env.DEV_HOST, 25 | port: process.env.DEV_PORT, 26 | disableHostCheck: true, 27 | headers: { 28 | 'Access-Control-Allow-Origin': '*', 29 | }, 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /shortcode-core.yaml: -------------------------------------------------------------------------------- 1 | enabled: true 2 | active: true 3 | active_admin: true 4 | admin_pages_only: true 5 | parser: regular 6 | include_default_shortcodes: true 7 | css: 8 | notice_enabled: true 9 | custom_shortcodes: 10 | fontawesome: 11 | load: true 12 | url: '//maxcdn.bootstrapcdn.com/font-awesome/4.6.1/css/font-awesome.min.css' 13 | v5: false 14 | nextgen-editor: 15 | env: production 16 | dev_host: localhost 17 | dev_port: 2001 18 | -------------------------------------------------------------------------------- /templates/shortcodes/notice.html.twig: -------------------------------------------------------------------------------- 1 |
2 |
{{ content|raw }}
3 |
-------------------------------------------------------------------------------- /vendor/autoload.php: -------------------------------------------------------------------------------- 1 | $vendorDir . '/composer/InstalledVersions.php', 10 | 'Grav\\Plugin\\ShortcodeCorePlugin' => $baseDir . '/shortcode-core.php', 11 | ); 12 | -------------------------------------------------------------------------------- /vendor/composer/autoload_namespaces.php: -------------------------------------------------------------------------------- 1 | array($vendorDir . '/thunderer/shortcode/src'), 10 | 'Grav\\Plugin\\Shortcodes\\' => array($baseDir . '/classes/shortcodes'), 11 | 'Grav\\Plugin\\ShortcodeCore\\' => array($baseDir . '/classes/plugin'), 12 | ); 13 | -------------------------------------------------------------------------------- /vendor/composer/autoload_real.php: -------------------------------------------------------------------------------- 1 | register(true); 35 | 36 | return $loader; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /vendor/composer/autoload_static.php: -------------------------------------------------------------------------------- 1 | 11 | array ( 12 | 'Thunder\\Shortcode\\' => 18, 13 | ), 14 | 'G' => 15 | array ( 16 | 'Grav\\Plugin\\Shortcodes\\' => 23, 17 | 'Grav\\Plugin\\ShortcodeCore\\' => 26, 18 | ), 19 | ); 20 | 21 | public static $prefixDirsPsr4 = array ( 22 | 'Thunder\\Shortcode\\' => 23 | array ( 24 | 0 => __DIR__ . '/..' . '/thunderer/shortcode/src', 25 | ), 26 | 'Grav\\Plugin\\Shortcodes\\' => 27 | array ( 28 | 0 => __DIR__ . '/../..' . '/classes/shortcodes', 29 | ), 30 | 'Grav\\Plugin\\ShortcodeCore\\' => 31 | array ( 32 | 0 => __DIR__ . '/../..' . '/classes/plugin', 33 | ), 34 | ); 35 | 36 | public static $classMap = array ( 37 | 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', 38 | 'Grav\\Plugin\\ShortcodeCorePlugin' => __DIR__ . '/../..' . '/shortcode-core.php', 39 | ); 40 | 41 | public static function getInitializer(ClassLoader $loader) 42 | { 43 | return \Closure::bind(function () use ($loader) { 44 | $loader->prefixLengthsPsr4 = ComposerStaticInit20dff4ef15e2090e54c04a9aa83321b9::$prefixLengthsPsr4; 45 | $loader->prefixDirsPsr4 = ComposerStaticInit20dff4ef15e2090e54c04a9aa83321b9::$prefixDirsPsr4; 46 | $loader->classMap = ComposerStaticInit20dff4ef15e2090e54c04a9aa83321b9::$classMap; 47 | 48 | }, null, ClassLoader::class); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /vendor/composer/installed.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | { 4 | "name": "thunderer/shortcode", 5 | "version": "v0.7.6", 6 | "version_normalized": "0.7.6.0", 7 | "source": { 8 | "type": "git", 9 | "url": "https://github.com/thunderer/Shortcode.git", 10 | "reference": "9f81424b4909007483d93c5defc0917d8a58debd" 11 | }, 12 | "dist": { 13 | "type": "zip", 14 | "url": "https://api.github.com/repos/thunderer/Shortcode/zipball/9f81424b4909007483d93c5defc0917d8a58debd", 15 | "reference": "9f81424b4909007483d93c5defc0917d8a58debd", 16 | "shasum": "" 17 | }, 18 | "require": { 19 | "php": ">=5.3" 20 | }, 21 | "require-dev": { 22 | "phpunit/phpunit": ">=4.1", 23 | "symfony/yaml": ">=2.0" 24 | }, 25 | "suggest": { 26 | "ext-dom": "if you want to use XML serializer", 27 | "ext-json": "if you want to use JSON serializer", 28 | "symfony/yaml": "if you want to use YAML serializer" 29 | }, 30 | "time": "2024-12-15T21:57:40+00:00", 31 | "type": "library", 32 | "installation-source": "dist", 33 | "autoload": { 34 | "psr-4": { 35 | "Thunder\\Shortcode\\": "src/" 36 | } 37 | }, 38 | "notification-url": "https://packagist.org/downloads/", 39 | "license": [ 40 | "MIT" 41 | ], 42 | "authors": [ 43 | { 44 | "name": "Tomasz Kowalczyk", 45 | "email": "tomasz@kowalczyk.cc" 46 | } 47 | ], 48 | "description": "Advanced shortcode (BBCode) parser and engine for PHP", 49 | "keywords": [ 50 | "bbcode", 51 | "engine", 52 | "library", 53 | "parser", 54 | "shortcode" 55 | ], 56 | "support": { 57 | "issues": "https://github.com/thunderer/Shortcode/issues", 58 | "source": "https://github.com/thunderer/Shortcode/tree/v0.7.6" 59 | }, 60 | "install-path": "../thunderer/shortcode" 61 | } 62 | ], 63 | "dev": true, 64 | "dev-package-names": [] 65 | } 66 | -------------------------------------------------------------------------------- /vendor/composer/installed.php: -------------------------------------------------------------------------------- 1 | array( 3 | 'name' => 'getgrav/shortcode-core', 4 | 'pretty_version' => 'dev-develop', 5 | 'version' => 'dev-develop', 6 | 'reference' => 'd92a78d8a9dca623ae6beab5614655ae0fb1a463', 7 | 'type' => 'grav-plugin', 8 | 'install_path' => __DIR__ . '/../../', 9 | 'aliases' => array(), 10 | 'dev' => true, 11 | ), 12 | 'versions' => array( 13 | 'getgrav/shortcode-core' => array( 14 | 'pretty_version' => 'dev-develop', 15 | 'version' => 'dev-develop', 16 | 'reference' => 'd92a78d8a9dca623ae6beab5614655ae0fb1a463', 17 | 'type' => 'grav-plugin', 18 | 'install_path' => __DIR__ . '/../../', 19 | 'aliases' => array(), 20 | 'dev_requirement' => false, 21 | ), 22 | 'thunderer/shortcode' => array( 23 | 'pretty_version' => 'v0.7.6', 24 | 'version' => '0.7.6.0', 25 | 'reference' => '9f81424b4909007483d93c5defc0917d8a58debd', 26 | 'type' => 'library', 27 | 'install_path' => __DIR__ . '/../thunderer/shortcode', 28 | 'aliases' => array(), 29 | 'dev_requirement' => false, 30 | ), 31 | ), 32 | ); 33 | -------------------------------------------------------------------------------- /vendor/composer/platform_check.php: -------------------------------------------------------------------------------- 1 | = 70103)) { 8 | $issues[] = 'Your Composer dependencies require a PHP version ">= 7.1.3". You are running ' . PHP_VERSION . '.'; 9 | } 10 | 11 | if ($issues) { 12 | if (!headers_sent()) { 13 | header('HTTP/1.1 500 Internal Server Error'); 14 | } 15 | if (!ini_get('display_errors')) { 16 | if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { 17 | fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); 18 | } elseif (!headers_sent()) { 19 | echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; 20 | } 21 | } 22 | trigger_error( 23 | 'Composer detected issues in your platform: ' . implode(' ', $issues), 24 | E_USER_ERROR 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/.github/workflows/test-old.yaml: -------------------------------------------------------------------------------- 1 | name: TestOld 2 | 3 | on: 4 | push: 5 | branches: ['master'] 6 | pull_request: ~ 7 | workflow_dispatch: ~ 8 | 9 | jobs: 10 | test: 11 | runs-on: '${{ matrix.os }}' 12 | strategy: 13 | matrix: 14 | php: ['5.3', '5.4', '5.5', '5.6', '7.0', '7.1', '7.2', '7.3'] 15 | os: ['ubuntu-latest'] 16 | steps: 17 | - name: 'Checkout' 18 | uses: 'actions/checkout@v4' 19 | - name: 'Install PHP' 20 | uses: 'shivammathur/setup-php@v2' 21 | with: 22 | php-version: '${{ matrix.php }}' 23 | tools: 'composer:v1' 24 | coverage: 'xdebug' 25 | - name: 'PHP' 26 | run: 'php -v' 27 | 28 | - name: 'Composer' 29 | run: 'composer install' 30 | continue-on-error: '${{ matrix.failure }}' 31 | - name: 'PHPUnit' 32 | run: 'php vendor/bin/phpunit --coverage-text' 33 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | push: 5 | branches: ['master'] 6 | pull_request: ~ 7 | workflow_dispatch: ~ 8 | 9 | jobs: 10 | test: 11 | runs-on: '${{ matrix.os }}' 12 | strategy: 13 | matrix: 14 | php: ['7.4', '8.0', '8.1', '8.2', '8.3'] 15 | os: ['ubuntu-latest'] 16 | failure: [false] 17 | include: 18 | - { php: '8.4', os: 'ubuntu-latest', failure: true } # Psalm does not support PHP 8.4 yet 19 | - { php: '8.5', os: 'ubuntu-latest', failure: true } # '8.5' means 'nightly' 20 | steps: 21 | - name: 'Checkout' 22 | uses: 'actions/checkout@v4' 23 | - name: 'Install PHP' 24 | uses: 'shivammathur/setup-php@v2' 25 | with: 26 | php-version: '${{ matrix.php }}' 27 | tools: 'composer:v2' 28 | coverage: 'xdebug' 29 | - name: 'PHP' 30 | run: 'php -v' 31 | 32 | - name: 'Composer' 33 | run: 'composer install' 34 | continue-on-error: '${{ matrix.failure }}' 35 | - name: 'PHPUnit' 36 | run: 'php vendor/bin/phpunit --coverage-text' 37 | continue-on-error: '${{ matrix.failure }}' 38 | - name: 'Psalm' 39 | run: | 40 | composer remove --dev -W 'phpunit/phpunit' 41 | composer require --dev -W 'vimeo/psalm=^5.0' 'nikic/php-parser=^4.0' 42 | php vendor/bin/psalm --shepherd --php-version=${{ matrix.php }} 43 | continue-on-error: '${{ matrix.failure }}' 44 | - name: 'Infection' 45 | run: | 46 | composer remove --dev -W 'vimeo/psalm' 47 | composer require --dev -W phpunit/phpunit infection/infection 48 | php vendor/bin/infection 49 | continue-on-error: '${{ matrix.failure }}' 50 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .phpunit.result.cache 3 | infection.log 4 | vendor 5 | composer.lock 6 | coverage 7 | coverage.xml 8 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2025 Tomasz Kowalczyk 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/Makefile: -------------------------------------------------------------------------------- 1 | PHP_VERSION ?= 8.0 2 | PHP := docker-compose run --rm php-${PHP_VERSION} 3 | 4 | php-version: 5 | ${PHP} php -v 6 | cache-clear: 7 | sudo rm -rfv coverage .phpunit.result.cache infection.log 8 | 9 | docker-build: 10 | docker-compose build 11 | 12 | composer-install: 13 | ${PHP} composer install 14 | composer-self-update: 15 | ${PHP} composer self-update 16 | composer-update: 17 | ${PHP} composer update 18 | composer-require: 19 | ${PHP} composer require ${PACKAGE} 20 | composer-require-dev: 21 | ${PHP} composer require --dev ${PACKAGE} 22 | 23 | test: test-phpunit test-infection qa-psalm 24 | test-phpunit: 25 | ${PHP} php -v 26 | ${PHP} php vendor/bin/phpunit --coverage-text 27 | test-phpunit-local: 28 | php -v 29 | php vendor/bin/phpunit --coverage-text 30 | php vendor/bin/psalm --no-cache 31 | php vendor/bin/infection 32 | test-infection: 33 | ${PHP} php vendor/bin/infection -j2 --min-msi=80 34 | 35 | travis: 36 | # PHP_VERSION=5.3 make travis-job 37 | PHP_VERSION=5.4 make travis-job 38 | PHP_VERSION=5.5 make travis-job 39 | PHP_VERSION=5.6 make travis-job 40 | PHP_VERSION=7.0 make travis-job 41 | PHP_VERSION=7.1 make travis-job 42 | PHP_VERSION=7.2 make travis-job 43 | PHP_VERSION=7.3 make travis-job 44 | PHP_VERSION=7.4 make travis-job 45 | PHP_VERSION=8.0 make travis-job 46 | travis-job: 47 | ${PHP} composer update --no-plugins 48 | ${PHP} php -v 49 | ${PHP} php vendor/bin/phpunit 50 | if ${PHP} php -r 'exit((int)(version_compare(PHP_VERSION, "7.1", ">=") === false));'; then \ 51 | ${PHP} composer require --dev vimeo/psalm infection/infection; \ 52 | ${PHP} vendor/bin/psalm --threads=1 --no-cache --shepherd --find-unused-psalm-suppress; \ 53 | ${PHP} vendor/bin/infection; \ 54 | ${PHP} composer remove --dev vimeo/psalm infection/infection; \ 55 | fi; 56 | 57 | qa-psalm: 58 | ${PHP} php vendor/bin/psalm --no-cache 59 | qa-psalm-suppressed: 60 | grep -rn psalm-suppress src 61 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thunderer/shortcode", 3 | "description": "Advanced shortcode (BBCode) parser and engine for PHP", 4 | "keywords": ["shortcode", "bbcode", "parser", "engine", "library"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Tomasz Kowalczyk", 9 | "email": "tomasz@kowalczyk.cc" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=5.3" 14 | }, 15 | "require-dev": { 16 | "phpunit/phpunit": ">=4.1", 17 | "symfony/yaml": ">=2.0" 18 | }, 19 | "autoload": { 20 | "psr-4": { 21 | "Thunder\\Shortcode\\": "src/" 22 | } 23 | }, 24 | "autoload-dev": { 25 | "psr-4": { 26 | "Thunder\\Shortcode\\Tests\\": "tests/" 27 | } 28 | }, 29 | "suggest": { 30 | "symfony/yaml": "if you want to use YAML serializer", 31 | "ext-dom": "if you want to use XML serializer", 32 | "ext-json": "if you want to use JSON serializer" 33 | }, 34 | "config": { 35 | "allow-plugins": { 36 | "infection/extension-installer": true 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | 3 | x-php: &php 4 | volumes: ['.:/app', './docker/php/php.ini:/usr/local/etc/php/conf.d/php.ini'] 5 | working_dir: '/app' 6 | 7 | services: 8 | # PHP 5.3 contains neither mbstring extension nor docker-php-ext-install script 9 | # Original Dockerfile can be found here https://github.com/docker-library/php/pull/20/files 10 | # Unfortunately it fails to build now because GPG signatures do not exist anymore 11 | # php-5.3: { build: { context: docker/php-5.x, args: { PHP_VERSION: 5.3 } } } 12 | php-5.4: { <<: *php, build: { context: docker/php-5.x, args: { PHP_VERSION: 5.4 } } } 13 | php-5.5: { <<: *php, build: { context: docker/php-5.x, args: { PHP_VERSION: 5.5 } } } 14 | php-5.6: { <<: *php, build: { context: docker/php-5.x, args: { PHP_VERSION: 5.6 } } } 15 | php-7.0: { <<: *php, build: { context: docker/php, args: { PHP_VERSION: 7.0 } } } 16 | php-7.1: { <<: *php, build: { context: docker/php, args: { PHP_VERSION: 7.1.3 } } } 17 | php-7.2: { <<: *php, build: { context: docker/php, args: { PHP_VERSION: 7.2 } } } 18 | php-7.3: { <<: *php, build: { context: docker/php, args: { PHP_VERSION: 7.3 } } } 19 | php-7.4: { <<: *php, build: { context: docker/php, args: { PHP_VERSION: 7.4 } } } 20 | php-8.0: { <<: *php, build: { context: docker/php, args: { PHP_VERSION: 8.0 } } } 21 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/docker/php-5.x/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG PHP_VERSION=7.4 2 | FROM php:$PHP_VERSION 3 | 4 | RUN apt update && apt install -y --force-yes libonig-dev libzip-dev 5 | RUN docker-php-ext-install mbstring zip 6 | 7 | RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \ 8 | && php -r "if (hash_file('sha384', 'composer-setup.php') === 'c31c1e292ad7be5f49291169c0ac8f683499edddcfd4e42232982d0fd193004208a58ff6f353fde0012d35fdd72bc394') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" \ 9 | && php composer-setup.php \ 10 | && php -r "unlink('composer-setup.php');" \ 11 | && mv composer.phar /usr/local/bin/composer 12 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/docker/php/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG PHP_VERSION=8.0 2 | FROM php:$PHP_VERSION 3 | 4 | RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \ 5 | && php -r "if (hash_file('sha384', 'composer-setup.php') === '906a84df04cea2aa72f40b5f787e49f22d4c2f19492ac310e8cba5b96ac8b64115ac402c8cd292b8a03482574915d1a8') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" \ 6 | && php composer-setup.php \ 7 | && php -r "unlink('composer-setup.php');" \ 8 | && mv composer.phar /usr/local/bin/composer 9 | 10 | RUN apt update && apt install -y libonig-dev libzip-dev 11 | RUN docker-php-ext-install mbstring zip 12 | RUN pecl install xdebug && docker-php-ext-enable xdebug 13 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/docker/php/php.ini: -------------------------------------------------------------------------------- 1 | xdebug.mode=coverage 2 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/infection.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": { 3 | "directories": ["src"] 4 | }, 5 | "logs": { 6 | "text": "infection.log" 7 | }, 8 | "timeout": 2, 9 | "mutators": { 10 | "@default": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | src 14 | 15 | 16 | 17 | 18 | 19 | tests 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/psalm.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Event/FilterShortcodesEvent.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class FilterShortcodesEvent 15 | { 16 | /** @var ProcessedShortcode|null */ 17 | private $parent; 18 | /** @var ParsedShortcodeInterface[] */ 19 | private $shortcodes = array(); 20 | 21 | /** 22 | * @param ParsedShortcodeInterface[] $shortcodes 23 | * @param ProcessedShortcode|null $parent 24 | */ 25 | public function __construct(array $shortcodes, $parent = null) 26 | { 27 | if(null !== $parent && false === $parent instanceof ProcessedShortcode) { 28 | throw new \LogicException('Parameter $parent must be an instance of ProcessedShortcode.'); 29 | } 30 | 31 | $this->parent = $parent; 32 | $this->setShortcodes($shortcodes); 33 | } 34 | 35 | /** @return ParsedShortcodeInterface[] */ 36 | public function getShortcodes() 37 | { 38 | return $this->shortcodes; 39 | } 40 | 41 | /** @return ProcessedShortcode|null */ 42 | public function getParent() 43 | { 44 | return $this->parent; 45 | } 46 | 47 | /** 48 | * @param ParsedShortcodeInterface[] $shortcodes 49 | * 50 | * @return void 51 | */ 52 | public function setShortcodes(array $shortcodes) 53 | { 54 | $this->shortcodes = array(); 55 | foreach($shortcodes as $shortcode) { 56 | $this->addShortcode($shortcode); 57 | } 58 | } 59 | 60 | /** 61 | * @param ParsedShortcodeInterface $shortcode 62 | * 63 | * @return void 64 | */ 65 | private function addShortcode(ParsedShortcodeInterface $shortcode) 66 | { 67 | $this->shortcodes[] = $shortcode; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Event/ReplaceShortcodesEvent.php: -------------------------------------------------------------------------------- 1 | 13 | */ 14 | class ReplaceShortcodesEvent 15 | { 16 | /** @var ShortcodeInterface|null */ 17 | private $shortcode; 18 | /** @var string */ 19 | private $text; 20 | /** @var ReplacedShortcode[] */ 21 | private $replacements = array(); 22 | /** @var string|null */ 23 | private $result; 24 | 25 | /** 26 | * @param string $text 27 | * @param ReplacedShortcode[] $replacements 28 | * @param ShortcodeInterface|null $shortcode 29 | */ 30 | public function __construct($text, array $replacements, $shortcode = null) 31 | { 32 | if(null !== $shortcode && false === $shortcode instanceof ShortcodeInterface) { 33 | throw new \LogicException('Parameter $shortcode must be an instance of ShortcodeInterface.'); 34 | } 35 | 36 | $this->shortcode = $shortcode; 37 | $this->text = $text; 38 | 39 | $this->setReplacements($replacements); 40 | } 41 | 42 | /** 43 | * @param ReplacedShortcode[] $replacements 44 | * 45 | * @return void 46 | */ 47 | private function setReplacements(array $replacements) 48 | { 49 | foreach($replacements as $replacement) { 50 | $this->addReplacement($replacement); 51 | } 52 | } 53 | 54 | /** @return void */ 55 | private function addReplacement(ReplacedShortcode $replacement) 56 | { 57 | $this->replacements[] = $replacement; 58 | } 59 | 60 | /** @return string */ 61 | public function getText() 62 | { 63 | return $this->text; 64 | } 65 | 66 | /** @return ReplacedShortcode[] */ 67 | public function getReplacements() 68 | { 69 | return $this->replacements; 70 | } 71 | 72 | /** @return ShortcodeInterface|null */ 73 | public function getShortcode() 74 | { 75 | return $this->shortcode; 76 | } 77 | 78 | /** 79 | * @param string $result 80 | * 81 | * @return void 82 | */ 83 | public function setResult($result) 84 | { 85 | $this->result = $result; 86 | } 87 | 88 | /** @return string|null */ 89 | public function getResult() 90 | { 91 | return $this->result; 92 | } 93 | 94 | /** @return bool */ 95 | public function hasResult() 96 | { 97 | return null !== $this->result; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/EventContainer/EventContainer.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | final class EventContainer implements EventContainerInterface 10 | { 11 | /** @psalm-var array> */ 12 | private $listeners = array(); 13 | 14 | public function __construct() 15 | { 16 | } 17 | 18 | /** 19 | * @param string $event 20 | * @param callable $handler 21 | * 22 | * @return void 23 | */ 24 | public function addListener($event, $handler) 25 | { 26 | if(!\in_array($event, Events::getEvents(), true)) { 27 | throw new \InvalidArgumentException(sprintf('Unsupported event %s!', $event)); 28 | } 29 | 30 | if(!array_key_exists($event, $this->listeners)) { 31 | $this->listeners[$event] = array(); 32 | } 33 | 34 | $this->listeners[$event][] = $handler; 35 | } 36 | 37 | /** 38 | * @param string $event 39 | * 40 | * @psalm-return list 41 | */ 42 | public function getListeners($event) 43 | { 44 | return $this->hasEvent($event) ? $this->listeners[$event] : array(); 45 | } 46 | 47 | /** 48 | * @param string $name 49 | * 50 | * @return bool 51 | */ 52 | private function hasEvent($name) 53 | { 54 | return array_key_exists($name, $this->listeners); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/EventContainer/EventContainerInterface.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | interface EventContainerInterface 8 | { 9 | /** 10 | * @param string $event 11 | * 12 | * @return callable[] 13 | */ 14 | public function getListeners($event); 15 | } 16 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/EventHandler/FilterRawEventHandler.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | final class FilterRawEventHandler 10 | { 11 | /** @var string[] */ 12 | private $names = array(); 13 | 14 | public function __construct(array $names) 15 | { 16 | foreach($names as $name) { 17 | if(false === is_string($name)) { 18 | throw new \InvalidArgumentException('Expected array of strings!'); 19 | } 20 | 21 | $this->names[] = $name; 22 | } 23 | } 24 | 25 | public function __invoke(FilterShortcodesEvent $event) 26 | { 27 | $parent = $event->getParent(); 28 | if($parent && \in_array($parent->getName(), $this->names, true)) { 29 | $event->setShortcodes(array()); 30 | 31 | return; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/EventHandler/ReplaceJoinEventHandler.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | final class ReplaceJoinEventHandler 10 | { 11 | /** @var string[] */ 12 | private $names = array(); 13 | 14 | public function __construct(array $names) 15 | { 16 | foreach($names as $name) { 17 | if(false === is_string($name)) { 18 | throw new \InvalidArgumentException('Expected array of strings!'); 19 | } 20 | 21 | $this->names[] = $name; 22 | } 23 | } 24 | 25 | public function __invoke(ReplaceShortcodesEvent $event) 26 | { 27 | $shortcode = $event->getShortcode(); 28 | if($shortcode && in_array($shortcode->getName(), $this->names)) { 29 | $replaces = array(); 30 | foreach($event->getReplacements() as $r) { 31 | $replaces[] = $r->getReplacement(); 32 | } 33 | 34 | $event->setResult(implode('', $replaces)); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Events.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | final class Events 8 | { 9 | const FILTER_SHORTCODES = 'event.filter-shortcodes'; 10 | const REPLACE_SHORTCODES = 'event.replace-shortcodes'; 11 | 12 | /** @return string[] */ 13 | public static function getEvents() 14 | { 15 | return array(static::FILTER_SHORTCODES, static::REPLACE_SHORTCODES); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Handler/ContentHandler.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | final class ContentHandler 10 | { 11 | /** 12 | * [content]text to display[/content] 13 | * 14 | * @param ShortcodeInterface $shortcode 15 | * 16 | * @return null|string 17 | */ 18 | public function __invoke(ShortcodeInterface $shortcode) 19 | { 20 | return $shortcode->getContent(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Handler/DeclareHandler.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final class DeclareHandler 11 | { 12 | /** @var HandlerContainer */ 13 | private $handlers; 14 | /** @var string */ 15 | private $delimiter; 16 | 17 | /** @param string $delimiter */ 18 | public function __construct(HandlerContainer $container, $delimiter = '%') 19 | { 20 | $this->handlers = $container; 21 | $this->delimiter = $delimiter; 22 | } 23 | 24 | /** 25 | * [declare name]Your name is %value%[/declare] 26 | * [name value="Thomas" /] 27 | * 28 | * @param ShortcodeInterface $shortcode 29 | */ 30 | public function __invoke(ShortcodeInterface $shortcode) 31 | { 32 | $args = $shortcode->getParameters(); 33 | if(empty($args)) { 34 | return; 35 | } 36 | $keys = array_keys($args); 37 | $name = array_shift($keys); 38 | $content = (string)$shortcode->getContent(); 39 | $delimiter = $this->delimiter; 40 | 41 | $this->handlers->add($name, function(ShortcodeInterface $shortcode) use($content, $delimiter) { 42 | $args = $shortcode->getParameters(); 43 | $keys = array_map(function($key) use($delimiter) { return $delimiter.$key.$delimiter; }, array_keys($args)); 44 | /** @var string[] $values */ 45 | $values = array_values($args); 46 | 47 | return str_replace($keys, $values, $content); 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Handler/EmailHandler.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | final class EmailHandler 10 | { 11 | /** 12 | * [email="example@example.org"]Contact me![/email] 13 | * [email="example@example.org" /] 14 | * [email]example@example.org[/email] 15 | * 16 | * @param ShortcodeInterface $shortcode 17 | * 18 | * @return string 19 | */ 20 | public function __invoke(ShortcodeInterface $shortcode) 21 | { 22 | $email = null !== $shortcode->getBbCode() ? $shortcode->getBbCode() : $shortcode->getContent(); 23 | $content = $shortcode->getContent() === null ? $email : $shortcode->getContent(); 24 | 25 | return ''.(string)$content.''; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Handler/NameHandler.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | final class NameHandler 10 | { 11 | /** 12 | * [name /] 13 | * [name]content is ignored[/name] 14 | * 15 | * @param ShortcodeInterface $shortcode 16 | * 17 | * @return string 18 | */ 19 | public function __invoke(ShortcodeInterface $shortcode) 20 | { 21 | return $shortcode->getName(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Handler/NullHandler.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | final class NullHandler 10 | { 11 | /** 12 | * Special shortcode to discard any input and return empty text 13 | * 14 | * @param ShortcodeInterface $shortcode 15 | * 16 | * @return null 17 | */ 18 | public function __invoke(ShortcodeInterface $shortcode) 19 | { 20 | return null; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Handler/PlaceholderHandler.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | final class PlaceholderHandler 10 | { 11 | /** @var string */ 12 | private $delimiter; 13 | 14 | /** @param string $delimiter */ 15 | public function __construct($delimiter = '%') 16 | { 17 | $this->delimiter = $delimiter; 18 | } 19 | 20 | /** 21 | * [placeholder value=18]You age is %value%[/placeholder] 22 | * 23 | * @param ShortcodeInterface $shortcode 24 | * 25 | * @return mixed 26 | */ 27 | public function __invoke(ShortcodeInterface $shortcode) 28 | { 29 | $args = $shortcode->getParameters(); 30 | $delimiter = $this->delimiter; 31 | $keys = array_map(function($key) use($delimiter) { return $delimiter.$key.$delimiter; }, array_keys($args)); 32 | /** @var string[] $values */ 33 | $values = array_values($args); 34 | 35 | return str_replace($keys, $values, (string)$shortcode->getContent()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Handler/RawHandler.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | final class RawHandler 10 | { 11 | public function __construct() 12 | { 13 | } 14 | 15 | /** 16 | * [raw]any content [with] or [without /] shortcodes[/raw] 17 | * 18 | * @param ProcessedShortcode $shortcode 19 | * 20 | * @return string 21 | */ 22 | public function __invoke(ProcessedShortcode $shortcode) 23 | { 24 | return $shortcode->getTextContent(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Handler/SerializerHandler.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final class SerializerHandler 11 | { 12 | /** @var SerializerInterface */ 13 | private $serializer; 14 | 15 | public function __construct(SerializerInterface $serializer) 16 | { 17 | $this->serializer = $serializer; 18 | } 19 | 20 | /** 21 | * [text arg=val /] 22 | * [text arg=val]content[/text] 23 | * [json arg=val /] 24 | * [json arg=val]content[/json] 25 | * [xml arg=val /] 26 | * [xml arg=val]content[/xml] 27 | * [yaml arg=val /] 28 | * [yaml arg=val]content[/yaml] 29 | * 30 | * @param ShortcodeInterface $shortcode 31 | * 32 | * @return string 33 | */ 34 | public function __invoke(ShortcodeInterface $shortcode) 35 | { 36 | return $this->serializer->serialize($shortcode); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Handler/UrlHandler.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | final class UrlHandler 10 | { 11 | /** 12 | * [url="http://example.org"]Click![/url] 13 | * [url="http://example.org" /] 14 | * [url]http://example.org[/url] 15 | * 16 | * @param ShortcodeInterface $shortcode 17 | * 18 | * @return string 19 | */ 20 | public function __invoke(ShortcodeInterface $shortcode) 21 | { 22 | $url = null !== $shortcode->getBbCode() ? $shortcode->getBbCode() : $shortcode->getContent(); 23 | 24 | return ''.(string)$shortcode->getContent().''; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Handler/WrapHandler.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | final class WrapHandler 10 | { 11 | /** @var string */ 12 | private $before; 13 | /** @var string */ 14 | private $after; 15 | 16 | /** 17 | * @param string $before 18 | * @param string $after 19 | */ 20 | public function __construct($before, $after) 21 | { 22 | $this->before = $before; 23 | $this->after = $after; 24 | } 25 | 26 | /** @return self */ 27 | public static function createBold() 28 | { 29 | return new self('', ''); 30 | } 31 | 32 | /** 33 | * [b]content[b] 34 | * [strong]content[/strong] 35 | * 36 | * @param ShortcodeInterface $shortcode 37 | * 38 | * @return string 39 | */ 40 | public function __invoke(ShortcodeInterface $shortcode) 41 | { 42 | return $this->before.(string)$shortcode->getContent().$this->after; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/HandlerContainer/HandlerContainer.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | final class HandlerContainer implements HandlerContainerInterface 10 | { 11 | /** @psalm-var array */ 12 | private $handlers = array(); 13 | /** @psalm-var (callable(ShortcodeInterface):string)|null */ 14 | private $default = null; 15 | 16 | /** 17 | * @param string $name 18 | * @param callable $handler 19 | * @psalm-param callable(ShortcodeInterface):string $handler 20 | * 21 | * @return $this 22 | */ 23 | public function add($name, $handler) 24 | { 25 | $this->guardHandler($handler); 26 | 27 | if (empty($name) || $this->has($name)) { 28 | $msg = 'Invalid name or duplicate shortcode handler for %s!'; 29 | throw new \RuntimeException(sprintf($msg, $name)); 30 | } 31 | 32 | $this->handlers[$name] = $handler; 33 | 34 | return $this; 35 | } 36 | 37 | /** 38 | * @param string $alias 39 | * @param string $name 40 | * 41 | * @return $this 42 | */ 43 | public function addAlias($alias, $name) 44 | { 45 | if (false === $this->has($name)) { 46 | $msg = 'Failed to add an alias %s to non existent handler %s!'; 47 | throw new \RuntimeException(sprintf($msg, $alias, $name)); 48 | } 49 | 50 | /** @psalm-suppress PossiblyNullArgument */ 51 | return $this->add($alias, $this->get($name)); 52 | } 53 | 54 | /** 55 | * @param string $name 56 | * 57 | * @return void 58 | */ 59 | public function remove($name) 60 | { 61 | if (false === $this->has($name)) { 62 | $msg = 'Failed to remove non existent handler %s!'; 63 | throw new \RuntimeException(sprintf($msg, $name)); 64 | } 65 | 66 | unset($this->handlers[$name]); 67 | } 68 | 69 | /** 70 | * @param callable $handler 71 | * @psalm-param callable(ShortcodeInterface):string $handler 72 | * 73 | * @return $this 74 | */ 75 | public function setDefault($handler) 76 | { 77 | $this->guardHandler($handler); 78 | 79 | $this->default = $handler; 80 | 81 | return $this; 82 | } 83 | 84 | /** @return string[] */ 85 | public function getNames() 86 | { 87 | return array_keys($this->handlers); 88 | } 89 | 90 | /** 91 | * @param string $name 92 | * 93 | * @return callable|null 94 | * @psalm-return (callable(ShortcodeInterface):string)|null 95 | */ 96 | public function get($name) 97 | { 98 | return $this->has($name) ? $this->handlers[$name] : $this->default; 99 | } 100 | 101 | /** 102 | * @param string $name 103 | * 104 | * @return bool 105 | */ 106 | public function has($name) 107 | { 108 | return array_key_exists($name, $this->handlers); 109 | } 110 | 111 | /** 112 | * @param callable $handler 113 | * 114 | * @return void 115 | */ 116 | private function guardHandler($handler) 117 | { 118 | if (!is_callable($handler)) { 119 | throw new \RuntimeException('Shortcode handler must be callable!'); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/HandlerContainer/HandlerContainerInterface.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | interface HandlerContainerInterface 10 | { 11 | /** 12 | * Returns handler for given shortcode name or default if it was set before. 13 | * If no handler is found, returns null. 14 | * 15 | * @param string $name Shortcode name 16 | * 17 | * @return callable|null 18 | * @psalm-return (callable(ShortcodeInterface):string)|null 19 | */ 20 | public function get($name); 21 | } 22 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/HandlerContainer/ImmutableHandlerContainer.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | final class ImmutableHandlerContainer implements HandlerContainerInterface 10 | { 11 | /** @var HandlerContainer */ 12 | private $handlers; 13 | 14 | public function __construct(HandlerContainer $handlers) 15 | { 16 | $this->handlers = clone $handlers; 17 | } 18 | 19 | /** 20 | * @param string $name 21 | * 22 | * @return callable|null 23 | * @psalm-return (callable(ShortcodeInterface):string)|null 24 | */ 25 | public function get($name) 26 | { 27 | return $this->handlers->get($name); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Parser/ParserInterface.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | interface ParserInterface 10 | { 11 | /** 12 | * Parse single shortcode match into object 13 | * 14 | * @param string $text 15 | * 16 | * @return ParsedShortcodeInterface[] 17 | */ 18 | public function parse($text); 19 | } 20 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Parser/RegexParser.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final class RegexParser implements ParserInterface 14 | { 15 | /** @var SyntaxInterface */ 16 | private $syntax; 17 | /** @var non-empty-string */ 18 | private $shortcodeRegex; 19 | /** @var non-empty-string */ 20 | private $singleShortcodeRegex; 21 | /** @var non-empty-string */ 22 | private $parametersRegex; 23 | 24 | /** @param SyntaxInterface|null $syntax */ 25 | public function __construct($syntax = null) 26 | { 27 | if(null !== $syntax && false === $syntax instanceof SyntaxInterface) { 28 | throw new \LogicException('Parameter $syntax must be an instance of SyntaxInterface.'); 29 | } 30 | 31 | $this->syntax = $syntax ?: new Syntax(); 32 | $this->shortcodeRegex = RegexBuilderUtility::buildShortcodeRegex($this->syntax); 33 | $this->singleShortcodeRegex = RegexBuilderUtility::buildSingleShortcodeRegex($this->syntax); 34 | $this->parametersRegex = RegexBuilderUtility::buildParametersRegex($this->syntax); 35 | } 36 | 37 | /** 38 | * @param string $text 39 | * 40 | * @return ParsedShortcode[] 41 | */ 42 | public function parse($text) 43 | { 44 | preg_match_all($this->shortcodeRegex, $text, $matches, PREG_OFFSET_CAPTURE); 45 | 46 | // loop instead of array_map to pass the arguments explicitly 47 | $shortcodes = array(); 48 | foreach($matches[0] as $match) { 49 | $offset = mb_strlen(substr($text, 0, $match[1]), 'utf-8'); 50 | $shortcodes[] = $this->parseSingle($match[0], $offset); 51 | } 52 | 53 | return array_filter($shortcodes); 54 | } 55 | 56 | /** 57 | * @param string $text 58 | * @param int $offset 59 | * 60 | * @return ParsedShortcode 61 | */ 62 | private function parseSingle($text, $offset) 63 | { 64 | preg_match($this->singleShortcodeRegex, $text, $matches, PREG_OFFSET_CAPTURE); 65 | 66 | /** @psalm-var array $matches */ 67 | $name = $matches['name'][0]; 68 | $parameters = isset($matches['parameters'][0]) ? $this->parseParameters($matches['parameters'][0]) : array(); 69 | $bbCode = isset($matches['bbCode'][0]) && $matches['bbCode'][1] !== -1 70 | ? $this->extractValue($matches['bbCode'][0]) 71 | : null; 72 | $content = isset($matches['content'][0]) && $matches['content'][1] !== -1 ? $matches['content'][0] : null; 73 | 74 | return new ParsedShortcode(new Shortcode($name, $parameters, $content, $bbCode), $text, $offset); 75 | } 76 | 77 | /** 78 | * @param string $text 79 | * 80 | * @psalm-return array 81 | */ 82 | private function parseParameters($text) 83 | { 84 | preg_match_all($this->parametersRegex, $text, $argsMatches); 85 | 86 | // loop because PHP 5.3 can't handle $this properly and I want separate methods 87 | $return = array(); 88 | foreach ($argsMatches[1] as $item) { 89 | /** @psalm-var array{0:string,1:string} $parts */ 90 | $parts = explode($this->syntax->getParameterValueSeparator(), $item, 2); 91 | $return[trim($parts[0])] = $this->parseValue(isset($parts[1]) ? $parts[1] : null); 92 | } 93 | 94 | return $return; 95 | } 96 | 97 | /** 98 | * @param string|null $value 99 | * 100 | * @return string|null 101 | */ 102 | private function parseValue($value) 103 | { 104 | return null === $value ? null : $this->extractValue(trim($value)); 105 | } 106 | 107 | /** 108 | * @param string $value 109 | * 110 | * @return string 111 | */ 112 | private function extractValue($value) 113 | { 114 | $length = strlen($this->syntax->getParameterValueDelimiter()); 115 | 116 | return $this->isDelimitedValue($value) ? substr($value, $length, -1 * $length) : $value; 117 | } 118 | 119 | /** 120 | * @param string $value 121 | * 122 | * @return bool 123 | */ 124 | private function isDelimitedValue($value) 125 | { 126 | return preg_match('/^'.$this->syntax->getParameterValueDelimiter().'/us', $value) 127 | && preg_match('/'.$this->syntax->getParameterValueDelimiter().'$/us', $value); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Processor/ProcessorContext.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final class ProcessorContext 11 | { 12 | /** 13 | * @var ShortcodeInterface 14 | * @psalm-suppress PropertyNotSetInConstructor 15 | */ 16 | public $shortcode; 17 | 18 | /** @var ProcessedShortcode|null */ 19 | public $parent = null; 20 | 21 | /** 22 | * @var ProcessorInterface 23 | * @psalm-suppress PropertyNotSetInConstructor 24 | */ 25 | public $processor; 26 | 27 | /** 28 | * @var string 29 | * @psalm-suppress PropertyNotSetInConstructor 30 | */ 31 | public $textContent; 32 | /** @var int */ 33 | public $position = 0; 34 | /** @psalm-var array */ 35 | public $namePosition = array(); 36 | /** @var string */ 37 | public $text = ''; 38 | /** @var string */ 39 | public $shortcodeText = ''; 40 | /** @var int */ 41 | public $iterationNumber = 0; 42 | /** @var int */ 43 | public $recursionLevel = 0; 44 | /** 45 | * @var int 46 | * @psalm-suppress PropertyNotSetInConstructor 47 | */ 48 | public $offset; 49 | /** 50 | * @var int 51 | * @psalm-suppress PropertyNotSetInConstructor 52 | */ 53 | public $baseOffset; 54 | 55 | public function __construct() 56 | { 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Processor/ProcessorInterface.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | interface ProcessorInterface 8 | { 9 | /** 10 | * Process text using registered shortcode handlers 11 | * 12 | * @param string $text Text containing shortcodes 13 | * 14 | * @return string Text with replaced shortcodes 15 | */ 16 | public function process($text); 17 | } 18 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Serializer/JsonSerializer.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final class JsonSerializer implements SerializerInterface 11 | { 12 | public function serialize(ShortcodeInterface $shortcode) 13 | { 14 | return json_encode(array( 15 | 'name' => $shortcode->getName(), 16 | 'parameters' => $shortcode->getParameters(), 17 | 'content' => $shortcode->getContent(), 18 | 'bbCode' => $shortcode->getBbCode(), 19 | )); 20 | } 21 | 22 | /** 23 | * @param string $text 24 | * 25 | * @return Shortcode 26 | */ 27 | public function unserialize($text) 28 | { 29 | /** @psalm-var array{name:string,parameters:array,bbCode:string|null,content:string|null}|null $data */ 30 | $data = json_decode($text, true); 31 | 32 | if (!is_array($data)) { 33 | throw new \InvalidArgumentException('Invalid JSON, cannot unserialize Shortcode!'); 34 | } 35 | if (!array_diff_key($data, array('name', 'parameters', 'content'))) { 36 | throw new \InvalidArgumentException('Malformed Shortcode JSON, expected name, parameters, and content!'); 37 | } 38 | 39 | /** @var string $name */ 40 | $name = array_key_exists('name', $data) ? $data['name'] : null; 41 | $parameters = array_key_exists('parameters', $data) ? $data['parameters'] : array(); 42 | $content = array_key_exists('content', $data) ? $data['content'] : null; 43 | $bbCode = array_key_exists('bbCode', $data) ? $data['bbCode'] : null; 44 | 45 | /** @psalm-suppress DocblockTypeContradiction */ 46 | if(!is_array($parameters)) { 47 | throw new \InvalidArgumentException('Parameters must be an array!'); 48 | } 49 | 50 | return new Shortcode($name, $parameters, $content, $bbCode); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Serializer/SerializerInterface.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | interface SerializerInterface 10 | { 11 | /** 12 | * Serializes Shortcode class instance into given format 13 | * 14 | * @param ShortcodeInterface $shortcode Instance to serialize 15 | * 16 | * @return string 17 | */ 18 | public function serialize(ShortcodeInterface $shortcode); 19 | 20 | /** 21 | * Loads back Shortcode instance from serialized format 22 | * 23 | * @param string $text 24 | * 25 | * @return ShortcodeInterface 26 | */ 27 | public function unserialize($text); 28 | } 29 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Serializer/TextSerializer.php: -------------------------------------------------------------------------------- 1 | 12 | */ 13 | final class TextSerializer implements SerializerInterface 14 | { 15 | /** @var SyntaxInterface */ 16 | private $syntax; 17 | 18 | /** @param SyntaxInterface|null $syntax */ 19 | public function __construct($syntax = null) 20 | { 21 | if(null !== $syntax && false === $syntax instanceof SyntaxInterface) { 22 | throw new \LogicException('Parameter $syntax must be an instance of SyntaxInterface.'); 23 | } 24 | 25 | $this->syntax = $syntax ?: new Syntax(); 26 | } 27 | 28 | /** @inheritDoc */ 29 | public function serialize(ShortcodeInterface $shortcode) 30 | { 31 | $open = $this->syntax->getOpeningTag(); 32 | $close = $this->syntax->getClosingTag(); 33 | $marker = $this->syntax->getClosingTagMarker(); 34 | 35 | $parameters = $this->serializeParameters($shortcode->getParameters()); 36 | $bbCode = null !== $shortcode->getBbCode() 37 | ? $this->serializeValue($shortcode->getBbCode()) 38 | : ''; 39 | $return = $open.$shortcode->getName().$bbCode.$parameters; 40 | 41 | return null === $shortcode->getContent() 42 | ? $return.' '.$marker.$close 43 | : $return.$close.(string)$shortcode->getContent().$open.$marker.$shortcode->getName().$close; 44 | } 45 | 46 | /** 47 | * @psalm-param array $parameters 48 | * 49 | * @return string 50 | */ 51 | private function serializeParameters(array $parameters) 52 | { 53 | // unfortunately array_reduce() does not support keys 54 | $return = ''; 55 | foreach ($parameters as $key => $value) { 56 | $return .= ' '.$key.$this->serializeValue($value); 57 | } 58 | 59 | return $return; 60 | } 61 | 62 | /** 63 | * @param string|null $value 64 | * 65 | * @return string 66 | */ 67 | private function serializeValue($value) 68 | { 69 | if (null === $value) { 70 | return ''; 71 | } 72 | 73 | $delimiter = $this->syntax->getParameterValueDelimiter(); 74 | $separator = $this->syntax->getParameterValueSeparator(); 75 | 76 | return $separator.(preg_match('/^\w+$/u', $value) 77 | ? $value 78 | : $delimiter.$value.$delimiter); 79 | } 80 | 81 | public function unserialize($text) 82 | { 83 | $parser = new RegexParser(); 84 | 85 | $shortcodes = $parser->parse($text); 86 | 87 | if (empty($shortcodes)) { 88 | $msg = 'Failed to unserialize shortcode from text %s!'; 89 | throw new \InvalidArgumentException(sprintf($msg, $text)); 90 | } 91 | if (count($shortcodes) > 1) { 92 | $msg = 'Provided text %s contains more than one shortcode!'; 93 | throw new \InvalidArgumentException(sprintf($msg, $text)); 94 | } 95 | 96 | /** @var ShortcodeInterface $parsed */ 97 | $parsed = array_shift($shortcodes); 98 | 99 | $name = $parsed->getName(); 100 | $parameters = $parsed->getParameters(); 101 | $content = $parsed->getContent(); 102 | $bbCode = $parsed->getBbCode(); 103 | 104 | return new Shortcode($name, $parameters, $content, $bbCode); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Serializer/XmlSerializer.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final class XmlSerializer implements SerializerInterface 11 | { 12 | /** 13 | * 14 | * BBCODE 15 | * 16 | * VALUE 17 | * VALUE 18 | * 19 | * CONTENT> 20 | * 21 | * 22 | * @param ShortcodeInterface $shortcode 23 | * 24 | * @return string 25 | */ 26 | public function serialize(ShortcodeInterface $shortcode) 27 | { 28 | $doc = new \DOMDocument('1.0', 'UTF-8'); 29 | $doc->preserveWhiteSpace = false; 30 | $doc->formatOutput = true; 31 | 32 | $code = $doc->createElement('shortcode'); 33 | $code->setAttribute('name', $shortcode->getName()); 34 | $xml = $doc->appendChild($code); 35 | $xml->appendChild($this->createCDataNode($doc, 'bbCode', $shortcode->getBbCode())); 36 | 37 | $parameters = $xml->appendChild($doc->createElement('parameters')); 38 | foreach($shortcode->getParameters() as $key => $value) { 39 | $parameter = $doc->createElement('parameter'); 40 | $parameter->setAttribute('name', $key); 41 | if(null !== $value) { 42 | $parameter->appendChild($doc->createCDATASection($value)); 43 | } 44 | 45 | $parameters->appendChild($parameter); 46 | } 47 | 48 | $xml->appendChild($this->createCDataNode($doc, 'content', $shortcode->getContent())); 49 | 50 | return $doc->saveXML(); 51 | } 52 | 53 | /** 54 | * @param \DOMDocument $doc 55 | * @param string $name 56 | * @param string|null $content 57 | * 58 | * @return \DOMElement 59 | */ 60 | private function createCDataNode(\DOMDocument $doc, $name, $content) 61 | { 62 | $node = $doc->createElement($name); 63 | 64 | if(null !== $content) { 65 | $node->appendChild($doc->createCDATASection($content)); 66 | } 67 | 68 | return $node; 69 | } 70 | 71 | /** 72 | * @param string $text 73 | * 74 | * @return Shortcode 75 | */ 76 | public function unserialize($text) 77 | { 78 | $xml = new \DOMDocument(); 79 | $internalErrors = libxml_use_internal_errors(true); 80 | if(!$text || !$xml->loadXML($text)) { 81 | libxml_use_internal_errors($internalErrors); 82 | throw new \InvalidArgumentException('Failed to parse provided XML!'); 83 | } 84 | libxml_use_internal_errors($internalErrors); 85 | 86 | $xpath = new \DOMXPath($xml); 87 | $shortcode = $xpath->query('/shortcode'); 88 | if($shortcode->length !== 1) { 89 | throw new \InvalidArgumentException('Invalid shortcode XML!'); 90 | } 91 | /** @psalm-suppress PossiblyNullArgument */ 92 | $name = $this->getAttribute($shortcode->item(0), 'name'); 93 | 94 | $bbCode = $this->getValue($xpath->query('/shortcode/bbCode')); 95 | $content = $this->getValue($xpath->query('/shortcode/content')); 96 | 97 | $parameters = array(); 98 | $elements = $xpath->query('/shortcode/parameters/parameter'); 99 | for($i = 0; $i < $elements->length; $i++) { 100 | $node = $elements->item($i); 101 | 102 | /** @psalm-suppress PossiblyNullReference */ 103 | $parameters[$this->getAttribute($node, 'name')] = $node->hasChildNodes() ? $node->nodeValue : null; 104 | } 105 | 106 | return new Shortcode($name, $parameters, $content, $bbCode); 107 | } 108 | 109 | /** 110 | * @param \DOMNodeList $node 111 | * 112 | * @return string|null 113 | */ 114 | private function getValue(\DOMNodeList $node) 115 | { 116 | /** @psalm-suppress PossiblyNullReference,PossiblyNullPropertyFetch */ 117 | return $node->length === 1 && $node->item(0)->hasChildNodes() 118 | ? $node->item(0)->nodeValue 119 | : null; 120 | } 121 | 122 | /** 123 | * @param \DOMNode $node 124 | * @param string $name 125 | * 126 | * @return string 127 | */ 128 | private function getAttribute(\DOMNode $node, $name) 129 | { 130 | /** 131 | * @var \DOMNode $attribute 132 | * @psalm-suppress NullReference 133 | */ 134 | $attribute = $node->attributes->getNamedItem($name); 135 | 136 | /** @psalm-suppress DocblockTypeContradiction,RiskyTruthyFalsyComparison */ 137 | if(!$attribute || !$attribute->nodeValue) { 138 | throw new \InvalidArgumentException('Invalid shortcode XML!'); 139 | } 140 | 141 | return $attribute->nodeValue; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Serializer/YamlSerializer.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | final class YamlSerializer implements SerializerInterface 12 | { 13 | public function serialize(ShortcodeInterface $shortcode) 14 | { 15 | return Yaml::dump(array( 16 | 'name' => $shortcode->getName(), 17 | 'parameters' => $shortcode->getParameters(), 18 | 'content' => $shortcode->getContent(), 19 | 'bbCode' => $shortcode->getBbCode(), 20 | )); 21 | } 22 | 23 | /** 24 | * @param string $text 25 | * 26 | * @return Shortcode 27 | */ 28 | public function unserialize($text) 29 | { 30 | /** @psalm-var array{name:string,parameters:array,bbCode:string|null,content:string|null}|null $data */ 31 | $data = Yaml::parse($text); 32 | 33 | if(!is_array($data)) { 34 | throw new \InvalidArgumentException('Invalid YAML, cannot unserialize Shortcode!'); 35 | } 36 | if (!array_intersect(array_keys($data), array('name', 'parameters', 'content'))) { 37 | throw new \InvalidArgumentException('Malformed shortcode YAML, expected name, parameters, and content!'); 38 | } 39 | 40 | /** @var string $name */ 41 | $name = array_key_exists('name', $data) ? $data['name'] : null; 42 | $parameters = array_key_exists('parameters', $data) ? $data['parameters'] : array(); 43 | $content = array_key_exists('content', $data) ? $data['content'] : null; 44 | $bbCode = array_key_exists('bbCode', $data) ? $data['bbCode'] : null; 45 | 46 | /** @psalm-suppress DocblockTypeContradiction */ 47 | if(!is_array($parameters)) { 48 | throw new \InvalidArgumentException('Parameters must be an array!'); 49 | } 50 | 51 | return new Shortcode($name, $parameters, $content, $bbCode); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Shortcode/AbstractShortcode.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | abstract class AbstractShortcode 8 | { 9 | /** @var string */ 10 | protected $name; 11 | /** @psalm-var array */ 12 | protected $parameters = array(); 13 | /** @var string|null */ 14 | protected $content; 15 | /** @var string|null */ 16 | protected $bbCode; 17 | 18 | /** @return bool */ 19 | public function hasContent() 20 | { 21 | return $this->content !== null; 22 | } 23 | 24 | /** @return string */ 25 | public function getName() 26 | { 27 | return $this->name; 28 | } 29 | 30 | /** @psalm-return array */ 31 | public function getParameters() 32 | { 33 | return $this->parameters; 34 | } 35 | 36 | /** 37 | * @param string $name 38 | * 39 | * @return bool 40 | */ 41 | public function hasParameter($name) 42 | { 43 | return array_key_exists($name, $this->parameters); 44 | } 45 | 46 | /** @return bool */ 47 | public function hasParameters() 48 | { 49 | return (bool)$this->parameters; 50 | } 51 | 52 | /** 53 | * @param string $name 54 | * @param string|null $default 55 | * 56 | * @psalm-return string|null 57 | */ 58 | public function getParameter($name, $default = null) 59 | { 60 | return $this->hasParameter($name) ? $this->parameters[$name] : $default; 61 | } 62 | 63 | /** 64 | * @param int $index 65 | * 66 | * @return string|null 67 | */ 68 | public function getParameterAt($index) 69 | { 70 | $keys = array_keys($this->parameters); 71 | 72 | return array_key_exists($index, $keys) ? $keys[$index] : null; 73 | } 74 | 75 | /** @return string|null */ 76 | public function getContent() 77 | { 78 | return $this->content; 79 | } 80 | 81 | /** @return string|null */ 82 | public function getBbCode() 83 | { 84 | return $this->bbCode; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Shortcode/ParsedShortcode.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | final class ParsedShortcode extends AbstractShortcode implements ParsedShortcodeInterface 8 | { 9 | /** @var string */ 10 | private $text; 11 | /** @var int */ 12 | private $offset; 13 | 14 | /** 15 | * @param string $text 16 | * @param int $offset 17 | */ 18 | public function __construct(ShortcodeInterface $shortcode, $text, $offset) 19 | { 20 | $this->name = $shortcode->getName(); 21 | $this->parameters = $shortcode->getParameters(); 22 | $this->content = $shortcode->getContent(); 23 | $this->bbCode = $shortcode->getBbCode(); 24 | $this->text = $text; 25 | $this->offset = $offset; 26 | } 27 | 28 | public function withContent($content) 29 | { 30 | $self = clone $this; 31 | $self->content = $content; 32 | 33 | return $self; 34 | } 35 | 36 | /** @return string */ 37 | public function getText() 38 | { 39 | return $this->text; 40 | } 41 | 42 | /** @return int */ 43 | public function getOffset() 44 | { 45 | return $this->offset; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Shortcode/ParsedShortcodeInterface.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | interface ParsedShortcodeInterface extends ShortcodeInterface 8 | { 9 | /** 10 | * Returns exact shortcode text 11 | * 12 | * @return string 13 | */ 14 | public function getText(); 15 | 16 | /** 17 | * Returns string position in the parent text 18 | * 19 | * @return int 20 | */ 21 | public function getOffset(); 22 | } 23 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Shortcode/ProcessedShortcode.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | final class ProcessedShortcode extends AbstractShortcode implements ParsedShortcodeInterface 11 | { 12 | /** @var ProcessedShortcode|null */ 13 | private $parent; 14 | /** @var int */ 15 | private $position; 16 | /** @var int */ 17 | private $namePosition; 18 | /** @var string */ 19 | private $text; 20 | /** @var string */ 21 | private $textContent; 22 | /** @var int */ 23 | private $offset; 24 | /** @var int */ 25 | private $baseOffset; 26 | /** @var string */ 27 | private $shortcodeText; 28 | /** @var int */ 29 | private $iterationNumber; 30 | /** @var int */ 31 | private $recursionLevel; 32 | /** @var ProcessorInterface */ 33 | private $processor; 34 | 35 | private function __construct(ProcessorContext $context) 36 | { 37 | // basic properties 38 | $this->name = $context->shortcode->getName(); 39 | $this->parameters = $context->shortcode->getParameters(); 40 | $this->content = $context->shortcode->getContent(); 41 | $this->bbCode = $context->shortcode->getBbCode(); 42 | $this->textContent = $context->textContent; 43 | 44 | // runtime context 45 | $this->parent = $context->parent; 46 | $this->position = $context->position; 47 | $this->namePosition = $context->namePosition[$this->name]; 48 | $this->text = $context->text; 49 | $this->shortcodeText = $context->shortcodeText; 50 | 51 | // processor state 52 | $this->iterationNumber = $context->iterationNumber; 53 | $this->recursionLevel = $context->recursionLevel; 54 | $this->processor = $context->processor; 55 | 56 | // text context 57 | $this->offset = $context->offset; 58 | $this->baseOffset = $context->baseOffset; 59 | } 60 | 61 | /** @return self */ 62 | public static function createFromContext(ProcessorContext $context) 63 | { 64 | return new self($context); 65 | } 66 | 67 | /** 68 | * @param string|null $content 69 | * 70 | * @return self 71 | */ 72 | public function withContent($content) 73 | { 74 | $self = clone $this; 75 | $self->content = $content; 76 | 77 | return $self; 78 | } 79 | 80 | /** 81 | * @param string $name 82 | * 83 | * @return bool 84 | */ 85 | public function hasAncestor($name) 86 | { 87 | $self = $this; 88 | 89 | while($self = $self->getParent()) { 90 | if($self->getName() === $name) { 91 | return true; 92 | } 93 | } 94 | 95 | return false; 96 | } 97 | 98 | /** @return ProcessedShortcode|null */ 99 | public function getParent() 100 | { 101 | return $this->parent; 102 | } 103 | 104 | /** @return string */ 105 | public function getTextContent() 106 | { 107 | return $this->textContent; 108 | } 109 | 110 | /** @return int */ 111 | public function getPosition() 112 | { 113 | return $this->position; 114 | } 115 | 116 | /** @return int */ 117 | public function getNamePosition() 118 | { 119 | return $this->namePosition; 120 | } 121 | 122 | /** @return string */ 123 | public function getText() 124 | { 125 | return $this->text; 126 | } 127 | 128 | /** @return string */ 129 | public function getShortcodeText() 130 | { 131 | return $this->shortcodeText; 132 | } 133 | 134 | /** @return int */ 135 | public function getOffset() 136 | { 137 | return $this->offset; 138 | } 139 | 140 | /** @return int */ 141 | public function getBaseOffset() 142 | { 143 | return $this->baseOffset; 144 | } 145 | 146 | /** @return int */ 147 | public function getIterationNumber() 148 | { 149 | return $this->iterationNumber; 150 | } 151 | 152 | /** @return int */ 153 | public function getRecursionLevel() 154 | { 155 | return $this->recursionLevel; 156 | } 157 | 158 | /** @return ProcessorInterface */ 159 | public function getProcessor() 160 | { 161 | return $this->processor; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Shortcode/ReplacedShortcode.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | final class ReplacedShortcode extends AbstractShortcode 8 | { 9 | /** @var string */ 10 | private $replacement; 11 | /** @var string */ 12 | private $text; 13 | /** @var int */ 14 | private $offset; 15 | 16 | /** @param string $replacement */ 17 | public function __construct(ParsedShortcodeInterface $shortcode, $replacement) 18 | { 19 | $this->name = $shortcode->getName(); 20 | $this->parameters = $shortcode->getParameters(); 21 | $this->content = $shortcode->getContent(); 22 | $this->bbCode = $shortcode->getBbCode(); 23 | $this->text = $shortcode->getText(); 24 | $this->offset = $shortcode->getOffset(); 25 | 26 | $this->replacement = $replacement; 27 | } 28 | 29 | /** @return string */ 30 | public function getReplacement() 31 | { 32 | return $this->replacement; 33 | } 34 | 35 | /** @return string */ 36 | public function getText() 37 | { 38 | return $this->text; 39 | } 40 | 41 | /** @return int */ 42 | public function getOffset() 43 | { 44 | return $this->offset; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Shortcode/Shortcode.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | final class Shortcode extends AbstractShortcode implements ShortcodeInterface 8 | { 9 | /** 10 | * @param string $name 11 | * @param array $parameters 12 | * @psalm-param array $parameters 13 | * @param string|null $content 14 | * @param string|null $bbCode 15 | */ 16 | public function __construct($name, array $parameters, $content, $bbCode = null) 17 | { 18 | /** @psalm-suppress RedundantConditionGivenDocblockType, DocblockTypeContradiction */ 19 | if(false === is_string($name) || '' === $name) { 20 | throw new \InvalidArgumentException('Shortcode name must be a non-empty string!'); 21 | } 22 | 23 | /** @psalm-suppress MissingClosureParamType, MissingClosureReturnType */ 24 | $isStringOrNull = function($value) { return is_string($value) || null === $value; }; 25 | if(count(array_filter($parameters, $isStringOrNull)) !== count($parameters)) { 26 | throw new \InvalidArgumentException('Parameter values must be either string or empty (null)!'); 27 | } 28 | 29 | $this->name = $name; 30 | $this->parameters = $parameters; 31 | $this->content = $content; 32 | $this->bbCode = $bbCode; 33 | } 34 | 35 | public function withContent($content) 36 | { 37 | return new self($this->name, $this->parameters, $content, $this->bbCode); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Shortcode/ShortcodeInterface.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | interface ShortcodeInterface 8 | { 9 | /** 10 | * Returns new instance of given shortcode with changed content 11 | * 12 | * @param string $content 13 | * 14 | * @return self 15 | */ 16 | public function withContent($content); 17 | 18 | /** 19 | * Returns shortcode name 20 | * 21 | * @return string 22 | */ 23 | public function getName(); 24 | 25 | /** 26 | * Returns associative array(name => value) of shortcode parameters 27 | * 28 | * @return array 29 | * @psalm-return array 30 | */ 31 | public function getParameters(); 32 | 33 | /** 34 | * Returns parameter value using its name, will return null for parameter 35 | * without value 36 | * 37 | * @param string $name Parameter name 38 | * @param string|null $default Value returned if there is no parameter with given name 39 | * 40 | * @return string|null 41 | */ 42 | public function getParameter($name, $default = null); 43 | 44 | /** 45 | * Returns shortcode content (data between opening and closing tag). Null 46 | * means that shortcode had no content (was self closing), do not confuse 47 | * that with empty string (hint: use strict comparison operator ===). 48 | * 49 | * @return string|null 50 | */ 51 | public function getContent(); 52 | 53 | /** 54 | * Returns the so-called "BBCode" fragment when shortcode name is treated 55 | * like a parameter, eg.: [name="value" /] 56 | * 57 | * @return string|null 58 | */ 59 | public function getBbCode(); 60 | } 61 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Syntax/CommonSyntax.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | final class CommonSyntax implements SyntaxInterface 8 | { 9 | /** @return non-empty-string */ 10 | public function getOpeningTag() 11 | { 12 | return '['; 13 | } 14 | 15 | /** @return non-empty-string */ 16 | public function getClosingTag() 17 | { 18 | return ']'; 19 | } 20 | 21 | /** @return non-empty-string */ 22 | public function getClosingTagMarker() 23 | { 24 | return '/'; 25 | } 26 | 27 | /** @return non-empty-string */ 28 | public function getParameterValueSeparator() 29 | { 30 | return '='; 31 | } 32 | 33 | /** @return non-empty-string */ 34 | public function getParameterValueDelimiter() 35 | { 36 | return '"'; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Syntax/Syntax.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | final class Syntax implements SyntaxInterface 8 | { 9 | /** @var non-empty-string|null */ 10 | private $openingTag; 11 | /** @var non-empty-string|null */ 12 | private $closingTag; 13 | /** @var non-empty-string|null */ 14 | private $closingTagMarker; 15 | /** @var non-empty-string|null */ 16 | private $parameterValueSeparator; 17 | /** @var non-empty-string|null */ 18 | private $parameterValueDelimiter; 19 | 20 | /** 21 | * @param non-empty-string|null $openingTag 22 | * @param non-empty-string|null $closingTag 23 | * @param non-empty-string|null $closingTagMarker 24 | * @param non-empty-string|null $parameterValueSeparator 25 | * @param non-empty-string|null $parameterValueDelimiter 26 | */ 27 | public function __construct( 28 | $openingTag = null, 29 | $closingTag = null, 30 | $closingTagMarker = null, 31 | $parameterValueSeparator = null, 32 | $parameterValueDelimiter = null 33 | ) { 34 | $this->openingTag = $openingTag; 35 | $this->closingTag = $closingTag; 36 | $this->closingTagMarker = $closingTagMarker; 37 | $this->parameterValueSeparator = $parameterValueSeparator; 38 | $this->parameterValueDelimiter = $parameterValueDelimiter; 39 | } 40 | 41 | /** @return non-empty-string */ 42 | public function getOpeningTag() 43 | { 44 | return null !== $this->openingTag ? $this->openingTag : '['; 45 | } 46 | 47 | /** @return non-empty-string */ 48 | public function getClosingTag() 49 | { 50 | return null !== $this->closingTag ? $this->closingTag : ']'; 51 | } 52 | 53 | /** @return non-empty-string */ 54 | public function getClosingTagMarker() 55 | { 56 | return null !== $this->closingTagMarker ? $this->closingTagMarker : '/'; 57 | } 58 | 59 | /** @return non-empty-string */ 60 | public function getParameterValueSeparator() 61 | { 62 | return null !== $this->parameterValueSeparator ? $this->parameterValueSeparator : '='; 63 | } 64 | 65 | /** @return non-empty-string */ 66 | public function getParameterValueDelimiter() 67 | { 68 | return null !== $this->parameterValueDelimiter ? $this->parameterValueDelimiter : '"'; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Syntax/SyntaxBuilder.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | final class SyntaxBuilder 8 | { 9 | /** @var non-empty-string|null */ 10 | private $openingTag; 11 | /** @var non-empty-string|null */ 12 | private $closingTag; 13 | /** @var non-empty-string|null */ 14 | private $closingTagMarker; 15 | /** @var non-empty-string|null */ 16 | private $parameterValueSeparator; 17 | /** @var non-empty-string|null */ 18 | private $parameterValueDelimiter; 19 | 20 | public function __construct() 21 | { 22 | } 23 | 24 | /** @return Syntax */ 25 | public function getSyntax() 26 | { 27 | return new Syntax( 28 | $this->openingTag, 29 | $this->closingTag, 30 | $this->closingTagMarker, 31 | $this->parameterValueSeparator, 32 | $this->parameterValueDelimiter 33 | ); 34 | } 35 | 36 | /** 37 | * @param non-empty-string $tag 38 | * 39 | * @return $this 40 | */ 41 | public function setOpeningTag($tag) 42 | { 43 | $this->openingTag = $tag; 44 | 45 | return $this; 46 | } 47 | 48 | /** 49 | * @param non-empty-string $tag 50 | * 51 | * @return $this 52 | */ 53 | public function setClosingTag($tag) 54 | { 55 | $this->closingTag = $tag; 56 | 57 | return $this; 58 | } 59 | 60 | /** 61 | * @param non-empty-string $marker 62 | * 63 | * @return $this 64 | */ 65 | public function setClosingTagMarker($marker) 66 | { 67 | $this->closingTagMarker = $marker; 68 | 69 | return $this; 70 | } 71 | 72 | /** 73 | * @param non-empty-string $separator 74 | * 75 | * @return $this 76 | */ 77 | public function setParameterValueSeparator($separator) 78 | { 79 | $this->parameterValueSeparator = $separator; 80 | 81 | return $this; 82 | } 83 | 84 | /** 85 | * @param non-empty-string $delimiter 86 | * 87 | * @return $this 88 | */ 89 | public function setParameterValueDelimiter($delimiter) 90 | { 91 | $this->parameterValueDelimiter = $delimiter; 92 | 93 | return $this; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Syntax/SyntaxInterface.php: -------------------------------------------------------------------------------- 1 | 6 | */ 7 | interface SyntaxInterface 8 | { 9 | /** @return non-empty-string */ 10 | public function getOpeningTag(); 11 | 12 | /** @return non-empty-string */ 13 | public function getClosingTag(); 14 | 15 | /** @return non-empty-string */ 16 | public function getClosingTagMarker(); 17 | 18 | /** @return non-empty-string */ 19 | public function getParameterValueSeparator(); 20 | 21 | /** @return non-empty-string */ 22 | public function getParameterValueDelimiter(); 23 | } 24 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/src/Utility/RegexBuilderUtility.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | final class RegexBuilderUtility 10 | { 11 | /** @return non-empty-string */ 12 | public static function buildNameRegex() 13 | { 14 | return '[a-zA-Z0-9-_\\*]+'; 15 | } 16 | 17 | /** @return non-empty-string */ 18 | public static function buildShortcodeRegex(SyntaxInterface $syntax) 19 | { 20 | return '~('.self::createShortcodeRegexContent($syntax).')~us'; 21 | } 22 | 23 | /** @return non-empty-string */ 24 | public static function buildSingleShortcodeRegex(SyntaxInterface $syntax) 25 | { 26 | return '~(\A'.self::createShortcodeRegexContent($syntax).'\Z)~us'; 27 | } 28 | 29 | /** @return non-empty-string */ 30 | public static function buildParametersRegex(SyntaxInterface $syntax) 31 | { 32 | $equals = self::quote($syntax->getParameterValueSeparator()); 33 | $string = self::quote($syntax->getParameterValueDelimiter()); 34 | 35 | $space = '\s*'; 36 | // lookahead test for either space or end of string 37 | $empty = '(?=\s|$)'; 38 | // equals sign and alphanumeric value 39 | $simple = $space.$equals.$space.'[^\s]+'; 40 | // equals sign and value without unescaped string delimiters enclosed in them 41 | $complex = $space.$equals.$space.$string.'([^'.$string.'\\\\]*(?:\\\\.[^'.$string.'\\\\]*)*?)'.$string; 42 | 43 | return '~(?:\s*(\w+(?:'.$complex.'|'.$simple.'|'.$empty.')))~us'; 44 | } 45 | 46 | /** @return non-empty-string */ 47 | private static function createShortcodeRegexContent(SyntaxInterface $syntax) 48 | { 49 | $open = self::quote($syntax->getOpeningTag()); 50 | $slash = self::quote($syntax->getClosingTagMarker()); 51 | $close = self::quote($syntax->getClosingTag()); 52 | $equals = self::quote($syntax->getParameterValueSeparator()); 53 | $string = self::quote($syntax->getParameterValueDelimiter()); 54 | 55 | $space = '\s*'; 56 | 57 | // parameter and value separator can have any number of spaces around itself 58 | $equalsSpaced = $space.$equals.$space; 59 | // lookahead test for space, closing tag, self-closing tag or end of string 60 | $empty = '(?=\s|'.$close.'|'.$slash.$space.$close.'|$)'; 61 | // equals sign and alphanumeric value 62 | $simple = '((?:(?!=\s*|'.$close.'|'.$slash.$close.')[^\s])+)'; 63 | // equals sign and value without unescaped string delimiters enclosed in them 64 | $complex = $string.'(?:[^'.$string.'\\\\]*(?:\\\\.[^'.$string.'\\\\]*)*)'.$string; 65 | // complete parameters matching regex 66 | $parameters = '(?(?:\s*(?:\w+(?:'.$equalsSpaced.$complex.'|'.$equalsSpaced.$simple.'|'.$empty.')))*)'; 67 | // BBCode is the part after name that makes it behave like a non-empty parameter value 68 | $bbCode = '(?:'.$equals.$space.'(?'.$complex.'|'.$simple.'))?'; 69 | 70 | // alphanumeric characters and dash 71 | $name = '(?'.static::buildNameRegex().')'; 72 | // non-greedy match for any characters 73 | $content = '(?.*?)'; 74 | 75 | // equal beginning for each variant: open tag, name and parameters 76 | $common = $open.$space.$name.$space.$bbCode.$space.$parameters.$space; 77 | // closing tag variants: just closing tag, self closing tag or content 78 | // and closing block with backreference name validation 79 | $justClosed = $close; 80 | $selfClosed = '(?'.$slash.')'.$space.$close; 81 | $withContent = $close.$content.$open.$space.'(?'.$slash.')'.$space.'(\k)'.$space.$close; 82 | 83 | return '(?:'.$common.'(?:'.$withContent.'|'.$justClosed.'|'.$selfClosed.'))'; 84 | } 85 | 86 | /** 87 | * @param non-empty-string $text 88 | * 89 | * @return non-empty-string 90 | */ 91 | private static function quote($text) 92 | { 93 | /** @var non-empty-string $quoted */ 94 | $quoted = preg_replace('/(.)/us', '\\\\$0', $text); 95 | 96 | return $quoted; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/tests/AbstractTestCase.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | abstract class AbstractTestCase extends TestCase 10 | { 11 | public function willThrowException($exception) 12 | { 13 | version_compare(phpversion(), '7.0.0') > 0 14 | ? $this->expectException($exception) 15 | : $this->setExpectedException($exception); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/tests/EventsTest.php: -------------------------------------------------------------------------------- 1 | 18 | */ 19 | final class EventsTest extends AbstractTestCase 20 | { 21 | public function testRaw() 22 | { 23 | $times = 0; 24 | $handlers = new HandlerContainer(); 25 | $handlers->add('raw', function(ShortcodeInterface $s) { return $s->getContent(); }); 26 | $handlers->add('n', function(ShortcodeInterface $s) use(&$times) { ++$times; return $s->getName(); }); 27 | $handlers->add('c', function(ShortcodeInterface $s) use(&$times) { ++$times; return $s->getContent(); }); 28 | 29 | $events = new EventContainer(); 30 | $events->addListener(Events::FILTER_SHORTCODES, new FilterRawEventHandler(array('raw'))); 31 | 32 | $processor = new Processor(new RegularParser(), $handlers); 33 | $processor = $processor->withEventContainer($events); 34 | 35 | $this->assertSame(' [n] [c]cnt[/c] [/n] ', $processor->process('[raw] [n] [c]cnt[/c] [/n] [/raw]')); 36 | $this->assertSame('x un [n] [c]cnt[/c] [/n] y', $processor->process('x [c]u[n][/c][raw] [n] [c]cnt[/c] [/n] [/raw] y')); 37 | $this->assertEquals(2, $times); 38 | } 39 | 40 | public function testStripContentOutsideShortcodes() 41 | { 42 | $handlers = new HandlerContainer(); 43 | $handlers->add('name', function(ShortcodeInterface $s) { return $s->getName(); }); 44 | $handlers->add('content', function(ShortcodeInterface $s) { return $s->getContent(); }); 45 | $handlers->add('root', function(ProcessedShortcode $s) { return 'root['.$s->getContent().']'; }); 46 | 47 | $events = new EventContainer(); 48 | $events->addListener(Events::REPLACE_SHORTCODES, new ReplaceJoinEventHandler(array('root'))); 49 | 50 | $processor = new Processor(new RegularParser(), $handlers); 51 | $processor = $processor->withEventContainer($events); 52 | 53 | $this->assertSame('a root[name name name] b', $processor->process('a [root]x [name] c[content] [name /] [/content] y[name/][/root] b')); 54 | } 55 | 56 | public function testDefaultApplier() 57 | { 58 | $handlers = new HandlerContainer(); 59 | $handlers->add('name', function(ShortcodeInterface $s) { return $s->getName(); }); 60 | $handlers->add('content', function(ShortcodeInterface $s) { return $s->getContent(); }); 61 | $handlers->add('root', function(ProcessedShortcode $s) { return 'root['.$s->getContent().']'; }); 62 | 63 | $events = new EventContainer(); 64 | $events->addListener(Events::REPLACE_SHORTCODES, function(ReplaceShortcodesEvent $event) { 65 | $event->setResult(array_reduce(array_reverse($event->getReplacements()), function($state, ReplacedShortcode $r) { 66 | $offset = $r->getOffset(); 67 | $length = mb_strlen($r->getText()); 68 | 69 | return mb_substr($state, 0, $offset).$r->getReplacement().mb_substr($state, $offset + $length); 70 | }, $event->getText())); 71 | }); 72 | 73 | $processor = new Processor(new RegularParser(), $handlers); 74 | $processor = $processor->withEventContainer($events); 75 | 76 | $this->assertSame('a root[x name c name y] b', $processor->process('a [root]x [name] c[content] [name /] [/content] y[/root] b')); 77 | } 78 | 79 | public function testExceptionOnHandlerForUnknownEvent() 80 | { 81 | $events = new EventContainer(); 82 | $this->willThrowException('InvalidArgumentException'); 83 | $events->addListener('invalid', function() {}); 84 | } 85 | 86 | public function testInvalidFilterRawShortcodesNames() 87 | { 88 | $this->willThrowException('InvalidArgumentException'); 89 | new FilterRawEventHandler(array(new \stdClass())); 90 | } 91 | 92 | public function testInvalidReplaceJoinNames() 93 | { 94 | $this->willThrowException('InvalidArgumentException'); 95 | new ReplaceJoinEventHandler(array(new \stdClass())); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/tests/FacadeTest.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | final class FacadeTest extends AbstractTestCase 17 | { 18 | public function testFacade() 19 | { 20 | $handlers = new HandlerContainer(); 21 | $handlers 22 | ->add('name', function (ShortcodeInterface $s) { return $s->getName(); }) 23 | ->addAlias('n', 'name'); 24 | 25 | $facade = ShortcodeFacade::create($handlers, new CommonSyntax()); 26 | $facade->addHandler('content', function (ShortcodeInterface $s) { return $s->getContent(); }); 27 | $facade->addHandlerAlias('c', 'content'); 28 | $facade->setParser(new RegexParser()); 29 | 30 | $this->assertSame('n', $facade->process('[n]')); 31 | $this->assertSame('c', $facade->process('[c]c[/c]')); 32 | 33 | $shortcodes = $facade->parse('[b]'); 34 | $this->assertInstanceOf('Thunder\\Shortcode\\Shortcode\\ParsedShortcodeInterface', $shortcodes[0]); 35 | } 36 | 37 | public function testFacadeEvents() 38 | { 39 | $facade = new ShortcodeFacade(); 40 | $facade->addHandler('n', function (ShortcodeInterface $s) { return $s->getName(); }); 41 | $facade->addEventHandler(Events::FILTER_SHORTCODES, new FilterRawEventHandler(array('raw'))); 42 | 43 | $this->assertSame('[raw] [n] [/raw]', $facade->process('[raw] [n] [/raw]')); 44 | } 45 | 46 | public function testSerialization() 47 | { 48 | $facade = new ShortcodeFacade(); 49 | 50 | $s = new Shortcode('c', array(), null); 51 | $this->assertSame('[c /]', $facade->serializeToText($s)); 52 | $this->assertSame('c', $facade->unserializeFromText('[c]')->getName()); 53 | $this->assertSame('[c /]', $facade->serialize($s, 'text')); 54 | $this->assertSame('c', $facade->unserialize('[c]', 'text')->getName()); 55 | 56 | $json = '{"name":"c","parameters":[],"content":null,"bbCode":null}'; 57 | $this->assertSame($json, $facade->serializeToJson($s)); 58 | $this->assertSame('c', $facade->unserializeFromJson($json)->getName()); 59 | $this->assertSame($json, $facade->serialize($s, 'json')); 60 | $this->assertSame('c', $facade->unserialize($json, 'json')->getName()); 61 | 62 | $yaml = <<assertSame($yaml, $facade->serialize($s, 'yaml')); 70 | $this->assertSame('c', $facade->unserialize($yaml, 'yaml')->getName()); 71 | 72 | $xml = << 74 | 75 | 76 | 77 | 78 | 79 | 80 | EOF; 81 | $this->assertSame($xml, $facade->serialize($s, 'xml')); 82 | $this->assertSame('c', $facade->unserialize($xml, 'xml')->getName()); 83 | } 84 | 85 | public function testInvalidSerializationFormatException() 86 | { 87 | $this->willThrowException('InvalidArgumentException'); 88 | $facade = new ShortcodeFacade(); 89 | $facade->serialize(new Shortcode('name', array(), null), 'invalid'); 90 | } 91 | 92 | public function testInvalidUnserializationFormatException() 93 | { 94 | $this->willThrowException('InvalidArgumentException'); 95 | $facade = new ShortcodeFacade(); 96 | $facade->unserialize('[c]', 'invalid'); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/tests/Fake/ReverseShortcode.php: -------------------------------------------------------------------------------- 1 | getContent()); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/tests/HandlerContainerTest.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | final class HandlerContainerTest extends AbstractTestCase 12 | { 13 | public function testExceptionOnDuplicateHandler() 14 | { 15 | $handlers = new HandlerContainer(); 16 | $handlers->add('name', function () {}); 17 | $this->willThrowException('RuntimeException'); 18 | $handlers->add('name', function () {}); 19 | } 20 | 21 | public function testRemove() 22 | { 23 | $handlers = new HandlerContainer(); 24 | static::assertFalse($handlers->has('code')); 25 | $handlers->add('code', function(ShortcodeInterface $s) {}); 26 | static::assertTrue($handlers->has('code')); 27 | $handlers->remove('code'); 28 | static::assertFalse($handlers->has('code')); 29 | } 30 | 31 | public function testRemoveException() 32 | { 33 | $handlers = new HandlerContainer(); 34 | $this->willThrowException('RuntimeException'); 35 | $handlers->remove('code'); 36 | } 37 | 38 | public function testNames() 39 | { 40 | $handlers = new HandlerContainer(); 41 | static::assertEmpty($handlers->getNames()); 42 | $handlers->add('code', function(ShortcodeInterface $s) {}); 43 | static::assertSame(array('code'), $handlers->getNames()); 44 | $handlers->addAlias('c', 'code'); 45 | static::assertSame(array('code', 'c'), $handlers->getNames()); 46 | } 47 | 48 | public function testHandlerContainer() 49 | { 50 | $x = function () {}; 51 | 52 | $handler = new HandlerContainer(); 53 | $handler->add('x', $x); 54 | $handler->addAlias('y', 'x'); 55 | 56 | static::assertSame($x, $handler->get('x')); 57 | } 58 | 59 | public function testInvalidHandler() 60 | { 61 | $handlers = new HandlerContainer(); 62 | $this->willThrowException('RuntimeException'); 63 | $handlers->add('invalid', new \stdClass()); 64 | } 65 | 66 | public function testDefaultHandler() 67 | { 68 | $handlers = new HandlerContainer(); 69 | static::assertNull($handlers->get('missing')); 70 | 71 | $handlers->setDefault(function () {}); 72 | static::assertNotNull($handlers->get('missing')); 73 | } 74 | 75 | public function testExceptionIfAliasingNonExistentHandler() 76 | { 77 | $handlers = new HandlerContainer(); 78 | $this->willThrowException('RuntimeException'); 79 | $handlers->addAlias('m', 'missing'); 80 | } 81 | 82 | public function testImmutableHandlerContainer() 83 | { 84 | $handlers = new HandlerContainer(); 85 | $handlers->add('code', function () {}); 86 | $handlers->addAlias('c', 'code'); 87 | $imHandlers = new ImmutableHandlerContainer($handlers); 88 | $handlers->add('not', function() {}); 89 | 90 | static::assertNull($imHandlers->get('missing')); 91 | static::assertNotNull($imHandlers->get('code')); 92 | static::assertNotNull($imHandlers->get('c')); 93 | static::assertNull($imHandlers->get('not')); 94 | 95 | $defaultHandlers = new HandlerContainer(); 96 | $defaultHandlers->setDefault(function () {}); 97 | $imDefaultHandlers = new ImmutableHandlerContainer($defaultHandlers); 98 | static::assertNotNull($imDefaultHandlers->get('missing')); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /vendor/thunderer/shortcode/tests/SyntaxTest.php: -------------------------------------------------------------------------------- 1 | 11 | */ 12 | final class SyntaxTest extends AbstractTestCase 13 | { 14 | /** 15 | * @dataProvider provideSyntaxes 16 | */ 17 | public function testSyntax(SyntaxInterface $syntax, $open, $close, $slash, $parameter, $value) 18 | { 19 | static::assertSame($open, $syntax->getOpeningTag()); 20 | static::assertSame($close, $syntax->getClosingTag()); 21 | static::assertSame($slash, $syntax->getClosingTagMarker()); 22 | static::assertSame($parameter, $syntax->getParameterValueSeparator()); 23 | static::assertSame($value, $syntax->getParameterValueDelimiter()); 24 | } 25 | 26 | public static function provideSyntaxes() 27 | { 28 | return array( 29 | array(new Syntax(), '[', ']', '/', '=', '"'), 30 | array(new Syntax('[[', ']]', '//', '==', '""'), '[[', ']]', '//', '==', '""'), 31 | array(new CommonSyntax(), '[', ']', '/', '=', '"'), 32 | ); 33 | } 34 | 35 | /** 36 | * Note: do not merge this test with data provider above, code coverage 37 | * does not understand this and marks builder class as untested. 38 | */ 39 | public function testBuilder() 40 | { 41 | $builder = new SyntaxBuilder(); 42 | $this->testSyntax($builder->getSyntax(), '[', ']', '/', '=', '"'); 43 | 44 | $builder = new SyntaxBuilder(); 45 | $doubleBuiltSyntax = $builder 46 | ->setOpeningTag('[[') 47 | ->setClosingTag(']]') 48 | ->setClosingTagMarker('//') 49 | ->setParameterValueSeparator('==') 50 | ->setParameterValueDelimiter('""') 51 | ->getSyntax(); 52 | $this->testSyntax($doubleBuiltSyntax, '[[', ']]', '//', '==', '""'); 53 | } 54 | } 55 | --------------------------------------------------------------------------------