[ ['autoload', 100001], ['onPluginsInitialized', 10] ], 'registerNextGenEditorPlugin' => [ ['registerNextGenEditorPlugin', 0], ['registerNextGenEditorPluginShortcodes', 0], ], 'registerEditorProPlugin' => [ ['registerEditorProPlugin', 0], ], 'onEditorProShortcodeRegister' => [ ['onEditorProShortcodeRegister', 0], ] ]; } /** * [onPluginsInitialized:100000] Composer autoload. * * @return ClassLoader */ public function autoload() { return require __DIR__ . '/vendor/autoload.php'; } /** * Initialize configuration */ public function onPluginsInitialized() { $this->config = $this->grav['config']; $forceFrontend = (bool)($this->grav['shortcode_force_frontend'] ?? false); // don't continue if this is admin and plugin is disabled for admin if (!$forceFrontend && !$this->config->get('plugins.shortcode-core.active_admin') && $this->isAdmin()) { return; } $this->enable([ 'onThemeInitialized' => ['onThemeInitialized', 0], 'onMarkdownInitialized' => ['onMarkdownInitialized', 0], 'onShortcodeHandlers' => ['onShortcodeHandlers', 0], 'onPageContentRaw' => ['onPageContentRaw', 0], 'onPageContentProcessed' => ['onPageContentProcessed', -10], 'onPageContent' => ['onPageContent', 0], 'onTwigInitialized' => ['onTwigInitialized', 0], 'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0], ]); $this->grav['shortcode'] = $this->shortcodes = new ShortcodeManager(); } /** * Theme initialization is best place to fire onShortcodeHandler event * in order to support both plugins and themes */ public function onThemeInitialized() { $this->grav->fireEvent('onShortcodeHandlers'); } /** * Handle the markdown Initialized event by setting up shortcode block tags * * @param Event $event the event containing the markdown parser */ public function onMarkdownInitialized(Event $event) { $this->shortcodes->setupMarkdown($event['markdown']); } /** * Process shortcodes before Grav's processing * * @param Event $e */ public function onPageContentRaw(Event $e) { $this->processShortcodes($e['page'], 'processRawContent'); } /** * Process shortcodes after Grav's processing, but before caching * * @param Event $e */ public function onPageContentProcessed(Event $e) { $this->processShortcodes($e['page'], 'processContent'); } /** * @param PageInterface $page * @param string $type */ protected function processShortcodes(PageInterface $page, $type = 'processContent') { $meta = []; $this->shortcodes->resetObjects(); // clear shortcodes that may have been processed in this execution thread before $config = $this->mergeConfig($page); // Don't run in admin pages other than content $admin_pages_only = $config['admin_pages_only'] ?? true; if ($admin_pages_only && $this->isAdmin() && !Utils::startsWith($page->filePath(), $this->grav['locator']->findResource('page://'))) { return; } $this->active = $config->get('active', true); // if the plugin is not active (either global or on page) exit if (!$this->active) { return; } // process the content for shortcodes $page->setRawContent($this->shortcodes->$type($page, $config)); // if objects found set them as page content meta $shortcode_objects = $this->shortcodes->getObjects(); if (!empty($shortcode_objects)) { $meta['shortcode'] = $shortcode_objects; } // if assets founds set them as page content meta $shortcode_assets = $this->shortcodes->getAssets(); if (!empty($shortcode_assets)) { $meta['shortcodeAssets'] = $shortcode_assets; } // if we have meta set, let's add it to the content meta if (!empty($meta)) { $page->addContentMeta('shortcodeMeta', $meta); } } /** * @param PageInterface $page * @return \Grav\Common\Data\Data */ protected function getConfig(PageInterface $page) { $config = $this->mergeConfig($page); $this->active = false; // Don't run in admin pages other than content $admin_pages_only = isset($config['admin_pages_only']) ? $config['admin_pages_only'] : true; if ($admin_pages_only && $this->isAdmin() && !Utils::startsWith($page->filePath(), $this->grav['locator']->findResource('page://'))) { } else { $this->active = $config->get('active', true); } return $config; } /** * Handle the assets that might be associated with this page */ public function onPageContent(Event $event) { if (!$this->active) { return; } $page = $event['page']; // get the meta and check for assets $page_meta = $page->getContentMeta('shortcodeMeta'); if (is_array($page_meta)) { if (isset($page_meta['shortcodeAssets'])) { $page_assets = (array) $page_meta['shortcodeAssets']; /** @var Assets $assets */ $assets = $this->grav['assets']; // if we actually have data now, add it to asset manager foreach ($page_assets as $type => $asset) { foreach ($asset as $item) { $method = 'add'.ucfirst($type); if (is_array($item)) { $assets->$method($item[0], $item[1]); } else { $assets->$method($item); } } } } } } /** * Event that handles registering handler for shortcodes */ public function onShortcodeHandlers() { $include_default_shortcodes = $this->config->get('plugins.shortcode-core.include_default_shortcodes', true); if ($include_default_shortcodes) { $this->shortcodes->registerAllShortcodes(__DIR__ . '/classes/shortcodes', ['ignore' => ['Shortcode', 'ShortcodeObject']]); } // Add custom shortcodes directory if provided $custom_shortcodes = $this->config->get('plugins.shortcode-core.custom_shortcodes'); if (isset($custom_shortcodes)) { $this->shortcodes->registerAllShortcodes(GRAV_ROOT . $custom_shortcodes); } // Register config-defined shortcodes (the "shortcode builder" — no PHP class needed) $this->registerConfigShortcodes(); } /** * Register shortcodes defined in config, each backed by a Twig template or * an inline Twig output string — the "shortcode builder". Lets a site add * its own shortcodes without writing a PHP class or a plugin. * * Config (`plugins.shortcode-core.shortcodes`) is a list of definitions, * each `{ name, template? | output? }`. A keyed map (tag => definition) is * also accepted. The template/output is trusted code (it lives on disk or * in admin-authored config, not in page content), and the author-supplied * `params`/`content` it receives are auto-escaped by Twig on output — so * this stays inside the Grav 2 content-security model. */ protected function registerConfigShortcodes(): void { $defined = $this->config->get('plugins.shortcode-core.shortcodes'); if (!is_array($defined)) { return; } $twig = $this->grav['twig']; foreach ($defined as $key => $def) { if (!is_array($def)) { continue; } // `name` field (list form) or the map key (keyed form). $name = trim((string) ($def['name'] ?? (is_string($key) ? $key : ''))); $template = trim((string) ($def['template'] ?? '')); $output = (string) ($def['output'] ?? ''); if ($name === '' || ($template === '' && $output === '')) { continue; } $this->shortcodes->getHandlers()->add($name, static function (ShortcodeInterface $sc) use ($twig, $template, $output) { $vars = [ 'params' => $sc->getParameters(), 'content' => $sc->getContent(), 'shortcode' => $sc, ]; // A template file wins over inline output when both are set. if ($template !== '') { return $twig->processTemplate($template, $vars); } return $twig->processString($output, $vars); }); } } /** * Add a twig filter for processing shortcodes in templates */ public function onTwigInitialized() { $this->grav['twig']->twig()->addFilter(new TwigFilter('shortcodes', [$this->shortcodes, 'processShortcodes'])); $this->grav['twig']->twig_vars['shortcode'] = new ShortcodeTwigVar(); } public function onTwigTemplatePaths() { $this->grav['twig']->twig_paths[] = __DIR__ . '/templates'; } public function registerNextGenEditorPlugin($event) { $config = $this->config->get('plugins.shortcode-core.nextgen-editor'); $plugins = $event['plugins']; if ($config['env'] !== 'development') { $plugins['css'][] = 'plugin://shortcode-core/nextgen-editor/dist/css/app.css'; $plugins['js'][] = 'plugin://shortcode-core/nextgen-editor/dist/js/app.js'; } else { $plugins['js'][] = 'http://' . $config['dev_host'] . ':' . $config['dev_port'] . '/js/app.js'; } $event['plugins'] = $plugins; return $event; } public function registerNextGenEditorPluginShortcodes($event) { $include_default_shortcodes = $this->config->get('plugins.shortcode-core.include_default_shortcodes', true); if ($include_default_shortcodes) { $plugins = $event['plugins']; $plugins['js'][] = 'plugin://shortcode-core/nextgen-editor/shortcodes/shortcode-core.js'; $plugins['js'][] = 'plugin://shortcode-core/nextgen-editor/shortcodes/align/align.js'; $plugins['js'][] = 'plugin://shortcode-core/nextgen-editor/shortcodes/color/color.js'; $plugins['js'][] = 'plugin://shortcode-core/nextgen-editor/shortcodes/columns/columns.js'; $plugins['css'][] = 'plugin://shortcode-core/nextgen-editor/shortcodes/details/details.css'; $plugins['js'][] = 'plugin://shortcode-core/nextgen-editor/shortcodes/details/details.js'; $plugins['js'][] = 'plugin://shortcode-core/nextgen-editor/shortcodes/div/div.js'; $plugins['js'][] = 'plugin://shortcode-core/nextgen-editor/shortcodes/figure/figure.js'; $plugins['js'][] = 'plugin://shortcode-core/nextgen-editor/shortcodes/fontawesome/fontawesome.js'; $plugins['css'][] = 'plugin://shortcode-core/nextgen-editor/shortcodes/headers/headers.css'; $plugins['js'][] = 'plugin://shortcode-core/nextgen-editor/shortcodes/headers/headers.js'; $plugins['js'][] = 'plugin://shortcode-core/nextgen-editor/shortcodes/language/language.js'; $plugins['js'][] = 'plugin://shortcode-core/nextgen-editor/shortcodes/lorem/lorem.js'; $plugins['css'][] = 'plugin://shortcode-core/nextgen-editor/shortcodes/mark/mark.css'; $plugins['js'][] = 'plugin://shortcode-core/nextgen-editor/shortcodes/mark/mark.js'; $plugins['css'][] = 'plugin://shortcode-core/nextgen-editor/shortcodes/notice/notice.css'; $plugins['js'][] = 'plugin://shortcode-core/nextgen-editor/shortcodes/notice/notice.js'; $plugins['js'][] = 'plugin://shortcode-core/nextgen-editor/shortcodes/raw/raw.js'; $plugins['js'][] = 'plugin://shortcode-core/nextgen-editor/shortcodes/safe-email/safe-email.js'; $plugins['js'][] = 'plugin://shortcode-core/nextgen-editor/shortcodes/section/section.js'; $plugins['js'][] = 'plugin://shortcode-core/nextgen-editor/shortcodes/size/size.js'; $plugins['js'][] = 'plugin://shortcode-core/nextgen-editor/shortcodes/span/span.js'; $plugins['js'][] = 'plugin://shortcode-core/nextgen-editor/shortcodes/u/u.js'; $event['plugins'] = $plugins; } return $event; } public function registerEditorProPlugin($event) { $plugins = $event['plugins']; // Add Editor Pro shortcode integration JavaScript $plugins['js'][] = 'plugin://shortcode-core/editor-pro/shortcode-integration.js'; $event['plugins'] = $plugins; return $event; } public function onEditorProShortcodeRegister($event) { error_log('ShortcodeCore: onEditorProShortcodeRegister called'); $shortcodes = $event['shortcodes']; // Register core shortcodes for Editor Pro $coreShortcodes = [ [ 'name' => 'center', 'title' => 'Center Align', 'description' => 'Center align content', 'type' => 'block', 'plugin' => 'shortcode-core', 'category' => 'formatting', 'group' => 'Core Shortcodes', 'icon' => '', 'attributes' => [], 'titleBarAttributes' => [], 'hasContent' => true, 'cssTemplate' => 'text-align: center;' ], [ 'name' => 'left', 'title' => 'Left Align', 'description' => 'Left align content', 'type' => 'block', 'plugin' => 'shortcode-core', 'category' => 'formatting', 'group' => 'Core Shortcodes', 'icon' => '', 'attributes' => [], 'titleBarAttributes' => [], 'hasContent' => true, 'cssTemplate' => 'text-align: left;' ], [ 'name' => 'right', 'title' => 'Right Align', 'description' => 'Right align content', 'type' => 'block', 'plugin' => 'shortcode-core', 'category' => 'formatting', 'group' => 'Core Shortcodes', 'icon' => '', 'attributes' => [], 'titleBarAttributes' => [], 'hasContent' => true, 'cssTemplate' => 'text-align: right;' ], [ 'name' => 'justify', 'title' => 'Justify Align', 'description' => 'Justify align content', 'type' => 'block', 'plugin' => 'shortcode-core', 'category' => 'formatting', 'group' => 'Core Shortcodes', 'icon' => '', 'attributes' => [], 'titleBarAttributes' => [], 'hasContent' => true, 'cssTemplate' => 'text-align: justify;' ], [ 'name' => 'columns', 'title' => 'Columns Layout', 'description' => 'Create multi-column layout with customizable spacing', 'type' => 'block', 'plugin' => 'shortcode-core', 'category' => 'layout', 'group' => 'Core Shortcodes', 'icon' => '', 'attributes' => [ 'count' => [ 'type' => 'number', 'title' => 'Column Count', 'min' => 2, 'max' => 6, 'default' => 2, 'required' => true ], 'width' => [ 'type' => 'text', 'title' => 'Column Width', 'default' => '200px', 'placeholder' => 'e.g., 200px or auto' ], 'gap' => [ 'type' => 'text', 'title' => 'Gap', 'default' => '30px', 'placeholder' => 'e.g., 30px or 1rem' ], 'rule' => [ 'type' => 'text', 'title' => 'Column Rule', 'default' => '', 'placeholder' => 'e.g., 1px solid #ccc' ] ], 'titleBarAttributes' => ['count', 'width'], 'hasContent' => true, 'cssTemplate' => 'columns: {{count}} {{width}}; column-gap: {{gap}}; column-rule: {{rule}};' ], [ 'name' => 'div', 'title' => 'Div element', 'description' => 'Create a custom Div element', 'type' => 'block', 'plugin' => 'shortcode-core', 'category' => 'layout', 'group' => 'Core Shortcodes', 'icon' => '', 'attributes' => [ 'id' => [ 'type' => 'text', 'title' => 'ID', 'default' => null, 'required' => false ], 'class' => [ 'type' => 'text', 'title' => 'Class', 'default' => null, 'placeholder' => 'e.g., font-bold text-blue-500' ], 'style' => [ 'type' => 'text', 'title' => 'Style', 'default' => null, 'placeholder' => 'e.g., color: red; padding: 10px;' ] ], 'titleBarAttributes' => [], 'hasContent' => true, 'cssTemplate' => '' ], [ 'name' => 'span', 'title' => 'Span element', 'description' => 'Create a custom Span element', 'type' => 'inline', 'plugin' => 'shortcode-core', 'category' => 'layout', 'group' => 'Core Shortcodes', 'icon' => '', 'attributes' => [ 'id' => [ 'type' => 'text', 'title' => 'ID', 'default' => null, 'required' => false ], 'class' => [ 'type' => 'text', 'title' => 'Class', 'default' => null, 'placeholder' => 'e.g., font-bold text-blue-500' ], 'style' => [ 'type' => 'text', 'title' => 'Style', 'default' => null, 'placeholder' => 'e.g., color: red; font-weight: bold;' ] ], 'titleBarAttributes' => [], 'hasContent' => true, 'cssTemplate' => '' ], [ 'name' => 'section', 'title' => 'Section Container', 'description' => 'Semantic section with optional styling', 'type' => 'block', 'plugin' => 'shortcode-core', 'category' => 'layout', 'group' => 'Core Shortcodes', 'icon' => '', 'attributes' => [ 'name' => [ 'type' => 'text', 'title' => 'Section Name', 'default' => '', 'placeholder' => 'Optional section identifier' ], 'page' => [ 'type' => 'text', 'title' => 'Page of Content', 'default' => '', 'placeholder' => '/content/my-page', 'required' => false ], ], 'titleBarAttributes' => ['name'], 'hasContent' => true, 'cssTemplate' => null ], [ 'name' => 'notice', 'title' => 'Notice Box', 'description' => 'Create styled notice/alert boxes', 'type' => 'block', 'plugin' => 'shortcode-core', 'category' => 'content', 'group' => 'Core Shortcodes', 'icon' => '', 'attributes' => [ 'type' => [ 'type' => 'select', 'title' => 'Notice Type', 'options' => ['note', 'info', 'warning', 'error'], 'default' => 'note', 'required' => true ] ], 'titleBarAttributes' => ['type'], 'hasContent' => true, 'cssTemplate' => 'padding: 12px 16px; border-radius: 4px; margin: 16px 0; border-left: 4px solid #0ea5e9; background: #f0f9ff; color: #0c4a6e;' ], [ 'name' => 'mark', 'title' => 'Highlight Text', 'description' => 'Highlight text with background color', 'type' => 'inline', 'plugin' => 'shortcode-core', 'category' => 'formatting', 'group' => 'Core Shortcodes', 'icon' => '', 'attributes' => [ 'color' => [ 'type' => 'color', 'title' => 'Highlight Color', 'default' => '#ffff00' ] ], 'titleBarAttributes' => [], 'hasContent' => true, 'cssTemplate' => 'background-color: {{color}}; padding: 1px 2px; border-radius: 2px;' ], [ 'name' => 'fa', 'title' => 'Font Awesome Icon', 'description' => 'Insert Font Awesome icon', 'type' => 'inline', 'plugin' => 'shortcode-core', 'category' => 'media', 'group' => 'Core Shortcodes', 'icon' => '', 'attributes' => [ 'icon' => [ 'type' => 'text', 'title' => 'Icon Name', 'default' => 'heart', 'required' => true, 'placeholder' => 'e.g., heart, star, user' ], 'size' => [ 'type' => 'select', 'title' => 'Size', 'options' => ['', 'xs', 'sm', 'lg', 'xl', '2x', '3x'], 'default' => '' ] ], 'titleBarAttributes' => ['icon'], 'hasContent' => false, 'cssTemplate' => '', 'customRenderer' => 'function(blockData, config) { // Extract icon name from params or content let iconName = ""; // Try to get icon from attributes first if (blockData.attributes && blockData.attributes.icon) { iconName = blockData.attributes.icon; } else if (blockData.params) { const iconMatch = blockData.params.match(/icon\\s*=\\s*["\']([^"\']+)["\']|icon\\s*=\\s*([^\\s\\]]+)/); iconName = iconMatch ? (iconMatch[1] || iconMatch[2]) : ""; } // If no icon in params, check if content is the icon name if (!iconName && blockData.content && !blockData.content.includes(" ") && !blockData.content.includes("<")) { iconName = blockData.content; } if (iconName) { // Create FontAwesome icon HTML const iconClass = iconName.startsWith("fa-") ? iconName : "fa-" + iconName; let sizeClass = ""; // Add size class if specified if (blockData.attributes && blockData.attributes.size) { sizeClass = " fa-" + blockData.attributes.size; } let displayText = ""; // Add any additional content after the icon if it is not the icon name if (blockData.content && blockData.content !== iconName) { displayText += " " + blockData.content; } return displayText; } else { // Fallback to showing the content or tagName return blockData.content || blockData.tagName; } }' ], [ 'name' => 'color', 'title' => 'Text Color', 'description' => 'Apply color to text', 'type' => 'inline', 'plugin' => 'shortcode-core', 'category' => 'formatting', 'group' => 'Core Shortcodes', 'icon' => '', 'attributes' => [ 'color' => [ 'type' => 'color', 'title' => 'Text Color', 'default' => '#000000', 'required' => true ] ], 'titleBarAttributes' => ['color'], 'hasContent' => true, 'cssTemplate' => 'color: {{color}};' ], [ 'name' => 'size', 'title' => 'Text Size', 'description' => 'Change text size', 'type' => 'inline', 'plugin' => 'shortcode-core', 'category' => 'formatting', 'group' => 'Core Shortcodes', 'icon' => '', 'attributes' => [ 'size' => [ 'type' => 'text', 'title' => 'Font Size', 'default' => '16px', 'required' => true, 'placeholder' => 'e.g., 16px, 1.2em, 120%' ] ], 'titleBarAttributes' => ['size'], 'hasContent' => true, 'cssTemplate' => 'font-size: {{size}};' ], [ 'name' => 'u', 'title' => 'Underline', 'description' => 'Underline text', 'type' => 'inline', 'plugin' => 'shortcode-core', 'category' => 'formatting', 'group' => 'Core Shortcodes', 'icon' => '', 'attributes' => [], 'titleBarAttributes' => [], 'hasContent' => true, 'cssTemplate' => 'text-decoration: underline;' ], [ 'name' => 'figure', 'title' => 'Figure', 'description' => 'Figure with optional caption', 'type' => 'block', 'plugin' => 'shortcode-core', 'category' => 'media', 'group' => 'Core Shortcodes', 'icon' => '', 'attributes' => [ 'id' => [ 'type' => 'text', 'title' => 'ID', 'default' => '', 'required' => false ], 'class' => [ 'type' => 'text', 'title' => 'Class', 'default' => '', 'placeholder' => 'e.g., full-width centered' ], 'caption' => [ 'type' => 'text', 'title' => 'Caption', 'default' => '', 'placeholder' => 'Figure caption text' ] ], 'titleBarAttributes' => ['caption'], 'hasContent' => true, 'cssTemplate' => '' ], [ 'name' => 'details', 'title' => 'Details/Summary', 'description' => 'Collapsible content section', 'type' => 'block', 'plugin' => 'shortcode-core', 'category' => 'content', 'group' => 'Core Shortcodes', 'icon' => '', 'attributes' => [ 'summary' => [ 'type' => 'text', 'title' => 'Summary Text', 'default' => 'Click to expand', 'required' => true ], 'class' => [ 'type' => 'text', 'title' => 'Class', 'default' => '', 'placeholder' => 'e.g., accordion-item' ] ], 'titleBarAttributes' => ['summary'], 'hasContent' => true, 'cssTemplate' => '' ], [ 'name' => 'h1', 'title' => 'Heading 1', 'description' => 'Level 1 heading', 'type' => 'block', 'plugin' => 'shortcode-core', 'category' => 'content', 'group' => 'Core Shortcodes', 'icon' => '', 'attributes' => [ 'id' => [ 'type' => 'text', 'title' => 'ID', 'default' => '', 'required' => false ], 'class' => [ 'type' => 'text', 'title' => 'Class', 'default' => '', 'placeholder' => 'e.g., section-title' ] ], 'titleBarAttributes' => [], 'hasContent' => true, 'cssTemplate' => '' ], [ 'name' => 'h2', 'title' => 'Heading 2', 'description' => 'Level 2 heading', 'type' => 'block', 'plugin' => 'shortcode-core', 'category' => 'content', 'group' => 'Core Shortcodes', 'icon' => '', 'attributes' => [ 'id' => [ 'type' => 'text', 'title' => 'ID', 'default' => '', 'required' => false ], 'class' => [ 'type' => 'text', 'title' => 'Class', 'default' => '', 'placeholder' => 'e.g., subsection-title' ] ], 'titleBarAttributes' => [], 'hasContent' => true, 'cssTemplate' => '' ], [ 'name' => 'h3', 'title' => 'Heading 3', 'description' => 'Level 3 heading', 'type' => 'block', 'plugin' => 'shortcode-core', 'category' => 'content', 'group' => 'Core Shortcodes', 'icon' => '', 'attributes' => [ 'id' => [ 'type' => 'text', 'title' => 'ID', 'default' => '', 'required' => false ], 'class' => [ 'type' => 'text', 'title' => 'Class', 'default' => '', 'placeholder' => 'e.g., minor-heading' ] ], 'titleBarAttributes' => [], 'hasContent' => true, 'cssTemplate' => '' ], [ 'name' => 'h4', 'title' => 'Heading 4', 'description' => 'Level 4 heading', 'type' => 'block', 'plugin' => 'shortcode-core', 'category' => 'content', 'group' => 'Core Shortcodes', 'icon' => '', 'attributes' => [ 'id' => [ 'type' => 'text', 'title' => 'ID', 'default' => '', 'required' => false ], 'class' => [ 'type' => 'text', 'title' => 'Class', 'default' => '', 'placeholder' => 'e.g., minor-heading' ] ], 'titleBarAttributes' => [], 'hasContent' => true, 'cssTemplate' => '' ], [ 'name' => 'h5', 'title' => 'Heading 5', 'description' => 'Level 5 heading', 'type' => 'block', 'plugin' => 'shortcode-core', 'category' => 'content', 'group' => 'Core Shortcodes', 'icon' => '', 'attributes' => [ 'id' => [ 'type' => 'text', 'title' => 'ID', 'default' => '', 'required' => false ], 'class' => [ 'type' => 'text', 'title' => 'Class', 'default' => '', 'placeholder' => 'e.g., minor-heading' ] ], 'titleBarAttributes' => [], 'hasContent' => true, 'cssTemplate' => '' ], [ 'name' => 'h6', 'title' => 'Heading 6', 'description' => 'Level 6 heading', 'type' => 'block', 'plugin' => 'shortcode-core', 'category' => 'content', 'group' => 'Core Shortcodes', 'icon' => '', 'attributes' => [ 'id' => [ 'type' => 'text', 'title' => 'ID', 'default' => '', 'required' => false ], 'class' => [ 'type' => 'text', 'title' => 'Class', 'default' => '', 'placeholder' => 'e.g., minor-heading' ] ], 'titleBarAttributes' => [], 'hasContent' => true, 'cssTemplate' => '' ], [ 'name' => 'raw', 'title' => 'Raw Content', 'description' => 'Prevent shortcode processing in content', 'type' => 'block', 'plugin' => 'shortcode-core', 'category' => 'content', 'group' => 'Core Shortcodes', 'icon' => '', 'attributes' => [], 'titleBarAttributes' => [], 'hasContent' => true, 'cssTemplate' => '', 'customRenderer' => 'function(blockData, config) { // Raw content should be displayed as-is without processing return blockData.content || ""; }' ], [ 'name' => 'lang', 'title' => 'Language Filter', 'description' => 'Show content only for specific languages', 'type' => 'block', 'plugin' => 'shortcode-core', 'category' => 'content', 'group' => 'Core Shortcodes', 'icon' => '', 'attributes' => [ 'lang' => [ 'type' => 'text', 'title' => 'Language Codes', 'default' => 'en', 'required' => true, 'placeholder' => 'e.g., en, fr, de (comma-separated)' ] ], 'titleBarAttributes' => ['lang'], 'hasContent' => true, 'cssTemplate' => '' ], [ 'name' => 'lorem', 'title' => 'Lorem Ipsum', 'description' => 'Generate placeholder text', 'type' => 'block', 'plugin' => 'shortcode-core', 'category' => 'content', 'group' => 'Core Shortcodes', 'icon' => '', 'attributes' => [ 'p' => [ 'type' => 'number', 'title' => 'Paragraphs', 'default' => 1, 'min' => 1, 'max' => 10 ], 's' => [ 'type' => 'number', 'title' => 'Sentences', 'default' => 0, 'min' => 0, 'max' => 20 ], 'w' => [ 'type' => 'number', 'title' => 'Words', 'default' => 0, 'min' => 0, 'max' => 100 ] ], 'titleBarAttributes' => ['p', 's', 'w'], 'hasContent' => false, 'cssTemplate' => '', 'customRenderer' => 'function(blockData, config) { const p = blockData.attributes?.p || 1; const s = blockData.attributes?.s || 0; const w = blockData.attributes?.w || 0; let display = "Lorem ipsum"; if (p > 0) display += " - " + p + " paragraph" + (p > 1 ? "s" : ""); if (s > 0) display += ", " + s + " sentence" + (s > 1 ? "s" : ""); if (w > 0) display += ", " + w + " word" + (w > 1 ? "s" : ""); return display; }' ], [ 'name' => 'safe-email', 'title' => 'Safe Email', 'description' => 'Obfuscated email address', 'type' => 'inline', 'plugin' => 'shortcode-core', 'category' => 'content', 'group' => 'Core Shortcodes', 'icon' => '', 'attributes' => [ 'email' => [ 'type' => 'text', 'title' => 'Email Address', 'default' => '', 'required' => true, 'placeholder' => 'user@example.com' ], 'autolink' => [ 'type' => 'checkbox', 'title' => 'Auto Link', 'default' => true ] ], 'titleBarAttributes' => ['email'], 'hasContent' => false, 'cssTemplate' => '', 'customRenderer' => 'function(blockData, config) { const email = blockData.attributes?.email || blockData.content || ""; if (email) { // Display obfuscated version in editor const parts = email.split("@"); if (parts.length === 2) { return parts[0] + "[at]" + parts[1].replace(/\\./g, "[dot]"); } } return email || "safe-email"; }' ] ]; // Add all core shortcodes to the registry foreach ($coreShortcodes as $shortcode) { $shortcodes[] = $shortcode; } error_log('ShortcodeCore: Added ' . count($coreShortcodes) . ' shortcodes, total: ' . count($shortcodes)); $event['shortcodes'] = $shortcodes; return $event; } }