diff --git a/.gitattributes b/.gitattributes index dfe0770..d83533d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,9 @@ -# Auto detect text files and perform LF normalization * text=auto +.github export-ignore +assets-repos export-ignore +tests export-ignore +tools export-ignore +docs/wiki export-ignore +_config.yml export-ignore +.gitignore export-ignore +.gitattributes export-ignore diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 0000000..2a83110 --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,27 @@ +name: Plugin checks +on: + push: + pull_request: +jobs: + syntax: + runs-on: ubuntu-latest + strategy: + matrix: + php: ['8.0', '8.4'] + steps: + - uses: actions/checkout@v4 + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + - name: Check PHP syntax + run: find . -name '*.php' -print0 | xargs -0 -n1 php -l + - uses: actions/setup-node@v4 + with: + node-version: '22' + - name: Check generator JavaScript + run: node --check assets/admin/tools.js + - name: Check release documentation + run: python3 tools/check_release.py + - name: Build installable ZIP + run: python3 tools/package.py --output builder-shortcode-extras.zip diff --git a/README-de.md b/README-de.md new file mode 100644 index 0000000..0d5fc24 --- /dev/null +++ b/README-de.md @@ -0,0 +1,112 @@ +# Builder Shortcode Extras + +**Kleine dynamische Helfer. Schnell eingesetzt.** + +Copyright, Datumsangaben, Links und wiederverwendbare Inhalte ohne eigenes PHP. Mit einer durchsuchbaren Hilfe und einem kompakten Generator direkt in WordPress. + +Version **1.2.0** · WordPress **6.7+** · PHP **8.0+** · GPL-2.0-or-later + +![Builder Shortcode Extras](assets/banner-de-1544x500.png) + +[English](README.md) · [Anleitung & FAQ](docs/Deutsch.md) · [Changelog](docs/CHANGELOG-de.md) · [GitHub Releases](https://github.com/deckerweb/builder-shortcode-extras/releases) · [Support](https://github.com/deckerweb/builder-shortcode-extras/issues) + +## Inhalt + +- [Schnell starten](#schnell-starten) +- [Beispiele](#beispiele) +- [Updates und Library](#updates-und-library) +- [FAQ](#faq) +- [Changelog](#changelog) + +![Shortcode help and generator](assets-github/shortcode-tools-de.png) + +## Schnell starten + +1. Lade das Test-ZIP hoch: Plugins → Installieren → Plugin hochladen. +2. Aktiviere das Plugin und öffne Werkzeuge → Builder Shortcode Extras. +3. Suche einen Helfer oder konfiguriere einen der fünf Anwendungsfälle. +4. Kopiere den Shortcode in einen Shortcode-Block oder ein geeignetes Builder-Feld. +5. Prüfe die tatsächliche Ausgabe auf deiner Testseite. + +Es gibt keine zusätzlichen BSE-Einstellungen. Die Seite ändert keine Inhalte und speichert keine Generatorwerte. + +## Beispiele + +```text +[bse-copyright first="2019"] +[bse-post-date format="Y-m-d"] +[bse-post-modified-date label="Zuletzt aktualisiert: "] +[bse-post-link id="123" text="Mehr erfahren"] +[bse-item-content id="123"] +``` + +## Updates und Library + +Der deckerweb Updater integriert stabile GitHub-Releases in die WordPress-Updateverwaltung. Vorabversionen werden manuell installiert. Die eingebettete deckerweb Library 0.2.0 ergänzt einen kuratierten Plugin-Katalog unter Plugins → Installieren; ihre Optionen liegen unter Einstellungen → deckerweb Library. BSE wird durch dieses Testpaket nicht in den Katalog aufgenommen. + +## FAQ + +### Muss ich das Plugin konfigurieren? + +Nein. Öffne Werkzeuge → Builder Shortcode Extras, um einen Shortcode zu finden oder zu erstellen. Die Seite speichert keine Einstellungen. + +### Wo füge ich einen Shortcode ein? + +In einen WordPress-Shortcode-Block oder ein Builder-Feld mit ausdrücklicher Shortcode-Unterstützung. Beliebige Text-, URL- und HTML-Felder führen Shortcodes nicht automatisch aus. + +### Was kann der Generator erstellen? + +Copyright, Veröffentlichungsdatum, Änderungsdatum, einen Beitrags-/Seiten-Link und Inhaltseinbettungen. Weitere Helfer findest du in der durchsuchbaren Übersicht. + +### Kann ich HTML und Klassen ändern? + +Die meisten Texthelfer unterstützen wrapper, class, before und after. Mehrere Klassen sind möglich. Unsichere oder nicht unterstützte Wrapper werden durch span ersetzt. Diese Version ergänzt keinen Modus ohne Wrapper. + +### Werden Frontend-CSS oder JavaScript geladen? + +Hilfe und Generator laden Ressourcen nur auf ihrer eigenen Admin-Seite. Eingebettete Builder-Vorlagen können Ressourcen ihres Builders laden; das Kommentarformular kann WordPress-Ressourcen benötigen. + +### Warum bleibt eine Inhaltseinbettung leer? + +Prüfe ID, Veröffentlichungsstatus und erforderlichen Builder. Private, unveröffentlichte, gelöschte oder passwortgeschützte Inhalte werden Besuchern ohne erforderlichen Zugriff nicht ausgegeben. Rekursive Referenzen werden gestoppt. + +### Wie funktionieren Updates? + +Der mitgelieferte deckerweb Updater prüft das öffentliche GitHub-Repository auf neuere stabile Releases über die normale WordPress-Updateverwaltung. Er installiert keine Vorabversionen automatisch und aktiviert keine automatischen Updates. Testversionen installierst du manuell. + +## Changelog + +## 1.2.0 — 01.10.2026 + +- **Neu:** Durchsuchbare Shortcode-Hilfe mit Kopierfunktion und lokalem Generator für Copyright, Veröffentlichungsdatum, Änderungsdatum, Beitragslinks und Inhaltseinbettung. +- **Neu:** deckerweb Plugin Library 0.2.0 und gemeinsamer deckerweb GitHub Release Updater v2 eingebunden. +- **Verbessert:** Einheitliche Wrapper-Prüfung, mehrere CSS-Klassen, boolesche Attribute und bereinigte Textausgabe; Shortcode-Namen, Aliase und Ausgabefilter bleiben erhalten. +- **Verbessert:** Native Blöcke werden gerendert; Builder werden passend zum Inhalt gewählt; direkte und indirekte rekursive Einbettung wird verhindert. +- **Verbessert:** Header/Footer nach Brand Admin Schemes, lokaler Changelog-Dialog, englische/deutsche Dokumentation und aktualisierte deutsche Übersetzungen. +- **Verbessert:** Drei originale skalierbare Icon-/Banner-Entwürfe; Variante A mit dem violetten Hintergrund von B ist ausgewählt. +- **Behoben:** Admin-Registrierung, Rückgabe bereinigter Integrationsdaten und versehentlich registrierter Platzhalter korrigiert. +- **Behoben:** Veröffentlichungsdatum berücksichtigt die Beitrags-ID; Slug-Links werden korrekt aufgelöst; ungültige IDs und unbekannte Zählstatus werden behandelt. +- **Behoben:** Nicht definierte Versionskonstanten, Datenbankversionsabfrage sowie Escaping von Benutzerwerten, Ersatztext, Datumsbeschriftungen, Linktext und Tooltip korrigiert. +- **Behoben:** Website-Änderungsdatum verwendet die WordPress-Zeitzone und liefert bei fehlenden veröffentlichten Inhalten kein falsches Datum. +- **Sonstiges:** Benötigt WordPress 6.7+ und PHP 8.0+; PHP 8.0 wird von der Library vorausgesetzt. Die Helfer-Oberfläche lädt keine Frontend-Ressourcen. +- **Sonstiges:** Genesis und Beaver bleiben erhalten; doppelter Astra-Loader, altes Blocks-Menü und sachfremde Werbe-/Core-Admin-Eingriffe entfernt. + +## 1.1.0 — 2025-03-15 + +- **Verbessert:** Plugin wieder in einen nutzbaren Zustand gebracht. +- **Sonstiges:** Verteilung von WordPress.org zu GitHub verlagert und alten DDWlib-Empfehlungsinstaller entfernt. +- **Sonstiges:** Eigenen Übersetzungsloader entfernt; mitgelieferte Übersetzungen beibehalten. + +## 1.0.0 — 2019-09-10 + +- **Neu:** Erste Veröffentlichung mit 25 allgemeinen Helfern und 5 Integrations-Shortcodes. + +## 0.9.0 — 2019-09-09 + +- **Neu:** Erste öffentliche Beta auf GitHub. + +## Über das Plugin + +Direkte Verteilung über GitHub. Keine Shortcode-Gestaltungselemente wie Slider oder Akkordeons. Genesis, Beaver, Elementor, Astra und synchronisierte Vorlagen bleiben optionale Integrationen; eine umfassende Builder-Zertifizierung wird nicht behauptet. + +© 2019–2026 David Decker – DECKERWEB. GPL-2.0-or-later. diff --git a/README.md b/README.md index 40f2222..cb4f745 100644 --- a/README.md +++ b/README.md @@ -1,277 +1,112 @@ -# Builder Shortcode Extras – WordPress Shortcodes Collection to Save You Time +# Builder Shortcode Extras -A collection of totally useful extra Shortcodes to make the life of Site Builders more easy. +**Small dynamic helpers. Ready when you need them.** -![Builder Shortcode Extras plugin banner](https://raw.githubusercontent.com/deckerweb/builder-shortcode-extras/refs/heads/master/assets/banner-1544x500.png) +Copyright, dates, links and reusable content without custom PHP. Includes searchable help and a compact generator directly in WordPress. ---- +Version **1.2.0** · WordPress **6.7+** · PHP **8.0+** · GPL-2.0-or-later -[Support Project](#support-the-project) | [Installation](#installation) | [Updates](#updates) | [Description](#description) | [FAQ](#frequently-asked-questions) | [Screenshots](#screenshots) | [Changelog](#changelog) | [Plugin Scope / Disclaimer](#plugin-scope--disclaimer) +![Builder Shortcode Extras](assets/banner-1544x500.png) ---- +[Deutsch](README-de.md) · [Guide & FAQ](docs/English.md) · [Changelog](docs/CHANGELOG.md) · [GitHub Releases](https://github.com/deckerweb/builder-shortcode-extras/releases) · [Support](https://github.com/deckerweb/builder-shortcode-extras/issues) -## Support the Project +## Contents -If you find this project helpful, consider showing your support by buying me a coffee! Your contribution helps me keep developing and improving this plugin. +- [Quick start](#quick-start) +- [Examples](#examples) +- [Updates and Library](#updates-and-library) +- [FAQ](#faq) +- [Changelog](#changelog) -Enjoying the plugin? Feel free to treat me to a cup of coffee ☕🙂 through the following options: +![Shortcode help and generator](assets-github/shortcode-tools-en.png) -- [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/W7W81BNTZE) -- [Buy me a coffee](https://buymeacoffee.com/daveshine) -- [PayPal donation](https://paypal.me/deckerweb) -- [Join my **newsletter** for DECKERWEB WordPress Plugins](https://eepurl.com/gbAUUn) +## Quick start ---- +1. Upload the test ZIP through Plugins → Add New → Upload Plugin. +2. Activate it and open Tools → Builder Shortcode Extras. +3. Find a helper or configure one of the five use cases. +4. Copy the shortcode into a Shortcode block or a supported builder field. +5. Check the actual output on your test page. -## Installation +There are no additional BSE settings. The page does not edit content or save generator values. -**Quick Install** -1. **Download ZIP:** [**builder-shortcode-extras.zip**](https://github.com/deckerweb/builder-shortcode-extras/releases/latest/download/builder-shortcode-extras.zip) -2. Upload via WordPress Plugins > Add New > Upload Plugin -3. There are no settings whatsover. Just start using the Shortcodes everywhere. -4. Now enjoy displaying special stuff without coding and saving some time ;-) +## Examples +```text +[bse-copyright first="2019"] +[bse-post-date format="Y-m-d"] +[bse-post-modified-date label="Last updated: "] +[bse-post-link id="123" text="Read more"] +[bse-item-content id="123"] +``` -### Requirements +## Updates and Library -* WordPress version 6.7 or higher -* PHP version 7.4 or higher -* MySQL version 5.6 or higher +The deckerweb updater integrates stable GitHub releases with WordPress updates. Install prereleases manually. Embedded deckerweb Library 0.2.0 adds a curated catalog under Plugins → Add New; its preferences live under Settings → deckerweb Library. This test package does not add BSE to the catalog. ---- +## FAQ -## Updates +### Do I need to configure the plugin? -1) Alternative 1: Just download a new [ZIP file](https://github.com/deckerweb/builder-shortcode-extras/releases/latest/download/builder-shortcode-extras.zip) (see above), upload and override existing version. Done. +No. Open Tools → Builder Shortcode Extras to find or generate a shortcode. The page does not save settings. -2) Alternative 2: Use the (free) [**_Git Updater_ plugin**](https://git-updater.com/) and get updates automatically. +### Where do I paste a shortcode? -3) Alternative 3: Upcoming! – In future I will built-in our own deckerweb updater. This is currently being worked on for my plugins. Stay tuned! +Use a WordPress Shortcode block or a builder field that explicitly supports shortcodes. Arbitrary text, URL and HTML fields do not necessarily execute them. ---- +### What can the generator create? -## Description +Copyright, publication date, last-updated date, a post/page link and embedded content. Other helpers remain available in the searchable catalog. -A totally useful collection of helper Shortcodes to make your life as a Site Builder and non-coder way easier and save you time: Shortcodes for Last Updated Date/ Time, Post Counter, Versions, Post Info, Copyright and Footer Info, Users, Reusable Blocks (Gutenberg), Elementor Templates, Genesis Footer and even more. +### Can I change the HTML and classes? -These are Shortcodes you don't get anywhere else. These are smart and really helpful - perfect for Non-Coder Designers, for Non Techies, for Site Builders. Those small parts and strings that make your life easier, now available as Shortcodes. Use them anywhere in WordPress: in Gutenberg Block Editor, in Classic Editor, Widgets, Post Types, Page Builders (Widgets, Elements...) and layout/ hook post types from various (Premium) Themes. +Most text helpers accept wrapper, class, before and after. Multiple classes are supported. Unsafe or unsupported wrapper tags fall back to span. This version does not add a wrapper-free mode. -**This is my personaly story, why this plugin here was born:** When building a site and for example you need the date when a post type item was last updated? You just need this value as a simple string to put in a page builder widget or somewhere else, right? So where do you find the code snippet for it? Or a plugin? And you need it now, easy and fast? -I was there, I know exactly how it feels at 02.00 a.m. in the middle of night. So this Shortcode collection was born. Totally simple. No styles, no scripts. But included CSS classes so I can style it my own - or apply my own classes or those from my theme. And I can change the HTML wrapper tag. Problem solved. It only took 2 minutes: 1 minute of installing and activing this plugin, plus 1 minute of inserting the actual Shortcode. Problem solved. Now I can go to sleep. With peace of mind. And the presentation the next morning for my client will be a success. Oh yeah, I love it! ;-) +### Does it load frontend CSS or JavaScript? +The help and generator load assets only on their own admin page. Embedded builder templates may load their builder’s own assets, and the comment form may rely on WordPress resources. -### ♥️ What the Plugin does? -* Up to 30 useful helper Shortcodes -* Optional: Shortcode for Reusable Blocks - for Gutenberg Block Editor - reuse them everyhwere -* Optional: Shortcode for Elementor Templates - for Elementor free version! -* Optional: Shortcode for complete Footer Creds/ Copyright of Genesis 3.1 or higher (just use without hooks everywhere you can insert a Shortcode!) -* Totally user friendly! -* Use in Gutenberg Block Editor - works fine in these Core Blocks: Shortcode, Paragraph, List, Heading and everywhere else! -* Use in Classic Editor - Visual and Text/ HTML mode, as you were used to -* Use in Widgets - Text/ HTML widget is automatically prepared for Shortcodes with this plugin here! -* Use in Page Builders like *Elementor, Beaver Builder, SiteOrigin Page Builder, WPBakery Page Builder, Visual Composer Website Builder, Oxygen Builder, Brizy Page Builder, Thrive Architect* etc. -* Use in Theme Pro versions with special layout post types: *Astra Custom Layouts, OceanWP Library, GeneratePress Elements, Page Builder Framework Sections, Customify Hooks, Suki Blocks, Hestia Hooks, Genesis Blox, Genesis Simple Hooks* -* Optionally set a custom wrapper (`span`, `p`, `div`, `h`, or any other appropriate HTML5 element, like `article`, `section`, whatever) -* Optionally set a custom CSS class for the wrapper to allow for even better custom styling -* No frontend bloat: no scripts, no styles - nothing! :-) -* Lightweight, efficient -* Developer friendly: clean code, inline documentation, lots of filters available +### Why is embedded content empty? +Check the ID, publication status and required builder. Visitors cannot embed private, draft, trashed or password-protected content without the required access. Recursive references stop with empty output. -### 🚀 Typical Use Cases of this Plugin -1️⃣ **First example:** You want a footer copyright with the current year as dynamic - so you set it once and forget. Our Shortcode `[bse-copyright]` does exactly that. -2️⃣ **Second example:** You want to put a "Last updated" date under each single post or page - use our Shortcode `[bse-post-modified-date]`, set your custom date format, set a custom label and place it into a (footer) widget. Done. -3️⃣ **Third example:** You are using Elementor free, *not* Pro, and want to display one of your templates via a Shortcode? Now you can! (with our Shortcode `[bse-elementor-template]`) -4️⃣ **Fourth example:** You want to display a Reusable Block via a Shortcode? Now you can! (with our Shortcode `[bse-wpblock]`) +### How do updates work? +The bundled deckerweb updater checks this public GitHub repository for newer stable releases through the regular WordPress update system. It does not automatically install prereleases or enable automatic updates. Test releases are installed manually. -### 🍰 Available Shortcodes: -* **`[bse-version]`** -- attributes: wrapper, class, before, after, type, plugin, custom -* **`[bse-copyright]`** -- attributes: wrapper, class, before, after, copyright, first -* **`[bse-site-updated]`** -- attributes: wrapper, class, before, after, type, label_date, date_format, label_time, time_format, tooltip -* **`[bse-site-title]`** -- attributes: wrapper, class, before, after -* **`[bse-site-slogan]`** -- attributes: wrapper, class, before, after -* **`[bse-home-link]`** -- attributes: wrapper, class, before, after, text, target, rel -* **`[bse-loginout]`** -- attributes: wrapper, class, before, after, login_text, logout_text, login_target, logout_target, login_redirect, logout_redirect -* **`[bse-user]`** -- attributes: wrapper, class, before, after, user_id, field, default -* **`[bse-post-count]`** -- attributes: wrapper, class, before, after, post_type, status -* **`[bse-post-date]`** -- attributes: wrapper, class, before, after, post_id, format, label, relative_depth -* **`[bse-post-time]`** -- attributes: wrapper, class, before, after, post_id, format, label -* **`[bse-post-modified-date]`** / **`[bse-item-last-updated]`** -- attributes: wrapper, class, before, after, post_id, format, label, relative_depth -* **`[bse-post-modified-time]`** -- attributes: wrapper, class, before, after, post_id, format, label -* **`[bse-post-author]`** -- attributes: wrapper, class, before, after -* **`[bse-post-author-link]`** -- attributes: wrapper, class, before, after, target, rel -* **`[bse-post-author-posts-link]`** -- attributes: wrapper, class, before, after, target, rel -* **`[bse-post-tags]`** -- attributes: wrapper, class, before, after, sep -* **`[bse-post-categories]`** -- attributes: wrapper, class, before, after, sep -* **`[bse-post-terms]`** -- attributes: wrapper, class, before, after, sep, taxonomy -* **`[bse-post-edit]`** (Post Edit Link) -- attributes: wrapper, class, before, after, label -* **`[bse-post-link]`** -- attributes: wrapper, class, before, after, id, slug, post_type, privacy, text, target, rel -* **`[bse-item-content]`** -- attributes: id, css -* **`[bse-nav-menu]`** -- attributes: wrapper, class, before, after, menu, container, container_class, container_id, menu_class, menu_id, fallback_cb, item_before, item_after, link_before, link_after, depth, walker, theme_location, items_wrap, item_spacing -* **`[bse-comment-form]`** -- attributes: wrapper, class, before, after, post_id, id_form, class_form, title_reply, title_reply_to, cancel_reply_link, label_submit +## Changelog +## 1.2.0 — 2026-10-01 -### 🍰 Available Integrations - Shortcodes: -* *Gutenberg Block Editor, Reusable Blocks:* **`[bse-wpblock]`** -- attributes: id -* *Elementor free version:* **`[bse-elementor-template]`** -- attributes: id, css -* *Astra Custom Layouts (via Astra Pro):* **`[bse-astra-layout]`** -- attributes: id -* *Genesis Framework v3.1.0 or higher:* **`[bse-genesis-footer]`** -- attributes: wrapper, class -* *Genesis Framework:* **`[bse-genesis-breadcrumbs]`** -- attributes: wrapper, class -* **Note:** All these integrations are optional, only if the supported plugin or theme is installed and currently active. For the Block Editor there is smart logic in place recognizing popular "disable" plugins like "Classic Editor", "Disable Gutenberg" amongst others. +- **New:** Searchable shortcode help with copy buttons and a local generator for copyright, publication date, modification date, post links and embedded content. +- **New:** Embedded deckerweb Plugin Library 0.2.0 and shared deckerweb GitHub Release Updater v2. +- **Improved:** Unified wrapper validation, multiple CSS classes, boolean attributes and escaped text boundaries; existing shortcode names, aliases and output filters remain. +- **Improved:** Native block rendering, builder-specific routing and guarded content embedding with direct/indirect recursion protection. +- **Improved:** Brand Admin Schemes-style header/footer, local changelog dialog, English/German documentation and German/formal German translations. +- **Improved:** Three original scalable icon/banner concepts; selected concept A uses the purple background from concept B. +- **Fixed:** Corrected admin registration policy, persistent integration normalization and placeholder integration registration. +- **Fixed:** Publication dates honor explicit post IDs; slug links resolve correctly; missing IDs and unknown post-count statuses are handled. +- **Fixed:** Undefined version constants, database version lookup, user/fallback escaping, date labels, link text and tooltip escaping. +- **Fixed:** Site-updated dates use the WordPress timezone and return no false date when no published item exists. +- **Misc:** Requires WordPress 6.7+ and PHP 8.0+; the library requires PHP 8.0. No frontend assets are added by the helper UI. +- **Misc:** Retains Genesis and Beaver compatibility, removes the duplicate Astra loader, legacy Blocks menu and unrelated promotion/Core admin overrides. ---- +## 1.1.0 — 2025-03-15 -## Frequently Asked Questions +- **Improved:** Restored the plugin to a usable state. +- **Misc:** Moved distribution away from WordPress.org and removed the old DDWlib recommendation installer. +- **Misc:** Removed the custom translation loader; retained packaged translations. +## 1.0.0 — 2019-09-10 -### How does styling work? -The plugin itself loads NO styles or scripts for the Shortcodes! It only adds very minimal markup by default (HTML span tag), with one specific class for the Shortcode. It's **completely up to you** to style these default classes (if you want) or add your own. -- In most cases the Shortcodes put out only simple strings - so, when used within regular text paragraphs you won't see any difference compared to the text around. But, if you need some special styling you can always add it and tweak your styles. +- **New:** Initial plugin release with 25 general helpers and 5 integration shortcodes. +## 0.9.0 — 2019-09-09 +- **New:** First public beta on GitHub. -### What does the Gutenberg Block Editor Shortcode do? -It can display any Reusable Block from WordPress Core Reusable Blocks (post type: `wp_block`) via a Shortcode. Just copy the Shortcode for a certain Reusable Block from the post type list table - look for the "Blocks" left-hand admin menu. This will make the Reusable Blocks way more useful and you can use them everyhwere - without coding! +## About +Distributed directly through GitHub. Focused on dynamic helpers rather than styled sliders or accordions. Genesis, Beaver, Elementor, Astra and synced patterns remain optional integrations; broad builder certification is not claimed. - -### What does the Astra Layout Shortcode do? -It can display any Astra Custom Layout via a Shortcode. Just copy the Shortcode for a certain Astra Custom Layout from the post type list table - look for the "Custom Layouts" under the left-hand "Appearance" admin menu. This will make those Custom Layouts even more useful and you can use them everyhwere - without coding! This Shortcode feature comes in handy especially for "Hook" type Shortcodes: you don't need to specify a Hook if used via Shortcode. This makes the Astra Custom Layouts more independent from Hooks and gives you even more possibilities. - - - -### What does the Elementor Template Shortcode do? -It displays the a template from Elementor "Saved Templates" (former "My Templates") library via a Shortcode. It is the same thing Elementor Pro does - but our plugin brings this feature to Elementor free version. This will make Elementor free version way more useful as this brings so many new possibilities to work with Templates! - - - -### What are the two Genesis Shortcodes doing? -**Genesis Footer Shortcode** - It takes the whole content block of your Genesis Footer settings from the Customizer - since Genesis 3.1.0 or higher - and displays it wherever you want. That way it becomes totally easy to customize your footer info text even if you want to show it somewhere else than the default location. It is easier than to use hooks or code for that. - -**Genesis Breadcrumbs Shortcode** - It takes the whole content block of Breadcrumbs for a current item/view and displays it wherever you want via Shortcode. This could be useful in some situations. It can make you a bit more independent from hooks and coding work. Please note: At the time being it is not possible to tweak the Breadcrumb arguments via the Shortcode! - - - -### Can I use these Shortcodes in my Page Builder? -Yes, of course! Everywhere a Shortcode is working in your Page Builder, our plugin's Shortcodes will also work. For more info on that you might also have a look in the documentation of your favorite Page Builder or similar plugin about Shortcode support for that tool. - - - -### Does this Plugin work with Gutenberg / WordPress 5.0+ / Block Editor? -Yes, of course! - The plugin is fully compatible with Gutenberg Block Editor: you can use Shortcodes almost everywhere within the Block Editor, not just in the so-called "Shortcode" block but also in Paragraph, List, Heading, whereever you can enter regular text, most likely Shortcodes will also be rendered. And, for our type of Shortcodes this makes total sense, for example to display a "last updated date", a post counter, a version number, some user info field, whatever. Just try it out. - - - -### Does this Plugin work with Classic Editor and even ClassicPress? -Yes, this plugin works with the Classic Editor plugin perfectly fine. - -By default the plugin is also fully compatible with ClassicPress. I will try my best to also have my plugin work perfectly in ClassicPress, the fork of WordPress without Gutenberg. It should already be fully compatible but I will follow all events closely to adjust compat if needed. - - - -### Will this Plugin slow down my site? -Absolutely not. Since the plugin is NOT loading any scripts or styles all is really lightweight. A no brainer to install and use. - - - -### Does the Plugin work with Multisite? -Yes, it works fine in Multisite, you could even activate it Network-wide. You can use the Shortcodes in each sub site like you would in a regular (single) install. - - - -### Will other third-party Plugins be supported? -Only if it makes sense, like the current Elementor Template Shortcode for example. Remember: we don't want to load any scripts or styles. So, if a potential Shortcode fits our philosophy and could be totally useful, then the chance of adding it to this plugin gets very high! ;-) - -If you have any suggestion for such a Shortcode, please feel free to [**open a new GitHub Issue**](https://github.com/deckerweb/builder-shortcode-extras/issues) in our development repository and tell us all about it. Thanks in advance! - ---- - -## Screenshots - -### 1. Use the Shortcodes in Gutenberg Block Editor: Shortcode Block, Paragraph Block, List Block, Heading Block etc. -![Use the Shortcodes in Gutenberg Block Editor: Shortcode Block, Paragraph Block, List Block, Heading Block etc.](https://raw.githubusercontent.com/deckerweb/builder-shortcode-extras/master/assets-repos/wordpress-org/screenshot-1.png) - - -### 2. The Shortcodes from Block Editor rendered on the Frontend -![The Shortcodes from Block Editor rendered on the Frontend](https://raw.githubusercontent.com/deckerweb/builder-shortcode-extras/master/assets-repos/wordpress-org/screenshot-2.png) - - -### 3. Shortcode for each Reusable Block to insert & display anywhere (especially outside of the Block Editor...) -![Shortcode for each Reusable Block to insert & display anywhere (especially outside of the Block Editor...)](https://raw.githubusercontent.com/deckerweb/builder-shortcode-extras/master/assets-repos/wordpress-org/screenshot-3.png) - - -### 4. Use the Shortcodes in Classic Editor as well: Visual Mode or Text Mode (HTML) - doesn't matter, will work -![Use the Shortcodes in Classic Editor as well: Visual Mode or Text Mode (HTML) - doesn't matter, will work](https://raw.githubusercontent.com/deckerweb/builder-shortcode-extras/master/assets-repos/wordpress-org/screenshot-4.png) - - -### 5. Insert a Shortcode into Text Widget or Custom HTML Widget -![Insert a Shortcode into Text Widget or Custom HTML Widget](https://raw.githubusercontent.com/deckerweb/builder-shortcode-extras/master/assets-repos/wordpress-org/screenshot-5.png) - - -### 6. For Elementor Free Version: Shortcode for each Saved Template! -![For Elementor Free Version: Shortcode for each Saved Template!](https://raw.githubusercontent.com/deckerweb/builder-shortcode-extras/master/assets-repos/wordpress-org/screenshot-6.png) - - -### 7. Insert a Shortcode into Elementor Page Builder: Shortcode Widget -![Insert a Shortcode into Elementor Page Builder: Shortcode Widget](https://raw.githubusercontent.com/deckerweb/builder-shortcode-extras/master/assets-repos/wordpress-org/screenshot-7.png) - - -### 8. Insert a Shortcode into Elementor Text Editor Widget -![Insert a Shortcode into Elementor Text Editor Widget](https://raw.githubusercontent.com/deckerweb/builder-shortcode-extras/master/assets-repos/wordpress-org/screenshot-8.png) - - -### 9. Insert a Shortcode into Elementor Heading widget (will be rendered fully on the frontend, of course!) -![Insert a Shortcode into Elementor Heading widget (will be rendered fully on the frontend, of course!)](https://raw.githubusercontent.com/deckerweb/builder-shortcode-extras/master/assets-repos/wordpress-org/screenshot-9.png) - - -### 10. For Astra Pro: Shortcode for each Astra Custom Layout - great for reusage -![For Astra Pro: Shortcode for each Astra Custom Layout - great for reusage](https://raw.githubusercontent.com/deckerweb/builder-shortcode-extras/master/assets-repos/wordpress-org/screenshot-10.png) - ---- - -## Changelog - -### 🎉 v1.2.0 – 2025-04-?? -* Improved: Better compatibility for [_Git Updater_](https://git-updater.com/) integration -* Change: Re-added translations loader as translations display is otherwise not optimal for non-.org hosted plugins -* Update: `.pot` file, plus packaged German translations, now including new `l10n.php` files! - -### 🎉 v1.1.0 – 2025-3-15 -* Bring back the plugin to a workable state -* Removed all wordpress.org stuff -* Removed the _DDWlib Plugin Installer Recommendations_ library got removed, it is no longer wanted/supported anyways -* Removed own translations loader; WordPress now does it all itself; the packaged translations remain :) -* **Note:** No longer in the .org plugin repo available – thanks to Matt... (I've taken it out myself as I have no longer interest in WordPress.org repo strategy) – just install it yourself via ZIP file, see above under "Installation" - - -### 🎉 1.0.0 - 2019-09-10 -* *Plugin launch. Everything's new!* -* New: 25 general Shortcodes -* New: 5 Shortcodes for Integrations - - -### ⚡ 0.9.0 - 2019-09-09 -* New: Beta release of the plugin on [its public GitHub repository](https://github.com/deckerweb/builder-shortcode-extras) - - -## ☺️ Donate -Enjoy using *Builder Shortcode Extras*? **[Please consider making a donation](https://www.paypal.me/deckerweb)** - every donation helps to support the project's continued development, maintenance and support. -**Thank you very much in advance for your support!** - ---- - -## Plugin Scope / Disclaimer - -This plugin comes as is. - -_Disclaimer 1:_ So far I will support the plugin for breaking errors to keep it working. Otherwise support will be very limited. Also, it will NEVER be released to WordPress.org Plugin Repository for a lot of reasons (ah, thanks, Matt!). - -_Disclaimer 2:_ All of the above might change. I do all this stuff only in my spare time. - -_Most of all:_ Have fun building great sites!!! ;-) - ---- - -Icon used in promo graphics: [© Tabler Icons by Paweł Kuna](https://tabler.io/icons) - -Readme & Plugin Copyright: © 2019–2025, David Decker – DECKERWEB.de \ No newline at end of file +© 2019–2026 David Decker – DECKERWEB. GPL-2.0-or-later. diff --git a/assets-github/banner.png b/assets-github/banner.png new file mode 100644 index 0000000..7fe6082 Binary files /dev/null and b/assets-github/banner.png differ diff --git a/assets-github/banner.svg b/assets-github/banner.svg new file mode 100644 index 0000000..900db41 --- /dev/null +++ b/assets-github/banner.svg @@ -0,0 +1 @@ +DECKERWEBBuilder Shortcode ExtrasSmall dynamic helpers.Ready when you need them. \ No newline at end of file diff --git a/assets-github/shortcode-tools-de.png b/assets-github/shortcode-tools-de.png new file mode 100644 index 0000000..0e156ba Binary files /dev/null and b/assets-github/shortcode-tools-de.png differ diff --git a/assets-github/shortcode-tools-en.png b/assets-github/shortcode-tools-en.png new file mode 100644 index 0000000..05b49a9 Binary files /dev/null and b/assets-github/shortcode-tools-en.png differ diff --git a/assets-repos/github-com/bse-banner.png b/assets-repos/github-com/bse-banner.png index 7b5f53d..7fe6082 100644 Binary files a/assets-repos/github-com/bse-banner.png and b/assets-repos/github-com/bse-banner.png differ diff --git a/assets-repos/wordpress-org/banner-1544x500.png b/assets-repos/wordpress-org/banner-1544x500.png index 9767fb5..c1e21a6 100644 Binary files a/assets-repos/wordpress-org/banner-1544x500.png and b/assets-repos/wordpress-org/banner-1544x500.png differ diff --git a/assets-repos/wordpress-org/banner-772x250.png b/assets-repos/wordpress-org/banner-772x250.png index 0259e39..50c242a 100644 Binary files a/assets-repos/wordpress-org/banner-772x250.png and b/assets-repos/wordpress-org/banner-772x250.png differ diff --git a/assets-repos/wordpress-org/icon-128x128.png b/assets-repos/wordpress-org/icon-128x128.png index 0b48d80..22d9d1e 100644 Binary files a/assets-repos/wordpress-org/icon-128x128.png and b/assets-repos/wordpress-org/icon-128x128.png differ diff --git a/assets-repos/wordpress-org/icon-256x256.png b/assets-repos/wordpress-org/icon-256x256.png index 480690b..8bf22d6 100644 Binary files a/assets-repos/wordpress-org/icon-256x256.png and b/assets-repos/wordpress-org/icon-256x256.png differ diff --git a/assets/admin/tools.css b/assets/admin/tools.css new file mode 100644 index 0000000..ea06f25 --- /dev/null +++ b/assets/admin/tools.css @@ -0,0 +1,16 @@ +.bse-root{max-width:1280px;--bse-accent:#2563eb;--bse-border:#dcdcde} +.bse-root [hidden]{display:none!important} +.bse-page-heading{display:flex;align-items:center;gap:16px;margin:26px 0 12px} +.bse-page-heading img{border-radius:14px}.bse-page-heading h1{padding:0;font-weight:650}.bse-page-heading p{margin:7px 0 0;color:#50575e;font-size:14px} +.bse-intro{max-width:850px;margin-bottom:24px}.bse-layout{display:grid;grid-template-columns:minmax(280px,350px) minmax(0,1fr);gap:24px;align-items:start} +.bse-panel{background:#fff;border:1px solid var(--bse-border);border-radius:12px;padding:24px}.bse-panel h2{margin:0 0 20px;font-size:20px} +.bse-panel label{display:block;font-weight:600;margin:0 0 7px}.bse-panel select,.bse-panel input,.bse-panel textarea{width:100%;max-width:none;min-height:38px}.bse-panel textarea{padding:10px;font-family:monospace;overflow-wrap:anywhere} +.bse-field{margin-bottom:16px}#bse-case-description{color:#50575e;margin:16px 0 22px}#bse-generated{margin-bottom:12px}.bse-error{color:#b32d2e} +#bse-catalog{max-height:70vh;overflow:auto;padding:4px 8px 4px 2px;margin-top:20px}.bse-group h3{margin:18px 0 12px;font-size:15px;color:#50575e}.bse-group:first-child h3{margin-top:0}.bse-card{border:1px solid #e5e7eb;border-radius:9px;padding:16px;margin-bottom:12px}.bse-card h4{font-size:15px;margin:0 0 8px}.bse-card p{margin:8px 0 12px}.bse-card code{word-break:break-word;background:#f4f6fa;padding:7px;border-radius:5px}.bse-example{display:flex;align-items:center;gap:12px;justify-content:space-between}.bse-example code{flex:1}.bse-configure{display:inline-block;margin:12px 0}.bse-card details{margin-top:12px}.bse-card summary{cursor:pointer}.bse-badge{display:inline-block;background:#eef2ff;border-radius:5px;padding:3px 7px;color:#3730a3;font-size:12px} +.bse-footer{display:flex;justify-content:space-between;gap:20px;margin:28px 0 16px;padding-top:20px;border-top:1px solid var(--bse-border);color:#50575e}.bse-footer p{margin:8px 0}.bse-footer>div:last-child{display:flex;flex-direction:column;gap:8px;text-align:right} +.bse-document-dialog{width:min(760px,90vw);max-height:80vh;padding:0;border:1px solid var(--bse-border);border-radius:12px}.bse-document-dialog::backdrop{background:rgba(0,0,0,.45)}.bse-document-header{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:20px;border-bottom:1px solid var(--bse-border)}.bse-document-header h2{margin:0}.bse-document-content{max-height:55vh;overflow:auto;padding:20px;line-height:1.7}.bse-document-content ul{list-style:disc;padding-left:24px}.bse-document-source{padding:0 20px 12px}.bse-root :focus-visible{outline:2px solid #2271b1;outline-offset:3px} +@media(max-width:900px){.bse-layout{grid-template-columns:1fr}#bse-catalog{max-height:none}.bse-footer{flex-direction:column}.bse-footer>div:last-child{text-align:left}.bse-panel{padding:18px}} + +#bse-copy-status:not(:empty){position:fixed;z-index:1001;bottom:20px;right:24px;max-width:min(420px,calc(100vw - 48px));padding:14px 18px;border:1px solid #8c8f94;border-radius:8px;background:#fff;box-shadow:0 5px 24px #0002;color:#1d2327}#bse-copy-status textarea{width:100%;margin-bottom:8px} + +.bse-guide-table{overflow:auto}.bse-guide-table table{border-collapse:collapse;width:100%;font-size:12px}.bse-guide-table th,.bse-guide-table td{padding:8px;border:1px solid #dcdcde;text-align:left}.bse-document-content pre{white-space:pre-wrap;overflow-wrap:anywhere;background:#f6f7f7;padding:12px;border-radius:6px} diff --git a/assets/admin/tools.js b/assets/admin/tools.js new file mode 100644 index 0000000..d82760a --- /dev/null +++ b/assets/admin/tools.js @@ -0,0 +1,115 @@ +/* Search and generate BSE shortcodes locally. No requests or saved preferences. */ +(() => { + 'use strict'; + const data = window.BSE_TOOLS; + const choice = document.getElementById('bse-use-case'); + if (!data || !choice) return; + const fields = document.getElementById('bse-fields'); + const output = document.getElementById('bse-generated'); + const error = document.getElementById('bse-generator-error'); + const copyGenerated = document.getElementById('bse-copy-generated'); + const status = document.getElementById('bse-copy-status'); + const drafts = new Map(); + let activeTag = ''; + let rendering = false; + function generate() { + if (rendering) return; + const entry = data.catalog[activeTag]; + let message = ''; + const attributes = []; + for (const field of entry.fields) { + const input = document.getElementById(`bse-field-${field.key}`); + const value = field.type === 'text' ? input.value : input.value.trim(); + const required = field.key === 'id'; + input.removeAttribute('aria-invalid'); + if (required && !/^[1-9]\d*$/.test(value)) message = data.i18n.required; + else if (field.type === 'number' && value && !/^[1-9]\d*$/.test(value)) message = data.i18n.invalid; + else if (/["'\[\]\r\n]/.test(value)) message = data.i18n.quote; + if (message) { input.setAttribute('aria-invalid', 'true'); break; } + // Empty fields use the shortcode's own defaults. Select defaults are omitted. + if (value && !(field.type === 'select' && value === field.default)) { + attributes.push(`${field.key}="${value}"`); + } + } + error.textContent = message; + error.hidden = !message; + copyGenerated.disabled = !!message; + output.value = message ? '' : `[${activeTag}${attributes.length ? ' ' + attributes.join(' ') : ''}]`; + } + function remember() { + if (!activeTag) return; + drafts.set(activeTag, Object.fromEntries(data.catalog[activeTag].fields.map(field => [field.key, document.getElementById(`bse-field-${field.key}`).value]))); + } + function renderFields() { + rendering = true; + remember(); + activeTag = choice.value; + const entry = data.catalog[activeTag]; + document.getElementById('bse-case-description').textContent = entry.description; + fields.replaceChildren(); + for (const field of entry.fields) { + const row = document.createElement('div'); + row.className = 'bse-field'; + const label = document.createElement('label'); + label.htmlFor = `bse-field-${field.key}`; + label.textContent = field.label; + const input = document.createElement(field.type === 'select' ? 'select' : 'input'); + input.id = label.htmlFor; + if (field.type === 'select') { + for (const [value, title] of Object.entries(field.options)) { + const option = document.createElement('option'); option.value = value; option.textContent = title; input.append(option); + } + } else { + input.type = field.type; + if (field.type === 'number') { input.min = '1'; input.step = '1'; } + if (field.key === 'id') { input.required = true; input.setAttribute('aria-describedby', error.id); } + } + input.value = drafts.get(activeTag)?.[field.key] ?? field.default; + input.addEventListener('input', generate); + input.addEventListener('change', generate); + row.append(label, input); fields.append(row); + } + rendering = false; + generate(); + } + async function copy(text) { + status.textContent = ''; + try { + if (!navigator.clipboard?.writeText) throw new Error('manual'); + await navigator.clipboard.writeText(text); + status.textContent = data.i18n.copied; + } catch (_) { + const fallback = document.createElement('textarea'); fallback.value = text; + fallback.setAttribute('aria-label', choice.labels[0].textContent); + status.append(fallback); fallback.focus(); fallback.select(); + try { + if (document.execCommand('copy')) { status.textContent = data.i18n.copied; return; } + } catch (_) { /* Keep the selected text available. */ } + const instruction = document.createElement('span'); instruction.textContent = data.i18n.manual; status.append(instruction); + } + } + choice.addEventListener('change', renderFields); + copyGenerated.addEventListener('click', () => { if (!copyGenerated.disabled) copy(output.value); }); + document.querySelectorAll('.bse-copy').forEach(button => button.addEventListener('click', () => copy(button.previousElementSibling.textContent))); + document.querySelectorAll('.bse-configure').forEach(button => button.addEventListener('click', () => { + choice.value = button.dataset.tag; renderFields(); choice.focus(); + document.querySelector('.bse-generator').scrollIntoView({ behavior: 'smooth', block: 'start' }); + })); + document.getElementById('bse-search').addEventListener('input', event => { + const query = event.target.value.trim().toLocaleLowerCase(); + let matches = 0; + document.querySelectorAll('.bse-card').forEach(card => { + card.hidden = !card.textContent.toLocaleLowerCase().includes(query); + if (!card.hidden) matches++; + }); + document.querySelectorAll('.bse-group').forEach(group => { group.hidden = !group.querySelector('.bse-card:not([hidden])'); }); + document.getElementById('bse-no-results').hidden = matches !== 0; + }); + document.querySelectorAll('[data-bse-document]').forEach(link => link.addEventListener('click', event => { + const dialog = document.getElementById(`bse-document-${link.dataset.bseDocument}`); + if (!dialog || !dialog.showModal) return; + event.preventDefault(); dialog.showModal(); + })); + document.querySelectorAll('[data-bse-close]').forEach(button => button.addEventListener('click', () => button.closest('dialog').close())); + renderFields(); +})(); diff --git a/assets/banner-1544x500.png b/assets/banner-1544x500.png index db1e425..c1e21a6 100644 Binary files a/assets/banner-1544x500.png and b/assets/banner-1544x500.png differ diff --git a/assets/banner-1544x500.svg b/assets/banner-1544x500.svg new file mode 100644 index 0000000..1a0165f --- /dev/null +++ b/assets/banner-1544x500.svg @@ -0,0 +1 @@ +DECKERWEB · WORDPRESS HELPERSBuilder Shortcode ExtrasSmall dynamic helpers. Ready when you need them.COPYRIGHT / DATES / LINKS / CONTENT \ No newline at end of file diff --git a/assets/banner-772x250.png b/assets/banner-772x250.png index e1dba00..50c242a 100644 Binary files a/assets/banner-772x250.png and b/assets/banner-772x250.png differ diff --git a/assets/banner-de-1544x500.png b/assets/banner-de-1544x500.png new file mode 100644 index 0000000..5f147fc Binary files /dev/null and b/assets/banner-de-1544x500.png differ diff --git a/assets/banner-de-1544x500.svg b/assets/banner-de-1544x500.svg new file mode 100644 index 0000000..43f8b78 --- /dev/null +++ b/assets/banner-de-1544x500.svg @@ -0,0 +1 @@ +DECKERWEB · WORDPRESS HELPERSBuilder Shortcode ExtrasKleine dynamische Helfer. Schnell eingesetzt.COPYRIGHT / DATUM / LINKS / INHALTE \ No newline at end of file diff --git a/assets/banner-de-772x250.png b/assets/banner-de-772x250.png new file mode 100644 index 0000000..4d48963 Binary files /dev/null and b/assets/banner-de-772x250.png differ diff --git a/assets/icon-128x128.png b/assets/icon-128x128.png index ae55db0..22d9d1e 100644 Binary files a/assets/icon-128x128.png and b/assets/icon-128x128.png differ diff --git a/assets/icon-256x256.png b/assets/icon-256x256.png index f12bc9a..8bf22d6 100644 Binary files a/assets/icon-256x256.png and b/assets/icon-256x256.png differ diff --git a/assets/icon-512x512.png b/assets/icon-512x512.png new file mode 100644 index 0000000..ac2ad5a Binary files /dev/null and b/assets/icon-512x512.png differ diff --git a/assets/icon.svg b/assets/icon.svg index 7a20a4e..371dc32 100644 --- a/assets/icon.svg +++ b/assets/icon.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/assets/images/bse-icon.png b/assets/images/bse-icon.png index c79c6b1..8bf22d6 100644 Binary files a/assets/images/bse-icon.png and b/assets/images/bse-icon.png differ diff --git a/builder-shortcode-extras.php b/builder-shortcode-extras.php index d9d9ad1..46f3b75 100644 --- a/builder-shortcode-extras.php +++ b/builder-shortcode-extras.php @@ -2,20 +2,22 @@ /** * Plugin Name: Builder Shortcode Extras * Plugin URI: https://github.com/deckerweb/builder-shortcode-extras - * Description: A collection of totally useful extra Shortcodes to make the life of Site Builders more easy. - * Version: 1.1.0 + * Description: Small dynamic helpers for your WordPress website. Find, configure and copy useful shortcodes. + * Version: 1.2.0 * Author: David Decker - DECKERWEB - * Author URI: https://deckerweb.de/ + * Author URI: https://github.com/deckerweb * License: GPL-2.0-or-later * License URI: https://opensource.org/licenses/GPL-2.0 * Text Domain: builder-shortcode-extras * Domain Path: /languages/ - * Requires WP: 6.7 - * Requires PHP: 7.4 + * Requires at least: 6.7 + * Requires PHP: 8.0 * GitHub Plugin URI: https://github.com/deckerweb/builder-shortcode-extras + * Update URI: https://github.com/deckerweb/builder-shortcode-extras * GitHub Branch: master * - * Copyright (c) 2019-2025 David Decker - DECKERWEB + * Copyright © 2019–2026 David Decker – DECKERWEB. + * SPDX-License-Identifier: GPL-2.0-or-later */ /** @@ -32,9 +34,11 @@ * @since 1.0.0 */ /** Plugin version */ -define( 'BSE_PLUGIN_VERSION', '1.1.0' ); +define( 'BSE_PLUGIN_VERSION', '1.2.0' ); /** Plugin directory */ +define( 'BSE_PLUGIN_FILE', __FILE__ ); + define( 'BSE_PLUGIN_DIR', trailingslashit( dirname( __FILE__ ) ) ); /** Plugin base directory */ @@ -44,6 +48,16 @@ /** Include global functions */ require_once BSE_PLUGIN_DIR . 'includes/functions-global.php'; require_once BSE_PLUGIN_DIR . 'includes/functions-conditionals.php'; +require_once BSE_PLUGIN_DIR . 'includes/functions-rendering.php'; +require_once BSE_PLUGIN_DIR . 'includes/class-bse-shortcode-catalog.php'; +require_once BSE_PLUGIN_DIR . 'includes/admin/class-bse-tools.php'; +require_once BSE_PLUGIN_DIR . 'includes/class-bse-changelog.php'; +require_once BSE_PLUGIN_DIR . 'includes/class-bse-guide.php'; +require_once BSE_PLUGIN_DIR . 'includes/class-bse-github-updates.php'; +\Deckerweb\BuilderShortcodeExtras\Tools::register(); +( new \Deckerweb\BuilderShortcodeExtras\GitHubUpdates() )->register(); +require_once BSE_PLUGIN_DIR . 'includes/deckerweb-plugin-library/bootstrap.php'; +deckerweb_library_register( __FILE__, [], BSE_PLUGIN_DIR . 'includes/deckerweb-plugin-library' ); add_action( 'init', 'ddw_bse_setup_plugin', 1 ); @@ -59,6 +73,8 @@ */ function ddw_bse_setup_plugin() { + load_plugin_textdomain( 'builder-shortcode-extras', false, dirname( plugin_basename( BSE_PLUGIN_FILE ) ) . '/languages/' ); + /** Include admin helper functions */ if ( is_admin() ) { require_once BSE_PLUGIN_DIR . 'includes/admin/admin-extras.php'; @@ -77,25 +93,12 @@ function ddw_bse_setup_plugin() { ] ); - foreach ( $shortcode_groups as $shortcode_group ) { - - if ( ! is_admin() || ! is_network_admin() ) { - - require_once BSE_PLUGIN_DIR . 'includes/shortcodes/' . sanitize_key( $shortcode_group ) . '.php'; - - } elseif ( apply_filters( 'bse/filter/shortcodes_in_admin', FALSE ) ) { - - if ( 'content' === $shortcode_group || 'integrations' === $shortcode_group ) { - continue; - } - - if ( is_admin() || is_network_admin() ) { - require_once BSE_PLUGIN_DIR . 'includes/shortcodes/' . sanitize_key( $shortcode_group ) . '.php'; - } - - } // end if - - } // end foreach + // Load callbacks in every context; registration policy is handled separately. + foreach ( (array) $shortcode_groups as $shortcode_group ) { + if ( is_string( $shortcode_group ) && preg_match( '/^[a-z0-9_-]+$/D', $shortcode_group ) && is_readable( BSE_PLUGIN_DIR . 'includes/shortcodes/' . $shortcode_group . '.php' ) ) { + require_once BSE_PLUGIN_DIR . 'includes/shortcodes/' . $shortcode_group . '.php'; + } + } /** Load Integrations */ require_once BSE_PLUGIN_DIR . 'includes/load-integrations.php'; diff --git a/composer.json b/composer.json index 2056dfe..07c6608 100644 --- a/composer.json +++ b/composer.json @@ -17,6 +17,6 @@ "source": "https://github.com/deckerweb/builder-shortcode-extras" }, "require": { - "php": ">=7.4" + "php": ">=8.0" } } \ No newline at end of file diff --git a/docs/CHANGELOG-de.md b/docs/CHANGELOG-de.md new file mode 100644 index 0000000..16533c5 --- /dev/null +++ b/docs/CHANGELOG-de.md @@ -0,0 +1,30 @@ +# Änderungsprotokoll + +## 1.2.0 — 01.10.2026 + +- **Neu:** Durchsuchbare Shortcode-Hilfe mit Kopierfunktion und lokalem Generator für Copyright, Veröffentlichungsdatum, Änderungsdatum, Beitragslinks und Inhaltseinbettung. +- **Neu:** deckerweb Plugin Library 0.2.0 und gemeinsamer deckerweb GitHub Release Updater v2 eingebunden. +- **Verbessert:** Einheitliche Wrapper-Prüfung, mehrere CSS-Klassen, boolesche Attribute und bereinigte Textausgabe; Shortcode-Namen, Aliase und Ausgabefilter bleiben erhalten. +- **Verbessert:** Native Blöcke werden gerendert; Builder werden passend zum Inhalt gewählt; direkte und indirekte rekursive Einbettung wird verhindert. +- **Verbessert:** Header/Footer nach Brand Admin Schemes, lokaler Changelog-Dialog, englische/deutsche Dokumentation und aktualisierte deutsche Übersetzungen. +- **Verbessert:** Drei originale skalierbare Icon-/Banner-Entwürfe; Variante A mit dem violetten Hintergrund von B ist ausgewählt. +- **Behoben:** Admin-Registrierung, Rückgabe bereinigter Integrationsdaten und versehentlich registrierter Platzhalter korrigiert. +- **Behoben:** Veröffentlichungsdatum berücksichtigt die Beitrags-ID; Slug-Links werden korrekt aufgelöst; ungültige IDs und unbekannte Zählstatus werden behandelt. +- **Behoben:** Nicht definierte Versionskonstanten, Datenbankversionsabfrage sowie Escaping von Benutzerwerten, Ersatztext, Datumsbeschriftungen, Linktext und Tooltip korrigiert. +- **Behoben:** Website-Änderungsdatum verwendet die WordPress-Zeitzone und liefert bei fehlenden veröffentlichten Inhalten kein falsches Datum. +- **Sonstiges:** Benötigt WordPress 6.7+ und PHP 8.0+; PHP 8.0 wird von der Library vorausgesetzt. Die Helfer-Oberfläche lädt keine Frontend-Ressourcen. +- **Sonstiges:** Genesis und Beaver bleiben erhalten; doppelter Astra-Loader, altes Blocks-Menü und sachfremde Werbe-/Core-Admin-Eingriffe entfernt. + +## 1.1.0 — 2025-03-15 + +- **Verbessert:** Plugin wieder in einen nutzbaren Zustand gebracht. +- **Sonstiges:** Verteilung von WordPress.org zu GitHub verlagert und alten DDWlib-Empfehlungsinstaller entfernt. +- **Sonstiges:** Eigenen Übersetzungsloader entfernt; mitgelieferte Übersetzungen beibehalten. + +## 1.0.0 — 2019-09-10 + +- **Neu:** Erste Veröffentlichung mit 25 allgemeinen Helfern und 5 Integrations-Shortcodes. + +## 0.9.0 — 2019-09-09 + +- **Neu:** Erste öffentliche Beta auf GitHub. diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md new file mode 100644 index 0000000..3df5a1b --- /dev/null +++ b/docs/CHANGELOG.md @@ -0,0 +1,30 @@ +# Changelog + +## 1.2.0 — 2026-10-01 + +- **New:** Searchable shortcode help with copy buttons and a local generator for copyright, publication date, modification date, post links and embedded content. +- **New:** Embedded deckerweb Plugin Library 0.2.0 and shared deckerweb GitHub Release Updater v2. +- **Improved:** Unified wrapper validation, multiple CSS classes, boolean attributes and escaped text boundaries; existing shortcode names, aliases and output filters remain. +- **Improved:** Native block rendering, builder-specific routing and guarded content embedding with direct/indirect recursion protection. +- **Improved:** Brand Admin Schemes-style header/footer, local changelog dialog, English/German documentation and German/formal German translations. +- **Improved:** Three original scalable icon/banner concepts; selected concept A uses the purple background from concept B. +- **Fixed:** Corrected admin registration policy, persistent integration normalization and placeholder integration registration. +- **Fixed:** Publication dates honor explicit post IDs; slug links resolve correctly; missing IDs and unknown post-count statuses are handled. +- **Fixed:** Undefined version constants, database version lookup, user/fallback escaping, date labels, link text and tooltip escaping. +- **Fixed:** Site-updated dates use the WordPress timezone and return no false date when no published item exists. +- **Misc:** Requires WordPress 6.7+ and PHP 8.0+; the library requires PHP 8.0. No frontend assets are added by the helper UI. +- **Misc:** Retains Genesis and Beaver compatibility, removes the duplicate Astra loader, legacy Blocks menu and unrelated promotion/Core admin overrides. + +## 1.1.0 — 2025-03-15 + +- **Improved:** Restored the plugin to a usable state. +- **Misc:** Moved distribution away from WordPress.org and removed the old DDWlib recommendation installer. +- **Misc:** Removed the custom translation loader; retained packaged translations. + +## 1.0.0 — 2019-09-10 + +- **New:** Initial plugin release with 25 general helpers and 5 integration shortcodes. + +## 0.9.0 — 2019-09-09 + +- **New:** First public beta on GitHub. diff --git a/docs/Deutsch.md b/docs/Deutsch.md new file mode 100644 index 0000000..512c3fc --- /dev/null +++ b/docs/Deutsch.md @@ -0,0 +1,183 @@ +# Builder Shortcode Extras + +**Kleine dynamische Helfer. Schnell eingesetzt.** + +Copyright, Datumsangaben, Links und wiederverwendbare Inhalte ohne eigenes PHP. Mit einer durchsuchbaren Hilfe und einem kompakten Generator direkt in WordPress. + +Version **1.2.0** · WordPress **6.7+** · PHP **8.0+** · GPL-2.0-or-later + +## Inhalt + +- [Schnell starten](#schnell-starten) +- [Beispiele](#beispiele) +- [Shortcode-Referenz](#shortcode-referenz) +- [FAQ](#faq) + +## Schnell starten + +1. Lade das Test-ZIP hoch: Plugins → Installieren → Plugin hochladen. +2. Aktiviere das Plugin und öffne Werkzeuge → Builder Shortcode Extras. +3. Suche einen Helfer oder konfiguriere einen der fünf Anwendungsfälle. +4. Kopiere den Shortcode in einen Shortcode-Block oder ein geeignetes Builder-Feld. +5. Prüfe die tatsächliche Ausgabe auf deiner Testseite. + +Es gibt keine zusätzlichen BSE-Einstellungen. Die Seite ändert keine Inhalte und speichert keine Generatorwerte. + +## Beispiele + +```text +[bse-copyright first="2019"] +[bse-post-date format="Y-m-d"] +[bse-post-modified-date label="Zuletzt aktualisiert: "] +[bse-post-link id="123" text="Mehr erfahren"] +[bse-item-content id="123"] +``` + +## Shortcode-Referenz + +| Shortcode | Attribute | Voraussetzung | +|---|---|---| +| `bse-nav-menu` | `before`, `after`, `class`, `wrapper`, `menu`, `container`, `container_class`, `container_id`, `menu_class`, `menu_id`, `fallback_cb`, `item_before`, `item_after`, `link_before`, `link_after`, `depth`, `walker`, `theme_location`, `items_wrap`, `item_spacing` | WordPress | +| `bse-item-content` | `id`, `css` | WordPress | +| `bse-comment-form` | `before`, `after`, `class`, `wrapper`, `post_id`, `id_form`, `class_form`, `title_reply`, `title_reply_to`, `cancel_reply_link`, `label_submit` | WordPress | +| `bse-elementor-template` | `id`, `css` | Elementor Free | +| `bse-genesis-footer` | `class`, `wrapper` | Genesis 3.1+ | +| `bse-genesis-breadcrumbs` | `class`, `wrapper` | Genesis 3.1+ | +| `bse-copyright` | `after`, `before`, `copyright`, `first`, `class`, `wrapper` | WordPress | +| `bse-site-title` | `after`, `before`, `class`, `wrapper` | WordPress | +| `bse-site-slogan` | `after`, `before`, `class`, `wrapper` | WordPress | +| `bse-home-link` | `after`, `before`, `text`, `target`, `rel`, `class`, `wrapper` | WordPress | +| `bse-loginout` | `after`, `before`, `login_text`, `logout_text`, `login_target`, `logout_target`, `login_redirect`, `logout_redirect`, `class`, `wrapper` | WordPress | +| `bse-site-updated` | `before`, `after`, `type`, `label_date`, `date_format`, `label_time`, `time_format`, `tooltip`, `class`, `wrapper` | WordPress | +| `bse-post-count` | `post_type`, `status`, `before`, `after`, `class`, `wrapper` | WordPress | +| `bse-post-date` | `after`, `before`, `post_id`, `format`, `label`, `relative_depth`, `class`, `wrapper` | WordPress | +| `bse-post-time` | `after`, `before`, `post_id`, `format`, `label`, `class`, `wrapper` | WordPress | +| `bse-post-modified-date` | `after`, `before`, `post_id`, `format`, `label`, `relative_depth`, `class`, `wrapper` | WordPress | +| `bse-item-last-updated` | `after`, `before`, `post_id`, `format`, `label`, `class`, `wrapper` | WordPress | +| `bse-post-modified-time` | `after`, `before`, `post_id`, `format`, `label`, `class`, `wrapper` | WordPress | +| `bse-post-author` | `after`, `before`, `class`, `wrapper` | WordPress | +| `bse-post-author-link` | `after`, `before`, `target`, `rel`, `class`, `wrapper` | WordPress | +| `bse-post-author-posts-link` | `after`, `before`, `target`, `rel`, `class`, `wrapper` | WordPress | +| `bse-post-tags` | `after`, `before`, `sep`, `class`, `wrapper` | WordPress | +| `bse-post-categories` | `sep`, `before`, `after`, `class`, `wrapper` | WordPress | +| `bse-post-terms` | `after`, `before`, `sep`, `taxonomy`, `class`, `wrapper` | WordPress | +| `bse-post-edit` | `after`, `before`, `label`, `class`, `wrapper` | WordPress | +| `bse-post-link` | `id`, `slug`, `post_type`, `privacy`, `text`, `target`, `rel`, `before`, `after`, `class`, `wrapper` | WordPress | +| `bse-user` | `user_id`, `field`, `default`, `before`, `after`, `class`, `wrapper` | WordPress | +| `bse-userid` | | WordPress | +| `bse-email` | | WordPress | +| `bse-login` | | WordPress | +| `bse-displayname` | | WordPress | +| `bse-firstname` | | WordPress | +| `bse-lastname` | | WordPress | +| `bse-version` | `type`, `plugin`, `constant`, `custom`, `before`, `after`, `class`, `wrapper` | WordPress | +| `bse-wpblock` | `id`, `css` | WordPress synced patterns | +| `bse-astra-layout` | `id`, `css` | Astra Pro custom layouts | + +## FAQ + +### Muss ich das Plugin konfigurieren? + +Nein. Öffne Werkzeuge → Builder Shortcode Extras, um einen Shortcode zu finden oder zu erstellen. Die Seite speichert keine Einstellungen. + +### Wo füge ich einen Shortcode ein? + +In einen WordPress-Shortcode-Block oder ein Builder-Feld mit ausdrücklicher Shortcode-Unterstützung. Beliebige Text-, URL- und HTML-Felder führen Shortcodes nicht automatisch aus. + +### Was kann der Generator erstellen? + +Copyright, Veröffentlichungsdatum, Änderungsdatum, einen Beitrags-/Seiten-Link und Inhaltseinbettungen. Weitere Helfer findest du in der durchsuchbaren Übersicht. + +### Kann ich auf Deutsch suchen? + +Ja. Oberfläche und Aufgabenbeschreibungen folgen der WordPress-Administrationssprache. Shortcode- und Attributnamen bleiben unverändert. + +### Wo finde ich eine Beitrags- oder Vorlagen-ID? + +Öffne den Inhalt in WordPress. Die Zahl hinter post= in der Editor-Adresse ist seine ID. Unterstützte Vorlagenlisten zeigen zusätzlich Shortcode-Beispiele. + +### Auf welchen Beitrag bezieht sich ein Datum? + +Ohne post_id auf den aktuellen WordPress-Beitrag. Gib eine ID für einen anderen Inhalt an. Der Kontext in einer Builder-Vorschau kann von der fertigen Seite abweichen. + +### Gibt es relative Datumsangaben? + +Wähle Relatives Datum im Generator oder verwende format="relative". Die Ausgabe verwendet den vorhandenen Helfer für verstrichene Zeit. + +### Kann ich HTML und Klassen ändern? + +Die meisten Texthelfer unterstützen wrapper, class, before und after. Mehrere Klassen sind möglich. Unsichere oder nicht unterstützte Wrapper werden durch span ersetzt. Diese Version ergänzt keinen Modus ohne Wrapper. + +### Werden Frontend-CSS oder JavaScript geladen? + +Hilfe und Generator laden Ressourcen nur auf ihrer eigenen Admin-Seite. Eingebettete Builder-Vorlagen können Ressourcen ihres Builders laden; das Kommentarformular kann WordPress-Ressourcen benötigen. + +### Warum bleibt eine Inhaltseinbettung leer? + +Prüfe ID, Veröffentlichungsstatus und erforderlichen Builder. Private, unveröffentlichte, gelöschte oder passwortgeschützte Inhalte werden Besuchern ohne erforderlichen Zugriff nicht ausgegeben. Rekursive Referenzen werden gestoppt. + +### Werden native Blöcke unterstützt? + +Ja. Eingebettete Inhalte werden durch die native Block-Ausgabe und Shortcode-Verarbeitung geführt. Die vollständigen Inhaltsfilter des Themes werden bewusst nicht angewendet. + +### Brauche ich Elementor Pro? + +bse-elementor-template wird bei Elementor Free ohne aktives Pro registriert. Der allgemeine Inhaltshelfer kann Elementor-Inhalte rendern, wenn Elementor verfügbar ist. + +### Bleibt Beaver Builder unterstützt? + +Ja. Inhalte mit _fl_builder_enabled verwenden Beavers eigenen Layout-Shortcode. Die Installation von Beaver verändert die Ausgabe normaler Beiträge nicht. Zusätzliche Beaver-Funktionen wurden nicht ergänzt. + +### Bleibt Genesis unterstützt? + +Ja. Die vorhandenen Footer- und Breadcrumb-Helfer bleiben mit Genesis 3.1+ als Kompatibilitätsfunktionen erhalten. + +### Funktionieren synchronisierte Vorlagen mit Classic Editor? + +bse-wpblock ist verfügbar, sobald WordPress den Inhaltstyp wp_block registriert. Das zusätzliche alte Blocks-Menü entfällt. + +### Warum hat der Generator keine Live-Vorschau? + +Dieser erste Generator erstellt die Shortcode-Syntax lokal. Prüfe die tatsächliche Ausgabe auf einer Testseite mit dem vorgesehenen Beitrags- und Builder-Kontext. + +### Wie funktioniert Kopieren ohne HTTPS? + +Zuerst wird die Browser-Zwischenablage versucht, danach eine lokale Kopierfunktion. Ist beides nicht verfügbar, wird der Text zum manuellen Kopieren markiert. Beispiele bleiben ohne JavaScript lesbar. + +### Wie funktionieren Updates? + +Der mitgelieferte deckerweb Updater prüft das öffentliche GitHub-Repository auf neuere stabile Releases über die normale WordPress-Updateverwaltung. Er installiert keine Vorabversionen automatisch und aktiviert keine automatischen Updates. Testversionen installierst du manuell. + +### Was ist die deckerweb Library? + +Sie ergänzt einen deckerweb-Tab unter Plugins → Installieren, ist eingebettet und teilt eine Laufzeit mit anderen deckerweb-Plugins. Unter Einstellungen → deckerweb Library lässt sie sich ausblenden. Der mitgelieferte Katalog enthält freigegebene Releases; BSE RC1 wird dadurch nicht veröffentlicht oder neu in den Katalog aufgenommen. + +### Sendet das Plugin Nutzungsdaten? + +BSE enthält keine Nutzungstelemetrie. Der Updater fragt GitHub-Release-Metadaten ab. Die Library nutzt standardmäßig ihren lokalen Katalog; Online-Katalogupdates erfordern eine ausdrückliche Konfiguration. Server sehen technisch die anfragende IP-Adresse. + +### Was ändert sich für bestehende Installationen? + +Namen, Aliase und Ausgabefilter bleiben. PHP 8.0 ist jetzt erforderlich. Ungültige Wrapper werden zu span, Text wird konsistenter escaped, unzugängliche Inhalte werden nicht eingebettet und die Admin-Registrierung folgt dem Opt-in-Filter. + +### Kann ich Texthelfer im Backend aktivieren? + +Entwickler können über bse/filter/shortcodes_in_admin true zurückgeben. Inhaltseinbettungen, Navigation, Kommentarformulare sowie Builder-/Genesis-Ausgaben bleiben dort ausgeschlossen. REST-Anfragen bleiben für Frontend-Ausgaben verfügbar. + +### Funktioniert das Plugin mit Multisite? + +BSE kann netzwerkweit aktiviert werden. Inhalte beziehen sich auf die jeweilige Website; autorisierte Website-Administratoren können die Werkzeuge öffnen. Die Library besitzt eigene Netzwerkeinstellungen. Ein Generator in der Netzwerkverwaltung wurde nicht ergänzt. + +### Welche Anforderungen und welchen Support gibt es? + +WordPress 6.7+ und PHP 8.0+. Die Library-Installation benötigt ZipArchive. Melde reproduzierbare Fehler auf GitHub mit Plugin-, WordPress-, PHP- und Builder-Version. Kommerzielle Builder wurden mit repräsentativen Testfällen geprüft, nicht mit lizenzierten Installationen. + +## Entwickler + +```php +add_filter( 'bse/filter/shortcodes_in_admin', '__return_true' ); +``` + +Bestehende Filter unter `bse/filter/shortcode_defaults/*` und `bse/filter/shortcode/*` bleiben erhalten. Filterausgaben gelten als vertrauenswürdiger Entwicklercode und können HTML zurückgeben. Die Registrierungsdateien liegen weiterhin nach Themen getrennt unter `includes/shortcodes/`. + +[Changelog](CHANGELOG-de.md) · [Testanleitung](TESTING-de.md) diff --git a/docs/English.md b/docs/English.md new file mode 100644 index 0000000..f864b54 --- /dev/null +++ b/docs/English.md @@ -0,0 +1,183 @@ +# Builder Shortcode Extras + +**Small dynamic helpers. Ready when you need them.** + +Copyright, dates, links and reusable content without custom PHP. Includes searchable help and a compact generator directly in WordPress. + +Version **1.2.0** · WordPress **6.7+** · PHP **8.0+** · GPL-2.0-or-later + +## Contents + +- [Quick start](#quick-start) +- [Examples](#examples) +- [Shortcode reference](#shortcode-reference) +- [FAQ](#faq) + +## Quick start + +1. Upload the test ZIP through Plugins → Add New → Upload Plugin. +2. Activate it and open Tools → Builder Shortcode Extras. +3. Find a helper or configure one of the five use cases. +4. Copy the shortcode into a Shortcode block or a supported builder field. +5. Check the actual output on your test page. + +There are no additional BSE settings. The page does not edit content or save generator values. + +## Examples + +```text +[bse-copyright first="2019"] +[bse-post-date format="Y-m-d"] +[bse-post-modified-date label="Last updated: "] +[bse-post-link id="123" text="Read more"] +[bse-item-content id="123"] +``` + +## Shortcode reference + +| Shortcode | Attributes | Prerequisite | +|---|---|---| +| `bse-nav-menu` | `before`, `after`, `class`, `wrapper`, `menu`, `container`, `container_class`, `container_id`, `menu_class`, `menu_id`, `fallback_cb`, `item_before`, `item_after`, `link_before`, `link_after`, `depth`, `walker`, `theme_location`, `items_wrap`, `item_spacing` | WordPress | +| `bse-item-content` | `id`, `css` | WordPress | +| `bse-comment-form` | `before`, `after`, `class`, `wrapper`, `post_id`, `id_form`, `class_form`, `title_reply`, `title_reply_to`, `cancel_reply_link`, `label_submit` | WordPress | +| `bse-elementor-template` | `id`, `css` | Elementor Free | +| `bse-genesis-footer` | `class`, `wrapper` | Genesis 3.1+ | +| `bse-genesis-breadcrumbs` | `class`, `wrapper` | Genesis 3.1+ | +| `bse-copyright` | `after`, `before`, `copyright`, `first`, `class`, `wrapper` | WordPress | +| `bse-site-title` | `after`, `before`, `class`, `wrapper` | WordPress | +| `bse-site-slogan` | `after`, `before`, `class`, `wrapper` | WordPress | +| `bse-home-link` | `after`, `before`, `text`, `target`, `rel`, `class`, `wrapper` | WordPress | +| `bse-loginout` | `after`, `before`, `login_text`, `logout_text`, `login_target`, `logout_target`, `login_redirect`, `logout_redirect`, `class`, `wrapper` | WordPress | +| `bse-site-updated` | `before`, `after`, `type`, `label_date`, `date_format`, `label_time`, `time_format`, `tooltip`, `class`, `wrapper` | WordPress | +| `bse-post-count` | `post_type`, `status`, `before`, `after`, `class`, `wrapper` | WordPress | +| `bse-post-date` | `after`, `before`, `post_id`, `format`, `label`, `relative_depth`, `class`, `wrapper` | WordPress | +| `bse-post-time` | `after`, `before`, `post_id`, `format`, `label`, `class`, `wrapper` | WordPress | +| `bse-post-modified-date` | `after`, `before`, `post_id`, `format`, `label`, `relative_depth`, `class`, `wrapper` | WordPress | +| `bse-item-last-updated` | `after`, `before`, `post_id`, `format`, `label`, `class`, `wrapper` | WordPress | +| `bse-post-modified-time` | `after`, `before`, `post_id`, `format`, `label`, `class`, `wrapper` | WordPress | +| `bse-post-author` | `after`, `before`, `class`, `wrapper` | WordPress | +| `bse-post-author-link` | `after`, `before`, `target`, `rel`, `class`, `wrapper` | WordPress | +| `bse-post-author-posts-link` | `after`, `before`, `target`, `rel`, `class`, `wrapper` | WordPress | +| `bse-post-tags` | `after`, `before`, `sep`, `class`, `wrapper` | WordPress | +| `bse-post-categories` | `sep`, `before`, `after`, `class`, `wrapper` | WordPress | +| `bse-post-terms` | `after`, `before`, `sep`, `taxonomy`, `class`, `wrapper` | WordPress | +| `bse-post-edit` | `after`, `before`, `label`, `class`, `wrapper` | WordPress | +| `bse-post-link` | `id`, `slug`, `post_type`, `privacy`, `text`, `target`, `rel`, `before`, `after`, `class`, `wrapper` | WordPress | +| `bse-user` | `user_id`, `field`, `default`, `before`, `after`, `class`, `wrapper` | WordPress | +| `bse-userid` | | WordPress | +| `bse-email` | | WordPress | +| `bse-login` | | WordPress | +| `bse-displayname` | | WordPress | +| `bse-firstname` | | WordPress | +| `bse-lastname` | | WordPress | +| `bse-version` | `type`, `plugin`, `constant`, `custom`, `before`, `after`, `class`, `wrapper` | WordPress | +| `bse-wpblock` | `id`, `css` | WordPress synced patterns | +| `bse-astra-layout` | `id`, `css` | Astra Pro custom layouts | + +## FAQ + +### Do I need to configure the plugin? + +No. Open Tools → Builder Shortcode Extras to find or generate a shortcode. The page does not save settings. + +### Where do I paste a shortcode? + +Use a WordPress Shortcode block or a builder field that explicitly supports shortcodes. Arbitrary text, URL and HTML fields do not necessarily execute them. + +### What can the generator create? + +Copyright, publication date, last-updated date, a post/page link and embedded content. Other helpers remain available in the searchable catalog. + +### Can I search in German? + +Yes. The interface and task descriptions follow your WordPress admin language. Shortcode and attribute names remain unchanged. + +### How do I find a post or template ID? + +Open the item in WordPress. The number after post= in the editor URL is its ID. Supported template list tables also show shortcode examples. + +### Which post does a date refer to? + +Leave post_id empty to use the current WordPress post. Specify an ID for another item. A page-builder preview may have a different context from the final page. + +### Can I use a relative date? + +Choose Relative date in the generator or use format="relative". Relative output uses the existing elapsed-time helper. + +### Can I change the HTML and classes? + +Most text helpers accept wrapper, class, before and after. Multiple classes are supported. Unsafe or unsupported wrapper tags fall back to span. This version does not add a wrapper-free mode. + +### Does it load frontend CSS or JavaScript? + +The help and generator load assets only on their own admin page. Embedded builder templates may load their builder’s own assets, and the comment form may rely on WordPress resources. + +### Why is embedded content empty? + +Check the ID, publication status and required builder. Visitors cannot embed private, draft, trashed or password-protected content without the required access. Recursive references stop with empty output. + +### Are native blocks supported? + +Yes. Embedded content is passed through native block rendering and shortcode processing. Full theme content filters are intentionally not applied. + +### Does Elementor need Pro? + +The bse-elementor-template integration is registered with Elementor Free when Pro is inactive. The generic content helper can route Elementor-built items when Elementor is available. + +### Is Beaver Builder supported? + +Yes. Items with _fl_builder_enabled use Beaver’s own saved-layout shortcode. Merely installing Beaver does not reroute ordinary posts. No additional Beaver features were added. + +### Is Genesis still supported? + +Yes. The existing Genesis footer and breadcrumb helpers remain available with Genesis 3.1+. They are maintained as compatibility features. + +### Are synced patterns supported with Classic Editor? + +The bse-wpblock helper is available whenever WordPress registers the wp_block post type. A legacy extra Blocks menu is no longer added. + +### Why is there no live preview in the generator? + +This first generator constructs valid shortcode syntax locally. View the actual output on a test page with the intended post and builder context. + +### How does copying work without HTTPS? + +The interface tries the browser clipboard, then a local copy fallback. If copying is unavailable, it selects the text for manual copying. Examples remain readable without JavaScript. + +### How do updates work? + +The bundled deckerweb updater checks this public GitHub repository for newer stable releases through the regular WordPress update system. It does not automatically install prereleases or enable automatic updates. Test releases are installed manually. + +### What is the deckerweb Library? + +It adds a deckerweb tab to Plugins → Add New. It is bundled, shares one runtime with other deckerweb plugins, and can be hidden in Settings → deckerweb Library. Its bundled curated catalog installs approved releases; BSE RC1 is not newly published or added to that catalog. + +### Does the plugin send usage data? + +BSE has no usage telemetry. The updater requests GitHub release metadata. The Library uses its bundled catalog by default; online catalog updates require explicit configuration. Servers naturally see the requesting IP address. + +### What changed for existing installations? + +Names, aliases and output filters remain. PHP 8.0 is now required. Invalid wrappers fall back to span, text is escaped more consistently, inaccessible embedded content is withheld, and wp-admin shortcode registration follows its opt-in filter. + +### Can I enable text helpers in wp-admin? + +Developers can return true from bse/filter/shortcodes_in_admin. Content embedding, navigation, comment forms and builder/Genesis rendering remain excluded there. REST requests remain available for frontend rendering. + +### Is multisite supported? + +BSE can be network-activated. Each site has its own content context; the tools page is available to authorized site administrators. The shared Library has its own network settings. A network-admin generator was not added. + +### What are the requirements and support scope? + +WordPress 6.7+ and PHP 8.0+. Library installation requires ZipArchive. Report reproducible issues on GitHub with plugin, WordPress, PHP and builder versions. Commercial builder adapters were checked with representative fixtures, not licensed builder installations. + +## Developers + +```php +add_filter( 'bse/filter/shortcodes_in_admin', '__return_true' ); +``` + +Existing `bse/filter/shortcode_defaults/*` and `bse/filter/shortcode/*` filters remain. Filter output is trusted developer code and may return HTML. Registration files remain grouped by topic under `includes/shortcodes/`. + +[Changelog](CHANGELOG.md) · [Test guide](TESTING-de.md) diff --git a/docs/RELEASE-NOTES.md b/docs/RELEASE-NOTES.md new file mode 100644 index 0000000..8b40eda --- /dev/null +++ b/docs/RELEASE-NOTES.md @@ -0,0 +1,39 @@ +# Builder Shortcode Extras 1.2.0 + +## Deutsch + +WordPress 6.7+ · PHP 8.0+ + +- **Neu:** Durchsuchbare Shortcode-Hilfe mit Kopierfunktion und lokalem Generator für Copyright, Veröffentlichungsdatum, Änderungsdatum, Beitragslinks und Inhaltseinbettung. +- **Neu:** deckerweb Plugin Library 0.2.0 und gemeinsamer deckerweb GitHub Release Updater v2 eingebunden. +- **Verbessert:** Einheitliche Wrapper-Prüfung, mehrere CSS-Klassen, boolesche Attribute und bereinigte Textausgabe; Shortcode-Namen, Aliase und Ausgabefilter bleiben erhalten. +- **Verbessert:** Native Blöcke werden gerendert; Builder werden passend zum Inhalt gewählt; direkte und indirekte rekursive Einbettung wird verhindert. +- **Verbessert:** Header/Footer nach Brand Admin Schemes, lokaler Changelog-Dialog, englische/deutsche Dokumentation und aktualisierte deutsche Übersetzungen. +- **Verbessert:** Drei originale skalierbare Icon-/Banner-Entwürfe; Variante A mit dem violetten Hintergrund von B ist ausgewählt. +- **Behoben:** Admin-Registrierung, Rückgabe bereinigter Integrationsdaten und versehentlich registrierter Platzhalter korrigiert. +- **Behoben:** Veröffentlichungsdatum berücksichtigt die Beitrags-ID; Slug-Links werden korrekt aufgelöst; ungültige IDs und unbekannte Zählstatus werden behandelt. +- **Behoben:** Nicht definierte Versionskonstanten, Datenbankversionsabfrage sowie Escaping von Benutzerwerten, Ersatztext, Datumsbeschriftungen, Linktext und Tooltip korrigiert. +- **Behoben:** Website-Änderungsdatum verwendet die WordPress-Zeitzone und liefert bei fehlenden veröffentlichten Inhalten kein falsches Datum. +- **Sonstiges:** Benötigt WordPress 6.7+ und PHP 8.0+; PHP 8.0 wird von der Library vorausgesetzt. Die Helfer-Oberfläche lädt keine Frontend-Ressourcen. +- **Sonstiges:** Genesis und Beaver bleiben erhalten; doppelter Astra-Loader, altes Blocks-Menü und sachfremde Werbe-/Core-Admin-Eingriffe entfernt. + + +## English + +WordPress 6.7+ · PHP 8.0+ + +- **New:** Searchable shortcode help with copy buttons and a local generator for copyright, publication date, modification date, post links and embedded content. +- **New:** Embedded deckerweb Plugin Library 0.2.0 and shared deckerweb GitHub Release Updater v2. +- **Improved:** Unified wrapper validation, multiple CSS classes, boolean attributes and escaped text boundaries; existing shortcode names, aliases and output filters remain. +- **Improved:** Native block rendering, builder-specific routing and guarded content embedding with direct/indirect recursion protection. +- **Improved:** Brand Admin Schemes-style header/footer, local changelog dialog, English/German documentation and German/formal German translations. +- **Improved:** Three original scalable icon/banner concepts; selected concept A uses the purple background from concept B. +- **Fixed:** Corrected admin registration policy, persistent integration normalization and placeholder integration registration. +- **Fixed:** Publication dates honor explicit post IDs; slug links resolve correctly; missing IDs and unknown post-count statuses are handled. +- **Fixed:** Undefined version constants, database version lookup, user/fallback escaping, date labels, link text and tooltip escaping. +- **Fixed:** Site-updated dates use the WordPress timezone and return no false date when no published item exists. +- **Misc:** Requires WordPress 6.7+ and PHP 8.0+; the library requires PHP 8.0. No frontend assets are added by the helper UI. +- **Misc:** Retains Genesis and Beaver compatibility, removes the duplicate Astra loader, legacy Blocks menu and unrelated promotion/Core admin overrides. + + +[English guide & FAQ](https://github.com/deckerweb/builder-shortcode-extras/blob/master/docs/English.md) · [Deutsche Anleitung & FAQ](https://github.com/deckerweb/builder-shortcode-extras/blob/master/docs/Deutsch.md) diff --git a/docs/RELEASE-de.md b/docs/RELEASE-de.md new file mode 100644 index 0000000..28fc882 --- /dev/null +++ b/docs/RELEASE-de.md @@ -0,0 +1,17 @@ +# GitHub-Release 1.2.0 + +Version: 1.2.0 · Datum: 01.10.2026 · WordPress 6.7+ · PHP 8.0+ + +Die Testversion wurde vom Plugin-Autor freigegeben. Ausgewählt ist Variante A mit dem violetten Hintergrund von B und dem Plugin-Namen in einer Zeile. Der Library-Katalog wurde nicht verändert. + +## Veröffentlichung + +1. Den vorbereiteten Quellstand in das GitHub-Repository übernehmen. +2. Einen stabilen Tag `v1.2.0` auf genau diesem Stand erstellen. +3. Ein reguläres GitHub-Release mit dem Titel „Builder Shortcode Extras 1.2.0“ und den zweisprachigen Release-Notizen erstellen. Nicht als Vorabversion markieren. +4. Das geprüfte Installationspaket als `builder-shortcode-extras.zip` anhängen. Sein Wurzelordner heißt `builder-shortcode-extras` und die Plugin-Version ist 1.2.0. +5. Die Wiki-Quellen unter `docs/wiki/` in das separate GitHub-Wiki übernehmen, sofern das Wiki verwendet wird. Die Anleitungen und FAQ sind zusätzlich im Repository verfügbar. + +Der GitHub-Updater berücksichtigt stabile Releases. Die Footer-Dokumentation öffnet die mitgelieferte Anleitung lokal. Deutsche Changelog-Prefixe sind vollständig übersetzt: Neu, Verbessert, Behoben, Sonstiges. + +Die tatsächlichen Prüfungen und ihre Grenzen stehen in VALIDATION-de.md. Paketbau und Vorbereitung veröffentlichen selbst nichts auf GitHub. diff --git a/docs/TESTING-de.md b/docs/TESTING-de.md new file mode 100644 index 0000000..b42ec7a --- /dev/null +++ b/docs/TESTING-de.md @@ -0,0 +1,40 @@ +# Builder Shortcode Extras 1.2.0 testen + +## Installation + +1. PHP 8.0+ und WordPress 6.7+ prüfen. +2. Das Test-ZIP in einer Testwebsite hochladen und das vorhandene Plugin ersetzen. +3. Werkzeuge → Builder Shortcode Extras öffnen. +4. Einstellungen werden von BSE nicht gespeichert; vorhandene Shortcodes bleiben erhalten. + +## Aufgaben + +- Copyright mit erstem Jahr erzeugen, kopieren und auf einer Testseite ausgeben. +- Veröffentlichungsdatum ohne ID und mit der ID eines anderen Beitrags vergleichen. +- Änderungsdatum mit relativer und absoluter Ausgabe testen. +- Einen Link mit Beitrags-ID und benutzerdefiniertem Linktext erzeugen. +- Einen veröffentlichten Beitrag und eine synchronisierte Vorlage einbetten. +- Mit „Datum“, „Autor“, „Copyright“ und einem Attributnamen in der Hilfe suchen. +- Zwei CSS-Klassen einsetzen; beide müssen erhalten bleiben. +- Kopierfunktion, manuelle Kopiermöglichkeit, Tastaturbedienung und kleinen Bildschirm prüfen. +- Changelog-Dialog öffnen und mit Escape schließen. +- deckerweb-Tab unter Plugins → Installieren und Library-Ausblendung prüfen. +- Optional: Elementor Free, Elementor CSS, Beaver-Inhalte, Astra Pro und Genesis auf lizenzierten Installationen prüfen. + +## Automatisierte Regressionen + +Nur in einer separaten, wegwerfbaren WordPress-Installation ausführen; die Tests legen Inhalte an und löschen diese wieder. + +```sh +BSE_WP_TEST_ROOT=/absolute/path/to/test-wordpress php tests/regression.php +BSE_WP_TEST_ROOT=/absolute/path/to/test-wordpress php tests/admin-context.php +BSE_WP_TEST_ROOT=/absolute/path/to/test-wordpress BSE_TEST_ADMIN_CONTEXT=network php tests/admin-context.php +BSE_WP_TEST_ROOT=/absolute/path/to/test-wordpress php tests/builder-adapters.php +BSE_WP_TEST_ROOT=/absolute/path/to/test-wordpress php tests/updater.php +``` + +Die Builder-Tests verwenden repräsentative Adapter, keine kommerziellen Builder-Pakete. Netzwerk-Admin-Tests prüfen den Anfragekontext; sie ersetzen keinen vollständigen Multisite-End-to-End-Test. Der Updater wird mit lokalen Release-/Paketdaten getestet; ein echtes Fernupdate ist für RC1 nicht erfolgt. + +## Grafikentscheidung + +Drei Entwürfe sind separat beigefügt. Variante A mit dem violetten Hintergrund von B ist ausgewählt. Alle Varianten enthalten SVG, PNG-Icons und englische/deutsche Banner. Die Entscheidung verändert die Plugin-Funktionen nicht. diff --git a/docs/VALIDATION-de.md b/docs/VALIDATION-de.md new file mode 100644 index 0000000..8228f2a --- /dev/null +++ b/docs/VALIDATION-de.md @@ -0,0 +1,24 @@ +# Prüfung von 1.2.0 + +Stand: 01.10.2026. Die Prüfungen liefen in getrennten lokalen WordPress-Installationen mit SQLite und PHP 8.4.5. + +- WordPress 6.7 und 7.1.2: jeweils 62 Regressionen bestanden, einschließlich ausgewählter Beitrags-IDs, relativer Datumsangaben, mehrerer CSS-Klassen, Ausgabegrenzen, nativer Blockausgabe, rekursiver Einbettung, Zugriffsschutz, Integrationsdaten und Generatorumfang. +- Beide WordPress-Versionen: getrennte Website-/Netzwerk-Admin-Kontexte, Opt-in, Zugriffsrechte und Admin-Seite geprüft. +- Beide WordPress-Versionen: repräsentative Elementor-/Beaver-Adapter, CSS-Flag und Wiederherstellung nach einer Adapterausnahme bestanden. +- Beide WordPress-Versionen: Updater-Metadaten, Plugin-Zuordnung, Grafiken, Release-Texte, Vorabversionen, fremde Paket-URLs, Paketidentität, Anforderungen und Versionsabgleich mit lokalen Testdaten bestanden. +- Browser: alle fünf Generatorfälle, Feldwechsel, validierte IDs, Beschriftungsabstände, Kopieren, lokale Dokumentation, Suche, Changelog, Escape und Fokus geprüft. Keine JavaScript-Laufzeitfehler. +- Englisch, Deutsch und formelles Deutsch geprüft; deutsche Aufgabenbegriffe sind suchbar. Mobile Darstellung ohne horizontales Überlaufen. Beispiele sind ohne JavaScript lesbar. +- PHP-Syntax und JavaScript-Syntax bestanden. Deutsche PO-Dateien mit msgfmt geprüft; POT aktualisiert. +- Die eingebettete Library 0.2.0 und der gemeinsame Updater v2 wurden unverändert aus den lokalen deckerweb-Referenzen übernommen. Der Adapter ist pluginspezifisch. + +## Grenzen + +PHP 8.0 wurde in dieser Sitzung nicht als Laufzeit ausgeführt; die Library setzt PHP 8.0 voraus. Der vorbereitete GitHub-Workflow prüft Syntax unter PHP 8.0 und 8.4, ist aber noch nicht auf GitHub gelaufen. Die Datenbanktests ersetzen keine MySQL-/MariaDB-Matrix. Kommerzielle Elementor-, Beaver-, Astra- und Genesis-Installationen sowie ein vollständiges Multisite-Netzwerk wurden nicht ausgeführt. Ein echtes Fernupdate und die Installation anderer Library-Plugins gehören nicht zu diesen BSE-Tests. + +Die ZIP-Installation und Aktivierung über WordPress Plugin_Upgrader in einer frischen WordPress-6.7-Umgebung bestanden. Auch aus dem installierten ZIP bestanden alle 62 Regressionen. + +## Freigegebenes Release 1.2.0 + +Nach der Freigabe wurden PHP-/JavaScript-Syntax, die jeweils 62 Regressionen, Website-/Netzwerk-Admin-Kontexte, Builder-Adapter und Updater-Prüfungen mit Version 1.2.0 erneut erfolgreich ausgeführt. Die Release-Dokumentationsprüfung kontrolliert Versionsstände, übersetzte deutsche Changelog-Prefixe, zweisprachige FAQ und synchrone Wiki-Inhalte. Die früheren Browser-Prüfungen gelten für die vom Autor getestete Oberfläche; für die Versionierung und Dokumentationskorrekturen wurde kein erneuter vollständiger Browserlauf benötigt. + +Das fertige 1.2.0-ZIP wurde erneut über WordPress Plugin_Upgrader installiert und aktiviert; anschließend bestanden alle 62 Regressionen aus dem installierten Paket. Der lokale Changelog wurde zusätzlich unter de_DE und de_DE_formal auf alle vier deutschen Prefixe und auf das Fehlen englischer Prefixe geprüft. diff --git a/docs/changelog-de.txt b/docs/changelog-de.txt new file mode 100644 index 0000000..fbbcd22 --- /dev/null +++ b/docs/changelog-de.txt @@ -0,0 +1,30 @@ +== Changelog == + += 1.2.0 — 01.10.2026 = + +* Neu: Durchsuchbare Shortcode-Hilfe mit Kopierfunktion und lokalem Generator für Copyright, Veröffentlichungsdatum, Änderungsdatum, Beitragslinks und Inhaltseinbettung. +* Neu: deckerweb Plugin Library 0.2.0 und gemeinsamer deckerweb GitHub Release Updater v2 eingebunden. +* Verbessert: Einheitliche Wrapper-Prüfung, mehrere CSS-Klassen, boolesche Attribute und bereinigte Textausgabe; Shortcode-Namen, Aliase und Ausgabefilter bleiben erhalten. +* Verbessert: Native Blöcke werden gerendert; Builder werden passend zum Inhalt gewählt; direkte und indirekte rekursive Einbettung wird verhindert. +* Verbessert: Header/Footer nach Brand Admin Schemes, lokaler Changelog-Dialog, englische/deutsche Dokumentation und aktualisierte deutsche Übersetzungen. +* Verbessert: Drei originale skalierbare Icon-/Banner-Entwürfe; Variante A mit dem violetten Hintergrund von B ist ausgewählt. +* Behoben: Admin-Registrierung, Rückgabe bereinigter Integrationsdaten und versehentlich registrierter Platzhalter korrigiert. +* Behoben: Veröffentlichungsdatum berücksichtigt die Beitrags-ID; Slug-Links werden korrekt aufgelöst; ungültige IDs und unbekannte Zählstatus werden behandelt. +* Behoben: Nicht definierte Versionskonstanten, Datenbankversionsabfrage sowie Escaping von Benutzerwerten, Ersatztext, Datumsbeschriftungen, Linktext und Tooltip korrigiert. +* Behoben: Website-Änderungsdatum verwendet die WordPress-Zeitzone und liefert bei fehlenden veröffentlichten Inhalten kein falsches Datum. +* Sonstiges: Benötigt WordPress 6.7+ und PHP 8.0+; PHP 8.0 wird von der Library vorausgesetzt. Die Helfer-Oberfläche lädt keine Frontend-Ressourcen. +* Sonstiges: Genesis und Beaver bleiben erhalten; doppelter Astra-Loader, altes Blocks-Menü und sachfremde Werbe-/Core-Admin-Eingriffe entfernt. + += 1.1.0 — 2025-03-15 = + +* Verbessert: Plugin wieder in einen nutzbaren Zustand gebracht. +* Sonstiges: Verteilung von WordPress.org zu GitHub verlagert und alten DDWlib-Empfehlungsinstaller entfernt. +* Sonstiges: Eigenen Übersetzungsloader entfernt; mitgelieferte Übersetzungen beibehalten. + += 1.0.0 — 2019-09-10 = + +* Neu: Erste Veröffentlichung mit 25 allgemeinen Helfern und 5 Integrations-Shortcodes. + += 0.9.0 — 2019-09-09 = + +* Neu: Erste öffentliche Beta auf GitHub. diff --git a/docs/changelog.txt b/docs/changelog.txt new file mode 100644 index 0000000..19e8ea6 --- /dev/null +++ b/docs/changelog.txt @@ -0,0 +1,30 @@ +== Changelog == + += 1.2.0 — 2026-10-01 = + +* New: Searchable shortcode help with copy buttons and a local generator for copyright, publication date, modification date, post links and embedded content. +* New: Embedded deckerweb Plugin Library 0.2.0 and shared deckerweb GitHub Release Updater v2. +* Improved: Unified wrapper validation, multiple CSS classes, boolean attributes and escaped text boundaries; existing shortcode names, aliases and output filters remain. +* Improved: Native block rendering, builder-specific routing and guarded content embedding with direct/indirect recursion protection. +* Improved: Brand Admin Schemes-style header/footer, local changelog dialog, English/German documentation and German/formal German translations. +* Improved: Three original scalable icon/banner concepts; selected concept A uses the purple background from concept B. +* Fixed: Corrected admin registration policy, persistent integration normalization and placeholder integration registration. +* Fixed: Publication dates honor explicit post IDs; slug links resolve correctly; missing IDs and unknown post-count statuses are handled. +* Fixed: Undefined version constants, database version lookup, user/fallback escaping, date labels, link text and tooltip escaping. +* Fixed: Site-updated dates use the WordPress timezone and return no false date when no published item exists. +* Misc: Requires WordPress 6.7+ and PHP 8.0+; the library requires PHP 8.0. No frontend assets are added by the helper UI. +* Misc: Retains Genesis and Beaver compatibility, removes the duplicate Astra loader, legacy Blocks menu and unrelated promotion/Core admin overrides. + += 1.1.0 — 2025-03-15 = + +* Improved: Restored the plugin to a usable state. +* Misc: Moved distribution away from WordPress.org and removed the old DDWlib recommendation installer. +* Misc: Removed the custom translation loader; retained packaged translations. + += 1.0.0 — 2019-09-10 = + +* New: Initial plugin release with 25 general helpers and 5 integration shortcodes. + += 0.9.0 — 2019-09-09 = + +* New: First public beta on GitHub. diff --git a/docs/wiki/CHANGELOG-de.md b/docs/wiki/CHANGELOG-de.md new file mode 100644 index 0000000..16533c5 --- /dev/null +++ b/docs/wiki/CHANGELOG-de.md @@ -0,0 +1,30 @@ +# Änderungsprotokoll + +## 1.2.0 — 01.10.2026 + +- **Neu:** Durchsuchbare Shortcode-Hilfe mit Kopierfunktion und lokalem Generator für Copyright, Veröffentlichungsdatum, Änderungsdatum, Beitragslinks und Inhaltseinbettung. +- **Neu:** deckerweb Plugin Library 0.2.0 und gemeinsamer deckerweb GitHub Release Updater v2 eingebunden. +- **Verbessert:** Einheitliche Wrapper-Prüfung, mehrere CSS-Klassen, boolesche Attribute und bereinigte Textausgabe; Shortcode-Namen, Aliase und Ausgabefilter bleiben erhalten. +- **Verbessert:** Native Blöcke werden gerendert; Builder werden passend zum Inhalt gewählt; direkte und indirekte rekursive Einbettung wird verhindert. +- **Verbessert:** Header/Footer nach Brand Admin Schemes, lokaler Changelog-Dialog, englische/deutsche Dokumentation und aktualisierte deutsche Übersetzungen. +- **Verbessert:** Drei originale skalierbare Icon-/Banner-Entwürfe; Variante A mit dem violetten Hintergrund von B ist ausgewählt. +- **Behoben:** Admin-Registrierung, Rückgabe bereinigter Integrationsdaten und versehentlich registrierter Platzhalter korrigiert. +- **Behoben:** Veröffentlichungsdatum berücksichtigt die Beitrags-ID; Slug-Links werden korrekt aufgelöst; ungültige IDs und unbekannte Zählstatus werden behandelt. +- **Behoben:** Nicht definierte Versionskonstanten, Datenbankversionsabfrage sowie Escaping von Benutzerwerten, Ersatztext, Datumsbeschriftungen, Linktext und Tooltip korrigiert. +- **Behoben:** Website-Änderungsdatum verwendet die WordPress-Zeitzone und liefert bei fehlenden veröffentlichten Inhalten kein falsches Datum. +- **Sonstiges:** Benötigt WordPress 6.7+ und PHP 8.0+; PHP 8.0 wird von der Library vorausgesetzt. Die Helfer-Oberfläche lädt keine Frontend-Ressourcen. +- **Sonstiges:** Genesis und Beaver bleiben erhalten; doppelter Astra-Loader, altes Blocks-Menü und sachfremde Werbe-/Core-Admin-Eingriffe entfernt. + +## 1.1.0 — 2025-03-15 + +- **Verbessert:** Plugin wieder in einen nutzbaren Zustand gebracht. +- **Sonstiges:** Verteilung von WordPress.org zu GitHub verlagert und alten DDWlib-Empfehlungsinstaller entfernt. +- **Sonstiges:** Eigenen Übersetzungsloader entfernt; mitgelieferte Übersetzungen beibehalten. + +## 1.0.0 — 2019-09-10 + +- **Neu:** Erste Veröffentlichung mit 25 allgemeinen Helfern und 5 Integrations-Shortcodes. + +## 0.9.0 — 2019-09-09 + +- **Neu:** Erste öffentliche Beta auf GitHub. diff --git a/docs/wiki/CHANGELOG.md b/docs/wiki/CHANGELOG.md new file mode 100644 index 0000000..3df5a1b --- /dev/null +++ b/docs/wiki/CHANGELOG.md @@ -0,0 +1,30 @@ +# Changelog + +## 1.2.0 — 2026-10-01 + +- **New:** Searchable shortcode help with copy buttons and a local generator for copyright, publication date, modification date, post links and embedded content. +- **New:** Embedded deckerweb Plugin Library 0.2.0 and shared deckerweb GitHub Release Updater v2. +- **Improved:** Unified wrapper validation, multiple CSS classes, boolean attributes and escaped text boundaries; existing shortcode names, aliases and output filters remain. +- **Improved:** Native block rendering, builder-specific routing and guarded content embedding with direct/indirect recursion protection. +- **Improved:** Brand Admin Schemes-style header/footer, local changelog dialog, English/German documentation and German/formal German translations. +- **Improved:** Three original scalable icon/banner concepts; selected concept A uses the purple background from concept B. +- **Fixed:** Corrected admin registration policy, persistent integration normalization and placeholder integration registration. +- **Fixed:** Publication dates honor explicit post IDs; slug links resolve correctly; missing IDs and unknown post-count statuses are handled. +- **Fixed:** Undefined version constants, database version lookup, user/fallback escaping, date labels, link text and tooltip escaping. +- **Fixed:** Site-updated dates use the WordPress timezone and return no false date when no published item exists. +- **Misc:** Requires WordPress 6.7+ and PHP 8.0+; the library requires PHP 8.0. No frontend assets are added by the helper UI. +- **Misc:** Retains Genesis and Beaver compatibility, removes the duplicate Astra loader, legacy Blocks menu and unrelated promotion/Core admin overrides. + +## 1.1.0 — 2025-03-15 + +- **Improved:** Restored the plugin to a usable state. +- **Misc:** Moved distribution away from WordPress.org and removed the old DDWlib recommendation installer. +- **Misc:** Removed the custom translation loader; retained packaged translations. + +## 1.0.0 — 2019-09-10 + +- **New:** Initial plugin release with 25 general helpers and 5 integration shortcodes. + +## 0.9.0 — 2019-09-09 + +- **New:** First public beta on GitHub. diff --git a/docs/wiki/Deutsch.md b/docs/wiki/Deutsch.md new file mode 100644 index 0000000..4d6e0a8 --- /dev/null +++ b/docs/wiki/Deutsch.md @@ -0,0 +1,183 @@ +# Builder Shortcode Extras + +**Kleine dynamische Helfer. Schnell eingesetzt.** + +Copyright, Datumsangaben, Links und wiederverwendbare Inhalte ohne eigenes PHP. Mit einer durchsuchbaren Hilfe und einem kompakten Generator direkt in WordPress. + +Version **1.2.0** · WordPress **6.7+** · PHP **8.0+** · GPL-2.0-or-later + +## Inhalt + +- [Schnell starten](#schnell-starten) +- [Beispiele](#beispiele) +- [Shortcode-Referenz](#shortcode-referenz) +- [FAQ](#faq) + +## Schnell starten + +1. Lade das Test-ZIP hoch: Plugins → Installieren → Plugin hochladen. +2. Aktiviere das Plugin und öffne Werkzeuge → Builder Shortcode Extras. +3. Suche einen Helfer oder konfiguriere einen der fünf Anwendungsfälle. +4. Kopiere den Shortcode in einen Shortcode-Block oder ein geeignetes Builder-Feld. +5. Prüfe die tatsächliche Ausgabe auf deiner Testseite. + +Es gibt keine zusätzlichen BSE-Einstellungen. Die Seite ändert keine Inhalte und speichert keine Generatorwerte. + +## Beispiele + +```text +[bse-copyright first="2019"] +[bse-post-date format="Y-m-d"] +[bse-post-modified-date label="Zuletzt aktualisiert: "] +[bse-post-link id="123" text="Mehr erfahren"] +[bse-item-content id="123"] +``` + +## Shortcode-Referenz + +| Shortcode | Attribute | Voraussetzung | +|---|---|---| +| `bse-nav-menu` | `before`, `after`, `class`, `wrapper`, `menu`, `container`, `container_class`, `container_id`, `menu_class`, `menu_id`, `fallback_cb`, `item_before`, `item_after`, `link_before`, `link_after`, `depth`, `walker`, `theme_location`, `items_wrap`, `item_spacing` | WordPress | +| `bse-item-content` | `id`, `css` | WordPress | +| `bse-comment-form` | `before`, `after`, `class`, `wrapper`, `post_id`, `id_form`, `class_form`, `title_reply`, `title_reply_to`, `cancel_reply_link`, `label_submit` | WordPress | +| `bse-elementor-template` | `id`, `css` | Elementor Free | +| `bse-genesis-footer` | `class`, `wrapper` | Genesis 3.1+ | +| `bse-genesis-breadcrumbs` | `class`, `wrapper` | Genesis 3.1+ | +| `bse-copyright` | `after`, `before`, `copyright`, `first`, `class`, `wrapper` | WordPress | +| `bse-site-title` | `after`, `before`, `class`, `wrapper` | WordPress | +| `bse-site-slogan` | `after`, `before`, `class`, `wrapper` | WordPress | +| `bse-home-link` | `after`, `before`, `text`, `target`, `rel`, `class`, `wrapper` | WordPress | +| `bse-loginout` | `after`, `before`, `login_text`, `logout_text`, `login_target`, `logout_target`, `login_redirect`, `logout_redirect`, `class`, `wrapper` | WordPress | +| `bse-site-updated` | `before`, `after`, `type`, `label_date`, `date_format`, `label_time`, `time_format`, `tooltip`, `class`, `wrapper` | WordPress | +| `bse-post-count` | `post_type`, `status`, `before`, `after`, `class`, `wrapper` | WordPress | +| `bse-post-date` | `after`, `before`, `post_id`, `format`, `label`, `relative_depth`, `class`, `wrapper` | WordPress | +| `bse-post-time` | `after`, `before`, `post_id`, `format`, `label`, `class`, `wrapper` | WordPress | +| `bse-post-modified-date` | `after`, `before`, `post_id`, `format`, `label`, `relative_depth`, `class`, `wrapper` | WordPress | +| `bse-item-last-updated` | `after`, `before`, `post_id`, `format`, `label`, `class`, `wrapper` | WordPress | +| `bse-post-modified-time` | `after`, `before`, `post_id`, `format`, `label`, `class`, `wrapper` | WordPress | +| `bse-post-author` | `after`, `before`, `class`, `wrapper` | WordPress | +| `bse-post-author-link` | `after`, `before`, `target`, `rel`, `class`, `wrapper` | WordPress | +| `bse-post-author-posts-link` | `after`, `before`, `target`, `rel`, `class`, `wrapper` | WordPress | +| `bse-post-tags` | `after`, `before`, `sep`, `class`, `wrapper` | WordPress | +| `bse-post-categories` | `sep`, `before`, `after`, `class`, `wrapper` | WordPress | +| `bse-post-terms` | `after`, `before`, `sep`, `taxonomy`, `class`, `wrapper` | WordPress | +| `bse-post-edit` | `after`, `before`, `label`, `class`, `wrapper` | WordPress | +| `bse-post-link` | `id`, `slug`, `post_type`, `privacy`, `text`, `target`, `rel`, `before`, `after`, `class`, `wrapper` | WordPress | +| `bse-user` | `user_id`, `field`, `default`, `before`, `after`, `class`, `wrapper` | WordPress | +| `bse-userid` | | WordPress | +| `bse-email` | | WordPress | +| `bse-login` | | WordPress | +| `bse-displayname` | | WordPress | +| `bse-firstname` | | WordPress | +| `bse-lastname` | | WordPress | +| `bse-version` | `type`, `plugin`, `constant`, `custom`, `before`, `after`, `class`, `wrapper` | WordPress | +| `bse-wpblock` | `id`, `css` | WordPress synced patterns | +| `bse-astra-layout` | `id`, `css` | Astra Pro custom layouts | + +## FAQ + +### Muss ich das Plugin konfigurieren? + +Nein. Öffne Werkzeuge → Builder Shortcode Extras, um einen Shortcode zu finden oder zu erstellen. Die Seite speichert keine Einstellungen. + +### Wo füge ich einen Shortcode ein? + +In einen WordPress-Shortcode-Block oder ein Builder-Feld mit ausdrücklicher Shortcode-Unterstützung. Beliebige Text-, URL- und HTML-Felder führen Shortcodes nicht automatisch aus. + +### Was kann der Generator erstellen? + +Copyright, Veröffentlichungsdatum, Änderungsdatum, einen Beitrags-/Seiten-Link und Inhaltseinbettungen. Weitere Helfer findest du in der durchsuchbaren Übersicht. + +### Kann ich auf Deutsch suchen? + +Ja. Oberfläche und Aufgabenbeschreibungen folgen der WordPress-Administrationssprache. Shortcode- und Attributnamen bleiben unverändert. + +### Wo finde ich eine Beitrags- oder Vorlagen-ID? + +Öffne den Inhalt in WordPress. Die Zahl hinter post= in der Editor-Adresse ist seine ID. Unterstützte Vorlagenlisten zeigen zusätzlich Shortcode-Beispiele. + +### Auf welchen Beitrag bezieht sich ein Datum? + +Ohne post_id auf den aktuellen WordPress-Beitrag. Gib eine ID für einen anderen Inhalt an. Der Kontext in einer Builder-Vorschau kann von der fertigen Seite abweichen. + +### Gibt es relative Datumsangaben? + +Wähle Relatives Datum im Generator oder verwende format="relative". Die Ausgabe verwendet den vorhandenen Helfer für verstrichene Zeit. + +### Kann ich HTML und Klassen ändern? + +Die meisten Texthelfer unterstützen wrapper, class, before und after. Mehrere Klassen sind möglich. Unsichere oder nicht unterstützte Wrapper werden durch span ersetzt. Diese Version ergänzt keinen Modus ohne Wrapper. + +### Werden Frontend-CSS oder JavaScript geladen? + +Hilfe und Generator laden Ressourcen nur auf ihrer eigenen Admin-Seite. Eingebettete Builder-Vorlagen können Ressourcen ihres Builders laden; das Kommentarformular kann WordPress-Ressourcen benötigen. + +### Warum bleibt eine Inhaltseinbettung leer? + +Prüfe ID, Veröffentlichungsstatus und erforderlichen Builder. Private, unveröffentlichte, gelöschte oder passwortgeschützte Inhalte werden Besuchern ohne erforderlichen Zugriff nicht ausgegeben. Rekursive Referenzen werden gestoppt. + +### Werden native Blöcke unterstützt? + +Ja. Eingebettete Inhalte werden durch die native Block-Ausgabe und Shortcode-Verarbeitung geführt. Die vollständigen Inhaltsfilter des Themes werden bewusst nicht angewendet. + +### Brauche ich Elementor Pro? + +bse-elementor-template wird bei Elementor Free ohne aktives Pro registriert. Der allgemeine Inhaltshelfer kann Elementor-Inhalte rendern, wenn Elementor verfügbar ist. + +### Bleibt Beaver Builder unterstützt? + +Ja. Inhalte mit _fl_builder_enabled verwenden Beavers eigenen Layout-Shortcode. Die Installation von Beaver verändert die Ausgabe normaler Beiträge nicht. Zusätzliche Beaver-Funktionen wurden nicht ergänzt. + +### Bleibt Genesis unterstützt? + +Ja. Die vorhandenen Footer- und Breadcrumb-Helfer bleiben mit Genesis 3.1+ als Kompatibilitätsfunktionen erhalten. + +### Funktionieren synchronisierte Vorlagen mit Classic Editor? + +bse-wpblock ist verfügbar, sobald WordPress den Inhaltstyp wp_block registriert. Das zusätzliche alte Blocks-Menü entfällt. + +### Warum hat der Generator keine Live-Vorschau? + +Dieser erste Generator erstellt die Shortcode-Syntax lokal. Prüfe die tatsächliche Ausgabe auf einer Testseite mit dem vorgesehenen Beitrags- und Builder-Kontext. + +### Wie funktioniert Kopieren ohne HTTPS? + +Zuerst wird die Browser-Zwischenablage versucht, danach eine lokale Kopierfunktion. Ist beides nicht verfügbar, wird der Text zum manuellen Kopieren markiert. Beispiele bleiben ohne JavaScript lesbar. + +### Wie funktionieren Updates? + +Der mitgelieferte deckerweb Updater prüft das öffentliche GitHub-Repository auf neuere stabile Releases über die normale WordPress-Updateverwaltung. Er installiert keine Vorabversionen automatisch und aktiviert keine automatischen Updates. Testversionen installierst du manuell. + +### Was ist die deckerweb Library? + +Sie ergänzt einen deckerweb-Tab unter Plugins → Installieren, ist eingebettet und teilt eine Laufzeit mit anderen deckerweb-Plugins. Unter Einstellungen → deckerweb Library lässt sie sich ausblenden. Der mitgelieferte Katalog enthält freigegebene Releases; BSE RC1 wird dadurch nicht veröffentlicht oder neu in den Katalog aufgenommen. + +### Sendet das Plugin Nutzungsdaten? + +BSE enthält keine Nutzungstelemetrie. Der Updater fragt GitHub-Release-Metadaten ab. Die Library nutzt standardmäßig ihren lokalen Katalog; Online-Katalogupdates erfordern eine ausdrückliche Konfiguration. Server sehen technisch die anfragende IP-Adresse. + +### Was ändert sich für bestehende Installationen? + +Namen, Aliase und Ausgabefilter bleiben. PHP 8.0 ist jetzt erforderlich. Ungültige Wrapper werden zu span, Text wird konsistenter escaped, unzugängliche Inhalte werden nicht eingebettet und die Admin-Registrierung folgt dem Opt-in-Filter. + +### Kann ich Texthelfer im Backend aktivieren? + +Entwickler können über bse/filter/shortcodes_in_admin true zurückgeben. Inhaltseinbettungen, Navigation, Kommentarformulare sowie Builder-/Genesis-Ausgaben bleiben dort ausgeschlossen. REST-Anfragen bleiben für Frontend-Ausgaben verfügbar. + +### Funktioniert das Plugin mit Multisite? + +BSE kann netzwerkweit aktiviert werden. Inhalte beziehen sich auf die jeweilige Website; autorisierte Website-Administratoren können die Werkzeuge öffnen. Die Library besitzt eigene Netzwerkeinstellungen. Ein Generator in der Netzwerkverwaltung wurde nicht ergänzt. + +### Welche Anforderungen und welchen Support gibt es? + +WordPress 6.7+ und PHP 8.0+. Die Library-Installation benötigt ZipArchive. Melde reproduzierbare Fehler auf GitHub mit Plugin-, WordPress-, PHP- und Builder-Version. Kommerzielle Builder wurden mit repräsentativen Testfällen geprüft, nicht mit lizenzierten Installationen. + +## Entwickler + +```php +add_filter( 'bse/filter/shortcodes_in_admin', '__return_true' ); +``` + +Bestehende Filter unter `bse/filter/shortcode_defaults/*` und `bse/filter/shortcode/*` bleiben erhalten. Filterausgaben gelten als vertrauenswürdiger Entwicklercode und können HTML zurückgeben. Die Registrierungsdateien liegen weiterhin nach Themen getrennt unter `includes/shortcodes/`. + +[Changelog](CHANGELOG-de) · [Testanleitung](https://github.com/deckerweb/builder-shortcode-extras/blob/master/docs/TESTING-de.md) diff --git a/docs/wiki/English.md b/docs/wiki/English.md new file mode 100644 index 0000000..e812935 --- /dev/null +++ b/docs/wiki/English.md @@ -0,0 +1,183 @@ +# Builder Shortcode Extras + +**Small dynamic helpers. Ready when you need them.** + +Copyright, dates, links and reusable content without custom PHP. Includes searchable help and a compact generator directly in WordPress. + +Version **1.2.0** · WordPress **6.7+** · PHP **8.0+** · GPL-2.0-or-later + +## Contents + +- [Quick start](#quick-start) +- [Examples](#examples) +- [Shortcode reference](#shortcode-reference) +- [FAQ](#faq) + +## Quick start + +1. Upload the test ZIP through Plugins → Add New → Upload Plugin. +2. Activate it and open Tools → Builder Shortcode Extras. +3. Find a helper or configure one of the five use cases. +4. Copy the shortcode into a Shortcode block or a supported builder field. +5. Check the actual output on your test page. + +There are no additional BSE settings. The page does not edit content or save generator values. + +## Examples + +```text +[bse-copyright first="2019"] +[bse-post-date format="Y-m-d"] +[bse-post-modified-date label="Last updated: "] +[bse-post-link id="123" text="Read more"] +[bse-item-content id="123"] +``` + +## Shortcode reference + +| Shortcode | Attributes | Prerequisite | +|---|---|---| +| `bse-nav-menu` | `before`, `after`, `class`, `wrapper`, `menu`, `container`, `container_class`, `container_id`, `menu_class`, `menu_id`, `fallback_cb`, `item_before`, `item_after`, `link_before`, `link_after`, `depth`, `walker`, `theme_location`, `items_wrap`, `item_spacing` | WordPress | +| `bse-item-content` | `id`, `css` | WordPress | +| `bse-comment-form` | `before`, `after`, `class`, `wrapper`, `post_id`, `id_form`, `class_form`, `title_reply`, `title_reply_to`, `cancel_reply_link`, `label_submit` | WordPress | +| `bse-elementor-template` | `id`, `css` | Elementor Free | +| `bse-genesis-footer` | `class`, `wrapper` | Genesis 3.1+ | +| `bse-genesis-breadcrumbs` | `class`, `wrapper` | Genesis 3.1+ | +| `bse-copyright` | `after`, `before`, `copyright`, `first`, `class`, `wrapper` | WordPress | +| `bse-site-title` | `after`, `before`, `class`, `wrapper` | WordPress | +| `bse-site-slogan` | `after`, `before`, `class`, `wrapper` | WordPress | +| `bse-home-link` | `after`, `before`, `text`, `target`, `rel`, `class`, `wrapper` | WordPress | +| `bse-loginout` | `after`, `before`, `login_text`, `logout_text`, `login_target`, `logout_target`, `login_redirect`, `logout_redirect`, `class`, `wrapper` | WordPress | +| `bse-site-updated` | `before`, `after`, `type`, `label_date`, `date_format`, `label_time`, `time_format`, `tooltip`, `class`, `wrapper` | WordPress | +| `bse-post-count` | `post_type`, `status`, `before`, `after`, `class`, `wrapper` | WordPress | +| `bse-post-date` | `after`, `before`, `post_id`, `format`, `label`, `relative_depth`, `class`, `wrapper` | WordPress | +| `bse-post-time` | `after`, `before`, `post_id`, `format`, `label`, `class`, `wrapper` | WordPress | +| `bse-post-modified-date` | `after`, `before`, `post_id`, `format`, `label`, `relative_depth`, `class`, `wrapper` | WordPress | +| `bse-item-last-updated` | `after`, `before`, `post_id`, `format`, `label`, `class`, `wrapper` | WordPress | +| `bse-post-modified-time` | `after`, `before`, `post_id`, `format`, `label`, `class`, `wrapper` | WordPress | +| `bse-post-author` | `after`, `before`, `class`, `wrapper` | WordPress | +| `bse-post-author-link` | `after`, `before`, `target`, `rel`, `class`, `wrapper` | WordPress | +| `bse-post-author-posts-link` | `after`, `before`, `target`, `rel`, `class`, `wrapper` | WordPress | +| `bse-post-tags` | `after`, `before`, `sep`, `class`, `wrapper` | WordPress | +| `bse-post-categories` | `sep`, `before`, `after`, `class`, `wrapper` | WordPress | +| `bse-post-terms` | `after`, `before`, `sep`, `taxonomy`, `class`, `wrapper` | WordPress | +| `bse-post-edit` | `after`, `before`, `label`, `class`, `wrapper` | WordPress | +| `bse-post-link` | `id`, `slug`, `post_type`, `privacy`, `text`, `target`, `rel`, `before`, `after`, `class`, `wrapper` | WordPress | +| `bse-user` | `user_id`, `field`, `default`, `before`, `after`, `class`, `wrapper` | WordPress | +| `bse-userid` | | WordPress | +| `bse-email` | | WordPress | +| `bse-login` | | WordPress | +| `bse-displayname` | | WordPress | +| `bse-firstname` | | WordPress | +| `bse-lastname` | | WordPress | +| `bse-version` | `type`, `plugin`, `constant`, `custom`, `before`, `after`, `class`, `wrapper` | WordPress | +| `bse-wpblock` | `id`, `css` | WordPress synced patterns | +| `bse-astra-layout` | `id`, `css` | Astra Pro custom layouts | + +## FAQ + +### Do I need to configure the plugin? + +No. Open Tools → Builder Shortcode Extras to find or generate a shortcode. The page does not save settings. + +### Where do I paste a shortcode? + +Use a WordPress Shortcode block or a builder field that explicitly supports shortcodes. Arbitrary text, URL and HTML fields do not necessarily execute them. + +### What can the generator create? + +Copyright, publication date, last-updated date, a post/page link and embedded content. Other helpers remain available in the searchable catalog. + +### Can I search in German? + +Yes. The interface and task descriptions follow your WordPress admin language. Shortcode and attribute names remain unchanged. + +### How do I find a post or template ID? + +Open the item in WordPress. The number after post= in the editor URL is its ID. Supported template list tables also show shortcode examples. + +### Which post does a date refer to? + +Leave post_id empty to use the current WordPress post. Specify an ID for another item. A page-builder preview may have a different context from the final page. + +### Can I use a relative date? + +Choose Relative date in the generator or use format="relative". Relative output uses the existing elapsed-time helper. + +### Can I change the HTML and classes? + +Most text helpers accept wrapper, class, before and after. Multiple classes are supported. Unsafe or unsupported wrapper tags fall back to span. This version does not add a wrapper-free mode. + +### Does it load frontend CSS or JavaScript? + +The help and generator load assets only on their own admin page. Embedded builder templates may load their builder’s own assets, and the comment form may rely on WordPress resources. + +### Why is embedded content empty? + +Check the ID, publication status and required builder. Visitors cannot embed private, draft, trashed or password-protected content without the required access. Recursive references stop with empty output. + +### Are native blocks supported? + +Yes. Embedded content is passed through native block rendering and shortcode processing. Full theme content filters are intentionally not applied. + +### Does Elementor need Pro? + +The bse-elementor-template integration is registered with Elementor Free when Pro is inactive. The generic content helper can route Elementor-built items when Elementor is available. + +### Is Beaver Builder supported? + +Yes. Items with _fl_builder_enabled use Beaver’s own saved-layout shortcode. Merely installing Beaver does not reroute ordinary posts. No additional Beaver features were added. + +### Is Genesis still supported? + +Yes. The existing Genesis footer and breadcrumb helpers remain available with Genesis 3.1+. They are maintained as compatibility features. + +### Are synced patterns supported with Classic Editor? + +The bse-wpblock helper is available whenever WordPress registers the wp_block post type. A legacy extra Blocks menu is no longer added. + +### Why is there no live preview in the generator? + +This first generator constructs valid shortcode syntax locally. View the actual output on a test page with the intended post and builder context. + +### How does copying work without HTTPS? + +The interface tries the browser clipboard, then a local copy fallback. If copying is unavailable, it selects the text for manual copying. Examples remain readable without JavaScript. + +### How do updates work? + +The bundled deckerweb updater checks this public GitHub repository for newer stable releases through the regular WordPress update system. It does not automatically install prereleases or enable automatic updates. Test releases are installed manually. + +### What is the deckerweb Library? + +It adds a deckerweb tab to Plugins → Add New. It is bundled, shares one runtime with other deckerweb plugins, and can be hidden in Settings → deckerweb Library. Its bundled curated catalog installs approved releases; BSE RC1 is not newly published or added to that catalog. + +### Does the plugin send usage data? + +BSE has no usage telemetry. The updater requests GitHub release metadata. The Library uses its bundled catalog by default; online catalog updates require explicit configuration. Servers naturally see the requesting IP address. + +### What changed for existing installations? + +Names, aliases and output filters remain. PHP 8.0 is now required. Invalid wrappers fall back to span, text is escaped more consistently, inaccessible embedded content is withheld, and wp-admin shortcode registration follows its opt-in filter. + +### Can I enable text helpers in wp-admin? + +Developers can return true from bse/filter/shortcodes_in_admin. Content embedding, navigation, comment forms and builder/Genesis rendering remain excluded there. REST requests remain available for frontend rendering. + +### Is multisite supported? + +BSE can be network-activated. Each site has its own content context; the tools page is available to authorized site administrators. The shared Library has its own network settings. A network-admin generator was not added. + +### What are the requirements and support scope? + +WordPress 6.7+ and PHP 8.0+. Library installation requires ZipArchive. Report reproducible issues on GitHub with plugin, WordPress, PHP and builder versions. Commercial builder adapters were checked with representative fixtures, not licensed builder installations. + +## Developers + +```php +add_filter( 'bse/filter/shortcodes_in_admin', '__return_true' ); +``` + +Existing `bse/filter/shortcode_defaults/*` and `bse/filter/shortcode/*` filters remain. Filter output is trusted developer code and may return HTML. Registration files remain grouped by topic under `includes/shortcodes/`. + +[Changelog](CHANGELOG) · [Test guide](https://github.com/deckerweb/builder-shortcode-extras/blob/master/docs/TESTING-de.md) diff --git a/docs/wiki/Home.md b/docs/wiki/Home.md new file mode 100644 index 0000000..0e6f3de --- /dev/null +++ b/docs/wiki/Home.md @@ -0,0 +1,3 @@ +# Builder Shortcode Extras + +[English](English) · [Deutsch](Deutsch) · [Changelog](CHANGELOG) · [Änderungsprotokoll](CHANGELOG-de) diff --git a/docs/wiki/_Sidebar.md b/docs/wiki/_Sidebar.md new file mode 100644 index 0000000..8410cb2 --- /dev/null +++ b/docs/wiki/_Sidebar.md @@ -0,0 +1,6 @@ +## Builder Shortcode Extras + +- [English guide & FAQ](English) +- [Deutsche Anleitung & FAQ](Deutsch) +- [Changelog](CHANGELOG) +- [Änderungsprotokoll](CHANGELOG-de) diff --git a/includes/admin/admin-extras.php b/includes/admin/admin-extras.php index 3a97efd..795b164 100644 --- a/includes/admin/admin-extras.php +++ b/includes/admin/admin-extras.php @@ -12,27 +12,6 @@ } -/** - * Remove unethical Jetpack search results Ads as no one needs these anyway. - * Additionally remove other promotions and Ads from Jetpack. - * - * @link https://wptavern.com/jetpack-7-1-adds-feature-suggestions-to-plugin-search-results#comment-284531 - * - * @since 1.0.0 - */ -add_filter( 'jetpack_show_promotions', '__return_false', 20 ); -add_filter( 'can_display_jetpack_manage_notice', '__return_false', 20 ); -add_filter( 'jetpack_just_in_time_msgs', '__return_false', 20 ); - - -/** - * Remove unethical WooCommerce Ads injections. - * - * @since 1.6.0 - */ -add_filter( 'woocommerce_allow_marketplace_suggestions', '__return_false' ); - - add_filter( 'plugin_row_meta', 'ddw_bse_plugin_links', 10, 2 ); /** * Add various support links to Plugins page. @@ -178,34 +157,3 @@ function ddw_bse_site_health_add_debug_info( $debug_info ) { return $debug_info; } // end function - - -if ( ! function_exists( 'ddw_wp_site_health_remove_percentage' ) ) : - - add_action( 'admin_head', 'ddw_wp_site_health_remove_percentage', 100 ); - /** - * Remove the "Percentage Progress" display in Site Health feature as this will - * get users obsessed with fullfilling a 100% where there are non-problems! - * - * @link https://make.wordpress.org/core/2019/04/25/site-health-check-in-5-2/ - * - * @since 1.0.0 - */ - function ddw_wp_site_health_remove_percentage() { - - /** Bail early if not on WP 5.2+ */ - if ( version_compare( $GLOBALS[ 'wp_version' ], '5.2-beta', '<' ) ) { - return; - } - - ?> - - '' . esc_html__( 'Shortcodes', 'builder-shortcode-extras' ) . '' ) + $links; + } + return $links; + } + public static function assets( $hook ): void { + if ( 'tools_page_builder-shortcode-extras' !== $hook ) { + return; + } + wp_enqueue_style( 'bse-tools', plugins_url( 'assets/admin/tools.css', BSE_PLUGIN_FILE ), array(), BSE_PLUGIN_VERSION ); + wp_enqueue_script( 'bse-tools', plugins_url( 'assets/admin/tools.js', BSE_PLUGIN_FILE ), array(), BSE_PLUGIN_VERSION, true ); + wp_add_inline_script( 'bse-tools', 'window.BSE_TOOLS=' . wp_json_encode( array( + 'catalog' => Catalog::all(), + 'i18n' => array( + 'copied' => __( 'Copied. Paste this shortcode into a shortcode-enabled field.', 'builder-shortcode-extras' ), + 'manual' => __( 'Copy the selected shortcode manually.', 'builder-shortcode-extras' ), + 'required' => __( 'Enter a valid content ID greater than zero.', 'builder-shortcode-extras' ), + 'invalid' => __( 'Use a whole number greater than zero.', 'builder-shortcode-extras' ), + 'quote' => __( 'Attribute values cannot contain quotation marks, square brackets or line breaks.', 'builder-shortcode-extras' ), + ), + ) ) . ';', 'before' ); + } + public static function page(): void { + if ( ! current_user_can( 'manage_options' ) ) { + return; + } + $catalog = Catalog::all(); + ?> +
+

Builder Shortcode Extras

+

+
+
+

+ + +

+ + + + +

+ +
+
+

+ + + +
+ $entry ) { $groups[$entry['group']][$tag] = $entry; } ?> + $entries ) : ?> +

+ $entry ) : ?> +
+

+

+
+ +

+
+
+
+
+
+

+ +
+
Builder Shortcode Extras ' . esc_html__( 'Version', 'builder-shortcode-extras' ) . ' ' . esc_html( BSE_PLUGIN_VERSION ) . ' · ' . esc_html__( 'Changelog', 'builder-shortcode-extras' ) . ' · ' . esc_html__( 'Documentation', 'builder-shortcode-extras' ) . '

' . esc_html__( 'Small dynamic helpers. Ready when you need them.', 'builder-shortcode-extras' ) . '

© 2019–2026 David Decker – DECKERWEB' . esc_html__( 'Plugin website', 'builder-shortcode-extras' ) . '
'; + Changelog::dialog(); + Guide::dialog(); + } +} diff --git a/includes/class-bse-changelog.php b/includes/class-bse-changelog.php new file mode 100644 index 0000000..b986946 --- /dev/null +++ b/includes/class-bse-changelog.php @@ -0,0 +1,83 @@ + 262144 ) { + return ''; + } + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Read one of two fixed local documentation files; no user-supplied paths or remote requests. + $text = file_get_contents( $file ); + if ( false === $text || ! preg_match( '/^== Changelog ==\s*\R(.*?)(?=^== [^\r\n]+ ==\s*$|\z)/ms', $text, $section ) ) { + return ''; + } + $html = ''; + $list = false; + foreach ( preg_split( '/\R/', trim( $section[1] ) ) as $line ) { + $line = trim( $line ); + if ( '' === $line ) { + continue; + } + if ( preg_match( '/^= (.+) =$/', $line, $heading ) ) { + $html .= ( $list ? '' : '' ) . '

' . esc_html( $heading[1] ) . '

'; + $list = false; + } elseif ( str_starts_with( $line, '* ' ) ) { + $html .= ( $list ? '' : '' : '' ) . '

' . esc_html( $line ) . '

'; + $list = false; + } + } + return $html . ( $list ? '' : '' ); + } + + /** + * Dialog markup; the footer link remains usable if content cannot be read. + * + * @return void + */ + public static function dialog(): void { + $content = self::content(); + if ( '' === $content ) { + return; + } + $close = __( 'Close', 'builder-shortcode-extras' ); + echo '

Builder Shortcode Extras · ' . esc_html__( 'Changelog', 'builder-shortcode-extras' ) . '

'; + echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- content() escapes every text node; only fixed heading, list and paragraph markup is emitted. + echo '

' . esc_html( self::is_german() ? 'docs/changelog-de.txt' : 'docs/changelog.txt' ) . '

'; + } +} diff --git a/includes/class-bse-github-updates.php b/includes/class-bse-github-updates.php new file mode 100644 index 0000000..33016e2 --- /dev/null +++ b/includes/class-bse-github-updates.php @@ -0,0 +1,139 @@ +artwork() + ); + } catch ( \InvalidArgumentException $error ) { + // An unsupported directory must not break the admin or login design. + return; + } + $updater->register(); + add_filter( 'http_request_args', array( $this, 'request_limits' ), 20, 2 ); + add_filter( 'upgrader_source_selection', array( $this, 'validate_source' ), 30, 4 ); + } + + /** + * Provide bundled artwork matching the current administrator's language. + * + * @return array> WordPress icon and banner maps. + */ + public function artwork(): array { + $language = 1 === preg_match( '/^de(?:_|$)/i', determine_locale() ) ? 'de-' : ''; + return array( + 'icons' => array( + 'svg' => plugins_url( 'assets/icon.svg', BSE_PLUGIN_FILE ), + '1x' => plugins_url( 'assets/icon-128x128.png', BSE_PLUGIN_FILE ), + '2x' => plugins_url( 'assets/icon-256x256.png', BSE_PLUGIN_FILE ), + ), + 'banners' => array( + 'low' => plugins_url( 'assets/banner-' . $language . '772x250.png', BSE_PLUGIN_FILE ), + 'high' => plugins_url( 'assets/banner-' . $language . '1544x500.png', BSE_PLUGIN_FILE ), + ), + ); + } + + /** + * Bound only this repository's metadata requests; do not change package downloads. + * + * @param array $args WordPress HTTP arguments. + * @param string $url Requested URL. + * @return array + */ + public function request_limits( array $args, string $url ): array { + if ( 'https://api.github.com/repos/deckerweb/builder-shortcode-extras/releases/latest' === $url ) { + $args['limit_response_size'] = 512 * 1024; + $args['timeout'] = 6; + $args['redirection'] = 0; + $args['sslverify'] = true; + $args['reject_unsafe_urls'] = true; + } + return $args; + } + + /** + * Validate the actual candidate before WordPress removes the installed plugin. + * Requirements in installed headers cannot describe a future release reliably. + * + * @param mixed $source Normalized directory or WP_Error from the shared updater. + * @param mixed $remote_source Unused extraction root. + * @param mixed $upgrader Unused WordPress upgrader instance. + * @param array $hook_extra Upgrade context. + * @return mixed Original source or localized WP_Error. + */ + public function validate_source( $source, $remote_source, $upgrader, array $hook_extra ) { + if ( ( $hook_extra['plugin'] ?? '' ) !== plugin_basename( BSE_PLUGIN_FILE ) || ( $hook_extra['type'] ?? '' ) !== 'plugin' || ( $hook_extra['action'] ?? '' ) !== 'update' ) { + return $source; + } + if ( is_wp_error( $source ) ) { + $messages = array( + 'ddw_ghru_filesystem' => __( 'The update filesystem is unavailable. Please try again.', 'builder-shortcode-extras' ), + 'ddw_ghru_archive' => __( 'The GitHub package does not contain the Builder Shortcode Extras plugin file.', 'builder-shortcode-extras' ), + 'ddw_ghru_rename' => __( 'The GitHub package could not be prepared. The installed version has been kept.', 'builder-shortcode-extras' ), + ); + $code = $source->get_error_code(); + return isset( $messages[ $code ] ) ? new \WP_Error( $code, $messages[ $code ], $source->get_error_data( $code ) ) : $source; + } + global $wp_filesystem; + if ( ! is_string( $source ) || ! $wp_filesystem ) { + return new \WP_Error( 'bse_update_source', __( 'The update package could not be checked.', 'builder-shortcode-extras' ) ); + } + $main = trailingslashit( $source ) . basename( BSE_PLUGIN_FILE ); + if ( ! $wp_filesystem->is_file( $main ) || $wp_filesystem->size( $main ) > 1024 * 1024 ) { + return new \WP_Error( 'bse_update_source', __( 'The update package could not be checked.', 'builder-shortcode-extras' ) ); + } + $text = $wp_filesystem->get_contents( $main ); + if ( ! is_string( $text ) ) { + return new \WP_Error( 'bse_update_source', __( 'The update package could not be checked.', 'builder-shortcode-extras' ) ); + } + $headers = array(); + foreach ( array( 'Plugin Name', 'Version', 'Update URI', 'Requires PHP', 'Requires at least' ) as $header ) { + $headers[ $header ] = preg_match( '/^[ \t\/*#@]*' . preg_quote( $header, '/' ) . ':(.*)$/mi', str_replace( "\r", "\n", substr( $text, 0, 8192 ) ), $match ) ? trim( $match[1] ) : ''; + } + if ( 'Builder Shortcode Extras' !== $headers['Plugin Name'] || self::REPOSITORY !== $headers['Update URI'] || ! preg_match( '/^\d+\.\d+\.\d+$/D', $headers['Version'] ) || version_compare( $headers['Version'], BSE_PLUGIN_VERSION, '<=' ) ) { + return new \WP_Error( 'bse_update_identity', __( 'The package identity or version does not match a newer Builder Shortcode Extras release.', 'builder-shortcode-extras' ) ); + } + $current = get_site_transient( 'update_plugins' ); + $expected = is_object( $current ) ? ( $current->response[ plugin_basename( BSE_PLUGIN_FILE ) ]->new_version ?? '' ) : ''; + if ( ! is_string( $expected ) || '' === $expected || $headers['Version'] !== $expected ) { + return new \WP_Error( 'bse_update_version', __( 'The package version differs from the offered update. Please check for updates again.', 'builder-shortcode-extras' ) ); + } + foreach ( array( 'Requires PHP', 'Requires at least' ) as $header ) { + if ( ! preg_match( '/^\d+\.\d+(?:\.\d+)?$/D', $headers[ $header ] ) ) { + return new \WP_Error( 'bse_update_requirements', __( 'The update package has missing or invalid WordPress/PHP requirements.', 'builder-shortcode-extras' ) ); + } + } + if ( ! is_php_version_compatible( $headers['Requires PHP'] ) || ! is_wp_version_compatible( $headers['Requires at least'] ) ) { + return new \WP_Error( 'bse_update_compatibility', __( 'This release requires a newer WordPress or PHP version. The installed version has been kept.', 'builder-shortcode-extras' ) ); + } + return $source; + } +} diff --git a/includes/class-bse-guide.php b/includes/class-bse-guide.php new file mode 100644 index 0000000..9cbb9ed --- /dev/null +++ b/includes/class-bse-guide.php @@ -0,0 +1,60 @@ +' . $match[1] . ''; + }, $line ); + $line = preg_replace( '/\*\*([^*]+)\*\*/', '$1', $line ); + return preg_replace( '/`([^`]+)`/', '$1', $line ); + } + public static function content(): string { + $file = BSE_PLUGIN_DIR . ( Changelog::is_german() ? 'docs/Deutsch.md' : 'docs/English.md' ); + if ( ! is_readable( $file ) || filesize( $file ) > 262144 ) { return ''; } + $html = ''; $code = false; $table = false; $list = false; + foreach ( preg_split( '/\R/', (string) file_get_contents( $file ) ) as $line ) { + if ( str_starts_with( $line, '```' ) ) { + $html .= $code ? '' : ( $list ? '' : '' ) . '
';
+				$code = ! $code; $list = false; continue;
+			}
+			if ( $code ) { $html .= esc_html( $line ) . "\n"; continue; }
+			if ( str_starts_with( $line, '|' ) ) {
+				if ( preg_match( '/^\|[\s|:\-]+\|$/', $line ) ) { continue; }
+				$cell = $table ? 'td' : 'th';
+				if ( ! $table ) { $html .= '
'; $table = true; } + $html .= ''; + foreach ( explode( '|', trim( $line, '| ' ) ) as $value ) { $html .= '<' . $cell . '>' . self::text( trim( $value ) ) . ''; } + $html .= ''; continue; + } + if ( $table ) { $html .= '
'; $table = false; } + if ( str_starts_with( $line, '- ' ) ) { + $html .= ( $list ? '' : '
    ' ) . '
  • ' . self::text( substr( $line, 2 ) ) . '
  • '; $list = true; continue; + } + if ( $list ) { $html .= '
'; $list = false; } + if ( preg_match( '/^(#{1,4}) (.+)$/', $line, $heading ) ) { + $level = min( 4, strlen( $heading[1] ) + 1 ); $html .= '' . self::text( $heading[2] ) . ''; + } elseif ( '' !== trim( $line ) ) { $html .= '

' . self::text( $line ) . '

'; } + } + return $html . ( $code ? '
' : '' ) . ( $table ? '' : '' ) . ( $list ? '' : '' ); + } + public static function dialog(): void { + $content = self::content(); if ( '' === $content ) { return; } + echo '

Builder Shortcode Extras · ' . esc_html__( 'Documentation', 'builder-shortcode-extras' ) . '

'; + echo $content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Fixed markup and escaped local text nodes from content(). + echo '

' . esc_html__( 'Documentation', 'builder-shortcode-extras' ) . '

'; + } +} diff --git a/includes/class-bse-shortcode-catalog.php b/includes/class-bse-shortcode-catalog.php new file mode 100644 index 0000000..ceeb084 --- /dev/null +++ b/includes/class-bse-shortcode-catalog.php @@ -0,0 +1,51 @@ + array( 'label' => __('Navigation menu', 'builder-shortcode-extras' ), 'description' => __('Output an existing WordPress navigation menu.', 'builder-shortcode-extras' ), 'group' => __('More helpers', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-nav-menu]', 'attributes' => array( 'before', 'after', 'class', 'wrapper', 'menu', 'container', 'container_class', 'container_id', 'menu_class', 'menu_id', 'fallback_cb', 'item_before', 'item_after', 'link_before', 'link_after', 'depth', 'walker', 'theme_location', 'items_wrap', 'item_spacing' ), 'fields' => array( ) ), + 'bse-item-content' => array( 'label' => __('Embed content', 'builder-shortcode-extras' ), 'description' => __('Reuse a published item by ID; native blocks and supported builders are rendered.', 'builder-shortcode-extras' ), 'group' => __('Essentials', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-item-content id="123"]', 'attributes' => array( 'id', 'css' ), 'fields' => array( array( 'key' => 'id', 'label' => __('Content ID', 'builder-shortcode-extras' ), 'type' => 'number', 'default' => '', 'options' => array( ) ), array( 'key' => 'css', 'label' => __('Include Elementor CSS', 'builder-shortcode-extras' ), 'type' => 'select', 'default' => 'false', 'options' => array( 'false' => __('No', 'builder-shortcode-extras' ), 'true' => __('Yes', 'builder-shortcode-extras' ) ) ) ) ), + 'bse-comment-form' => array( 'label' => __('Comment form', 'builder-shortcode-extras' ), 'description' => __('Output the WordPress comment form.', 'builder-shortcode-extras' ), 'group' => __('More helpers', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-comment-form]', 'attributes' => array( 'before', 'after', 'class', 'wrapper', 'post_id', 'id_form', 'class_form', 'title_reply', 'title_reply_to', 'cancel_reply_link', 'label_submit' ), 'fields' => array( ) ), + 'bse-elementor-template' => array( 'label' => __('Elementor template', 'builder-shortcode-extras' ), 'description' => __('Embed a saved Elementor template; offered with Elementor Free.', 'builder-shortcode-extras' ), 'group' => __('Integrations', 'builder-shortcode-extras' ), 'integration' => 'Elementor Free', 'example' => '[bse-elementor-template id="123"]', 'attributes' => array( 'id', 'css' ), 'fields' => array( ) ), + 'bse-genesis-footer' => array( 'label' => __('Genesis footer', 'builder-shortcode-extras' ), 'description' => __('The footer text configured in Genesis.', 'builder-shortcode-extras' ), 'group' => __('Integrations', 'builder-shortcode-extras' ), 'integration' => 'Genesis 3.1+', 'example' => '[bse-genesis-footer]', 'attributes' => array( 'class', 'wrapper' ), 'fields' => array( ) ), + 'bse-genesis-breadcrumbs' => array( 'label' => __('Genesis breadcrumbs', 'builder-shortcode-extras' ), 'description' => __('The breadcrumb trail provided by Genesis.', 'builder-shortcode-extras' ), 'group' => __('Integrations', 'builder-shortcode-extras' ), 'integration' => 'Genesis 3.1+', 'example' => '[bse-genesis-breadcrumbs]', 'attributes' => array( 'class', 'wrapper' ), 'fields' => array( ) ), + 'bse-copyright' => array( 'label' => __('Copyright', 'builder-shortcode-extras' ), 'description' => __('A copyright notice with the current year.', 'builder-shortcode-extras' ), 'group' => __('Essentials', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-copyright first="2019"]', 'attributes' => array( 'after', 'before', 'copyright', 'first', 'class', 'wrapper' ), 'fields' => array( array( 'key' => 'first', 'label' => __('First year', 'builder-shortcode-extras' ), 'type' => 'number', 'default' => '', 'options' => array( ) ), array( 'key' => 'before', 'label' => __('Text before', 'builder-shortcode-extras' ), 'type' => 'text', 'default' => '', 'options' => array( ) ), array( 'key' => 'after', 'label' => __('Text after', 'builder-shortcode-extras' ), 'type' => 'text', 'default' => '', 'options' => array( ) ), array( 'key' => 'class', 'label' => __('CSS classes (optional)', 'builder-shortcode-extras' ), 'type' => 'text', 'default' => '', 'options' => array() ), array( 'key' => 'wrapper', 'label' => __('Wrapper', 'builder-shortcode-extras' ), 'type' => 'select', 'default' => 'span', 'options' => array( 'span' => 'span', 'p' => 'p', 'div' => 'div', 'small' => 'small' ) ) ) ), + 'bse-site-title' => array( 'label' => __('Site title', 'builder-shortcode-extras' ), 'description' => __('The name of your website.', 'builder-shortcode-extras' ), 'group' => __('Essentials', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-site-title]', 'attributes' => array( 'after', 'before', 'class', 'wrapper' ), 'fields' => array( ) ), + 'bse-site-slogan' => array( 'label' => __('Site tagline', 'builder-shortcode-extras' ), 'description' => __('The tagline of your website.', 'builder-shortcode-extras' ), 'group' => __('Essentials', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-site-slogan]', 'attributes' => array( 'after', 'before', 'class', 'wrapper' ), 'fields' => array( ) ), + 'bse-home-link' => array( 'label' => __('Homepage link', 'builder-shortcode-extras' ), 'description' => __('A link to your homepage.', 'builder-shortcode-extras' ), 'group' => __('Essentials', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-home-link]', 'attributes' => array( 'after', 'before', 'text', 'target', 'rel', 'class', 'wrapper' ), 'fields' => array( ) ), + 'bse-loginout' => array( 'label' => __('Login / logout link', 'builder-shortcode-extras' ), 'description' => __('A link matching the visitor’s login state.', 'builder-shortcode-extras' ), 'group' => __('More helpers', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-loginout]', 'attributes' => array( 'after', 'before', 'login_text', 'logout_text', 'login_target', 'logout_target', 'login_redirect', 'logout_redirect', 'class', 'wrapper' ), 'fields' => array( ) ), + 'bse-site-updated' => array( 'label' => __('Website updated', 'builder-shortcode-extras' ), 'description' => __('The earliest or latest modification of published content.', 'builder-shortcode-extras' ), 'group' => __('Essentials', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-site-updated]', 'attributes' => array( 'before', 'after', 'type', 'label_date', 'date_format', 'label_time', 'time_format', 'tooltip', 'class', 'wrapper' ), 'fields' => array( ) ), + 'bse-post-count' => array( 'label' => __('Post count', 'builder-shortcode-extras' ), 'description' => __('Count content by post type and status.', 'builder-shortcode-extras' ), 'group' => __('More helpers', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-post-count]', 'attributes' => array( 'post_type', 'status', 'before', 'after', 'class', 'wrapper' ), 'fields' => array( ) ), + 'bse-post-date' => array( 'label' => __('Publication date', 'builder-shortcode-extras' ), 'description' => __('The publication date of the current or selected post.', 'builder-shortcode-extras' ), 'group' => __('Essentials', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-post-date]', 'attributes' => array( 'after', 'before', 'post_id', 'format', 'label', 'relative_depth', 'class', 'wrapper' ), 'fields' => array( array( 'key' => 'post_id', 'label' => __('Post ID (optional)', 'builder-shortcode-extras' ), 'type' => 'number', 'default' => '', 'options' => array( ) ), array( 'key' => 'format', 'label' => __('Date format', 'builder-shortcode-extras' ), 'type' => 'select', 'default' => '', 'options' => array( '' => __('Website default', 'builder-shortcode-extras' ), 'd.m.Y' => __('31.12.2026', 'builder-shortcode-extras' ), 'F j, Y' => __('December 31, 2026', 'builder-shortcode-extras' ), 'Y-m-d' => __('2026-12-31', 'builder-shortcode-extras' ), 'relative' => __('Relative date', 'builder-shortcode-extras' ) ) ), array( 'key' => 'label', 'label' => __('Label', 'builder-shortcode-extras' ), 'type' => 'text', 'default' => '', 'options' => array( ) ), array( 'key' => 'class', 'label' => __('CSS classes (optional)', 'builder-shortcode-extras' ), 'type' => 'text', 'default' => '', 'options' => array() ), array( 'key' => 'wrapper', 'label' => __('Wrapper', 'builder-shortcode-extras' ), 'type' => 'select', 'default' => 'span', 'options' => array( 'span' => 'span', 'p' => 'p', 'div' => 'div', 'small' => 'small' ) ) ) ), + 'bse-post-time' => array( 'label' => __('Publication time', 'builder-shortcode-extras' ), 'description' => __('The publication time of the current or selected post.', 'builder-shortcode-extras' ), 'group' => __('More helpers', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-post-time]', 'attributes' => array( 'after', 'before', 'post_id', 'format', 'label', 'class', 'wrapper' ), 'fields' => array( ) ), + 'bse-post-modified-date' => array( 'label' => __('Last updated', 'builder-shortcode-extras' ), 'description' => __('The modification date of the current or selected post.', 'builder-shortcode-extras' ), 'group' => __('Essentials', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-post-modified-date]', 'attributes' => array( 'after', 'before', 'post_id', 'format', 'label', 'relative_depth', 'class', 'wrapper' ), 'fields' => array( array( 'key' => 'post_id', 'label' => __('Post ID (optional)', 'builder-shortcode-extras' ), 'type' => 'number', 'default' => '', 'options' => array( ) ), array( 'key' => 'format', 'label' => __('Date format', 'builder-shortcode-extras' ), 'type' => 'select', 'default' => '', 'options' => array( '' => __('Website default', 'builder-shortcode-extras' ), 'd.m.Y' => __('31.12.2026', 'builder-shortcode-extras' ), 'F j, Y' => __('December 31, 2026', 'builder-shortcode-extras' ), 'Y-m-d' => __('2026-12-31', 'builder-shortcode-extras' ), 'relative' => __('Relative date', 'builder-shortcode-extras' ) ) ), array( 'key' => 'label', 'label' => __('Label', 'builder-shortcode-extras' ), 'type' => 'text', 'default' => '', 'options' => array( ) ), array( 'key' => 'class', 'label' => __('CSS classes (optional)', 'builder-shortcode-extras' ), 'type' => 'text', 'default' => '', 'options' => array() ), array( 'key' => 'wrapper', 'label' => __('Wrapper', 'builder-shortcode-extras' ), 'type' => 'select', 'default' => 'span', 'options' => array( 'span' => 'span', 'p' => 'p', 'div' => 'div', 'small' => 'small' ) ) ) ), + 'bse-item-last-updated' => array( 'label' => __('Last updated (alias)', 'builder-shortcode-extras' ), 'description' => __('An alias for bse-post-modified-date.', 'builder-shortcode-extras' ), 'group' => __('More helpers', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-item-last-updated]', 'attributes' => array( 'after', 'before', 'post_id', 'format', 'label', 'class', 'wrapper' ), 'fields' => array( ) ), + 'bse-post-modified-time' => array( 'label' => __('Last updated time', 'builder-shortcode-extras' ), 'description' => __('The modification time of the current or selected post.', 'builder-shortcode-extras' ), 'group' => __('More helpers', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-post-modified-time]', 'attributes' => array( 'after', 'before', 'post_id', 'format', 'label', 'class', 'wrapper' ), 'fields' => array( ) ), + 'bse-post-author' => array( 'label' => __('Post author', 'builder-shortcode-extras' ), 'description' => __('The author’s display name.', 'builder-shortcode-extras' ), 'group' => __('More helpers', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-post-author]', 'attributes' => array( 'after', 'before', 'class', 'wrapper' ), 'fields' => array( ) ), + 'bse-post-author-link' => array( 'label' => __('Author website link', 'builder-shortcode-extras' ), 'description' => __('The author’s name linked to their website.', 'builder-shortcode-extras' ), 'group' => __('More helpers', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-post-author-link]', 'attributes' => array( 'after', 'before', 'target', 'rel', 'class', 'wrapper' ), 'fields' => array( ) ), + 'bse-post-author-posts-link' => array( 'label' => __('Author archive link', 'builder-shortcode-extras' ), 'description' => __('The author’s name linked to their archive.', 'builder-shortcode-extras' ), 'group' => __('More helpers', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-post-author-posts-link]', 'attributes' => array( 'after', 'before', 'target', 'rel', 'class', 'wrapper' ), 'fields' => array( ) ), + 'bse-post-tags' => array( 'label' => __('Post tags', 'builder-shortcode-extras' ), 'description' => __('The tags assigned to the current post.', 'builder-shortcode-extras' ), 'group' => __('More helpers', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-post-tags]', 'attributes' => array( 'after', 'before', 'sep', 'class', 'wrapper' ), 'fields' => array( ) ), + 'bse-post-categories' => array( 'label' => __('Post categories', 'builder-shortcode-extras' ), 'description' => __('The categories assigned to the current post.', 'builder-shortcode-extras' ), 'group' => __('More helpers', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-post-categories]', 'attributes' => array( 'sep', 'before', 'after', 'class', 'wrapper' ), 'fields' => array( ) ), + 'bse-post-terms' => array( 'label' => __('Taxonomy terms', 'builder-shortcode-extras' ), 'description' => __('Terms from a selected taxonomy.', 'builder-shortcode-extras' ), 'group' => __('More helpers', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-post-terms]', 'attributes' => array( 'after', 'before', 'sep', 'taxonomy', 'class', 'wrapper' ), 'fields' => array( ) ), + 'bse-post-edit' => array( 'label' => __('Edit post link', 'builder-shortcode-extras' ), 'description' => __('An edit link for visitors with editing permission.', 'builder-shortcode-extras' ), 'group' => __('More helpers', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-post-edit]', 'attributes' => array( 'after', 'before', 'label', 'class', 'wrapper' ), 'fields' => array( ) ), + 'bse-post-link' => array( 'label' => __('Post / page link', 'builder-shortcode-extras' ), 'description' => __('Link to an item by ID or slug, or to the privacy policy.', 'builder-shortcode-extras' ), 'group' => __('Essentials', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-post-link id="123"]', 'attributes' => array( 'id', 'slug', 'post_type', 'privacy', 'text', 'target', 'rel', 'before', 'after', 'class', 'wrapper' ), 'fields' => array( array( 'key' => 'id', 'label' => __('Post ID', 'builder-shortcode-extras' ), 'type' => 'number', 'default' => '', 'options' => array( ) ), array( 'key' => 'text', 'label' => __('Link text (optional)', 'builder-shortcode-extras' ), 'type' => 'text', 'default' => '', 'options' => array( ) ), array( 'key' => 'class', 'label' => __('CSS classes (optional)', 'builder-shortcode-extras' ), 'type' => 'text', 'default' => '', 'options' => array() ), array( 'key' => 'wrapper', 'label' => __('Wrapper', 'builder-shortcode-extras' ), 'type' => 'select', 'default' => 'span', 'options' => array( 'span' => 'span', 'p' => 'p', 'div' => 'div', 'small' => 'small' ) ) ) ), + 'bse-user' => array( 'label' => __('User information', 'builder-shortcode-extras' ), 'description' => __('A selected user field with an optional fallback.', 'builder-shortcode-extras' ), 'group' => __('More helpers', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-user]', 'attributes' => array( 'user_id', 'field', 'default', 'before', 'after', 'class', 'wrapper' ), 'fields' => array( ) ), + 'bse-userid' => array( 'label' => __('Current user ID', 'builder-shortcode-extras' ), 'description' => __('The ID of the signed-in user.', 'builder-shortcode-extras' ), 'group' => __('More helpers', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-userid]', 'attributes' => array( ), 'fields' => array( ) ), + 'bse-email' => array( 'label' => __('Current user email', 'builder-shortcode-extras' ), 'description' => __('The email address of the signed-in user.', 'builder-shortcode-extras' ), 'group' => __('More helpers', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-email]', 'attributes' => array( ), 'fields' => array( ) ), + 'bse-login' => array( 'label' => __('Current user login', 'builder-shortcode-extras' ), 'description' => __('The login name of the signed-in user.', 'builder-shortcode-extras' ), 'group' => __('More helpers', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-login]', 'attributes' => array( ), 'fields' => array( ) ), + 'bse-displayname' => array( 'label' => __('Current user name', 'builder-shortcode-extras' ), 'description' => __('The display name of the signed-in user.', 'builder-shortcode-extras' ), 'group' => __('More helpers', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-displayname]', 'attributes' => array( ), 'fields' => array( ) ), + 'bse-firstname' => array( 'label' => __('Current user first name', 'builder-shortcode-extras' ), 'description' => __('The first name of the signed-in user.', 'builder-shortcode-extras' ), 'group' => __('More helpers', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-firstname]', 'attributes' => array( ), 'fields' => array( ) ), + 'bse-lastname' => array( 'label' => __('Current user last name', 'builder-shortcode-extras' ), 'description' => __('The last name of the signed-in user.', 'builder-shortcode-extras' ), 'group' => __('More helpers', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-lastname]', 'attributes' => array( ), 'fields' => array( ) ), + 'bse-version' => array( 'label' => __('Version information', 'builder-shortcode-extras' ), 'description' => __('Technical version information for documentation and support.', 'builder-shortcode-extras' ), 'group' => __('More helpers', 'builder-shortcode-extras' ), 'integration' => '', 'example' => '[bse-version]', 'attributes' => array( 'type', 'plugin', 'constant', 'custom', 'before', 'after', 'class', 'wrapper' ), 'fields' => array( ) ), + 'bse-wpblock' => array( 'label' => __('Synced pattern', 'builder-shortcode-extras' ), 'description' => __('Embed a WordPress synced pattern by ID.', 'builder-shortcode-extras' ), 'group' => __('Integrations', 'builder-shortcode-extras' ), 'integration' => 'WordPress synced patterns', 'example' => '[bse-wpblock id="123"]', 'attributes' => array( 'id', 'css' ), 'fields' => array( ) ), + 'bse-astra-layout' => array( 'label' => __('Astra layout', 'builder-shortcode-extras' ), 'description' => __('Embed an Astra Pro custom layout by ID.', 'builder-shortcode-extras' ), 'group' => __('Integrations', 'builder-shortcode-extras' ), 'integration' => 'Astra Pro custom layouts', 'example' => '[bse-astra-layout id="123"]', 'attributes' => array( 'id', 'css' ), 'fields' => array( ) ), + ); + $order = array( 'bse-copyright', 'bse-post-date', 'bse-post-modified-date', 'bse-post-link', 'bse-item-content' ); + $first = array(); + foreach ( $order as $tag ) { $first[$tag] = $catalog[$tag]; } + return $first + $catalog; + } +} diff --git a/includes/deckerweb-github-release-updater-v2.php b/includes/deckerweb-github-release-updater-v2.php new file mode 100644 index 0000000..2a78956 --- /dev/null +++ b/includes/deckerweb-github-release-updater-v2.php @@ -0,0 +1,332 @@ + + */ + private array $icons; + /** + * Validated banner URLs, keyed by low/high resolution. + * + * @var array + */ + private array $banners; + /** + * Validate the installed plugin basename and repository identity. + * + * @param string $main_file Absolute path to the main plugin file. + * @param string $repository_url Public https://github.com/owner/repository URL. + * @param string $name Plugin display name. + * @param string $description Short description for update details. + * @param array $artwork Optional icons (svg, 1x, 2x, default) and banners (low, high) URL maps. + * @throws \InvalidArgumentException If the slug or repository URL is invalid. + */ + public function __construct( string $main_file, string $repository_url, string $name, string $description, array $artwork = array() ) { + $this->file = \plugin_basename( $main_file ); + $this->slug = \dirname( $this->file ); + if ( '.' === $this->slug || ! \preg_match( '/^[a-z0-9_-]+$/D', $this->slug ) ) { + throw new \InvalidArgumentException( 'The plugin must be installed in a stable slug directory.' ); + } + if ( ! \preg_match( '~^https://github\.com/([A-Za-z0-9-]+)/([A-Za-z0-9._-]+)/?$~D', $repository_url, $matches ) ) { + throw new \InvalidArgumentException( 'Invalid public GitHub repository URL.' ); + } + $this->repo = 'https://github.com/' . $matches[1] . '/' . $matches[2]; + $this->api = 'https://api.github.com/repos/' . $matches[1] . '/' . $matches[2]; + $this->cache = 'ddw_ghru_' . \substr( \md5( $this->repo ), 0, 24 ); + $this->name = $name; + $this->description = $description; + $this->icons = $this->artwork_urls( $artwork['icons'] ?? array(), array( 'svg', '1x', '2x', 'default' ) ); + $this->banners = $this->artwork_urls( $artwork['banners'] ?? array(), array( 'low', 'high' ) ); + } + + /** + * Accept only known artwork keys and absolute HTTP(S) URLs without credentials. + * Values come from the integrating plugin, never from release metadata. + * No image fetch, remote probing or filesystem access is performed here. + * + * @param mixed $urls Optional URL map. + * @param array $keys Supported WordPress artwork keys. + * @return array Safe URLs. + */ + private function artwork_urls( $urls, array $keys ): array { + $result = array(); + if ( ! is_array( $urls ) ) { + return $result; + } + foreach ( $keys as $key ) { + if ( ! isset( $urls[ $key ] ) || ! is_string( $urls[ $key ] ) ) { + continue; + } + $url = $urls[ $key ]; + $parts = \wp_parse_url( $url ); + if ( ! is_array( $parts ) || ! in_array( $parts['scheme'] ?? '', array( 'https', 'http' ), true ) || empty( $parts['host'] ) || isset( $parts['user'] ) || isset( $parts['pass'] ) || preg_match( '/[\x00-\x20\x7f]/', $url ) ) { + continue; + } + $clean = \esc_url_raw( $url, array( 'https', 'http' ) ); + if ( '' !== $clean ) { + $result[ $key ] = $clean; + } + } + return $result; + } + + /** + * Add icons to an existing cached offer for this plugin only. + * Preserve cache timestamps, package fields and unrelated plugin objects. + * + * @param mixed $transient WordPress update transient. + * @return mixed Decorated copy or original value. + */ + public function cached_icons( $transient ) { + if ( ! $this->icons || ! is_object( $transient ) || ! isset( $transient->response[ $this->file ] ) || ! is_object( $transient->response[ $this->file ] ) ) { + return $transient; + } + $transient = clone $transient; + $transient->response[ $this->file ] = clone $transient->response[ $this->file ]; + $transient->response[ $this->file ]->icons = $this->icons; + return $transient; + } + + /** + * Register the update, information, and extracted archive hooks. + */ + public function register(): void { + \add_filter( 'update_plugins_github.com', array( $this, 'update' ), 10, 4 ); + \add_filter( 'site_transient_update_plugins', array( $this, 'cached_icons' ) ); + \add_filter( 'plugins_api', array( $this, 'information' ), 20, 3 ); + \add_filter( 'upgrader_source_selection', array( $this, 'select_source' ), 20, 4 ); + } + + /** + * Fetch the latest published release and choose its installable ZIP. + * Caches successful responses for 30 minutes and failures for 10 minutes. + * + * @return array{version:string,package:string,notes:string,published:string}|null + */ + private function release(): ?array { + $cached = \get_site_transient( $this->cache ); + if ( \is_array( $cached ) ) { + return ! empty( $cached['failed'] ) ? null : $cached; + } + $response = \wp_remote_get( + $this->api . '/releases/latest', + array( + 'timeout' => 6, + 'redirection' => 2, + 'headers' => array( + 'Accept' => 'application/vnd.github+json', + 'User-Agent' => 'DECKERWEB-WordPress-GitHub-Release-Updater', + 'X-GitHub-Api-Version' => '2022-11-28', + ), + ) + ); + if ( \is_wp_error( $response ) || \wp_remote_retrieve_response_code( $response ) !== 200 ) { + \set_site_transient( $this->cache, array( 'failed' => true ), 10 * MINUTE_IN_SECONDS ); + return null; + } + $data = \json_decode( \wp_remote_retrieve_body( $response ), true ); + if ( ! \is_array( $data ) || ! empty( $data['draft'] ) || ! empty( $data['prerelease'] ) || ! \is_string( $data['tag_name'] ?? null ) || ! \preg_match( '/^v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)$/D', $data['tag_name'], $match ) ) { + \set_site_transient( $this->cache, array( 'failed' => true ), 10 * MINUTE_IN_SECONDS ); + return null; + } + // Prefer a release asset with the stable slug; otherwise use the source archive. + $version = $match[1]; + $package = ''; + foreach ( ( \is_array( $data['assets'] ?? null ) ? $data['assets'] : array() ) as $asset ) { + if ( ! \is_array( $asset ) || ( $asset['state'] ?? 'uploaded' ) !== 'uploaded' ) { + continue; + } + $asset_name = \is_string( $asset['name'] ?? null ) ? $asset['name'] : ''; + if ( $asset_name !== $this->slug . '.zip' && $asset_name !== $this->slug . '-' . $version . '.zip' ) { + continue; + } + $url = \is_string( $asset['browser_download_url'] ?? null ) ? $asset['browser_download_url'] : ''; + if ( \strpos( $url, $this->repo . '/releases/download/' ) === 0 ) { + $package = $url; + break; + } + } + if ( ! $package ) { + $url = \is_string( $data['zipball_url'] ?? null ) ? $data['zipball_url'] : ''; + if ( \strpos( $url, $this->api . '/zipball/' ) === 0 ) { + $package = $url; + } + } + if ( ! $package ) { + \set_site_transient( $this->cache, array( 'failed' => true ), 10 * MINUTE_IN_SECONDS ); + return null; + } + $release = array( + 'version' => $version, + 'package' => $package, + 'notes' => \is_string( $data['body'] ?? null ) ? $data['body'] : '', + 'published' => \is_string( $data['published_at'] ?? null ) ? $data['published_at'] : '', + ); + \set_site_transient( $this->cache, $release, 30 * MINUTE_IN_SECONDS ); + return $release; + } + + /** + * Provide an update only for this plugin and its matching Update URI. + * + * @param mixed $response Current WordPress update response. + * @param array $plugin_data Installed plugin headers. + * @param string $plugin_file Plugin basename supplied by WordPress. + * @param mixed $locales Requested locales, unused. + * @return mixed Existing response or WordPress update data. + */ + public function update( $response, $plugin_data, $plugin_file, $locales ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed -- WordPress hostname filter signature. + // The hostname hook may run for multiple GitHub plugins on the same site. + if ( $plugin_file !== $this->file || ( $plugin_data['UpdateURI'] ?? '' ) !== $this->repo ) { + return $response; + } + $release = $this->release(); + if ( ! $release || \version_compare( $release['version'], (string) ( $plugin_data['Version'] ?? '0' ), '<=' ) ) { + return $response; + } + return array( + 'id' => $this->repo, + 'slug' => $this->slug, + 'version' => $release['version'], + 'url' => $this->repo . '/releases', + 'package' => $release['package'], + 'requires' => (string) ( $plugin_data['RequiresWP'] ?? '' ), + 'requires_php' => (string) ( $plugin_data['RequiresPHP'] ?? '' ), + 'icons' => $this->icons, + ); + } + + /** + * Populate the WordPress plugin details modal from release metadata. + * + * @param mixed $response Current plugin information response. + * @param string $action WordPress plugins API action. + * @param mixed $args Request arguments. + * @return mixed Existing response or plugin information object. + */ + public function information( $response, $action, $args ) { + if ( 'plugin_information' !== $action || ! \is_object( $args ) || ( $args->slug ?? '' ) !== $this->slug ) { + return $response; + } + $release = $this->release(); + if ( ! $release ) { + return $response; + } + return (object) array( + 'name' => $this->name, + 'slug' => $this->slug, + 'version' => $release['version'], + 'author' => 'David Decker – DECKERWEB', + 'author_profile' => 'https://github.com/deckerweb', + 'homepage' => $this->repo, + 'last_updated' => $release['published'], + 'download_link' => $release['package'], + 'icons' => $this->icons, + 'banners' => $this->banners, + 'sections' => array( + 'description' => \esc_html( $this->description ), + 'changelog' => \wpautop( \esc_html( ! empty( $release['notes'] ) ? $release['notes'] : 'See the release on GitHub.' ) ), + ), + ); + } + + /** + * Verify and normalize the extracted plugin directory before installation. + * + * @param mixed $source Path to extracted archive, or a WP_Error. + * @param mixed $remote_source Remote source path, unused. + * @param mixed $upgrader WordPress upgrader instance, unused. + * @param array $hook_extra Upgrade context. + * @return mixed Valid source directory or WP_Error. + */ + public function select_source( $source, $remote_source, $upgrader, $hook_extra ) { + if ( \is_wp_error( $source ) || ! \is_string( $source ) || ( $hook_extra['plugin'] ?? '' ) !== $this->file || ( $hook_extra['type'] ?? '' ) !== 'plugin' || ( $hook_extra['action'] ?? '' ) !== 'update' ) { + return $source; + } + global $wp_filesystem; + if ( ! $wp_filesystem ) { + return new \WP_Error( 'ddw_ghru_filesystem', 'Could not access the update filesystem.' ); + } + $root = \untrailingslashit( $source ); + $main = \basename( $this->file ); + // Accept an asset's slug folder; a GitHub source ZIP needs renaming. + // Refuse archives missing the expected main file before WordPress replaces anything. + if ( $wp_filesystem->is_file( $root . '/' . $this->slug . '/' . $main ) ) { + return \trailingslashit( $root . '/' . $this->slug ); + } + if ( ! $wp_filesystem->is_file( $root . '/' . $main ) ) { + return new \WP_Error( 'ddw_ghru_archive', 'GitHub release does not contain the plugin main file.' ); + } + $target = \dirname( $root ) . '/' . $this->slug; + if ( $root === $target ) { + return $source; + } + if ( $wp_filesystem->exists( $target ) || ! $wp_filesystem->move( $root, $target, false ) ) { + return new \WP_Error( 'ddw_ghru_rename', 'Could not prepare the GitHub release package.' ); + } + return \trailingslashit( $target ); + } +} + diff --git a/includes/deckerweb-plugin-library/assets/icons/advanced-scripts-quicknav.png b/includes/deckerweb-plugin-library/assets/icons/advanced-scripts-quicknav.png new file mode 100644 index 0000000..1436de3 Binary files /dev/null and b/includes/deckerweb-plugin-library/assets/icons/advanced-scripts-quicknav.png differ diff --git a/includes/deckerweb-plugin-library/assets/icons/brand-admin-schemes.png b/includes/deckerweb-plugin-library/assets/icons/brand-admin-schemes.png new file mode 100644 index 0000000..3109ef8 Binary files /dev/null and b/includes/deckerweb-plugin-library/assets/icons/brand-admin-schemes.png differ diff --git a/includes/deckerweb-plugin-library/assets/icons/breakdance-quicknav.png b/includes/deckerweb-plugin-library/assets/icons/breakdance-quicknav.png new file mode 100644 index 0000000..cee05b2 Binary files /dev/null and b/includes/deckerweb-plugin-library/assets/icons/breakdance-quicknav.png differ diff --git a/includes/deckerweb-plugin-library/assets/icons/bricks-quicknav.png b/includes/deckerweb-plugin-library/assets/icons/bricks-quicknav.png new file mode 100644 index 0000000..6ac3016 Binary files /dev/null and b/includes/deckerweb-plugin-library/assets/icons/bricks-quicknav.png differ diff --git a/includes/deckerweb-plugin-library/assets/icons/builder-list-pages.png b/includes/deckerweb-plugin-library/assets/icons/builder-list-pages.png new file mode 100644 index 0000000..0298404 Binary files /dev/null and b/includes/deckerweb-plugin-library/assets/icons/builder-list-pages.png differ diff --git a/includes/deckerweb-plugin-library/assets/icons/daily-scripture.png b/includes/deckerweb-plugin-library/assets/icons/daily-scripture.png new file mode 100644 index 0000000..56b2498 Binary files /dev/null and b/includes/deckerweb-plugin-library/assets/icons/daily-scripture.png differ diff --git a/includes/deckerweb-plugin-library/assets/icons/quick-edit-featured-image.png b/includes/deckerweb-plugin-library/assets/icons/quick-edit-featured-image.png new file mode 100644 index 0000000..7a4cfea Binary files /dev/null and b/includes/deckerweb-plugin-library/assets/icons/quick-edit-featured-image.png differ diff --git a/includes/deckerweb-plugin-library/assets/library.css b/includes/deckerweb-plugin-library/assets/library.css new file mode 100644 index 0000000..0a30c6d --- /dev/null +++ b/includes/deckerweb-plugin-library/assets/library.css @@ -0,0 +1,33 @@ +/* Scoped to the deckerweb tab; uses WordPress admin accent colors. */ +.plugin-install-tab-deckerweb .search-form.search-plugins{display:none} +.dwl-install .wrap{max-width:900px;margin:40px auto;padding:0 24px} +.dwl-install .dwl-action{display:inline-block;margin:0 12px 0 0} +#deckerweb-library{margin:20px 0;color:#1d2327} +#deckerweb-library *{box-sizing:border-box} +#deckerweb-library .dwl-heading{display:flex;justify-content:space-between;align-items:baseline;gap:20px;flex-wrap:wrap;margin-bottom:20px} +#deckerweb-library h2{margin:0;font-size:22px;font-weight:400} +#deckerweb-library .dwl-heading p{margin:6px 0 0;color:#50575e} +#deckerweb-library .dwl-filter{display:flex;align-items:center;gap:18px;flex-wrap:wrap;margin-bottom:20px} +#deckerweb-library .dwl-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,340px),1fr));gap:20px} +#deckerweb-library .dwl-card{background:#fff;border:1px solid #c3c4c7;display:flex;flex-direction:column} +#deckerweb-library .dwl-body{padding:22px;flex:1} +#deckerweb-library .dwl-title{display:flex;align-items:flex-start;gap:16px} +#deckerweb-library .dwl-title h3{margin:0 0 4px;font-size:18px;line-height:1.35} +#deckerweb-library .dwl-icon{width:54px;height:54px;flex-shrink:0;background:#f0f6fc;color:var(--wp-admin-theme-color,#2271b1);display:flex;align-items:center;justify-content:center;font-size:23px;font-weight:600} +#deckerweb-library .dwl-meta{font-size:12px;color:#50575e} +#deckerweb-library .dwl-active{display:inline-block;color:#135e3b;padding:5px 0;font-weight:600} +#deckerweb-library .dwl-requirements{margin-top:16px;padding:10px 12px;background:#f6f7f7;border-left:3px solid var(--wp-admin-theme-color,#2271b1)} +#deckerweb-library .dwl-requirements ul{margin:0 0 6px} +#deckerweb-library .dwl-details{margin-top:18px} +#deckerweb-library .dwl-details summary{color:var(--wp-admin-theme-color,#2271b1);cursor:pointer} +#deckerweb-library .dwl-details p{margin:8px 0} +#deckerweb-library footer{display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;padding:12px 22px;background:#f6f7f7;border-top:1px solid #dcdcde;color:#50575e} +#deckerweb-library .dwl-bottom{margin-top:22px;display:flex;gap:22px;flex-wrap:wrap;align-items:center;color:#50575e} +#deckerweb-library .dwl-action{display:inline-block;margin:0} +#deckerweb-library .notice{margin:12px 0} +@media(max-width:600px){#deckerweb-library .dwl-body{padding:16px}#deckerweb-library .dwl-filter{align-items:flex-start;flex-direction:column}#deckerweb-library .dwl-filter>label{max-width:100%}#deckerweb-library .dwl-filter>label:first-of-type{width:100%}#deckerweb-library .dwl-filter input[type=search]{margin:0;min-width:0;max-width:100%;width:100%}} + +#deckerweb-library .dwl-icon img{width:54px;height:54px;object-fit:contain} +#deckerweb-library .dwl-icon-fallback{display:flex;align-items:center;justify-content:center;width:100%;height:100%;color:#536273} +#deckerweb-library .dwl-stars{display:flex;flex-wrap:wrap;gap:8px;align-items:center} +#deckerweb-library .dwl-stars a{text-decoration:none;color:#50575e} diff --git a/includes/deckerweb-plugin-library/bootstrap.php b/includes/deckerweb-plugin-library/bootstrap.php new file mode 100644 index 0000000..503f936 --- /dev/null +++ b/includes/deckerweb-plugin-library/bootstrap.php @@ -0,0 +1,39 @@ + $version, 'dir' => $library_dir, 'host' => $plugin_file, 'config' => $config, + ]; + if ( empty( $GLOBALS['deckerweb_library_election_v1'] ) ) { + $GLOBALS['deckerweb_library_election_v1'] = true; + add_action( 'plugins_loaded', 'deckerweb_library_elect_v1', PHP_INT_MAX ); + } + } + + /** One runtime per request, independent of host plugin load order. */ + function deckerweb_library_elect_v1(): void { + if ( ! empty( $GLOBALS['deckerweb_library_runtime_v1'] ) ) { return; } + $candidates = $GLOBALS['deckerweb_library_candidates_v1'] ?? []; + if ( ! $candidates ) { return; } + usort( $candidates, static function( array $a, array $b ): int { + $comparison = version_compare( $b['version'], $a['version'] ); + return $comparison ?: strcmp( $a['host'], $b['host'] ); + } ); + $chosen = $candidates[0]; + $factory = require $chosen['dir'] . '/runtime.php'; + $GLOBALS['deckerweb_library_runtime_v1'] = $factory( $chosen, $candidates ); + } +} diff --git a/includes/deckerweb-plugin-library/catalog.json b/includes/deckerweb-plugin-library/catalog.json new file mode 100644 index 0000000..bf5e950 --- /dev/null +++ b/includes/deckerweb-plugin-library/catalog.json @@ -0,0 +1,225 @@ +{ + "schema_version": 1, + "plugins": [ + { + "approved": true, + "slug": "brand-admin-schemes", + "name": "Brand Admin Schemes", + "description": "Bring your brand colors to the WordPress admin, login, toolbar and browser tabs.", + "description_de": "Deine Markenfarben für WordPress-Admin, Login, Toolbar und Browser-Tabs.", + "version": "0.16.3", + "repository": "deckerweb/brand-admin-schemes", + "plugin_file": "brand-admin-schemes/brand-admin-schemes.php", + "download_url": "https://github.com/deckerweb/brand-admin-schemes/releases/download/v0.16.3/brand-admin-schemes-0.16.3.zip", + "sha256": "efbe8d2b9fec014dcbd3f1dd00ab8e661f50f3a432495f2e9202d11fe74c469c", + "requires_wp": "6.4", + "requires_php": "8.0", + "category": "Admin", + "dependencies": [], + "github_stars": 1, + "stars_checked_at": "2026-10-01", + "icon_label": "BA", + "icon_background": "#edf3f0", + "icon": "assets/icons/brand-admin-schemes.png" + }, + { + "approved": true, + "slug": "breakdance-quicknav", + "name": "Breakdance QuickNav", + "description": "Quick access to Breakdance templates, headers, footers, global blocks and settings from the toolbar.", + "description_de": "Schneller Zugriff auf Breakdance-Templates, Header, Footer, globale Blöcke und Einstellungen über die Toolbar.", + "version": "1.0.0", + "repository": "deckerweb/breakdance-quicknav", + "plugin_file": "breakdance-quicknav/breakdance-quicknav.php", + "download_url": "https://github.com/deckerweb/breakdance-quicknav/releases/download/v1.0.0/breakdance-quicknav.zip", + "sha256": "8c319c59a25da2ea500eb7691fa0e1c3b7b335f6cc0afffd211e3efae5f60b71", + "requires_wp": "6.7", + "requires_php": "7.4", + "category": "Breakdance", + "dependencies": [ + { + "name": "Breakdance Builder", + "plugin_file": "breakdance/plugin.php", + "min_version": "", + "detector": "breakdance", + "url": "https://breakdance.com/" + } + ], + "github_stars": 3, + "stars_checked_at": "2026-10-01", + "icon": "assets/icons/breakdance-quicknav.png", + "icon_label": "QN", + "icon_background": "#f0f3f6" + }, + { + "approved": true, + "slug": "daily-scripture", + "name": "Daily Scripture", + "description": "Daily Bible verses, flexible layouts, editor integrations and a compact dashboard widget.", + "description_de": "Tägliche Bibelverse, flexible Layouts, Editor-Integrationen und ein kompaktes Dashboard-Widget.", + "version": "0.16.4", + "repository": "deckerweb/daily-scripture", + "plugin_file": "daily-scripture/daily-scripture.php", + "download_url": "https://github.com/deckerweb/daily-scripture/releases/download/v0.16.4/daily-scripture-0.16.4.zip", + "sha256": "b702f85c0ce06bf3ac4352be82b1a6296c73d36aa83a61668f5050af5cd0dca4", + "requires_wp": "6.6", + "requires_php": "8.0", + "category": "Content", + "dependencies": [], + "github_stars": 1, + "stars_checked_at": "2026-10-01", + "icon": "assets/icons/daily-scripture.png", + "icon_label": "QN", + "icon_background": "#f0f3f6", + "network_activation": false + }, + { + "approved": true, + "slug": "bricks-quicknav", + "name": "Bricks QuickNav", + "description": "Quick access to Bricks templates, pages and settings from the toolbar.", + "description_de": "Schneller Zugriff auf Bricks-Templates, Seiten und Einstellungen über die Toolbar.", + "version": "1.0.0", + "repository": "deckerweb/bricks-quicknav", + "plugin_file": "bricks-quicknav/bricks-quicknav.php", + "download_url": "https://github.com/deckerweb/bricks-quicknav/releases/download/v1.0.0/bricks-quicknav.zip", + "sha256": "23a6bc16a4023783790efebc2a07ed2e59d11f5eadf32f6a19919981e4c8b33d", + "requires_wp": "6.7", + "requires_php": "7.4", + "category": "Bricks", + "dependencies": [ + { + "name": "Bricks Builder", + "plugin_file": "bricks/theme.php", + "min_version": "", + "detector": "bricks", + "url": "https://bricksbuilder.io/" + } + ], + "github_stars": 7, + "stars_checked_at": "2026-10-01", + "icon": "assets/icons/bricks-quicknav.png", + "icon_label": "QN", + "icon_background": "#f0f3f6" + }, + { + "approved": true, + "slug": "oxygen-quicknav", + "name": "Oxygen QuickNav", + "description": "Quick access to templates, components and settings for Oxygen 6 and newer.", + "description_de": "Schneller Zugriff auf Templates, Komponenten und Einstellungen für Oxygen 6 und neuer.", + "version": "1.0.0", + "repository": "deckerweb/oxygen-quicknav", + "plugin_file": "oxygen-quicknav/oxygen-quicknav.php", + "download_url": "https://github.com/deckerweb/oxygen-quicknav/releases/download/v1.0.0/oxygen-quicknav.zip", + "sha256": "3cd3a70d578d75fb5786b54e9dc5df60771cd63d9785234fe12eba19d218be3c", + "requires_wp": "6.7", + "requires_php": "7.4", + "category": "Oxygen", + "dependencies": [ + { + "name": "Oxygen Builder 6+", + "plugin_file": "oxygen/plugin.php", + "min_version": "", + "detector": "oxygen", + "url": "https://oxygenbuilder.com/" + } + ], + "github_stars": 1, + "stars_checked_at": "2026-10-01", + "icon_label": "OQ", + "icon_background": "#eef1f8" + }, + { + "approved": true, + "slug": "advanced-scripts-quicknav", + "name": "Advanced Scripts QuickNav", + "description": "Reach your Advanced Scripts snippets and tools directly from the toolbar.", + "description_de": "Advanced-Scripts-Snippets und Werkzeuge direkt über die Toolbar erreichen.", + "version": "1.1.0", + "repository": "deckerweb/advanced-scripts-quicknav", + "plugin_file": "advanced-scripts-quicknav/advanced-scripts-quicknav.php", + "download_url": "https://github.com/deckerweb/advanced-scripts-quicknav/releases/download/v1.1.0/advanced-scripts-quicknav.zip", + "sha256": "33678b2fcd310f21463c1e04bdd9f8b591008e28651a4dca129b5a7022ca2eba", + "requires_wp": "6.7", + "requires_php": "7.4", + "category": "Scripts", + "dependencies": [ + { + "name": "Advanced Scripts", + "plugin_file": "advanced-scripts/advanced-scripts.php", + "min_version": "", + "detector": "advanced_scripts", + "url": "https://cleanplugins.com/" + } + ], + "github_stars": 1, + "stars_checked_at": "2026-10-01", + "icon": "assets/icons/advanced-scripts-quicknav.png", + "icon_label": "QN", + "icon_background": "#f0f3f6" + }, + { + "approved": true, + "slug": "quick-edit-featured-image", + "name": "Quick Edit Featured Image", + "description": "Set or replace featured images directly in Quick Edit.", + "description_de": "Beitragsbilder direkt über Quick Edit setzen oder austauschen.", + "version": "1.3.0", + "repository": "deckerweb/quick-edit-featured-image", + "plugin_file": "quick-edit-featured-image/quick-edit-featured-image.php", + "download_url": "https://github.com/deckerweb/quick-edit-featured-image/releases/download/v1.3.0/quick-edit-featured-image.zip", + "sha256": "ffb0ecd37a16b73c93316ec4154c1d36bdf4d0a9f3b1b6c81762ff1a8e42d9b5", + "requires_wp": "6.7", + "requires_php": "7.4", + "category": "Content", + "dependencies": [], + "github_stars": 4, + "stars_checked_at": "2026-10-01", + "icon": "assets/icons/quick-edit-featured-image.png", + "icon_label": "QN", + "icon_background": "#f0f3f6" + }, + { + "approved": true, + "slug": "shortcode-item-updated", + "name": "Shortcode Item Updated", + "description": "Show the last updated date and time of posts or other content through a shortcode.", + "description_de": "Datum und Uhrzeit der letzten Inhaltsänderung per Shortcode anzeigen.", + "version": "2.2.0", + "repository": "deckerweb/shortcode-item-updated", + "plugin_file": "shortcode-item-updated/shortcode-item-updated.php", + "download_url": "https://github.com/deckerweb/shortcode-item-updated/releases/download/v2.2.0/shortcode-item-updated.zip", + "sha256": "a300cb4dc9c437b1aa126e7148266ea52f080072ed60565fee5117d608776d4a", + "requires_wp": "6.7", + "requires_php": "7.4", + "category": "Content", + "dependencies": [], + "github_stars": 4, + "stars_checked_at": "2026-10-01", + "icon_label": "SU", + "icon_background": "#f4f0e9" + }, + { + "approved": true, + "slug": "builder-list-pages", + "name": "Builder List Pages", + "description": "Find pages and other content edited with your page builder through filtered list views.", + "description_de": "Mit deinem Page Builder bearbeitete Seiten und Inhalte über gefilterte Listen finden.", + "version": "1.0.0", + "repository": "deckerweb/builder-list-pages", + "plugin_file": "builder-list-pages/builder-list-pages.php", + "download_url": "https://github.com/deckerweb/builder-list-pages/releases/download/v1.0.0/builder-list-pages.zip", + "sha256": "7a858fee0d6430707c15bf92e3bff4a4f56539f3d240a2ac2c0598f072a840d4", + "requires_wp": "6.7", + "requires_php": "7.4", + "category": "Builder", + "dependencies": [], + "github_stars": 3, + "stars_checked_at": "2026-10-01", + "icon": "assets/icons/builder-list-pages.png", + "icon_label": "QN", + "icon_background": "#f0f3f6" + } + ] +} diff --git a/includes/deckerweb-plugin-library/runtime.php b/includes/deckerweb-plugin-library/runtime.php new file mode 100644 index 0000000..a7ef494 --- /dev/null +++ b/includes/deckerweb-plugin-library/runtime.php @@ -0,0 +1,11 @@ +register(); + return $runtime; +}; diff --git a/includes/deckerweb-plugin-library/src/Catalog.php b/includes/deckerweb-plugin-library/src/Catalog.php new file mode 100644 index 0000000..60c1810 --- /dev/null +++ b/includes/deckerweb-plugin-library/src/Catalog.php @@ -0,0 +1,114 @@ +dir = $dir; } + + /** Only first-party catalog endpoints; HTTP redirects are deliberately disabled. */ + public static function trusted_source( string $url ): bool { + $p = wp_parse_url( $url ); + if ( ! is_array( $p ) || ( $p['scheme'] ?? '' ) !== 'https' || isset( $p['user'], $p['pass'] ) || isset( $p['port'] ) || isset( $p['query'] ) || isset( $p['fragment'] ) ) { return false; } + if ( isset( $p['user'] ) || isset( $p['pass'] ) ) { return false; } + $host = strtolower( $p['host'] ?? '' ); + $path = $p['path'] ?? ''; + if ( strpos( $path, '..' ) !== false || strpos( $path, '%' ) !== false ) { return false; } + return ( in_array( $host, [ 'deckerweb.de', 'www.deckerweb.de' ], true ) && preg_match( '~^/[a-zA-Z0-9_./-]+\.json$~D', $path ) ) + || ( $host === 'raw.githubusercontent.com' && preg_match( '~^/deckerweb/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_./-]+\.json$~D', $path ) ); + } + + public static function download_url( string $url, string $repo ): bool { + return (bool) preg_match( '~^https://github\.com/' . preg_quote( $repo, '~' ) . '/releases/download/[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+\.zip$~D', $url ); + } + + /** Validate the whole document atomically, including every dependency. */ + public static function validate( $data ) { + if ( ! is_array( $data ) || ( $data['schema_version'] ?? null ) !== 1 || ! isset( $data['plugins'] ) || ! is_array( $data['plugins'] ) || count( $data['plugins'] ) > 100 ) { + return new \WP_Error( 'dwl_catalog', 'Invalid catalog schema.' ); + } + $result = []; + foreach ( $data['plugins'] as $entry ) { + if ( ! is_array( $entry ) || ! isset( $entry['approved'] ) || ! is_bool( $entry['approved'] ) ) { return new \WP_Error( 'dwl_catalog', 'Invalid approval flag.' ); } + if ( ! $entry['approved'] ) { continue; } + foreach ( [ 'slug', 'name', 'description', 'version', 'repository', 'plugin_file', 'download_url', 'sha256', 'requires_wp', 'requires_php' ] as $field ) { + if ( ! isset( $entry[$field] ) || ! is_string( $entry[$field] ) || strlen( $entry[$field] ) > 2000 ) { return new \WP_Error( 'dwl_catalog', 'Invalid catalog field: ' . $field ); } + } + $slug = $entry['slug']; + if ( ! preg_match( '/^[a-z0-9]+(?:-[a-z0-9]+)*$/D', $slug ) || isset( $result[$slug] ) + || ! preg_match( '~^deckerweb/[a-zA-Z0-9_.-]+$~D', $entry['repository'] ) + || ! preg_match( '~^' . preg_quote( $slug, '~' ) . '/[a-zA-Z0-9_-]+\.php$~D', $entry['plugin_file'] ) + || ! self::download_url( $entry['download_url'], $entry['repository'] ) + || ! preg_match( '/^[a-f0-9]{64}$/D', $entry['sha256'] ) + || ! preg_match( '/^\d+\.\d+(?:\.\d+)?(?:-[a-zA-Z0-9.-]+)?$/D', $entry['version'] ) ) { + return new \WP_Error( 'dwl_catalog', 'Invalid identity or release metadata.' ); + } + foreach ( [ 'requires_wp', 'requires_php' ] as $field ) { + if ( ! preg_match( '/^\d+\.\d+(?:\.\d+)?$/D', $entry[$field] ) ) { return new \WP_Error( 'dwl_catalog', 'Invalid platform version.' ); } + } + if ( trim( $entry['name'] ) === '' || trim( $entry['description'] ) === '' ) { return new \WP_Error( 'dwl_catalog', 'Missing display text.' ); } + $dependencies = $entry['dependencies'] ?? []; + if ( ! is_array( $dependencies ) || count( $dependencies ) > 10 ) { return new \WP_Error( 'dwl_catalog', 'Invalid dependencies.' ); } + foreach ( $dependencies as $d ) { + if ( ! is_array( $d ) || ! is_string( $d['name'] ?? null ) || strlen( $d['name'] ) > 150 + || ! is_string( $d['plugin_file'] ?? null ) || ! preg_match( '~^[a-z0-9-]+/[a-zA-Z0-9_-]+\.php$~D', $d['plugin_file'] ) + || ! is_string( $d['min_version'] ?? null ) || ! preg_match( '/^(?:\d+\.\d+(?:\.\d+)?)?$/D', $d['min_version'] ) + || ! in_array( $d['detector'] ?? '', [ '', 'breakdance', 'bricks', 'oxygen', 'advanced_scripts' ], true ) ) { return new \WP_Error( 'dwl_catalog', 'Invalid dependency metadata.' ); } + if ( isset( $d['url'] ) && ( ! is_string( $d['url'] ) || ! preg_match( '~^https://(?:breakdance\.com|bricksbuilder\.io|oxygenbuilder\.com|cleanplugins\.com|github\.com/deckerweb|deckerweb\.de)/[a-zA-Z0-9_./-]*$~D', $d['url'] ) ) ) { return new \WP_Error( 'dwl_catalog', 'Invalid dependency URL.' ); } + } + foreach ( [ 'name_de', 'description_de', 'category' ] as $field ) { + if ( isset( $entry[$field] ) && ( ! is_string( $entry[$field] ) || strlen( $entry[$field] ) > 2000 ) ) { return new \WP_Error( 'dwl_catalog', 'Invalid display field.' ); } + } + + if ( isset( $entry['icon'] ) && ( ! is_string( $entry['icon'] ) || ! preg_match( '~^assets/icons/[a-z0-9-]+\.png$~D', $entry['icon'] ) ) ) { return new \WP_Error( 'dwl_catalog', 'Invalid local icon.' ); } + if ( isset( $entry['icon_label'] ) && ( ! is_string( $entry['icon_label'] ) || ! preg_match( '/^[A-Z0-9]{1,3}$/D', $entry['icon_label'] ) ) ) { return new \WP_Error( 'dwl_catalog', 'Invalid icon label.' ); } + if ( isset( $entry['icon_background'] ) && ( ! is_string( $entry['icon_background'] ) || ! preg_match( '/^#[a-f0-9]{6}$/D', $entry['icon_background'] ) ) ) { return new \WP_Error( 'dwl_catalog', 'Invalid icon color.' ); } + if ( isset( $entry['github_stars'] ) && ( ! is_int( $entry['github_stars'] ) || $entry['github_stars'] < 0 ) ) { return new \WP_Error( 'dwl_catalog', 'Invalid star count.' ); } + if ( isset( $entry['stars_checked_at'] ) && ( ! is_string( $entry['stars_checked_at'] ) || ! preg_match( '/^\d{4}-\d{2}-\d{2}$/D', $entry['stars_checked_at'] ) ) ) { return new \WP_Error( 'dwl_catalog', 'Invalid stars date.' ); } + if ( isset( $entry['network_activation'] ) && ! is_bool( $entry['network_activation'] ) ) { return new \WP_Error( 'dwl_catalog', 'Invalid network policy.' ); } + $entry['dependencies'] = $dependencies; + $result[$slug] = $entry; + } + return $result; + } + + /** Cached reads are cheap. Mutations can require a successful fresh remote read. */ + public function entries( bool $fresh = false ) { + $settings = Library::settings(); + $url = $settings['catalog_url']; + if ( $settings['online'] && ! self::trusted_source( $url ) ) { + if ( $fresh ) { return new \WP_Error( 'dwl_source', Library::t( 'The online catalog source is not trusted.', 'Die Online-Katalogquelle ist nicht vertrauenswürdig.' ) ); } + $this->status = 'offline'; + } + if ( $settings['online'] && self::trusted_source( $url ) ) { + $key = 'dwl_catalog_' . md5( $url ); + $cache = get_site_transient( $key ); + if ( ! $fresh && is_array( $cache ) && isset( $cache['data'] ) ) { + $this->status = 'online'; + return self::validate( $cache['data'] ); + } + if ( $fresh || ! get_site_transient( $key . '_retry' ) ) { + $response = wp_safe_remote_get( $url, [ 'timeout' => 6, 'redirection' => 0, 'limit_response_size' => 262145, 'headers' => [ 'Accept' => 'application/json' ] ] ); + $body = is_wp_error( $response ) ? '' : wp_remote_retrieve_body( $response ); + $data = json_decode( $body, true ); + $valid = self::validate( $data ); + if ( ! is_wp_error( $response ) && wp_remote_retrieve_response_code( $response ) === 200 && strlen( $body ) <= 262144 && ! is_wp_error( $valid ) ) { + set_site_transient( $key, [ 'data' => $data ], 12 * HOUR_IN_SECONDS ); + set_site_transient( $key . '_last', [ 'data' => $data ], 48 * HOUR_IN_SECONDS ); + delete_site_transient( $key . '_retry' ); + $this->status = 'online'; + return $valid; + } + set_site_transient( $key . '_retry', true, 15 * MINUTE_IN_SECONDS ); + } + if ( $fresh ) { return new \WP_Error( 'dwl_offline', Library::t( 'The catalog could not be verified. Please try again later.', 'Der Katalog konnte nicht geprüft werden. Bitte später erneut versuchen.' ) ); } + $last = get_site_transient( $key . '_last' ); + $this->status = 'offline'; + if ( is_array( $last ) && isset( $last['data'] ) ) { return self::validate( $last['data'] ); } + } + $data = json_decode( (string) file_get_contents( $this->dir . '/catalog.json' ), true ); + return self::validate( $data ); + } +} diff --git a/includes/deckerweb-plugin-library/src/Library.php b/includes/deckerweb-plugin-library/src/Library.php new file mode 100644 index 0000000..30b30a4 --- /dev/null +++ b/includes/deckerweb-plugin-library/src/Library.php @@ -0,0 +1,309 @@ +chosen = $chosen; $this->hosts = $hosts; + $this->catalog = new Catalog( $chosen['dir'] ); + } + + /** English source and a small built-in German dictionary, independent of host domains. */ + public static function t( string $en, string $de ): string { + return str_starts_with( determine_locale(), 'de' ) ? $de : $en; + } + public static function settings(): array { + $saved = get_site_option( self::OPTION, [] ); + $saved = is_array( $saved ) ? $saved : []; + return [ 'enabled' => ! isset( $saved['enabled'] ) || ! empty( $saved['enabled'] ), 'online' => ! empty( $saved['online'] ), 'catalog_url' => is_string( $saved['catalog_url'] ?? null ) ? $saved['catalog_url'] : '' ]; + } + public function register(): void { + add_filter( 'install_plugins_tabs', [ $this, 'tabs' ] ); + add_action( 'install_plugins_deckerweb', [ $this, 'render' ] ); + add_action( 'admin_notices', [ $this, 'notice' ] ); + add_action( 'network_admin_notices', [ $this, 'notice' ] ); + add_action( 'admin_enqueue_scripts', [ $this, 'assets' ] ); + add_action( 'admin_menu', [ $this, 'menu' ] ); + add_action( 'network_admin_menu', [ $this, 'menu' ] ); + add_action( 'admin_post_dwl_preferences', [ $this, 'preferences' ] ); + add_action( 'admin_post_dwl_dismiss', [ $this, 'dismiss' ] ); + add_action( 'admin_post_dwl_install', [ $this, 'install' ] ); + add_action( 'admin_post_dwl_activate', [ $this, 'activate' ] ); + add_action( 'admin_post_dwl_refresh', [ $this, 'refresh' ] ); + add_action( 'activate_plugin', [ $this, 'activation_guard' ], 0, 2 ); + add_filter( 'pre_set_site_transient_update_plugins', [ $this, 'updates' ] ); + add_filter( 'upgrader_pre_download', [ $this, 'update_download' ], 10, 4 ); + foreach ( $this->hosts as $host ) { + add_filter( 'plugin_action_links_' . plugin_basename( $host['host'] ), [ $this, 'links' ] ); + add_filter( 'network_admin_plugin_action_links_' . plugin_basename( $host['host'] ), [ $this, 'links' ] ); + } + } + public function links( array $links ): array { + if ( current_user_can( 'install_plugins' ) ) { $links['deckerweb_library'] = '' . esc_html( self::t( 'More by deckerweb', 'Mehr von deckerweb' ) ) . ''; } + return $links; + } + public function tabs( array $tabs ): array { + if ( self::settings()['enabled'] && current_user_can( 'install_plugins' ) ) { $tabs['deckerweb'] = 'deckerweb'; } + return $tabs; + } + public function assets( string $hook ): void { + if ( ! current_user_can( 'install_plugins' ) ) { return; } + if ( $hook === 'plugin-install.php' && ( $_GET['tab'] ?? '' ) === 'deckerweb' ) { + wp_enqueue_style( 'deckerweb-plugin-library', plugins_url( 'assets/library.css', $this->chosen['dir'] . '/bootstrap.php' ), [], self::VERSION ); + } + } + public function notice(): void { + global $pagenow; + if ( $pagenow !== 'plugin-install.php' || ( $_GET['tab'] ?? '' ) === 'deckerweb' || ! self::settings()['enabled'] || ! current_user_can( 'install_plugins' ) || get_user_meta( get_current_user_id(), 'deckerweb_library_intro_seen_v1', true ) ) { return; } + // Once per administrator, not once per request and not once per host plugin. + update_user_meta( get_current_user_id(), 'deckerweb_library_intro_seen_v1', 1 ); + echo '

' . esc_html( self::t( 'Discover more plugins by deckerweb', 'Weitere Plugins von deckerweb entdecken' ) ) . '
' . esc_html( self::t( 'You already use one of my plugins. Find selected additions in the deckerweb tab.', 'Du nutzt bereits ein Plugin von mir. Ausgewählte Ergänzungen findest du im deckerweb-Tab.' ) ) . ' ' . esc_html( self::t( 'View plugins', 'Plugins ansehen' ) ) . '

'; + $this->form( 'dismiss', '', self::t( 'Dismiss permanently', 'Dauerhaft schließen' ), 'button-link', [ 'location' => 'installer' ] ); + echo '

'; + } + public function menu(): void { + $cap = is_network_admin() ? 'manage_network_options' : 'manage_options'; + if ( is_multisite() && ! is_network_admin() ) { return; } + add_submenu_page( is_network_admin() ? 'settings.php' : 'options-general.php', 'deckerweb Library', 'deckerweb Library', $cap, 'deckerweb-library', [ $this, 'settings_page' ] ); + } + private function catalog_url(): string { return ( $this->network ? network_admin_url( 'plugin-install.php?tab=deckerweb' ) : self_admin_url( 'plugin-install.php?tab=deckerweb' ) ); } + private function settings_url(): string { return is_multisite() ? network_admin_url( 'settings.php?page=deckerweb-library' ) : admin_url( 'options-general.php?page=deckerweb-library' ); } + private function action_url( string $action, string $slug = '' ): string { + return add_query_arg( [ 'action' => 'dwl_' . $action, 'slug' => $slug, 'network' => ( is_network_admin() || $this->network ) ? '1' : '0' ], admin_url( 'admin-post.php' ) ); + } + private function form( string $action, string $slug, string $label, string $class = 'button', array $extra = [] ): void { + echo '
'; + wp_nonce_field( 'dwl_' . $action . '_' . $slug ); + foreach ( $extra as $key => $value ) { echo ''; } + echo '
'; + } + private static function text( array $entry, string $key ): string { + return str_starts_with( determine_locale(), 'de' ) && isset( $entry[$key . '_de'] ) ? $entry[$key . '_de'] : $entry[$key]; + } + + private function icon( array $entry ): string { + // Only bundled PNG files; catalog metadata never triggers remote image requests. + $file = $entry['icon'] ?? ''; + if ( $file && is_file( $this->chosen['dir'] . '/' . $file ) ) { + return ''; + } + return '' . esc_html( $entry['icon_label'] ?? 'DW' ) . ''; + } + public function render(): void { + if ( ! current_user_can( 'install_plugins' ) ) { wp_die( esc_html( self::t( 'Permission denied.', 'Keine Berechtigung.' ) ), '', [ 'response' => 403 ] ); } + if ( ! self::settings()['enabled'] ) { + echo '

' . esc_html( self::t( 'The catalog is hidden.', 'Der Katalog ist ausgeblendet.' ) ) . ' ' . esc_html( self::t( 'Library settings', 'Library-Einstellungen' ) ) . '

'; return; + } + update_user_meta( get_current_user_id(), 'deckerweb_library_intro_seen_v1', 1 ); + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + $entries = $this->catalog->entries(); + if ( is_wp_error( $entries ) ) { echo '

' . esc_html( $entries->get_error_message() ) . '

'; return; } + $plugins = get_plugins(); + $search = isset( $_GET['dwl_search'] ) && is_string( $_GET['dwl_search'] ) ? sanitize_text_field( wp_unslash( $_GET['dwl_search'] ) ) : ''; + $compatible = ! empty( $_GET['dwl_compatible'] ); + echo '

' . esc_html( self::t( 'Small plugins. Practical improvements.', 'Kleine Plugins. Konkrete Verbesserungen.' ) ) . '

' . esc_html( self::t( 'Selected by deckerweb · Installed from GitHub releases', 'Von deckerweb ausgewählt · Installation aus GitHub-Releases' ) ) . '

' . esc_html( self::t( 'Library settings', 'Library-Einstellungen' ) ) . '
'; + if ( isset( $_GET['dwl_done'] ) ) { echo '

' . esc_html( self::t( 'Done. The plugin is active.', 'Erledigt. Das Plugin ist aktiv.' ) ) . '

'; } + if ( $this->catalog->status === 'offline' ) { echo '

' . esc_html( self::t( 'The online catalog is unavailable. Showing cached or bundled entries; online approval is checked again before installation.', 'Der Online-Katalog ist nicht erreichbar. Angezeigt wird der gespeicherte oder mitgelieferte Katalog; vor der Installation wird die Online-Freigabe erneut geprüft.' ) ) . '

'; } + echo '
'; + echo '
'; $count = 0; + foreach ( $entries as $entry ) { + $name = self::text( $entry, 'name' ); $description = self::text( $entry, 'description' ); + $issues = Requirements::check( $entry, $plugins ); + if ( $search !== '' && stripos( remove_accents( $name . ' ' . $description ), remove_accents( $search ) ) === false ) { continue; } + if ( $compatible && $issues ) { continue; } + $count++; + $installed = isset( $plugins[$entry['plugin_file']] ); + $active = $installed && ( is_network_admin() ? is_plugin_active_for_network( $entry['plugin_file'] ) : is_plugin_active( $entry['plugin_file'] ) ); + echo '

' . esc_html( $name ) . '

deckerweb · ' . esc_html( $entry['category'] ?? 'WordPress' ) . '

' . esc_html( $description ) . '

'; + + if ( isset( $entry['github_stars'], $entry['stars_checked_at'] ) ) { + echo '

' . esc_html( number_format_i18n( $entry['github_stars'] ) . ' GitHub Stars' ) . ' ' . esc_html( self::t( 'As of ', 'Stand: ' ) . $entry['stars_checked_at'] ) . '

'; + } + if ( $active ) { echo '' . esc_html( self::t( 'Already active', 'Bereits aktiv' ) ) . ''; } + elseif ( $issues ) { echo ''; } + elseif ( $installed && current_user_can( 'activate_plugin', $entry['plugin_file'] ) ) { $this->form( 'activate', $entry['slug'], is_network_admin() ? self::t( 'Network activate', 'Netzwerkweit aktivieren' ) : self::t( 'Activate', 'Aktivieren' ), 'button button-primary' ); } + elseif ( ! $installed ) { $this->form( 'install', $entry['slug'], self::t( 'Install now', 'Jetzt installieren' ), 'button' ); } + else { echo '' . esc_html( self::t( 'Installed', 'Installiert' ) ) . ''; } + if ( $issues ) { echo '
    '; foreach ( $issues as $issue ) { echo '
  • ' . esc_html( $issue ) . '
  • '; } echo '
' . esc_html( self::t( 'Manage installed plugins', 'Installierte Plugins verwalten' ) ) . '
'; } + echo '
' . esc_html( self::t( 'Details & requirements', 'Details & Voraussetzungen' ) ) . '

WordPress ≥ ' . esc_html( $entry['requires_wp'] ) . ' · PHP ≥ ' . esc_html( $entry['requires_php'] ) . '

'; + foreach ( $entry['dependencies'] as $dep ) { echo '

' . esc_html( $dep['name'] . ( $dep['min_version'] !== '' ? ' ≥ ' . $dep['min_version'] : '' ) ) . ( isset( $dep['url'] ) ? ' · ' . esc_html( self::t( 'Product website', 'Produktwebsite' ) ) . '' : '' ) . '

'; } + echo '

' . esc_html( self::t( 'Repository & documentation', 'Repository & Dokumentation' ) ) . '

' . esc_html( self::t( 'Release checksum is verified before installation. Activation is a separate step.', 'Vor der Installation wird die Release-Prüfsumme geprüft. Die Aktivierung erfolgt separat.' ) ) . '

' . esc_html( self::t( 'Source: GitHub', 'Quelle: GitHub' ) ) . '' . esc_html( $installed ? self::t( 'Installed: ', 'Installiert: ' ) . $plugins[$entry['plugin_file']]['Version'] : 'v' . $entry['version'] ) . '
'; + } + echo '
'; + if ( ! $count ) { echo '

' . esc_html( self::t( 'No approved plugins match this selection.', 'Keine freigegebenen Plugins passen zu dieser Auswahl.' ) ) . '

'; } + echo '
' . esc_html( self::t( 'Library', 'Library' ) ) . ' ' . esc_html( self::VERSION ) . ' · ' . esc_html( self::t( 'Only approved releases', 'Nur freigegebene Releases' ) ) . ''; + $this->form( 'preferences', '', self::t( 'Hide catalog', 'Katalog ausblenden' ), 'button-link', [ 'enabled' => '0', 'hide_only' => '1' ] ); + if ( self::settings()['online'] ) { $this->form( 'refresh', '', self::t( 'Refresh catalog', 'Katalog aktualisieren' ), 'button-link' ); } + echo '
'; + } + + public function settings_page(): void { + $cap = is_multisite() ? 'manage_network_options' : 'manage_options'; + if ( ! current_user_can( $cap ) ) { wp_die( 'Permission denied.', '', [ 'response' => 403 ] ); } + $s = self::settings(); + echo '

deckerweb Library

'; + if ( isset( $_GET['saved'] ) ) { echo '

' . esc_html( self::t( 'Library settings saved.', 'Library-Einstellungen gespeichert.' ) ) . '

'; } + echo '
'; wp_nonce_field( 'dwl_preferences_' ); + echo ''; + echo ''; + submit_button( self::t( 'Save settings', 'Einstellungen speichern' ) ); echo '

' . esc_html( self::t( 'Open catalog', 'Katalog öffnen' ) ) . '

'; + } + private function authorize( string $action, string $cap, string $slug = '' ): void { + if ( ( $_SERVER['REQUEST_METHOD'] ?? '' ) !== 'POST' ) { wp_die( 'POST required.', '', [ 'response' => 405 ] ); } + if ( ! current_user_can( $cap ) ) { wp_die( 'Permission denied.', '', [ 'response' => 403 ] ); } + $this->network = is_multisite() && ( $_REQUEST['network'] ?? '' ) === '1'; + if ( $this->network && ! current_user_can( 'manage_network_plugins' ) ) { wp_die( 'Network permission denied.', '', [ 'response' => 403 ] ); } + check_admin_referer( 'dwl_' . $action . '_' . $slug ); + } + private function slug(): string { return isset( $_REQUEST['slug'] ) && is_string( $_REQUEST['slug'] ) ? sanitize_key( wp_unslash( $_REQUEST['slug'] ) ) : ''; } + private function entry( string $slug, bool $fresh = true ): array { + if ( ! self::settings()['enabled'] ) { wp_die( esc_html( self::t( 'The catalog is disabled.', 'Der Katalog ist deaktiviert.' ) ), '', [ 'response' => 403 ] ); } + $entries = $this->catalog->entries( $fresh ); + if ( is_wp_error( $entries ) ) { wp_die( esc_html( $entries->get_error_message() ) ); } + if ( ! isset( $entries[$slug] ) ) { wp_die( esc_html( self::t( 'This release is not approved.', 'Dieses Release ist nicht freigegeben.' ) ), '', [ 'response' => 404 ] ); } + return $entries[$slug]; + } + private function enforce_requirements( array $entry, ?bool $network = null ): void { + $issues = Requirements::check( $entry, null, $network ?? $this->network ); + if ( $issues ) { wp_die( esc_html( implode( ' ', $issues ) ) ); } + } + public function preferences(): void { + $this->authorize( 'preferences', is_multisite() ? 'manage_network_options' : 'manage_options' ); + $s = self::settings(); + if ( ! empty( $_POST['hide_only'] ) ) { $s['enabled'] = false; } + else { + $url = isset( $_POST['catalog_url'] ) && is_string( $_POST['catalog_url'] ) ? trim( wp_unslash( $_POST['catalog_url'] ) ) : ''; + if ( $url !== '' && ! Catalog::trusted_source( $url ) ) { wp_die( esc_html( self::t( 'The catalog URL is not an allowed first-party HTTPS endpoint.', 'Die Katalog-URL ist keine erlaubte eigene HTTPS-Quelle.' ) ) ); } + if ( ! empty( $_POST['online'] ) && $url === '' ) { wp_die( esc_html( self::t( 'Enter a catalog URL first.', 'Bitte zuerst eine Katalog-URL eintragen.' ) ) ); } + $s = [ 'enabled' => ! empty( $_POST['enabled'] ), 'online' => ! empty( $_POST['online'] ), 'catalog_url' => $url ]; + } + update_site_option( self::OPTION, $s ); + wp_safe_redirect( add_query_arg( 'saved', '1', $this->settings_url() ) ); exit; + } + public function dismiss(): void { + $this->authorize( 'dismiss', 'install_plugins' ); + update_user_meta( get_current_user_id(), 'deckerweb_library_intro_seen_v1', 1 ); + wp_safe_redirect( $this->network ? network_admin_url( 'plugin-install.php' ) : admin_url( 'plugin-install.php' ) ); exit; + } + public function refresh(): void { + $this->authorize( 'refresh', 'install_plugins' ); + $result = $this->catalog->entries( true ); + if ( is_wp_error( $result ) ) { wp_die( esc_html( $result->get_error_message() ) ); } + wp_safe_redirect( $this->catalog_url() ); exit; + } + public function install(): void { + $slug = $this->slug(); $this->authorize( 'install', 'install_plugins', $slug ); + $entry = $this->entry( $slug ); $this->enforce_requirements( $entry ); + if ( is_dir( WP_PLUGIN_DIR . '/' . $entry['slug'] ) ) { wp_die( esc_html( self::t( 'This plugin directory already exists. Use the installed plugin; it will not be overwritten.', 'Dieses Plugin-Verzeichnis existiert bereits. Bitte das installierte Plugin verwenden; es wird nicht überschrieben.' ) ) ); } + require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; + require_once ABSPATH . 'wp-admin/includes/file.php'; + $title = self::t( 'Install deckerweb plugin', 'deckerweb-Plugin installieren' ); + if ( $this->network && ! defined( 'WP_NETWORK_ADMIN' ) ) { define( 'WP_NETWORK_ADMIN', true ); } + // admin-post.php has no initialized sidebar/menu. Use WordPress's native + // standalone upgrader chrome instead of including admin-header prematurely. + require_once ABSPATH . 'wp-admin/includes/admin.php'; + $GLOBALS['hook_suffix'] = 'deckerweb-library-install'; + set_current_screen( $this->network ? 'plugin-install-network' : 'plugin-install' ); + wp_enqueue_style( 'common' ); wp_enqueue_style( 'forms' ); + wp_enqueue_style( 'deckerweb-plugin-library', plugins_url( 'assets/library.css', $this->chosen['dir'] . '/bootstrap.php' ), [], self::VERSION ); + $body_class = static fn( string $classes ): string => $classes . ' dwl-install'; + add_filter( 'admin_body_class', $body_class ); + iframe_header( esc_html( $title ) ); + remove_filter( 'admin_body_class', $body_class ); + echo '

' . esc_html( $title ) . '

'; + $url = wp_nonce_url( $this->action_url( 'install', $slug ), 'dwl_install_' . $slug ); + $credentials = request_filesystem_credentials( $url, '', false, WP_PLUGIN_DIR ); + if ( $credentials === false ) { echo '
'; iframe_footer(); return; } + if ( ! WP_Filesystem( $credentials, WP_PLUGIN_DIR ) ) { request_filesystem_credentials( $url, '', true, WP_PLUGIN_DIR ); echo ''; iframe_footer(); return; } + $package = Package::download( $entry ); + if ( is_wp_error( $package ) ) { echo '

' . esc_html( $package->get_error_message() ) . '

'; } + else { + $skin = new class( [ 'type' => 'web', 'url' => $url, 'nonce' => 'dwl_install_' . $slug, 'title' => $title, 'api' => (object) [ 'name' => $entry['name'], 'slug' => $entry['slug'], 'version' => $entry['version'] ] ] ) extends \Plugin_Installer_Skin { + // The standalone page already owns its heading and wrapper. + public function header() {} + public function footer() {} + }; + $upgrader = new \Plugin_Upgrader( $skin ); + $actions = function( array $links, $api, string $file ) use ( $entry ): array { + unset( $links['activate_plugin'], $links['network_activate'] ); + if ( $file === $entry['plugin_file'] && ! Requirements::check( $entry, null, $this->network ) && current_user_can( 'activate_plugin', $file ) ) { + ob_start(); $this->form( 'activate', $entry['slug'], $this->network ? self::t( 'Network activate', 'Netzwerkweit aktivieren' ) : self::t( 'Activate', 'Aktivieren' ), 'button button-primary' ); $links['dwl_activate'] = ob_get_clean(); + } + $links['dwl_catalog'] = '' . esc_html( self::t( 'Back to deckerweb', 'Zurück zu deckerweb' ) ) . ''; + return $links; + }; + add_filter( 'install_plugin_complete_actions', $actions, 10, 3 ); + try { $result = $upgrader->install( $package, [ 'clear_update_cache' => false, 'overwrite_package' => false ] ); } + finally { remove_filter( 'install_plugin_complete_actions', $actions, 10 ); wp_delete_file( $package ); } + if ( $result === true ) { + $managed = get_site_option( self::MANAGED, [] ); $managed = is_array( $managed ) ? $managed : []; + $managed[$entry['plugin_file']] = $entry['repository']; update_site_option( self::MANAGED, $managed ); + } + } + echo ''; iframe_footer(); + } + public function activate(): void { + $slug = $this->slug(); $this->authorize( 'activate', 'activate_plugins', $slug ); + $entry = $this->entry( $slug ); + if ( ! current_user_can( 'activate_plugin', $entry['plugin_file'] ) ) { wp_die( 'Permission denied.', '', [ 'response' => 403 ] ); } + $this->enforce_requirements( $entry ); + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + if ( ! is_file( WP_PLUGIN_DIR . '/' . $entry['plugin_file'] ) ) { wp_die( 'Plugin is not installed.' ); } + $result = activate_plugin( $entry['plugin_file'], '', $this->network, false ); + if ( is_wp_error( $result ) ) { wp_die( esc_html( $result->get_error_message() ) ); } + wp_safe_redirect( add_query_arg( 'dwl_done', '1', $this->catalog_url() ) ); exit; + } + /** Cover activation from the ordinary Plugins page too, while this library is loaded. */ + public function activation_guard( string $file, bool $network ): void { + $managed = get_site_option( self::MANAGED, [] ); + $bundled = json_decode( (string) file_get_contents( $this->chosen['dir'] . '/catalog.json' ), true ); + $known = array_column( $bundled['plugins'] ?? [], 'plugin_file' ); + if ( ! in_array( $file, $known, true ) && ! isset( $managed[$file] ) ) { return; } + // Hiding discovery must never remove runtime dependency protection. + $entries = $this->catalog->entries(); + if ( is_wp_error( $entries ) ) { return; } + foreach ( $entries as $entry ) { if ( $entry['plugin_file'] === $file ) { $this->enforce_requirements( $entry, $network ); return; } } + } + /** Only installed-through-library plugins lacking their own updater are managed here. */ + public function updates( $transient ) { + if ( ! is_object( $transient ) || empty( $transient->checked ) ) { return $transient; } + $managed = get_site_option( self::MANAGED, [] ); + if ( ! is_array( $managed ) || ! $managed ) { return $transient; } + $entries = $this->catalog->entries(); + if ( is_wp_error( $entries ) ) { return $transient; } + require_once ABSPATH . 'wp-admin/includes/plugin.php'; $plugins = get_plugins(); + foreach ( $entries as $e ) { + $file = $e['plugin_file']; + if ( ! isset( $managed[$file], $plugins[$file], $transient->checked[$file] ) || $managed[$file] !== $e['repository'] || ! empty( $plugins[$file]['UpdateURI'] ) || isset( $transient->response[$file] ) || ! version_compare( $e['version'], $transient->checked[$file], '>' ) ) { continue; } + $transient->response[$file] = (object) [ 'id' => 'https://github.com/' . $e['repository'], 'slug' => $e['slug'], 'plugin' => $file, 'new_version' => $e['version'], 'url' => 'https://github.com/' . $e['repository'], 'package' => $e['download_url'], 'requires' => $e['requires_wp'], 'requires_php' => $e['requires_php'], 'dwl_managed' => true ]; + } + return $transient; + } + /** Native update downloads retain the same hash, archive and dependency checks. */ + public function update_download( $reply, string $package, $upgrader, array $extra = [] ) { + $file = $extra['plugin'] ?? ''; + $managed = get_site_option( self::MANAGED, [] ); + if ( ! is_string( $file ) || ! is_array( $managed ) || ! isset( $managed[$file] ) ) { return $reply; } + $entries = $this->catalog->entries( true ); + if ( is_wp_error( $entries ) ) { return $entries; } + foreach ( $entries as $entry ) { + if ( $entry['plugin_file'] !== $file || $entry['repository'] !== $managed[$file] ) { continue; } + // A native updater owned by the plugin must remain in control of its releases. + $plugins = get_plugins(); + if ( ! empty( $plugins[$file]['UpdateURI'] ) ) { return $reply; } + if ( $package !== $entry['download_url'] ) { return new \WP_Error( 'dwl_release_changed', self::t( 'The approved release changed. Refresh updates first.', 'Das freigegebene Release hat sich geändert. Bitte zuerst die Updates aktualisieren.' ) ); } + $issues = Requirements::check( $entry, null, is_plugin_active_for_network( $file ) ); + if ( $issues ) { return new \WP_Error( 'dwl_requirements', implode( ' ', $issues ) ); } + return Package::download( $entry ); + } + return new \WP_Error( 'dwl_withdrawn', self::t( 'This release is no longer approved.', 'Dieses Release ist nicht mehr freigegeben.' ) ); + } +} diff --git a/includes/deckerweb-plugin-library/src/Package.php b/includes/deckerweb-plugin-library/src/Package.php new file mode 100644 index 0000000..4a993e6 --- /dev/null +++ b/includes/deckerweb-plugin-library/src/Package.php @@ -0,0 +1,73 @@ + 45, 'redirection' => 5, 'stream' => true, 'filename' => $file, + 'limit_response_size' => self::MAX_DOWNLOAD + 1, + 'headers' => [ 'Accept' => 'application/octet-stream' ], + ] ); + if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) { + wp_delete_file( $file ); + return new \WP_Error( 'dwl_download', Library::t( 'The GitHub release could not be downloaded.', 'Das GitHub-Release konnte nicht heruntergeladen werden.' ) ); + } + $result = self::verify( $file, $entry ); + wp_delete_file( $file ); + return $result; + } + + /** Return a new safe archive path; callers must delete it after use. */ + public static function verify( string $file, array $entry ) { + require_once ABSPATH . 'wp-admin/includes/file.php'; + if ( ! class_exists( '\ZipArchive' ) ) { return new \WP_Error( 'dwl_zip', 'PHP ZIP support is required.' ); } + if ( ! is_file( $file ) || filesize( $file ) > self::MAX_DOWNLOAD || ! hash_equals( $entry['sha256'], (string) hash_file( 'sha256', $file ) ) ) { + return new \WP_Error( 'dwl_hash', Library::t( 'The release checksum does not match. Installation stopped.', 'Die Prüfsumme des Releases stimmt nicht überein. Installation gestoppt.' ) ); + } + $zip = new \ZipArchive(); + if ( $zip->open( $file ) !== true ) { return new \WP_Error( 'dwl_zip', 'Invalid ZIP archive.' ); } + $names = []; $expanded = 0; $seen = []; + $error = ''; + if ( $zip->numFiles > 3000 ) { $error = 'Archive contains too many files.'; } + for ( $i = 0; $i < $zip->numFiles && $error === ''; $i++ ) { + $stat = $zip->statIndex( $i ); + if ( ! is_array( $stat ) ) { $error = 'Cannot read archive entry.'; break; } + $name = $stat['name']; + if ( strpos( $name, "\0" ) !== false || strpos( $name, '\\' ) !== false || preg_match( '~(^|/)\.\.?(/|$)~', $name ) || str_starts_with( $name, '/' ) ) { $error = 'Unsafe archive path.'; break; } + $expanded += (int) $stat['size']; + if ( $expanded > self::MAX_EXPANDED ) { $error = 'Expanded archive is too large.'; break; } + $opsys = 0; $attrs = 0; + $zip->getExternalAttributesIndex( $i, $opsys, $attrs ); + if ( ( ( $attrs >> 16 ) & 0170000 ) === 0120000 ) { $error = 'Archive symlinks are not permitted.'; break; } + // Old release ZIPs may contain Finder resource forks; never install them. + if ( str_starts_with( $name, '__MACOSX/' ) || basename( $name ) === '.DS_Store' ) { continue; } + if ( ! str_starts_with( $name, $entry['slug'] . '/' ) || isset( $seen[$name] ) ) { $error = 'Release must contain exactly the approved plugin directory.'; break; } + $seen[$name] = true; + $names[] = $name; + } + $main = $zip->getFromName( $entry['plugin_file'] ); + if ( $error === '' && ( ! is_string( $main ) || strlen( $main ) > 2097152 || ! preg_match( '/^[ \t\/*#@]*Version:\s*([^\r\n]+)/mi', substr( $main, 0, 8192 ), $match ) || trim( $match[1] ) !== $entry['version'] ) ) { $error = 'Plugin identity or version does not match the catalog.'; } + if ( $error === '' && ! preg_match( '/^[ \t\/*#@]*Plugin Name:\s*\S+/mi', substr( $main, 0, 8192 ) ) ) { $error = 'Missing plugin header.'; } + if ( $error !== '' ) { $zip->close(); return new \WP_Error( 'dwl_package', $error ); } + $safe_file = wp_tempnam( $entry['slug'] . '-verified.zip' ); + $safe = new \ZipArchive(); + if ( ! $safe_file || $safe->open( $safe_file, \ZipArchive::OVERWRITE ) !== true ) { $zip->close(); if ( $safe_file ) { wp_delete_file( $safe_file ); } return new \WP_Error( 'dwl_temp', 'Cannot create verified archive.' ); } + foreach ( $names as $name ) { + if ( str_ends_with( $name, '/' ) ) { $ok = $safe->addEmptyDir( rtrim( $name, '/' ) ); } + else { $content = $zip->getFromName( $name ); $ok = is_string( $content ) && $safe->addFromString( $name, $content ); } + if ( ! $ok ) { $error = 'Cannot normalize archive.'; break; } + } + $closed = $safe->close(); $zip->close(); + if ( $error !== '' || ! $closed ) { wp_delete_file( $safe_file ); return new \WP_Error( 'dwl_zip', $error ?: 'Cannot finalize archive.' ); } + return $safe_file; + } +} diff --git a/includes/deckerweb-plugin-library/src/Requirements.php b/includes/deckerweb-plugin-library/src/Requirements.php new file mode 100644 index 0000000..29a4655 --- /dev/null +++ b/includes/deckerweb-plugin-library/src/Requirements.php @@ -0,0 +1,61 @@ +exists() && $theme->get( 'Name' ) === 'Bricks'; + $active = $present && defined( 'BRICKS_VERSION' ) && function_exists( 'bricks_is_builder' ); + $version = $theme->get( 'Version' ); + } + if ( $detector === 'oxygen' || $detector === 'advanced_scripts' ) { + $loaded = $detector === 'oxygen' + ? ( defined( 'BREAKDANCE_MODE' ) && constant( 'BREAKDANCE_MODE' ) === 'oxygen' && defined( '__BREAKDANCE_VERSION' ) ) + : ( defined( 'EPXADVSC_VER' ) && function_exists( 'cpas_scripts_manager' ) ); + // Runtime markers also support renamed plugin directories. On a network, + // a matching active network plugin is required in addition to the marker. + foreach ( $plugins as $candidate => $info ) { + $expected = $detector === 'oxygen' ? 'Oxygen' : 'Advanced Scripts'; + if ( isset( $info['Name'] ) && $info['Name'] === $expected ) { + $present = true; + if ( $network && is_plugin_active_for_network( $candidate ) ) { $active = true; } + } + } + if ( ! $network && $loaded ) { $present = true; $active = true; } + $active = $active && $loaded; + if ( $loaded ) { $version = (string) constant( $detector === 'oxygen' ? '__BREAKDANCE_VERSION' : 'EPXADVSC_VER' ); } + } + if ( ! $present ) { $issues[] = sprintf( Library::t( '%s is missing. Install and activate it first.', '%s fehlt. Bitte zuerst installieren und aktivieren.' ), $dep['name'] ); } + elseif ( ! $active ) { $issues[] = sprintf( $network ? Library::t( '%s must be network activated first.', '%s muss zuerst netzwerkweit aktiviert werden.' ) : Library::t( '%s is installed but inactive. Activate it first.', '%s ist installiert, aber inaktiv. Bitte zuerst aktivieren.' ), $dep['name'] ); } + elseif ( $dep['min_version'] !== '' && ( $version === '' || version_compare( $version, $dep['min_version'], '<' ) ) ) { $issues[] = sprintf( Library::t( 'Update %1$s to version %2$s or newer.', '%1$s bitte auf Version %2$s oder neuer aktualisieren.' ), $dep['name'], $dep['min_version'] ); } + } + return $issues; + } +} diff --git a/includes/deckerweb-plugin-library/version.php b/includes/deckerweb-plugin-library/version.php new file mode 100644 index 0000000..c96f147 --- /dev/null +++ b/includes/deckerweb-plugin-library/version.php @@ -0,0 +1,3 @@ + $integration_data ) { - - $integration = sanitize_key( $integration ); - $integration_data[ 'label' ] = esc_attr( $integration_data[ 'label' ] ); - $integration_data[ 'post_type' ] = sanitize_key( $integration_data[ 'post_type' ] ); - $integration_data[ 'shortcode_tag' ] = sanitize_key( $integration_data[ 'shortcode_tag' ] ); - - } // end foreach - - /** Return registered integrations */ - return (array) $integrations; + $clean = array(); + foreach ( $integrations as $key => $data ) { + if ( 'default-none' === $key || ! is_array( $data ) ) { + continue; + } + if ( ! isset( $data['label'], $data['post_type'], $data['shortcode_tag'] ) + || ! is_string( $data['label'] ) || ! is_string( $data['post_type'] ) || ! is_string( $data['shortcode_tag'] ) ) { + continue; + } + $key = sanitize_key( (string) $key ); + $data['label'] = sanitize_text_field( $data['label'] ); + $data['post_type'] = sanitize_key( $data['post_type'] ); + $data['shortcode_tag'] = sanitize_key( $data['shortcode_tag'] ); + if ( $key && $data['post_type'] && $data['shortcode_tag'] ) { + $clean[$key] = $data; + } + } + return $clean; } // end function @@ -100,13 +106,13 @@ function ddw_bse_info_values() { /** For our Shortcodes specifically */ 'php_current' => phpversion(), - 'php_minimum' => '7.4', + 'php_minimum' => '8.0', 'php_recommended' => '8.2', 'wp_current' => $GLOBALS[ 'wp_version' ], 'wp_minimum' => '6.7', 'wp_recommended' => '6.7.2', //'db_current' => esc_attr( ddw_bse_get_db_version() ), - 'server_software' => esc_attr( wp_unslash( $_SERVER[ 'SERVER_SOFTWARE' ] ) ), + 'server_software' => esc_attr( wp_unslash( $_SERVER[ 'SERVER_SOFTWARE' ] ?? '' ) ), 'mysql_minimum' => '5.0', 'mysql_recommended' => '5.6', 'mariadb_minimum' => '10.0', @@ -140,10 +146,10 @@ function ddw_bse_get_info_url( $url_key = '', $raw = FALSE ) { $bse_info = (array) ddw_bse_info_values(); - $output = esc_url( $bse_info[ sanitize_key( $url_key ) ] ); + $output = esc_url( $bse_info[ sanitize_key( $url_key ) ] ?? '' ); if ( TRUE === $raw ) { - $output = esc_url_raw( $bse_info[ esc_attr( $url_key ) ] ); + $output = esc_url_raw( $bse_info[ sanitize_key( $url_key ) ] ?? '' ); } return $output; @@ -167,7 +173,7 @@ function ddw_bse_get_info_link( $url_key = '', $text = '', $class = '' ) { $link = sprintf( '%3$s', - strtolower( esc_attr( $class ) ), //sanitize_html_class( $class ), + esc_attr( ddw_bse_sanitize_html_classes( $class, 'string' ) ), //sanitize_html_class( $class ), ddw_bse_get_info_url( $url_key ), esc_html( $text ) ); @@ -194,9 +200,9 @@ function ddw_bse_coding_years( $first_year = '' ) { $first_year = ( empty( $first_year ) ) ? absint( $bse_info[ 'first_code' ] ) : absint( $first_year ); /** Set year of first released code */ - $code_first_year = ( date( 'Y' ) == $first_year || 0 === $first_year ) ? '' : $first_year . '–'; + $code_first_year = ( wp_date( 'Y' ) == $first_year || 0 === $first_year ) ? '' : $first_year . '–'; - return $code_first_year . date( 'Y' ); + return $code_first_year . wp_date( 'Y' ); } // end function @@ -278,7 +284,7 @@ function ddw_bse_human_time_diff( $older_date, $newer_date = false, $relative_de ]; // Build output with as many units as specified in $relative_depth. - $relative_depth = (int) $relative_depth ?: 2; + $relative_depth = max( 1, min( 7, (int) $relative_depth ?: 2 ) ); $i = 0; diff --git a/includes/functions-rendering.php b/includes/functions-rendering.php new file mode 100644 index 0000000..88a46ad --- /dev/null +++ b/includes/functions-rendering.php @@ -0,0 +1,73 @@ +post_status, array( 'trash', 'auto-draft' ), true ) ) { + return null; + } + if ( 'publish' === $post->post_status ) { + return $post; + } + $capability = 'private' === $post->post_status ? 'read_post' : 'edit_post'; + return current_user_can( $capability, $post->ID ) ? $post : null; +} + +/** Render content without echoing; protect both direct and indirect recursive embeds. */ +function ddw_bse_render_item_content( $id, $include_css = false, $renderer = 'auto' ) { + static $stack = array(); + $id = absint( $id ); + if ( ! $id || isset( $stack[$id] ) || count( $stack ) >= 20 ) { + return ''; + } + $post = ddw_bse_readable_item( $id ); + if ( ! $post ) { + return ''; + } + $stack[$id] = true; + try { + $elementor = get_post_meta( $id, '_elementor_edit_mode', true ); + if ( 'elementor' === $renderer || ( $elementor && ddw_bse_is_elementor_active() ) ) { + if ( ! ddw_bse_is_elementor_active() || ! class_exists( '\Elementor\Plugin' ) ) { + return ''; + } + return (string) \Elementor\Plugin::instance()->frontend->get_builder_content_for_display( $id, $include_css ); + } + if ( class_exists( 'FLBuilder' ) && get_post_meta( $id, '_fl_builder_enabled', true ) ) { + return do_shortcode( sprintf( '[fl_builder_insert_layout id="%d"]', $id ) ); + } + // Render native blocks too; do not run the_content, which may embed this item again. + return do_shortcode( do_blocks( $post->post_content ) ); + } finally { + unset( $stack[$id] ); + } +} diff --git a/includes/integrations/block-editor.php b/includes/integrations/block-editor.php index 4843ae9..236aeb6 100644 --- a/includes/integrations/block-editor.php +++ b/includes/integrations/block-editor.php @@ -14,7 +14,7 @@ add_filter( 'bse/filter/integrations/all', 'ddw_bse_register_integration_wpblocks' ); /** - * Register WordPress Reusable Blocks (post type: wp_block). + * Register WordPress Synced Patterns (post type: wp_block). * * @since 1.0.0 * @@ -24,7 +24,7 @@ function ddw_bse_register_integration_wpblocks( array $integrations ) { $integrations[ 'wp-reusable-blocks' ] = array( - 'label' => __( 'WordPress Reusable Blocks', 'builder-shortcode-extras' ), + 'label' => __( 'WordPress Synced Patterns', 'builder-shortcode-extras' ), 'post_type' => 'wp_block', 'shortcode_tag' => 'wpblock', ); @@ -32,49 +32,3 @@ function ddw_bse_register_integration_wpblocks( array $integrations ) { return $integrations; } // end function - - -add_action( 'admin_menu', 'ddw_bse_maybe_add_menu_wpblock_posttype', 20 ); -/** - * Add the appropriate admin menu - using the post type list table page as - * the callback. - * - * @since 1.0.0 - * - * @see WP Core /wp-includes/post.php for 'wp_block' capabilities - * - * @uses add_menu_page() - * @uses add_submenu_page() - * - * @global string $GLOBALS[ 'admin_page_hooks' ] - */ -function ddw_bse_maybe_add_menu_wpblock_posttype() { - - /** - * Bail early if the same stuff as below was already added by other plugins. - */ - if ( ! empty( $GLOBALS[ 'admin_page_hooks' ][ 'edit.php?post_type=wp_block' ] ) ) { - return; - } - - /** Add "Blocks" top-level Admin menu, below "Comments" */ - add_menu_page( - _x( 'Reusable Blocks', 'Admin page title', 'builder-shortcode-extras' ), - _x( 'Blocks', 'Admin menu label', 'builder-shortcode-extras' ), - 'publish_posts', - 'edit.php?post_type=wp_block', - '', - 'dashicons-screenoptions', - '25.1' // directly after comments - ); - - /** "Blocks" submenu: "Add New" (Reusable Block) */ - add_submenu_page( - 'edit.php?post_type=wp_block', - _x( 'Add New Reusable Block', 'Admin page title', 'builder-shortcode-extras' ), - _x( 'Add New', 'Admin menu label', 'builder-shortcode-extras' ), - 'publish_posts', - 'post-new.php?post_type=wp_block' - ); - -} // end function diff --git a/includes/load-integrations.php b/includes/load-integrations.php index 56d8a42..c8e8b35 100644 --- a/includes/load-integrations.php +++ b/includes/load-integrations.php @@ -51,15 +51,6 @@ * Block Editor (Gutenberg) - WP Core * @since 1.0.0 */ -if ( ddw_bse_is_block_editor_active() && ddw_bse_is_block_editor_wanted() ) { +if ( post_type_exists( 'wp_block' ) ) { require_once BSE_PLUGIN_DIR . 'includes/integrations/block-editor.php'; } - - -/** - * Astra Custom Layouts specific Shortcode (for Astra Pro only) - * @since 1.0.0 - */ -if ( ddw_bse_is_astra_active() && ddw_bse_is_astra_custom_layouts_active() ) { - require_once BSE_PLUGIN_DIR . 'includes/integrations/astra.php'; -} diff --git a/includes/shortcodes/content.php b/includes/shortcodes/content.php index c93745d..90da721 100644 --- a/includes/shortcodes/content.php +++ b/includes/shortcodes/content.php @@ -14,7 +14,7 @@ if ( ! function_exists( 'ddw_bse_shortcode_nav_menu' ) ) : - add_shortcode( 'bse-nav-menu', 'ddw_bse_shortcode_nav_menu' ); + ddw_bse_register_shortcode( 'bse-nav-menu', 'ddw_bse_shortcode_nav_menu' ); /** * Shortcode to output a full nav menu (registered as regular WP Nav Menu) * by a given menu name/ id/ slug/ object. @@ -85,11 +85,11 @@ function ddw_bse_shortcode_nav_menu( $atts, $content = null ) { /** Prepare output */ $output = sprintf( '<%1$s class="bse-nav-menu%2$s">%3$s%4$s%5$s', - strtolower( sanitize_html_class( $atts[ 'wrapper' ] ) ), - ! empty( $atts[ 'class' ] ) ? ' ' . sanitize_html_class( $atts[ 'class' ] ) : '', - ( ! empty( $atts[ 'before' ] ) ) ? esc_html__( $atts[ 'before' ] ) . ' ' : '', + ddw_bse_wrapper_tag( $atts[ 'wrapper' ] ), + ! empty( $atts[ 'class' ] ) ? ' ' . esc_attr( ddw_bse_sanitize_html_classes( $atts[ 'class' ], 'string' ) ) : '', + ( ! empty( $atts[ 'before' ] ) ) ? esc_html( $atts[ 'before' ] ) . ' ' : '', $nav_menu, - ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html__( $atts[ 'after' ] ) : '' + ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html( $atts[ 'after' ] ) : '' ); /** Return the output - filterable */ @@ -106,7 +106,7 @@ function ddw_bse_shortcode_nav_menu( $atts, $content = null ) { if ( ! function_exists( 'ddw_bse_shortcode_item_content' ) ) : - add_shortcode( 'bse-item-content', 'ddw_bse_shortcode_item_content' ); + ddw_bse_register_shortcode( 'bse-item-content', 'ddw_bse_shortcode_item_content' ); /** * Shortcode to output the content of a singular post type item by given ID. * @@ -131,59 +131,7 @@ function ddw_bse_shortcode_item_content( $atts, $content = null ) { /** Default shortcode attributes */ $atts = shortcode_atts( $defaults, $atts, 'bse-item-content' ); - /** Start output buffering (we have no chance...) */ - ob_start(); - - if ( $atts[ 'id' ] ) { - - /** Check if the post type item was created with Elementor */ - $elementor = get_post_meta( $atts[ 'id' ], '_elementor_edit_mode', TRUE ); - - $include_css = false; - - if ( isset( $atts[ 'css' ] ) && 'false' !== $atts[ 'css' ] ) { - $include_css = (bool) $atts[ 'css' ]; - } - - /** Case: Elementor */ - if ( ddw_bse_is_elementor_active() && $elementor ) { - - echo Elementor\Plugin::instance()->frontend->get_builder_content_for_display( $atts[ 'id' ], $include_css ); - - } - - /** Case: Beaver Builder */ - elseif ( class_exists( 'FLBuilder' ) && ! empty( $atts[ 'id' ] ) ) { - - echo do_shortcode( '[fl_builder_insert_layout id="' . $atts[ 'id' ] . '"]' ); - - } - - /** Else: Embed the regular post item content */ - else { - - /** Get item content */ - $content = $atts[ 'id' ]; - - if ( ! empty( $content ) ) { - - $template = get_post( $content ); - - if ( $template && ! is_wp_error( $template ) ) { - $content = $template->post_content; - } - - } - - /** Display item content */ - echo do_shortcode( $content ); - - } // end if - - } // end if - - /** Return the output - from the WordPress output buffer */ - return ob_get_clean(); + return ddw_bse_render_item_content( absint( $atts['id'] ), ddw_bse_boolean( $atts['css'] ) ); } // end function @@ -192,7 +140,7 @@ function ddw_bse_shortcode_item_content( $atts, $content = null ) { if ( ! function_exists( 'ddw_bse_shortcode_comment_form' ) ) : - add_shortcode( 'bse-comment-form', 'ddw_bse_shortcode_comment_form' ); + ddw_bse_register_shortcode( 'bse-comment-form', 'ddw_bse_shortcode_comment_form' ); /** * Shortcode to output a comment form for a specific Post ID (or the current * post). @@ -250,11 +198,11 @@ function ddw_bse_shortcode_comment_form( $atts, $content = null ) { /** Prepare output */ $output = sprintf( '<%1$s class="bse-comment-form%2$s">%3$s%4$s%5$s', - strtolower( sanitize_html_class( $atts[ 'wrapper' ] ) ), - ! empty( $atts[ 'class' ] ) ? ' ' . sanitize_html_class( $atts[ 'class' ] ) : '', - ( ! empty( $atts[ 'before' ] ) ) ? esc_html__( $atts[ 'before' ] ) . ' ' : '', + ddw_bse_wrapper_tag( $atts[ 'wrapper' ] ), + ! empty( $atts[ 'class' ] ) ? ' ' . esc_attr( ddw_bse_sanitize_html_classes( $atts[ 'class' ], 'string' ) ) : '', + ( ! empty( $atts[ 'before' ] ) ) ? esc_html( $atts[ 'before' ] ) . ' ' : '', $comment_form, - ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html__( $atts[ 'after' ] ) : '' + ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html( $atts[ 'after' ] ) : '' ); /** Return the output - filterable */ diff --git a/includes/shortcodes/elementor.php b/includes/shortcodes/elementor.php index c2cc6ef..10270dd 100644 --- a/includes/shortcodes/elementor.php +++ b/includes/shortcodes/elementor.php @@ -20,7 +20,7 @@ */ if ( ! ddw_bse_is_elementor_pro_active() ) : - add_action( 'manage_' . Source_Local::CPT . '_posts_columns', 'ddw_bse_admin_columns_headers_elementor_templates' ); + add_filter( 'manage_' . Source_Local::CPT . '_posts_columns', 'ddw_bse_admin_columns_headers_elementor_templates' ); /** * Add post type list table column for "Saved Templates" post type. * @@ -78,7 +78,7 @@ function ddw_bse_admin_columns_content_elementor_templates( $column_name, $post_ if ( ! function_exists( 'ddw_bse_shortcode_elementor_template' ) ) : - add_shortcode( 'bse-elementor-template', 'ddw_bse_shortcode_elementor_template' ); + ddw_bse_register_shortcode( 'bse-elementor-template', 'ddw_bse_shortcode_elementor_template' ); /** * Shortcode to output an Elementor Saved Template by given ID. * @@ -107,13 +107,7 @@ function ddw_bse_shortcode_elementor_template( $atts = [] ) { return ''; } - $include_css = false; - - if ( isset( $atts[ 'css' ] ) && 'false' !== $atts[ 'css' ] ) { - $include_css = (bool) $atts[ 'css' ]; - } - - $output = \Elementor\Plugin::$instance->frontend->get_builder_content_for_display( $atts[ 'id' ], $include_css ); + $output = ddw_bse_render_item_content( absint( $atts['id'] ), ddw_bse_boolean( $atts['css'] ), 'elementor' ); /** Return the output - filterable */ return apply_filters( diff --git a/includes/shortcodes/genesis.php b/includes/shortcodes/genesis.php index 1c7067d..ee26b7d 100644 --- a/includes/shortcodes/genesis.php +++ b/includes/shortcodes/genesis.php @@ -14,7 +14,7 @@ if ( ! function_exists( 'ddw_bse_shortcode_genesis_footer' ) ) : - add_shortcode( 'bse-genesis-footer', 'ddw_bse_shortcode_genesis_footer' ); + ddw_bse_register_shortcode( 'bse-genesis-footer', 'ddw_bse_shortcode_genesis_footer' ); /** * Shortcode to output the complete Genesis footer text, taken from the * Genesis Customizer settings since Genesis 3.1.0 or higher. @@ -51,7 +51,7 @@ function ddw_bse_shortcode_genesis_footer( $atts ) { /** Prepare output */ $output = sprintf( '<%1$s class="bse-genesis-footer%2$s">%3$s', - strtolower( sanitize_html_class( $atts[ 'wrapper' ] ) ), + ddw_bse_wrapper_tag( $atts[ 'wrapper' ] ), ( ! empty( $atts[ 'class' ] ) ) ? ' ' . ddw_bse_sanitize_html_classes( $atts[ 'class' ], 'string' ) : '', do_shortcode( genesis_strip_p_tags( $creds_text ) ) ); @@ -70,7 +70,7 @@ function ddw_bse_shortcode_genesis_footer( $atts ) { if ( ! function_exists( 'ddw_bse_shortcode_genesis_breadcrumbs' ) ) : - add_shortcode( 'bse-genesis-breadcrumbs', 'ddw_bse_shortcode_genesis_breadcrumbs' ); + ddw_bse_register_shortcode( 'bse-genesis-breadcrumbs', 'ddw_bse_shortcode_genesis_breadcrumbs' ); /** * Shortcode to output the complete Genesis Breadcrumb container. * @@ -108,7 +108,7 @@ function ddw_bse_shortcode_genesis_breadcrumbs( $atts ) { /** Prepare output */ $output = sprintf( '<%1$s class="bse-genesis-breadcrumbs%2$s">%3$s', - strtolower( sanitize_html_class( $atts[ 'wrapper' ] ) ), + ddw_bse_wrapper_tag( $atts[ 'wrapper' ] ), ( ! empty( $atts[ 'class' ] ) ) ? ' ' . ddw_bse_sanitize_html_classes( $atts[ 'class' ], 'string' ) : '', $genesis_breadcrumbs ); diff --git a/includes/shortcodes/info.php b/includes/shortcodes/info.php index d61c7be..a8d37ca 100644 --- a/includes/shortcodes/info.php +++ b/includes/shortcodes/info.php @@ -14,7 +14,7 @@ if ( ! function_exists( 'ddw_bse_shortcode_copyright' ) ) : - add_shortcode( 'bse-copyright', 'ddw_bse_shortcode_copyright' ); + ddw_bse_register_shortcode( 'bse-copyright', 'ddw_bse_shortcode_copyright' ); /** * Shortcode to output a typical copyright intro string. * @@ -49,18 +49,18 @@ function ddw_bse_shortcode_copyright( $atts ) { $date_first = ''; - if ( '' !== $atts[ 'first' ] && date( 'Y' ) !== $atts[ 'first' ] ) { - $date_first = $atts[ 'first' ] . '–'; + if ( '' !== $atts[ 'first' ] && wp_date( 'Y' ) !== $atts[ 'first' ] ) { + $date_first = esc_html( $atts[ 'first' ] ) . '–'; } /** Prepare output */ $output = sprintf( '<%1$s class="bse-copyright%2$s">%3$s%4$s%5$s', - strtolower( sanitize_html_class( $atts[ 'wrapper' ] ) ), - ! empty( $atts[ 'class' ] ) ? ' ' . sanitize_html_class( $atts[ 'class' ] ) : '', - ( ! empty( $atts[ 'before' ] ) ) ? esc_html__( $atts[ 'before' ] ) . ' ' : '', - $atts[ 'copyright' ] . ' ' . $date_first . date( 'Y' ), - ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html__( $atts[ 'after' ] ) : '' + ddw_bse_wrapper_tag( $atts[ 'wrapper' ] ), + ! empty( $atts[ 'class' ] ) ? ' ' . esc_attr( ddw_bse_sanitize_html_classes( $atts[ 'class' ], 'string' ) ) : '', + ( ! empty( $atts[ 'before' ] ) ) ? esc_html( $atts[ 'before' ] ) . ' ' : '', + esc_html( $atts[ 'copyright' ] ) . ' ' . $date_first . wp_date( 'Y' ), + ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html( $atts[ 'after' ] ) : '' ); /** Return the output - filterable */ @@ -77,7 +77,7 @@ function ddw_bse_shortcode_copyright( $atts ) { if ( ! function_exists( 'ddw_bse_shortcode_site_title' ) ) : - add_shortcode( 'bse-site-title', 'ddw_bse_shortcode_site_title' ); + ddw_bse_register_shortcode( 'bse-site-title', 'ddw_bse_shortcode_site_title' ); /** * Shortcode to output the site title. * @@ -112,11 +112,11 @@ function ddw_bse_shortcode_site_title( $atts ) { /** Prepare output */ $output = sprintf( '<%1$s class="bse-site-title%2$s">%3$s%4$s%5$s', - strtolower( sanitize_html_class( $atts[ 'wrapper' ] ) ), - ! empty( $atts[ 'class' ] ) ? ' ' . sanitize_html_class( $atts[ 'class' ] ) : '', - ( ! empty( $atts[ 'before' ] ) ) ? esc_html__( $atts[ 'before' ] ) . ' ' : '', + ddw_bse_wrapper_tag( $atts[ 'wrapper' ] ), + ! empty( $atts[ 'class' ] ) ? ' ' . esc_attr( ddw_bse_sanitize_html_classes( $atts[ 'class' ], 'string' ) ) : '', + ( ! empty( $atts[ 'before' ] ) ) ? esc_html( $atts[ 'before' ] ) . ' ' : '', get_bloginfo( 'name' ), - ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html__( $atts[ 'after' ] ) : '' + ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html( $atts[ 'after' ] ) : '' ); /** Return the output - filterable */ @@ -133,7 +133,7 @@ function ddw_bse_shortcode_site_title( $atts ) { if ( ! function_exists( 'ddw_bse_shortcode_site_slogan' ) ) : - add_shortcode( 'bse-site-slogan', 'ddw_bse_shortcode_site_slogan' ); + ddw_bse_register_shortcode( 'bse-site-slogan', 'ddw_bse_shortcode_site_slogan' ); /** * Shortcode to output the site slogan (aka Description/ Tag line). * @@ -168,11 +168,11 @@ function ddw_bse_shortcode_site_slogan( $atts ) { /** Prepare output */ $output = sprintf( '<%1$s class="bse-site-slogan%2$s">%3$s%4$s%5$s', - strtolower( sanitize_html_class( $atts[ 'wrapper' ] ) ), - ! empty( $atts[ 'class' ] ) ? ' ' . sanitize_html_class( $atts[ 'class' ] ) : '', - ( ! empty( $atts[ 'before' ] ) ) ? esc_html__( $atts[ 'before' ] ) . ' ' : '', + ddw_bse_wrapper_tag( $atts[ 'wrapper' ] ), + ! empty( $atts[ 'class' ] ) ? ' ' . esc_attr( ddw_bse_sanitize_html_classes( $atts[ 'class' ], 'string' ) ) : '', + ( ! empty( $atts[ 'before' ] ) ) ? esc_html( $atts[ 'before' ] ) . ' ' : '', get_bloginfo( 'description' ), - ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html__( $atts[ 'after' ] ) : '' + ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html( $atts[ 'after' ] ) : '' ); /** Return the output - filterable */ @@ -189,7 +189,7 @@ function ddw_bse_shortcode_site_slogan( $atts ) { if ( ! function_exists( 'ddw_bse_shortcode_home_link' ) ) : - add_shortcode( 'bse-home-link', 'ddw_bse_shortcode_home_link' ); + ddw_bse_register_shortcode( 'bse-home-link', 'ddw_bse_shortcode_home_link' ); /** * Shortcode to output a link to the home URL. * @@ -240,11 +240,11 @@ function ddw_bse_shortcode_home_link( $atts ) { /** Prepare output */ $output = sprintf( '<%1$s class="bse-home-link%2$s">%3$s%4$s%5$s', - strtolower( sanitize_html_class( $atts[ 'wrapper' ] ) ), - ! empty( $atts[ 'class' ] ) ? ' ' . sanitize_html_class( $atts[ 'class' ] ) : '', - ( ! empty( $atts[ 'before' ] ) ) ? esc_html__( $atts[ 'before' ] ) . ' ' : '', + ddw_bse_wrapper_tag( $atts[ 'wrapper' ] ), + ! empty( $atts[ 'class' ] ) ? ' ' . esc_attr( ddw_bse_sanitize_html_classes( $atts[ 'class' ], 'string' ) ) : '', + ( ! empty( $atts[ 'before' ] ) ) ? esc_html( $atts[ 'before' ] ) . ' ' : '', $home_link, - ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html__( $atts[ 'after' ] ) : '' + ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html( $atts[ 'after' ] ) : '' ); /** Return the output - filterable */ @@ -261,7 +261,7 @@ function ddw_bse_shortcode_home_link( $atts ) { if ( ! function_exists( 'ddw_bse_shortcode_loginout' ) ) : - add_shortcode( 'bse-loginout', 'ddw_bse_shortcode_loginout' ); + ddw_bse_register_shortcode( 'bse-loginout', 'ddw_bse_shortcode_loginout' ); /** * Shortcode to output an admin login/ logout link. * @@ -327,11 +327,11 @@ function ddw_bse_shortcode_loginout( $atts ) { /** Prepare output */ $output = sprintf( '<%1$s class="bse-loginout%2$s">%3$s%4$s%5$s', - strtolower( sanitize_html_class( $atts[ 'wrapper' ] ) ), - ! empty( $atts[ 'class' ] ) ? ' ' . sanitize_html_class( $atts[ 'class' ] ) : '', - ( ! empty( $atts[ 'before' ] ) ) ? esc_html__( $atts[ 'before' ] ) . ' ' : '', + ddw_bse_wrapper_tag( $atts[ 'wrapper' ] ), + ! empty( $atts[ 'class' ] ) ? ' ' . esc_attr( ddw_bse_sanitize_html_classes( $atts[ 'class' ], 'string' ) ) : '', + ( ! empty( $atts[ 'before' ] ) ) ? esc_html( $atts[ 'before' ] ) . ' ' : '', apply_filters( 'loginout', $link ), // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- core WP hook. - ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html__( $atts[ 'after' ] ) : '' + ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html( $atts[ 'after' ] ) : '' ); /** Return the output - filterable */ @@ -348,7 +348,7 @@ function ddw_bse_shortcode_loginout( $atts ) { if ( ! function_exists( 'ddw_bse_shortcode_site_updated' ) ) : - add_shortcode( 'bse-site-updated', 'ddw_bse_shortcode_site_updated' ); + ddw_bse_register_shortcode( 'bse-site-updated', 'ddw_bse_shortcode_site_updated' ); /** * Returns the date value when the content on the site was first published * or last updated. @@ -390,66 +390,25 @@ function ddw_bse_shortcode_site_updated( $atts ) { $type = sanitize_key( $atts[ 'type' ] ); $type = ( 'first' === $type ) ? $type : 'last'; - /** For "first": Get date & time format */ - $site_first_updated_date = date_i18n( - $atts[ 'date_format' ], - strtotime( ddw_bse_site_updated() ) - ); - - $site_first_updated_time = date_i18n( - $atts[ 'time_format' ], - strtotime( ddw_bse_site_updated() ) - ); - - /** For "last": Get date & time format */ - $site_last_updated_date = date_i18n( - $atts[ 'date_format' ], - strtotime( ddw_bse_site_updated( 'MAX' ) ) - ); - - $site_last_updated_time = date_i18n( - $atts[ 'time_format' ], - strtotime( ddw_bse_site_updated( 'MAX' ) ) - ); - - /** For "first": Date & time display logic */ - $show_updated_date = $site_first_updated_date ? ' ' . $site_first_updated_date . '' : ''; - - $show_updated_time = $site_first_updated_time ? ' ' . sprintf( - _x( '%s', 'Translators: shortcode, first published content time string', 'builder-shortcode-extras' ), - '' . $site_first_updated_time . '' - ) : ''; - - /** For "last": Date & time display logic */ - if ( 'last' === $type ) { - - $show_updated_date = $site_last_updated_date ? ' ' . $site_last_updated_date . '' : ''; - - $show_updated_time = $site_last_updated_time ? ' ' . sprintf( - _x( '%s', 'Translators: shortcode, last updated time string', 'builder-shortcode-extras' ), - '' . $site_last_updated_time . '' - ) : ''; - - } // end if - - $date_time_string = sprintf( - '%1$s %2$s %3$s %4$s', - $atts[ 'label_date' ], - $show_updated_date, - $atts[ 'label_time' ], - $show_updated_time - ); + $modified = ddw_bse_site_updated( 'first' === $type ? 'MIN' : 'MAX' ); + if ( ! $modified ) { + return ''; + } + $timestamp = ( new DateTimeImmutable( $modified, wp_timezone() ) )->getTimestamp(); + $show_updated_date = ' ' . esc_html( wp_date( $atts['date_format'], $timestamp ) ) . ''; + $show_updated_time = ' ' . esc_html( wp_date( $atts['time_format'], $timestamp ) ) . ''; + $date_time_string = esc_html( $atts['label_date'] ) . $show_updated_date . ' ' . esc_html( $atts['label_time'] ) . $show_updated_time; /** Prepare output */ $output = sprintf( '<%1$s class="bse-site-%2$s-updated%3$s"%4$s>%5$s%6$s%7$s', - strtolower( sanitize_html_class( $atts[ 'wrapper' ] ) ), + ddw_bse_wrapper_tag( $atts[ 'wrapper' ] ), $type, - ( ! empty( $atts[ 'class' ] ) ) ? ' ' . sanitize_html_class( $atts[ 'class' ] ) : '', - ( ! empty( $atts[ 'tooltip' ] ) ) ? ' title="' . esc_html( $atts[ 'tooltip' ] ) . '"' : '', - ( ! empty( $atts[ 'before' ] ) ) ? esc_html__( $atts[ 'before' ] ) . ' ' : '', + ( ! empty( $atts[ 'class' ] ) ) ? ' ' . esc_attr( ddw_bse_sanitize_html_classes( $atts[ 'class' ], 'string' ) ) : '', + ( ! empty( $atts[ 'tooltip' ] ) ) ? ' title="' . esc_attr( $atts[ 'tooltip' ] ) . '"' : '', + ( ! empty( $atts[ 'before' ] ) ) ? esc_html( $atts[ 'before' ] ) . ' ' : '', $date_time_string, - ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html__( $atts[ 'after' ] ) : '' + ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html( $atts[ 'after' ] ) : '' ); /** Return the output - filterable */ diff --git a/includes/shortcodes/integrations.php b/includes/shortcodes/integrations.php index ab38276..d802fd1 100644 --- a/includes/shortcodes/integrations.php +++ b/includes/shortcodes/integrations.php @@ -27,17 +27,17 @@ function ddw_bse_run_integrations() { $integrations = ddw_bse_get_integrations(); - foreach ( $integrations as $integration ) { + foreach ( $integrations as $key => $integration ) { - if ( 'default-none' !== $integration ) { + if ( 'default-none' !== $key ) { - add_action( "manage_{$integration[ 'post_type' ]}_posts_columns", 'ddw_bse_admin_columns_headers_integrations_post_type' ); + add_filter( "manage_{$integration[ 'post_type' ]}_posts_columns", 'ddw_bse_admin_columns_headers_integrations_post_type' ); add_action( "manage_{$integration[ 'post_type' ]}_posts_custom_column", 'ddw_bse_admin_columns_content_integrations_post_type', 10, 2 ); /** Add the actual Shortcode for this integration */ if ( function_exists( 'ddw_bse_shortcode_item_content' ) ) { - add_shortcode( "bse-{$integration[ 'shortcode_tag' ]}", 'ddw_bse_shortcode_item_content' ); + ddw_bse_register_shortcode( "bse-{$integration[ 'shortcode_tag' ]}", 'ddw_bse_shortcode_item_content' ); } } // end if @@ -83,7 +83,7 @@ function ddw_bse_admin_columns_content_integrations_post_type( $column_name, $po $integrations = ddw_bse_get_integrations(); - foreach ( $integrations as $integration ) { + foreach ( $integrations as $key => $integration ) { if ( 'shortcode' === $column_name && $integration[ 'post_type' ] === get_post_type( $post_id ) @@ -101,7 +101,7 @@ function ddw_bse_admin_columns_content_integrations_post_type( $column_name, $po printf( '', - $integration[ 'shortcode_tag' ], + esc_attr( $integration[ 'shortcode_tag' ] ), $shortcode ); diff --git a/includes/shortcodes/post.php b/includes/shortcodes/post.php index dbf1c9e..8713b8c 100644 --- a/includes/shortcodes/post.php +++ b/includes/shortcodes/post.php @@ -14,7 +14,7 @@ if ( ! function_exists( 'ddw_bse_shortcode_post_count' ) ) : - add_shortcode( 'bse-post-count', 'ddw_bse_shortcode_post_count' ); + ddw_bse_register_shortcode( 'bse-post-count', 'ddw_bse_shortcode_post_count' ); /** * Shortcode to output the current number of all items of a given post type * for a given post status. @@ -50,16 +50,16 @@ function ddw_bse_shortcode_post_count( $atts, $content ) { $post_count = wp_count_posts( $post_type ); - $post_count_result = $post_count->$post_status; + $post_count_result = $post_count->$post_status ?? 0; /** Prepare output */ $output = sprintf( '<%1$s class="bse-post-count%2$s">%3$s%4$s%5$s', - strtolower( sanitize_html_class( $atts[ 'wrapper' ] ) ), - ! empty( $atts[ 'class' ] ) ? ' ' . sanitize_html_class( $atts[ 'class' ] ) : '', - ( ! empty( $atts[ 'before' ] ) ) ? esc_html__( $atts[ 'before' ] ) . ' ' : '', + ddw_bse_wrapper_tag( $atts[ 'wrapper' ] ), + ! empty( $atts[ 'class' ] ) ? ' ' . esc_attr( ddw_bse_sanitize_html_classes( $atts[ 'class' ], 'string' ) ) : '', + ( ! empty( $atts[ 'before' ] ) ) ? esc_html( $atts[ 'before' ] ) . ' ' : '', absint( $post_count_result ), - ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html__( $atts[ 'after' ] ) : '' + ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html( $atts[ 'after' ] ) : '' ); /** Return the output - filterable */ @@ -76,7 +76,7 @@ function ddw_bse_shortcode_post_count( $atts, $content ) { if ( ! function_exists( 'ddw_bse_shortcode_post_date' ) ) : - add_shortcode( 'bse-post-date', 'ddw_bse_shortcode_post_date' ); + ddw_bse_register_shortcode( 'bse-post-date', 'ddw_bse_shortcode_post_date' ); /** * Shortcode to output the date of post publication (or for any post type). * @@ -113,29 +113,29 @@ function ddw_bse_shortcode_post_date( $atts ) { if ( 'relative' === $atts[ 'format' ] ) { - $display = ddw_bse_human_time_diff( get_the_time( 'U', $post_id ), current_time( 'timestamp' ), $atts[ 'relative_depth' ] ); + $display = ddw_bse_human_time_diff( get_post_timestamp( $post_id ), time(), $atts[ 'relative_depth' ] ); $display .= ' ' . __( 'ago', 'builder-shortcode-extras' ); } else { - $display = get_the_time( $atts[ 'format' ] ); + $display = get_the_time( $atts[ 'format' ], $post_id ); } // end if $date_string = sprintf( '', ddw_bse_attr( 'entry-time' ), - $atts[ 'label' ] . $display + esc_html( $atts[ 'label' ] . $display ) ); /** Prepare output */ $output = sprintf( '<%1$s class="bse-post-time%2$s">%3$s%4$s%5$s', - strtolower( sanitize_html_class( $atts[ 'wrapper' ] ) ), - ! empty( $atts[ 'class' ] ) ? ' ' . sanitize_html_class( $atts[ 'class' ] ) : '', - ( ! empty( $atts[ 'before' ] ) ) ? esc_html__( $atts[ 'before' ] ) . ' ' : '', + ddw_bse_wrapper_tag( $atts[ 'wrapper' ] ), + ! empty( $atts[ 'class' ] ) ? ' ' . esc_attr( ddw_bse_sanitize_html_classes( $atts[ 'class' ], 'string' ) ) : '', + ( ! empty( $atts[ 'before' ] ) ) ? esc_html( $atts[ 'before' ] ) . ' ' : '', $date_string, - ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html__( $atts[ 'after' ] ) : '' + ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html( $atts[ 'after' ] ) : '' ); /** Return the output - filterable */ @@ -152,7 +152,7 @@ function ddw_bse_shortcode_post_date( $atts ) { if ( ! function_exists( 'ddw_bse_shortcode_post_time' ) ) : - add_shortcode( 'bse-post-time', 'ddw_bse_shortcode_post_time' ); + ddw_bse_register_shortcode( 'bse-post-time', 'ddw_bse_shortcode_post_time' ); /** * Shortcode to output the time of post publication (or for any post type). * @@ -187,17 +187,17 @@ function ddw_bse_shortcode_post_time( $atts ) { $time_string = sprintf( '', ddw_bse_attr( 'entry-time' ), - $atts[ 'label' ] . get_the_time( $atts[ 'format' ], $post_id ) + esc_html( $atts[ 'label' ] . get_the_time( $atts[ 'format' ], $post_id ) ) ); /** Prepare output */ $output = sprintf( '<%1$s class="bse-post-time%2$s">%3$s%4$s%5$s', - strtolower( sanitize_html_class( $atts[ 'wrapper' ] ) ), - ! empty( $atts[ 'class' ] ) ? ' ' . sanitize_html_class( $atts[ 'class' ] ) : '', - ( ! empty( $atts[ 'before' ] ) ) ? esc_html__( $atts[ 'before' ] ) . ' ' : '', + ddw_bse_wrapper_tag( $atts[ 'wrapper' ] ), + ! empty( $atts[ 'class' ] ) ? ' ' . esc_attr( ddw_bse_sanitize_html_classes( $atts[ 'class' ], 'string' ) ) : '', + ( ! empty( $atts[ 'before' ] ) ) ? esc_html( $atts[ 'before' ] ) . ' ' : '', $time_string, - ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html__( $atts[ 'after' ] ) : '' + ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html( $atts[ 'after' ] ) : '' ); /** Return the output - filterable */ @@ -214,7 +214,7 @@ function ddw_bse_shortcode_post_time( $atts ) { if ( ! function_exists( 'ddw_bse_shortcode_post_modified_date' ) ) : - add_shortcode( 'bse-post-modified-date', 'ddw_bse_shortcode_post_modified_date' ); + ddw_bse_register_shortcode( 'bse-post-modified-date', 'ddw_bse_shortcode_post_modified_date' ); /** * Shortcode to output the post last modified date (or for any post type). * @@ -251,7 +251,7 @@ function ddw_bse_shortcode_post_modified_date( $atts ) { if ( 'relative' === $atts[ 'format' ] ) { - $display = ddw_bse_human_time_diff( get_the_modified_time( 'U', $post_id ), current_time( 'timestamp' ), $atts[ 'relative_depth' ] ); + $display = ddw_bse_human_time_diff( get_post_timestamp( $post_id, 'modified' ), time(), $atts[ 'relative_depth' ] ); $display .= ' ' . __( 'ago', 'builder-shortcode-extras' ); } else { @@ -263,17 +263,17 @@ function ddw_bse_shortcode_post_modified_date( $atts ) { $date_string = sprintf( '', ddw_bse_attr( 'entry-modified-time' ), - $atts[ 'label' ] . $display + esc_html( $atts[ 'label' ] . $display ) ); /** Prepare output */ $output = sprintf( '<%1$s class="bse-post-modified-date%2$s">%3$s%4$s%5$s', - strtolower( sanitize_html_class( $atts[ 'wrapper' ] ) ), - ! empty( $atts[ 'class' ] ) ? ' ' . sanitize_html_class( $atts[ 'class' ] ) : '', - ( ! empty( $atts[ 'before' ] ) ) ? esc_html__( $atts[ 'before' ] ) . ' ' : '', + ddw_bse_wrapper_tag( $atts[ 'wrapper' ] ), + ! empty( $atts[ 'class' ] ) ? ' ' . esc_attr( ddw_bse_sanitize_html_classes( $atts[ 'class' ], 'string' ) ) : '', + ( ! empty( $atts[ 'before' ] ) ) ? esc_html( $atts[ 'before' ] ) . ' ' : '', $date_string, - ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html__( $atts[ 'after' ] ) : '' + ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html( $atts[ 'after' ] ) : '' ); /** Return the output - filterable */ @@ -290,7 +290,7 @@ function ddw_bse_shortcode_post_modified_date( $atts ) { if ( ! function_exists( 'ddw_bse_shortcode_item_last_updated' ) ) : - add_shortcode( 'bse-item-last-updated', 'ddw_bse_shortcode_item_last_updated' ); + ddw_bse_register_shortcode( 'bse-item-last-updated', 'ddw_bse_shortcode_item_last_updated' ); /** * Shortcode to output the date when a post type item was last updated. * @@ -312,7 +312,7 @@ function ddw_bse_shortcode_item_last_updated( $atts ) { if ( ! function_exists( 'ddw_bse_shortcode_post_modified_time' ) ) : - add_shortcode( 'bse-post-modified-time', 'ddw_bse_shortcode_post_modified_time' ); + ddw_bse_register_shortcode( 'bse-post-modified-time', 'ddw_bse_shortcode_post_modified_time' ); /** * Shortcode to output the post last modified time (or for any post type). * @@ -347,17 +347,17 @@ function ddw_bse_shortcode_post_modified_time( $atts ) { $time_string = sprintf( '', ddw_bse_attr( 'entry-modified-time' ), - $atts[ 'label' ] . get_the_modified_time( $atts[ 'format' ], $post_id ) + esc_html( $atts[ 'label' ] . get_the_modified_time( $atts[ 'format' ], $post_id ) ) ); /** Prepare output */ $output = sprintf( '<%1$s class="bse-post-modified-time%2$s">%3$s%4$s%5$s', - strtolower( sanitize_html_class( $atts[ 'wrapper' ] ) ), - ! empty( $atts[ 'class' ] ) ? ' ' . sanitize_html_class( $atts[ 'class' ] ) : '', - ( ! empty( $atts[ 'before' ] ) ) ? esc_html__( $atts[ 'before' ] ) . ' ' : '', + ddw_bse_wrapper_tag( $atts[ 'wrapper' ] ), + ! empty( $atts[ 'class' ] ) ? ' ' . esc_attr( ddw_bse_sanitize_html_classes( $atts[ 'class' ], 'string' ) ) : '', + ( ! empty( $atts[ 'before' ] ) ) ? esc_html( $atts[ 'before' ] ) . ' ' : '', $time_string, - ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html__( $atts[ 'after' ] ) : '' + ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html( $atts[ 'after' ] ) : '' ); /** Return the output - filterable */ @@ -374,7 +374,7 @@ function ddw_bse_shortcode_post_modified_time( $atts ) { if ( ! function_exists( 'ddw_bse_shortcode_post_author' ) ) : - add_shortcode( 'bse-post-author', 'ddw_bse_shortcode_post_author' ); + ddw_bse_register_shortcode( 'bse-post-author', 'ddw_bse_shortcode_post_author' ); /** * Shortcode to output the author of the post/ post type item (unlinked * display name). @@ -427,11 +427,11 @@ function ddw_bse_shortcode_post_author( $atts ) { /** Prepare output */ $output = sprintf( '<%1$s class="entry-author bse-post-author%2$s">%3$s%4$s%5$s', - strtolower( sanitize_html_class( $atts[ 'wrapper' ] ) ), - ! empty( $atts[ 'class' ] ) ? ' ' . sanitize_html_class( $atts[ 'class' ] ) : '', - ( ! empty( $atts[ 'before' ] ) ) ? esc_html__( $atts[ 'before' ] ) . ' ' : '', + ddw_bse_wrapper_tag( $atts[ 'wrapper' ] ), + ! empty( $atts[ 'class' ] ) ? ' ' . esc_attr( ddw_bse_sanitize_html_classes( $atts[ 'class' ], 'string' ) ) : '', + ( ! empty( $atts[ 'before' ] ) ) ? esc_html( $atts[ 'before' ] ) . ' ' : '', $author_string, - ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html__( $atts[ 'after' ] ) : '' + ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html( $atts[ 'after' ] ) : '' ); /** Return the output - filterable */ @@ -448,7 +448,7 @@ function ddw_bse_shortcode_post_author( $atts ) { if ( ! function_exists( 'ddw_bse_shortcode_post_author_link' ) ) : - add_shortcode( 'bse-post-author-link', 'ddw_bse_shortcode_post_author_link' ); + ddw_bse_register_shortcode( 'bse-post-author-link', 'ddw_bse_shortcode_post_author_link' ); /** * Shortcode to output the author of the post/ post type item (link to * author URL). @@ -526,11 +526,11 @@ function ddw_bse_shortcode_post_author_link( $atts ) { /** Prepare output */ $output = sprintf( '<%1$s class="entry-author bse-post-author-link%2$s">%3$s%4$s%5$s', - strtolower( sanitize_html_class( $atts[ 'wrapper' ] ) ), - ( ! empty( $atts[ 'class' ] ) ) ? ' ' . sanitize_html_class( $atts[ 'class' ] ) : '', - ( ! empty( $atts[ 'before' ] ) ) ? esc_html__( $atts[ 'before' ] ) . ' ' : '', + ddw_bse_wrapper_tag( $atts[ 'wrapper' ] ), + ( ! empty( $atts[ 'class' ] ) ) ? ' ' . esc_attr( ddw_bse_sanitize_html_classes( $atts[ 'class' ], 'string' ) ) : '', + ( ! empty( $atts[ 'before' ] ) ) ? esc_html( $atts[ 'before' ] ) . ' ' : '', $author_link, - ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html__( $atts[ 'after' ] ) : '' + ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html( $atts[ 'after' ] ) : '' ); /** Return the output - filterable */ @@ -547,7 +547,7 @@ function ddw_bse_shortcode_post_author_link( $atts ) { if ( ! function_exists( 'ddw_bse_shortcode_post_author_posts_link' ) ) : - add_shortcode( 'bse-post-author-posts-link', 'ddw_bse_shortcode_post_author_posts_link' ); + ddw_bse_register_shortcode( 'bse-post-author-posts-link', 'ddw_bse_shortcode_post_author_posts_link' ); /** * Shortcode to output the author of the post/ post type item (link to * author archive). @@ -618,11 +618,11 @@ function ddw_bse_shortcode_post_author_posts_link( $atts ) { /** Prepare output */ $output = sprintf( '<%1$s class="entry-author bse-post-author-posts-link%2$s">%3$s%4$s%5$s', - strtolower( sanitize_html_class( $atts[ 'wrapper' ] ) ), - ! empty( $atts[ 'class' ] ) ? ' ' . sanitize_html_class( $atts[ 'class' ] ) : '', - ( ! empty( $atts[ 'before' ] ) ) ? esc_html__( $atts[ 'before' ] ) . ' ' : '', + ddw_bse_wrapper_tag( $atts[ 'wrapper' ] ), + ! empty( $atts[ 'class' ] ) ? ' ' . esc_attr( ddw_bse_sanitize_html_classes( $atts[ 'class' ], 'string' ) ) : '', + ( ! empty( $atts[ 'before' ] ) ) ? esc_html( $atts[ 'before' ] ) . ' ' : '', $author_link, - ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html__( $atts[ 'after' ] ) : '' + ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html( $atts[ 'after' ] ) : '' ); /** Return the output - filterable */ @@ -639,7 +639,7 @@ function ddw_bse_shortcode_post_author_posts_link( $atts ) { if ( ! function_exists( 'ddw_bse_shortcode_post_tags' ) ) : - add_shortcode( 'bse-post-tags', 'ddw_bse_shortcode_post_tags' ); + ddw_bse_register_shortcode( 'bse-post-tags', 'ddw_bse_shortcode_post_tags' ); /** * Shortcode to output the tag links list (Post Tags). * @@ -690,8 +690,8 @@ function ddw_bse_shortcode_post_tags( $atts ) { /** Prepare output */ $output = sprintf( '<%1$s class="entry-tags bse-post-tags%2$s">%3$s', - strtolower( sanitize_html_class( $atts[ 'wrapper' ] ) ), - ! empty( $atts[ 'class' ] ) ? ' ' . sanitize_html_class( $atts[ 'class' ] ) : '', + ddw_bse_wrapper_tag( $atts[ 'wrapper' ] ), + ! empty( $atts[ 'class' ] ) ? ' ' . esc_attr( ddw_bse_sanitize_html_classes( $atts[ 'class' ], 'string' ) ) : '', $tags ); @@ -709,7 +709,7 @@ function ddw_bse_shortcode_post_tags( $atts ) { if ( ! function_exists( 'ddw_bse_shortcode_post_categories' ) ) : - add_shortcode( 'bse-post-categories', 'ddw_bse_shortcode_post_categories' ); + ddw_bse_register_shortcode( 'bse-post-categories', 'ddw_bse_shortcode_post_categories' ); /** * Shortcode to output the category links list (Post Categories). * @@ -754,11 +754,11 @@ function ddw_bse_shortcode_post_categories( $atts ) { /** Prepare output */ $output = sprintf( '<%1$s class="entry-categories bse-post-categories%2$s">%3$s%4$s%5$s', - strtolower( sanitize_html_class( $atts[ 'wrapper' ] ) ), - ! empty( $atts[ 'class' ] ) ? ' ' . sanitize_html_class( $atts[ 'class' ] ) : '', - ( ! empty( $atts[ 'before' ] ) ) ? esc_html__( $atts[ 'before' ] ) . ' ' : '', + ddw_bse_wrapper_tag( $atts[ 'wrapper' ] ), + ! empty( $atts[ 'class' ] ) ? ' ' . esc_attr( ddw_bse_sanitize_html_classes( $atts[ 'class' ], 'string' ) ) : '', + ( ! empty( $atts[ 'before' ] ) ) ? esc_html( $atts[ 'before' ] ) . ' ' : '', $cats, - ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html__( $atts[ 'after' ] ) : '' + ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html( $atts[ 'after' ] ) : '' ); /** Return the output - filterable */ @@ -775,7 +775,7 @@ function ddw_bse_shortcode_post_categories( $atts ) { if ( ! function_exists( 'ddw_bse_shortcode_post_terms' ) ) : - add_shortcode( 'bse-post-terms', 'ddw_bse_shortcode_post_terms' ); + ddw_bse_register_shortcode( 'bse-post-terms', 'ddw_bse_shortcode_post_terms' ); /** * Shortcode to output the linked post taxonomy terms list (for any (custom) * taxonomy). @@ -844,8 +844,8 @@ function ddw_bse_shortcode_post_terms( $atts ) { /** Prepare output */ $output = sprintf( '<%1$s class="entry-terms bse-post-terms%2$s">%3$s', - strtolower( sanitize_html_class( $atts[ 'wrapper' ] ) ), - ! empty( $atts[ 'class' ] ) ? ' ' . sanitize_html_class( $atts[ 'class' ] ) : '', + ddw_bse_wrapper_tag( $atts[ 'wrapper' ] ), + ! empty( $atts[ 'class' ] ) ? ' ' . esc_attr( ddw_bse_sanitize_html_classes( $atts[ 'class' ], 'string' ) ) : '', $terms ); @@ -864,7 +864,7 @@ function ddw_bse_shortcode_post_terms( $atts ) { if ( ! function_exists( 'ddw_bse_shortcode_post_edit' ) ) : - add_shortcode( 'bse-post-edit', 'ddw_bse_shortcode_post_edit' ); + ddw_bse_register_shortcode( 'bse-post-edit', 'ddw_bse_shortcode_post_edit' ); /** * Shortcode to output the edit post link for logged in users. * @@ -905,8 +905,8 @@ function ddw_bse_shortcode_post_edit( $atts ) { /** Prepare output */ $output = sprintf( '<%1$s class="bse-post-edit%2$s">%3$s', - strtolower( sanitize_html_class( $atts[ 'wrapper' ] ) ), - ! empty( $atts[ 'class' ] ) ? ' ' . sanitize_html_class( $atts[ 'class' ] ) : '', + ddw_bse_wrapper_tag( $atts[ 'wrapper' ] ), + ! empty( $atts[ 'class' ] ) ? ' ' . esc_attr( ddw_bse_sanitize_html_classes( $atts[ 'class' ], 'string' ) ) : '', $edit ); @@ -926,7 +926,7 @@ function ddw_bse_shortcode_post_edit( $atts ) { if ( ! function_exists( 'ddw_bse_shortcode_post_link' ) ) : - add_shortcode( 'bse-post-link', 'ddw_bse_shortcode_post_link' ); + ddw_bse_register_shortcode( 'bse-post-link', 'ddw_bse_shortcode_post_link' ); /** * Shortcode to output the current number of all items of a given post type * for a given post status. @@ -978,34 +978,40 @@ function ddw_bse_shortcode_post_link( $atts, $content ) { $permalink = null; $title = null; - if ( '' !== $post_id ) { + if ( $post_id > 0 ) { $permalink = get_permalink( $post_id ); $title = get_the_title( $post_id ); - } elseif ( '' === $post_id && '' !== $atts[ 'slug' ] ) { + } elseif ( 0 === $post_id && '' !== $atts[ 'slug' ] ) { - $permalink = get_permalink( ddw_bse_get_post_id_by_slug( $atts[ 'slug' ], $atts[ 'post_type' ] ) ); - $title = get_the_title( ddw_bse_get_post_id_by_slug( $atts[ 'slug' ], $atts[ 'post_type' ] ) ); + $resolved_id = ddw_bse_get_post_id_by_slug( $atts[ 'slug' ], $atts[ 'post_type' ] ); + if ( ! $resolved_id ) { return ''; } + $permalink = get_permalink( $resolved_id ); + $title = get_the_title( $resolved_id ); } // end if + if ( ! $permalink ) { + return ''; + } + $post_link = sprintf( '%4$s', esc_url( $permalink ), ( ! empty( $atts[ 'target' ] ) ) ? $target : '', ( ! empty( $atts[ 'target' ] ) && ! empty( $atts[ 'rel' ] ) ) ? $rel : '', - ( empty( $atts[ 'text' ] ) ) ? $title : $atts[ 'text' ] + esc_html( ( empty( $atts[ 'text' ] ) ) ? (string) $title : $atts[ 'text' ] ) ); /** Prepare output */ $output = sprintf( '<%1$s class="bse-post-link%2$s">%3$s%4$s%5$s', - strtolower( sanitize_html_class( $atts[ 'wrapper' ] ) ), - ! empty( $atts[ 'class' ] ) ? ' ' . sanitize_html_class( $atts[ 'class' ] ) : '', - ( ! empty( $atts[ 'before' ] ) ) ? esc_html__( $atts[ 'before' ] ) . ' ' : '', + ddw_bse_wrapper_tag( $atts[ 'wrapper' ] ), + ! empty( $atts[ 'class' ] ) ? ' ' . esc_attr( ddw_bse_sanitize_html_classes( $atts[ 'class' ], 'string' ) ) : '', + ( ! empty( $atts[ 'before' ] ) ) ? esc_html( $atts[ 'before' ] ) . ' ' : '', $post_link, - ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html__( $atts[ 'after' ] ) : '' + ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html( $atts[ 'after' ] ) : '' ); /** Return the output - filterable */ diff --git a/includes/shortcodes/user.php b/includes/shortcodes/user.php index 0b026f6..33a85c6 100644 --- a/includes/shortcodes/user.php +++ b/includes/shortcodes/user.php @@ -14,7 +14,7 @@ if ( ! function_exists( 'ddw_bse_shortcode_user' ) ) : - add_shortcode( 'bse-user', 'ddw_bse_shortcode_user' ); + ddw_bse_register_shortcode( 'bse-user', 'ddw_bse_shortcode_user' ); /** * ??? * @@ -46,12 +46,12 @@ function ddw_bse_shortcode_user( $atts = null, $content = null ) { $output_text = ''; /** Bail early if password gets requested - not allowed! */ - if ( 'user_pass' === $atts[ 'field' ] ) { + if ( in_array( $atts['field'], array( 'user_pass', 'user_activation_key' ), true ) ) { return sprintf( '%s: %s', __( 'User', 'builder-shortcode-extras' ), - __( 'The password field is not allowed', 'builder-shortcode-extras' ) + __( 'This user field is not allowed', 'builder-shortcode-extras' ) ); } // end if @@ -81,11 +81,11 @@ function ddw_bse_shortcode_user( $atts = null, $content = null ) { /** Prepare output */ $output = sprintf( '<%1$s class="bse-user%2$s">%3$s%4$s%5$s', - strtolower( sanitize_html_class( $atts[ 'wrapper' ] ) ), - ! empty( $atts[ 'class' ] ) ? ' ' . sanitize_html_class( $atts[ 'class' ] ) : '', - ( ! empty( $atts[ 'before' ] ) ) ? esc_html__( $atts[ 'before' ] ) . ' ' : '', - ( $user_data ) ? $user_data : '', - ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html__( $atts[ 'after' ] ) : '' + ddw_bse_wrapper_tag( $atts[ 'wrapper' ] ), + ! empty( $atts[ 'class' ] ) ? ' ' . esc_attr( ddw_bse_sanitize_html_classes( $atts[ 'class' ], 'string' ) ) : '', + ( ! empty( $atts[ 'before' ] ) ) ? esc_html( $atts[ 'before' ] ) . ' ' : '', + esc_html( (string) $user_data ), + ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html( $atts[ 'after' ] ) : '' ); /** Return the output - filterable */ @@ -102,7 +102,7 @@ function ddw_bse_shortcode_user( $atts = null, $content = null ) { if ( ! function_exists( 'ddw_bse_shortcode_user_id' ) ) : - add_shortcode( 'bse-userid', 'ddw_bse_shortcode_user_id' ); + ddw_bse_register_shortcode( 'bse-userid', 'ddw_bse_shortcode_user_id' ); /** * Shortcode to output a users ID. * @@ -132,7 +132,7 @@ function ddw_bse_shortcode_user_id( $atts, $content ) { if ( ! function_exists( 'ddw_bse_shortcode_user_email' ) ) : - add_shortcode( 'bse-email', 'ddw_bse_shortcode_user_email' ); + ddw_bse_register_shortcode( 'bse-email', 'ddw_bse_shortcode_user_email' ); /** * Shortcode to output a users email. * @@ -162,7 +162,7 @@ function ddw_bse_shortcode_user_email( $atts, $content ) { if ( ! function_exists( 'ddw_bse_shortcode_user_login_name' ) ) : - add_shortcode( 'bse-login', 'ddw_bse_shortcode_user_login_name' ); + ddw_bse_register_shortcode( 'bse-login', 'ddw_bse_shortcode_user_login_name' ); /** * Shortcode to output a users login handle/name. * @@ -192,7 +192,7 @@ function ddw_bse_shortcode_user_login_name( $atts, $content ) { if ( ! function_exists( 'ddw_bse_shortcode_user_display_name' ) ) : - add_shortcode( 'bse-displayname', 'ddw_bse_shortcode_user_display_name' ); + ddw_bse_register_shortcode( 'bse-displayname', 'ddw_bse_shortcode_user_display_name' ); /** * Shortcode to output a users display name. * @@ -222,7 +222,7 @@ function ddw_bse_shortcode_user_display_name( $atts, $content ) { if ( ! function_exists( 'ddw_bse_shortcode_user_firstname' ) ) : - add_shortcode( 'bse-firstname', 'ddw_bse_shortcode_user_firstname' ); + ddw_bse_register_shortcode( 'bse-firstname', 'ddw_bse_shortcode_user_firstname' ); /** * Shortcode to output a users first name. * @@ -252,7 +252,7 @@ function ddw_bse_shortcode_user_firstname( $atts ) { if ( ! function_exists( 'ddw_bse_shortcode_user_lastname' ) ) : - add_shortcode( 'bse-lastname', 'ddw_bse_shortcode_user_lastname' ); + ddw_bse_register_shortcode( 'bse-lastname', 'ddw_bse_shortcode_user_lastname' ); /** * Shortcode to output a users last name. * diff --git a/includes/shortcodes/version.php b/includes/shortcodes/version.php index 1cda0b3..11560fa 100644 --- a/includes/shortcodes/version.php +++ b/includes/shortcodes/version.php @@ -14,7 +14,7 @@ if ( ! function_exists( 'ddw_bse_shortcode_version' ) ) { - add_shortcode( 'bse-version', 'ddw_bse_shortcode_version' ); + ddw_bse_register_shortcode( 'bse-version', 'ddw_bse_shortcode_version' ); /** * Shortcode to output a version number for various types and occeasions, * like WordPress, PHP, MySQL/ MariaDB, Database, Server, Elementor, @@ -47,6 +47,8 @@ function ddw_bse_shortcode_version( $atts ) { /** Default shortcode attributes */ $atts = shortcode_atts( $defaults, $atts, 'bse-version' ); + global $wpdb; + $type = sanitize_key( $atts[ 'type' ] ); $plugin = sanitize_key( $atts[ 'plugin' ] ); $version = ''; @@ -72,7 +74,7 @@ function ddw_bse_shortcode_version( $atts ) { } elseif ( 'required' === $type ) { $plugin = strtoupper( $plugin ); - $defined = constant( $plugin . '_REQUIRED_BASE_PLUGIN_VERSION' ); + $defined = ( defined( $plugin . '_REQUIRED_BASE_PLUGIN_VERSION' ) ? constant( $plugin . '_REQUIRED_BASE_PLUGIN_VERSION' ) : '' ); $version = $defined ? $defined : $bse; } elseif ( 'custom' === $type ) { @@ -82,7 +84,7 @@ function ddw_bse_shortcode_version( $atts ) { } elseif ( 'constant' === $type ) { $constant = strtoupper( sanitize_key( $atts[ 'constant' ] ) ); - $constant = constant( $constant ); + $constant = defined( $constant ) ? constant( $constant ) : ''; $version = $constant ? $constant : __( '(Not defined)', 'builder-shortcode-extras' ); } elseif ( 'db_current' === $type ) { @@ -99,15 +101,15 @@ function ddw_bse_shortcode_version( $atts ) { $version = $bse_info[ $type ]; } // end if - + /** Prepare output */ $output = sprintf( '<%1$s class="bse-version%2$s">%3$s%4$s%5$s', - strtolower( sanitize_html_class( $atts[ 'wrapper' ] ) ), - ! empty( $atts[ 'class' ] ) ? ' ' . sanitize_html_class( $atts[ 'class' ] ) : '', - ( ! empty( $atts[ 'before' ] ) ) ? esc_html__( $atts[ 'before' ] ) . ' ' : '', - ( ! is_null( $version ) ) ? $version : '', - ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html__( $atts[ 'after' ] ) : '' + ddw_bse_wrapper_tag( $atts[ 'wrapper' ] ), + ! empty( $atts[ 'class' ] ) ? ' ' . esc_attr( ddw_bse_sanitize_html_classes( $atts[ 'class' ], 'string' ) ) : '', + ( ! empty( $atts[ 'before' ] ) ) ? esc_html( $atts[ 'before' ] ) . ' ' : '', + esc_html( is_scalar( $version ) ? (string) $version : '' ), + ( ! empty( $atts[ 'after' ] ) ) ? ' ' . esc_html( $atts[ 'after' ] ) : '' ); /** Return the output - filterable */ diff --git a/languages/builder-shortcode-extras-de_DE.mo b/languages/builder-shortcode-extras-de_DE.mo index 51e3054..f8a9935 100644 Binary files a/languages/builder-shortcode-extras-de_DE.mo and b/languages/builder-shortcode-extras-de_DE.mo differ diff --git a/languages/builder-shortcode-extras-de_DE.po b/languages/builder-shortcode-extras-de_DE.po index b114859..8e7f8a6 100644 --- a/languages/builder-shortcode-extras-de_DE.po +++ b/languages/builder-shortcode-extras-de_DE.po @@ -1,8 +1,8 @@ msgid "" msgstr "" "Project-Id-Version: Builder Shortcode Extras\n" -"POT-Creation-Date: 2025-03-15 09:54+0100\n" -"PO-Revision-Date: 2025-03-15 09:54+0100\n" +"POT-Creation-Date: 2026-10-01 18:00+0200\n" +"PO-Revision-Date: 2026-10-01 18:00+0200\n" "Last-Translator: David Decker DECKERWEB.de \n" "Language-Team: David Decker DECKERWEB.de\n" "Language: de_DE\n" @@ -366,3 +366,531 @@ msgstr "https://deckerweb.de/" #~ msgstr "" #~ "Gefällt dir %1$s? Dann gib uns bitte eine %2$s. Wir schätzen deine " #~ "Unterstützung!" + +#: includes/class-bse-shortcode-catalog.php:15 +msgid "A copyright notice with the current year." +msgstr "Ein Copyright-Hinweis mit dem aktuellen Jahr." + +#: includes/class-bse-shortcode-catalog.php:19 +msgid "A link matching the visitor’s login state." +msgstr "Ein Link passend zum Anmeldestatus des Besuchers." + +#: includes/class-bse-shortcode-catalog.php:18 +msgid "A link to your homepage." +msgstr "Ein Link zu deiner Startseite." + +#: includes/class-bse-shortcode-catalog.php:35 +msgid "A selected user field with an optional fallback." +msgstr "Ein gewähltes Benutzerfeld mit optionalem Ersatztext." + +#: includes/class-bse-shortcode-catalog.php:25 +msgid "An alias for bse-post-modified-date." +msgstr "Ein Alias für bse-post-modified-date." + +#: includes/class-bse-shortcode-catalog.php:33 +msgid "An edit link for visitors with editing permission." +msgstr "Ein Bearbeitungslink für Besucher mit Bearbeitungsrechten." + +#: includes/class-bse-shortcode-catalog.php:44 +msgid "Astra layout" +msgstr "Astra-Layout" + +#: includes/admin/class-bse-tools.php:34 +msgid "Attribute values cannot contain quotation marks, square brackets or line breaks." +msgstr "Attributwerte dürfen keine Anführungszeichen, eckigen Klammern oder Zeilenumbrüche enthalten." + +#: includes/class-bse-shortcode-catalog.php:29 +msgid "Author archive link" +msgstr "Autorenarchiv-Link" + +#: includes/class-bse-shortcode-catalog.php:28 +msgid "Author website link" +msgstr "Autorenwebsite-Link" + +#: includes/admin/class-bse-tools.php:79 +msgid "Available attributes" +msgstr "Verfügbare Attribute" + +#: includes/admin/class-bse-tools.php:49 +msgid "Build your shortcode" +msgstr "Deinen Shortcode erstellen" + +#: includes/class-bse-shortcode-catalog.php:34 +msgid "CSS classes (optional)" +msgstr "CSS-Klassen (optional)" + +#: includes/admin/class-bse-tools.php:93 +msgid "Changelog" +msgstr "Änderungsprotokoll" + +#: includes/class-bse-changelog.php:78 +msgid "Close" +msgstr "Schließen" + +#: includes/class-bse-shortcode-catalog.php:11 +msgid "Comment form" +msgstr "Kommentarformular" + +#: includes/admin/class-bse-tools.php:78 +msgid "Configure in generator" +msgstr "Im Generator konfigurieren" + +#: includes/class-bse-shortcode-catalog.php:10 +msgid "Content ID" +msgstr "Inhalts-ID" + +#: includes/admin/class-bse-tools.php:30 +msgid "Copied. Paste this shortcode into a shortcode-enabled field." +msgstr "Kopiert. Füge den Shortcode in ein Feld mit Shortcode-Unterstützung ein." + +#: includes/admin/class-bse-tools.php:77 +msgid "Copy" +msgstr "Kopieren" + +#: includes/admin/class-bse-tools.php:77 +msgid "Copy shortcode" +msgstr "Shortcode kopieren" + +#: includes/admin/class-bse-tools.php:31 +msgid "Copy the selected shortcode manually." +msgstr "Kopiere den markierten Shortcode manuell." + +#: includes/class-bse-shortcode-catalog.php:15 +msgid "Copyright" +msgstr "Copyright" + +#: includes/class-bse-shortcode-catalog.php:21 +msgid "Count content by post type and status." +msgstr "Inhalte nach Inhaltstyp und Status zählen." + +#: includes/class-bse-shortcode-catalog.php:36 +msgid "Current user ID" +msgstr "Aktuelle Benutzer-ID" + +#: includes/class-bse-shortcode-catalog.php:37 +msgid "Current user email" +msgstr "Aktuelle Benutzer-E-Mail" + +#: includes/class-bse-shortcode-catalog.php:40 +msgid "Current user first name" +msgstr "Aktueller Benutzer-Vorname" + +#: includes/class-bse-shortcode-catalog.php:41 +msgid "Current user last name" +msgstr "Aktueller Benutzer-Nachname" + +#: includes/class-bse-shortcode-catalog.php:38 +msgid "Current user login" +msgstr "Aktueller Benutzer-Anmeldename" + +#: includes/class-bse-shortcode-catalog.php:39 +msgid "Current user name" +msgstr "Aktueller Benutzername" + +#: includes/class-bse-shortcode-catalog.php:24 +msgid "Date format" +msgstr "Datumsformat" + +#: includes/class-bse-shortcode-catalog.php:24 +msgid "December 31, 2026" +msgstr "31. Dezember 2026" + +#: includes/admin/class-bse-tools.php:93 +msgid "Documentation" +msgstr "Dokumentation" + +#: includes/class-bse-shortcode-catalog.php:33 +msgid "Edit post link" +msgstr "Beitrag-bearbeiten-Link" + +#: includes/class-bse-shortcode-catalog.php:12 +msgid "Elementor template" +msgstr "Elementor-Vorlage" + +#: includes/class-bse-shortcode-catalog.php:43 +msgid "Embed a WordPress synced pattern by ID." +msgstr "Eine synchronisierte WordPress-Vorlage per ID einbetten." + +#: includes/class-bse-shortcode-catalog.php:12 +msgid "Embed a saved Elementor template; offered with Elementor Free." +msgstr "Eine gespeicherte Elementor-Vorlage einbetten; verfügbar mit Elementor Free." + +#: includes/class-bse-shortcode-catalog.php:44 +msgid "Embed an Astra Pro custom layout by ID." +msgstr "Ein Astra-Pro-Layout per ID einbetten." + +#: includes/class-bse-shortcode-catalog.php:10 +msgid "Embed content" +msgstr "Inhalt einbetten" + +#: includes/admin/class-bse-tools.php:32 +msgid "Enter a valid content ID greater than zero." +msgstr "Gib eine gültige Inhalts-ID größer als null ein." + +#: includes/class-bse-shortcode-catalog.php:34 +msgid "Essentials" +msgstr "Die wichtigsten Helfer" + +#: includes/admin/class-bse-tools.php:46 +msgid "Find a helper, copy its shortcode and paste it into a Shortcode block or a shortcode-enabled builder field. No settings to save." +msgstr "Wähle einen Helfer, kopiere den Shortcode und füge ihn in einen Shortcode-Block oder ein geeignetes Builder-Feld ein. Es gibt keine Einstellungen zu speichern." + +#: includes/admin/class-bse-tools.php:65 +msgid "Find a shortcode" +msgstr "Einen Shortcode finden" + +#: includes/class-bse-shortcode-catalog.php:15 +msgid "First year" +msgstr "Erstes Jahr" + +#: includes/admin/class-bse-tools.php:61 +msgid "For dates, leave the post ID empty to use the current post. Insert generated shortcodes on your website to see their output." +msgstr "Lasse bei Datumsangaben die Beitrags-ID leer, um den aktuellen Beitrag zu verwenden. Füge den Shortcode auf deiner Website ein, um die Ausgabe zu sehen." + +#: includes/class-bse-shortcode-catalog.php:14 +msgid "Genesis breadcrumbs" +msgstr "Genesis-Breadcrumbs" + +#: includes/class-bse-shortcode-catalog.php:13 +msgid "Genesis footer" +msgstr "Genesis-Footer" + +#: includes/class-bse-shortcode-catalog.php:18 +msgid "Homepage link" +msgstr "Startseiten-Link" + +#: includes/class-bse-shortcode-catalog.php:10 +msgid "Include Elementor CSS" +msgstr "Elementor-CSS einbinden" + +#: includes/class-bse-shortcode-catalog.php:44 +msgid "Integrations" +msgstr "Integrationen" + +#: includes/class-bse-shortcode-catalog.php:24 +msgid "Label" +msgstr "Beschriftung" + +#: includes/class-bse-shortcode-catalog.php:24 +msgid "Last updated" +msgstr "Zuletzt aktualisiert" + +#: includes/class-bse-shortcode-catalog.php:25 +msgid "Last updated (alias)" +msgstr "Zuletzt aktualisiert (Alias)" + +#: includes/class-bse-shortcode-catalog.php:26 +msgid "Last updated time" +msgstr "Letzte Aktualisierungszeit" + +#: includes/class-bse-shortcode-catalog.php:34 +msgid "Link text (optional)" +msgstr "Linktext (optional)" + +#: includes/class-bse-shortcode-catalog.php:34 +msgid "Link to an item by ID or slug, or to the privacy policy." +msgstr "Ein Link zu einem Inhalt per ID oder Slug oder zur Datenschutzerklärung." + +#: includes/class-bse-shortcode-catalog.php:19 +msgid "Login / logout link" +msgstr "Anmelde- / Abmelde-Link" + +#: includes/class-bse-shortcode-catalog.php:42 +msgid "More helpers" +msgstr "Weitere Helfer" + +#: includes/class-bse-shortcode-catalog.php:9 +msgid "Navigation menu" +msgstr "Navigationsmenü" + +#: includes/class-bse-shortcode-catalog.php:10 +msgid "No" +msgstr "Nein" + +#: includes/admin/class-bse-tools.php:68 +msgid "No matching shortcode. Try another search term." +msgstr "Kein passender Shortcode. Versuche einen anderen Suchbegriff." + +#: includes/class-bse-shortcode-catalog.php:9 +msgid "Output an existing WordPress navigation menu." +msgstr "Ein vorhandenes WordPress-Navigationsmenü ausgeben." + +#: includes/class-bse-shortcode-catalog.php:11 +msgid "Output the WordPress comment form." +msgstr "Das WordPress-Kommentarformular ausgeben." + +#: includes/admin/class-bse-tools.php:93 +msgid "Plugin information" +msgstr "Plugin-Informationen" + +#: includes/admin/class-bse-tools.php:93 +msgid "Plugin website" +msgstr "Plugin-Website" + +#: includes/class-bse-shortcode-catalog.php:34 +msgid "Post / page link" +msgstr "Beitrags- / Seiten-Link" + +#: includes/class-bse-shortcode-catalog.php:34 +msgid "Post ID" +msgstr "Beitrags-ID" + +#: includes/class-bse-shortcode-catalog.php:24 +msgid "Post ID (optional)" +msgstr "Beitrags-ID (optional)" + +#: includes/class-bse-shortcode-catalog.php:27 +msgid "Post author" +msgstr "Beitragsautor" + +#: includes/class-bse-shortcode-catalog.php:31 +msgid "Post categories" +msgstr "Beitragskategorien" + +#: includes/class-bse-shortcode-catalog.php:21 +msgid "Post count" +msgstr "Beitragsanzahl" + +#: includes/class-bse-shortcode-catalog.php:30 +msgid "Post tags" +msgstr "Beitrags-Schlagwörter" + +#: includes/class-bse-shortcode-catalog.php:22 +msgid "Publication date" +msgstr "Veröffentlichungsdatum" + +#: includes/class-bse-shortcode-catalog.php:23 +msgid "Publication time" +msgstr "Veröffentlichungszeit" + +#: includes/class-bse-shortcode-catalog.php:24 +msgid "Relative date" +msgstr "Relatives Datum" + +#: includes/class-bse-shortcode-catalog.php:10 +msgid "Reuse a published item by ID; native blocks and supported builders are rendered." +msgstr "Einen veröffentlichten Inhalt per ID wiederverwenden; native Blöcke und unterstützte Builder werden gerendert." + +#: includes/admin/class-bse-tools.php:66 +msgid "Search by task, name or attribute" +msgstr "Nach Aufgabe, Name oder Attribut suchen" + +#: includes/admin/class-bse-tools.php:17 +msgid "Shortcodes" +msgstr "Shortcodes" + +#: includes/class-bse-shortcode-catalog.php:17 +msgid "Site tagline" +msgstr "Website-Untertitel" + +#: includes/class-bse-shortcode-catalog.php:16 +msgid "Site title" +msgstr "Website-Titel" + +#: includes/class-bse-github-updates.php:32 +msgid "Small dynamic helpers for your WordPress website. Find, configure and copy useful shortcodes." +msgstr "Kleine dynamische Helfer für deine WordPress-Website. Nützliche Shortcodes finden, konfigurieren und kopieren." + +#: includes/admin/class-bse-tools.php:93 +msgid "Small dynamic helpers. Ready when you need them." +msgstr "Kleine dynamische Helfer. Schnell eingesetzt." + +#: includes/class-bse-shortcode-catalog.php:43 +msgid "Synced pattern" +msgstr "Synchronisierte Vorlage" + +#: includes/class-bse-shortcode-catalog.php:32 +msgid "Taxonomy terms" +msgstr "Taxonomie-Begriffe" + +#: includes/class-bse-shortcode-catalog.php:42 +msgid "Technical version information for documentation and support." +msgstr "Technische Versionsinformationen für Dokumentation und Support." + +#: includes/class-bse-shortcode-catalog.php:32 +msgid "Terms from a selected taxonomy." +msgstr "Begriffe aus einer gewählten Taxonomie." + +#: includes/class-bse-shortcode-catalog.php:15 +msgid "Text after" +msgstr "Text danach" + +#: includes/class-bse-shortcode-catalog.php:15 +msgid "Text before" +msgstr "Text davor" + +#: includes/class-bse-github-updates.php:100 +msgid "The GitHub package could not be prepared. The installed version has been kept." +msgstr "Das GitHub-Paket konnte nicht vorbereitet werden. Die installierte Version wurde beibehalten." + +#: includes/class-bse-github-updates.php:99 +msgid "The GitHub package does not contain the Builder Shortcode Extras plugin file." +msgstr "Das GitHub-Paket enthält die Plugin-Datei von Builder Shortcode Extras nicht." + +#: includes/class-bse-shortcode-catalog.php:36 +msgid "The ID of the signed-in user." +msgstr "Die ID des angemeldeten Benutzers." + +#: includes/class-bse-shortcode-catalog.php:27 +msgid "The author’s display name." +msgstr "Der Anzeigename des Autors." + +#: includes/class-bse-shortcode-catalog.php:29 +msgid "The author’s name linked to their archive." +msgstr "Der Autorenname mit Link zu seinem Archiv." + +#: includes/class-bse-shortcode-catalog.php:28 +msgid "The author’s name linked to their website." +msgstr "Der Autorenname mit Link zu seiner Website." + +#: includes/class-bse-shortcode-catalog.php:14 +msgid "The breadcrumb trail provided by Genesis." +msgstr "Die von Genesis bereitgestellte Breadcrumb-Navigation." + +#: includes/class-bse-shortcode-catalog.php:31 +msgid "The categories assigned to the current post." +msgstr "Die Kategorien des aktuellen Beitrags." + +#: includes/class-bse-shortcode-catalog.php:39 +msgid "The display name of the signed-in user." +msgstr "Der Anzeigename des angemeldeten Benutzers." + +#: includes/class-bse-shortcode-catalog.php:20 +msgid "The earliest or latest modification of published content." +msgstr "Die früheste oder neueste Änderung veröffentlichter Inhalte." + +#: includes/class-bse-shortcode-catalog.php:37 +msgid "The email address of the signed-in user." +msgstr "Die E-Mail-Adresse des angemeldeten Benutzers." + +#: includes/class-bse-shortcode-catalog.php:40 +msgid "The first name of the signed-in user." +msgstr "Der Vorname des angemeldeten Benutzers." + +#: includes/class-bse-shortcode-catalog.php:13 +msgid "The footer text configured in Genesis." +msgstr "Der in Genesis eingestellte Footer-Text." + +#: includes/admin/class-bse-tools.php:62 +msgid "The generator needs JavaScript. You can still select and copy examples from the help below." +msgstr "Der Generator benötigt JavaScript. Die Beispiele in der Hilfe kannst du weiterhin markieren und kopieren." + +#: includes/class-bse-shortcode-catalog.php:41 +msgid "The last name of the signed-in user." +msgstr "Der Nachname des angemeldeten Benutzers." + +#: includes/class-bse-shortcode-catalog.php:38 +msgid "The login name of the signed-in user." +msgstr "Der Anmeldename des angemeldeten Benutzers." + +#: includes/class-bse-shortcode-catalog.php:24 +msgid "The modification date of the current or selected post." +msgstr "Das Änderungsdatum des aktuellen oder gewählten Beitrags." + +#: includes/class-bse-shortcode-catalog.php:26 +msgid "The modification time of the current or selected post." +msgstr "Die Änderungszeit des aktuellen oder gewählten Beitrags." + +#: includes/class-bse-shortcode-catalog.php:16 +msgid "The name of your website." +msgstr "Der Name deiner Website." + +#: includes/class-bse-github-updates.php:122 +msgid "The package identity or version does not match a newer Builder Shortcode Extras release." +msgstr "Die Paketidentität oder Version entspricht keiner neueren Version von Builder Shortcode Extras." + +#: includes/class-bse-github-updates.php:127 +msgid "The package version differs from the offered update. Please check for updates again." +msgstr "Die Paketversion weicht vom angebotenen Update ab. Bitte prüfe erneut auf Updates." + +#: includes/class-bse-shortcode-catalog.php:22 +msgid "The publication date of the current or selected post." +msgstr "Das Veröffentlichungsdatum des aktuellen oder gewählten Beitrags." + +#: includes/class-bse-shortcode-catalog.php:23 +msgid "The publication time of the current or selected post." +msgstr "Die Veröffentlichungszeit des aktuellen oder gewählten Beitrags." + +#: includes/class-bse-shortcode-catalog.php:17 +msgid "The tagline of your website." +msgstr "Der Untertitel deiner Website." + +#: includes/class-bse-shortcode-catalog.php:30 +msgid "The tags assigned to the current post." +msgstr "Die Schlagwörter des aktuellen Beitrags." + +#: includes/class-bse-github-updates.php:98 +msgid "The update filesystem is unavailable. Please try again." +msgstr "Das Dateisystem für das Update ist nicht verfügbar. Bitte versuche es erneut." + +#: includes/class-bse-github-updates.php:115 +msgid "The update package could not be checked." +msgstr "Das Update-Paket konnte nicht geprüft werden." + +#: includes/class-bse-github-updates.php:131 +msgid "The update package has missing or invalid WordPress/PHP requirements." +msgstr "Die WordPress/PHP-Anforderungen im Update-Paket fehlen oder sind ungültig." + +#: includes/class-bse-github-updates.php:135 +msgid "This release requires a newer WordPress or PHP version. The installed version has been kept." +msgstr "Diese Version benötigt eine neuere WordPress- oder PHP-Version. Die installierte Version wurde beibehalten." + +#: includes/admin/class-bse-tools.php:67 +msgid "Try: copyright, date, author…" +msgstr "Zum Beispiel: Copyright, Datum, Autor …" + +#: includes/admin/class-bse-tools.php:33 +msgid "Use a whole number greater than zero." +msgstr "Verwende eine ganze Zahl größer als null." + +#: includes/class-bse-shortcode-catalog.php:35 +msgid "User information" +msgstr "Benutzerinformationen" + +#: includes/admin/class-bse-tools.php:93 +msgid "Version" +msgstr "Version" + +#: includes/class-bse-shortcode-catalog.php:42 +msgid "Version information" +msgstr "Versionsinformationen" + +#: includes/class-bse-shortcode-catalog.php:24 +msgid "Website default" +msgstr "Website-Standard" + +#: includes/class-bse-shortcode-catalog.php:20 +msgid "Website updated" +msgstr "Website aktualisiert" + +#: includes/admin/class-bse-tools.php:50 +msgid "What would you like to add?" +msgstr "Was möchtest du hinzufügen?" + +#: includes/integrations/block-editor.php:27 +msgid "WordPress Synced Patterns" +msgstr "Synchronisierte WordPress-Vorlagen" + +#: includes/class-bse-shortcode-catalog.php:34 +msgid "Wrapper" +msgstr "HTML-Element" + +#: includes/class-bse-shortcode-catalog.php:10 +msgid "Yes" +msgstr "Ja" + +#: includes/admin/class-bse-tools.php:57 +msgid "Your shortcode" +msgstr "Dein Shortcode" + +#: includes/class-bse-shortcode-catalog.php:24 +msgid "2026-12-31" +msgstr "2026-12-31" + +#: includes/class-bse-shortcode-catalog.php:24 +msgid "31.12.2026" +msgstr "31.12.2026" + +#: includes/shortcodes/user.php:54 +msgid "This user field is not allowed" +msgstr "Dieses Benutzerfeld ist nicht erlaubt" diff --git a/languages/builder-shortcode-extras-de_DE_formal.mo b/languages/builder-shortcode-extras-de_DE_formal.mo index f7e08c1..4c4506a 100644 Binary files a/languages/builder-shortcode-extras-de_DE_formal.mo and b/languages/builder-shortcode-extras-de_DE_formal.mo differ diff --git a/languages/builder-shortcode-extras-de_DE_formal.po b/languages/builder-shortcode-extras-de_DE_formal.po index 525bfb2..0ef3dde 100644 --- a/languages/builder-shortcode-extras-de_DE_formal.po +++ b/languages/builder-shortcode-extras-de_DE_formal.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: Builder Shortcode Extras\n" "POT-Creation-Date: 2025-03-15 09:53+0100\n" -"PO-Revision-Date: 2025-03-15 09:54+0100\n" +"PO-Revision-Date: 2026-10-01 18:00+0200\n" "Last-Translator: David Decker DECKERWEB.de \n" "Language-Team: David Decker DECKERWEB.de\n" "Language: de_DE@formal\n" @@ -366,3 +366,531 @@ msgstr "https://deckerweb.de/" #~ msgstr "" #~ "Gefällt Ihnen %1$s? Dann geben Sie uns bitte eine %2$s. Wir schätzen Ihre " #~ "Unterstützung!" + +#: includes/class-bse-shortcode-catalog.php:15 +msgid "A copyright notice with the current year." +msgstr "Ein Copyright-Hinweis mit dem aktuellen Jahr." + +#: includes/class-bse-shortcode-catalog.php:19 +msgid "A link matching the visitor’s login state." +msgstr "Ein Link passend zum Anmeldestatus des Besuchers." + +#: includes/class-bse-shortcode-catalog.php:18 +msgid "A link to your homepage." +msgstr "Ein Link zu Ihrer Startseite." + +#: includes/class-bse-shortcode-catalog.php:35 +msgid "A selected user field with an optional fallback." +msgstr "Ein gewähltes Benutzerfeld mit optionalem Ersatztext." + +#: includes/class-bse-shortcode-catalog.php:25 +msgid "An alias for bse-post-modified-date." +msgstr "Ein Alias für bse-post-modified-date." + +#: includes/class-bse-shortcode-catalog.php:33 +msgid "An edit link for visitors with editing permission." +msgstr "Ein Bearbeitungslink für Besucher mit Bearbeitungsrechten." + +#: includes/class-bse-shortcode-catalog.php:44 +msgid "Astra layout" +msgstr "Astra-Layout" + +#: includes/admin/class-bse-tools.php:34 +msgid "Attribute values cannot contain quotation marks, square brackets or line breaks." +msgstr "Attributwerte dürfen keine Anführungszeichen, eckigen Klammern oder Zeilenumbrüche enthalten." + +#: includes/class-bse-shortcode-catalog.php:29 +msgid "Author archive link" +msgstr "Autorenarchiv-Link" + +#: includes/class-bse-shortcode-catalog.php:28 +msgid "Author website link" +msgstr "Autorenwebsite-Link" + +#: includes/admin/class-bse-tools.php:79 +msgid "Available attributes" +msgstr "Verfügbare Attribute" + +#: includes/admin/class-bse-tools.php:49 +msgid "Build your shortcode" +msgstr "Ihren Shortcode erstellen" + +#: includes/class-bse-shortcode-catalog.php:34 +msgid "CSS classes (optional)" +msgstr "CSS-Klassen (optional)" + +#: includes/admin/class-bse-tools.php:93 +msgid "Changelog" +msgstr "Änderungsprotokoll" + +#: includes/class-bse-changelog.php:78 +msgid "Close" +msgstr "Schließen" + +#: includes/class-bse-shortcode-catalog.php:11 +msgid "Comment form" +msgstr "Kommentarformular" + +#: includes/admin/class-bse-tools.php:78 +msgid "Configure in generator" +msgstr "Im Generator konfigurieren" + +#: includes/class-bse-shortcode-catalog.php:10 +msgid "Content ID" +msgstr "Inhalts-ID" + +#: includes/admin/class-bse-tools.php:30 +msgid "Copied. Paste this shortcode into a shortcode-enabled field." +msgstr "Kopiert. Fügen Sie den Shortcode in ein Feld mit Shortcode-Unterstützung ein." + +#: includes/admin/class-bse-tools.php:77 +msgid "Copy" +msgstr "Kopieren Sien" + +#: includes/admin/class-bse-tools.php:77 +msgid "Copy shortcode" +msgstr "Shortcode kopieren Sien" + +#: includes/admin/class-bse-tools.php:31 +msgid "Copy the selected shortcode manually." +msgstr "Kopieren Sie den markierten Shortcode manuell." + +#: includes/class-bse-shortcode-catalog.php:15 +msgid "Copyright" +msgstr "Copyright" + +#: includes/class-bse-shortcode-catalog.php:21 +msgid "Count content by post type and status." +msgstr "Inhalte nach Inhaltstyp und Status zählen." + +#: includes/class-bse-shortcode-catalog.php:36 +msgid "Current user ID" +msgstr "Aktuelle Benutzer-ID" + +#: includes/class-bse-shortcode-catalog.php:37 +msgid "Current user email" +msgstr "Aktuelle Benutzer-E-Mail" + +#: includes/class-bse-shortcode-catalog.php:40 +msgid "Current user first name" +msgstr "Aktueller Benutzer-Vorname" + +#: includes/class-bse-shortcode-catalog.php:41 +msgid "Current user last name" +msgstr "Aktueller Benutzer-Nachname" + +#: includes/class-bse-shortcode-catalog.php:38 +msgid "Current user login" +msgstr "Aktueller Benutzer-Anmeldename" + +#: includes/class-bse-shortcode-catalog.php:39 +msgid "Current user name" +msgstr "Aktueller Benutzername" + +#: includes/class-bse-shortcode-catalog.php:24 +msgid "Date format" +msgstr "Datumsformat" + +#: includes/class-bse-shortcode-catalog.php:24 +msgid "December 31, 2026" +msgstr "31. Dezember 2026" + +#: includes/admin/class-bse-tools.php:93 +msgid "Documentation" +msgstr "Dokumentation" + +#: includes/class-bse-shortcode-catalog.php:33 +msgid "Edit post link" +msgstr "Beitrag-bearbeiten-Link" + +#: includes/class-bse-shortcode-catalog.php:12 +msgid "Elementor template" +msgstr "Elementor-Vorlage" + +#: includes/class-bse-shortcode-catalog.php:43 +msgid "Embed a WordPress synced pattern by ID." +msgstr "Eine synchronisierte WordPress-Vorlage per ID einbetten." + +#: includes/class-bse-shortcode-catalog.php:12 +msgid "Embed a saved Elementor template; offered with Elementor Free." +msgstr "Eine gespeicherte Elementor-Vorlage einbetten; verfügbar mit Elementor Free." + +#: includes/class-bse-shortcode-catalog.php:44 +msgid "Embed an Astra Pro custom layout by ID." +msgstr "Ein Astra-Pro-Layout per ID einbetten." + +#: includes/class-bse-shortcode-catalog.php:10 +msgid "Embed content" +msgstr "Inhalt einbetten" + +#: includes/admin/class-bse-tools.php:32 +msgid "Enter a valid content ID greater than zero." +msgstr "Geben Sie eine gültige Inhalts-ID größer als null ein." + +#: includes/class-bse-shortcode-catalog.php:34 +msgid "Essentials" +msgstr "Die wichtigsten Helfer" + +#: includes/admin/class-bse-tools.php:46 +msgid "Find a helper, copy its shortcode and paste it into a Shortcode block or a shortcode-enabled builder field. No settings to save." +msgstr "Wählen Sie einen Helfer, kopieren Sie den Shortcode und fügen Sie ihn in einen Shortcode-Block oder ein geeignetes Builder-Feld ein. Es gibt keine Einstellungen zu speichern." + +#: includes/admin/class-bse-tools.php:65 +msgid "Find a shortcode" +msgstr "Einen Shortcode finden" + +#: includes/class-bse-shortcode-catalog.php:15 +msgid "First year" +msgstr "Erstes Jahr" + +#: includes/admin/class-bse-tools.php:61 +msgid "For dates, leave the post ID empty to use the current post. Insert generated shortcodes on your website to see their output." +msgstr "Lassen Sie bei Datumsangaben die Beitrags-ID leer, um den aktuellen Beitrag zu verwenden. Fügen Sie den Shortcode auf Ihrer Website ein, um die Ausgabe zu sehen." + +#: includes/class-bse-shortcode-catalog.php:14 +msgid "Genesis breadcrumbs" +msgstr "Genesis-Breadcrumbs" + +#: includes/class-bse-shortcode-catalog.php:13 +msgid "Genesis footer" +msgstr "Genesis-Footer" + +#: includes/class-bse-shortcode-catalog.php:18 +msgid "Homepage link" +msgstr "Startseiten-Link" + +#: includes/class-bse-shortcode-catalog.php:10 +msgid "Include Elementor CSS" +msgstr "Elementor-CSS einbinden" + +#: includes/class-bse-shortcode-catalog.php:44 +msgid "Integrations" +msgstr "Integrationen" + +#: includes/class-bse-shortcode-catalog.php:24 +msgid "Label" +msgstr "Beschriftung" + +#: includes/class-bse-shortcode-catalog.php:24 +msgid "Last updated" +msgstr "Zuletzt aktualisiert" + +#: includes/class-bse-shortcode-catalog.php:25 +msgid "Last updated (alias)" +msgstr "Zuletzt aktualisiert (Alias)" + +#: includes/class-bse-shortcode-catalog.php:26 +msgid "Last updated time" +msgstr "Letzte Aktualisierungszeit" + +#: includes/class-bse-shortcode-catalog.php:34 +msgid "Link text (optional)" +msgstr "Linktext (optional)" + +#: includes/class-bse-shortcode-catalog.php:34 +msgid "Link to an item by ID or slug, or to the privacy policy." +msgstr "Ein Link zu einem Inhalt per ID oder Slug oder zur Datenschutzerklärung." + +#: includes/class-bse-shortcode-catalog.php:19 +msgid "Login / logout link" +msgstr "Anmelde- / Abmelde-Link" + +#: includes/class-bse-shortcode-catalog.php:42 +msgid "More helpers" +msgstr "Weitere Helfer" + +#: includes/class-bse-shortcode-catalog.php:9 +msgid "Navigation menu" +msgstr "Navigationsmenü" + +#: includes/class-bse-shortcode-catalog.php:10 +msgid "No" +msgstr "Nein" + +#: includes/admin/class-bse-tools.php:68 +msgid "No matching shortcode. Try another search term." +msgstr "Kein passender Shortcode. Versuchen Sie einen anderen Suchbegriff." + +#: includes/class-bse-shortcode-catalog.php:9 +msgid "Output an existing WordPress navigation menu." +msgstr "Ein vorhandenes WordPress-Navigationsmenü ausgeben." + +#: includes/class-bse-shortcode-catalog.php:11 +msgid "Output the WordPress comment form." +msgstr "Das WordPress-Kommentarformular ausgeben." + +#: includes/admin/class-bse-tools.php:93 +msgid "Plugin information" +msgstr "Plugin-Informationen" + +#: includes/admin/class-bse-tools.php:93 +msgid "Plugin website" +msgstr "Plugin-Website" + +#: includes/class-bse-shortcode-catalog.php:34 +msgid "Post / page link" +msgstr "Beitrags- / Seiten-Link" + +#: includes/class-bse-shortcode-catalog.php:34 +msgid "Post ID" +msgstr "Beitrags-ID" + +#: includes/class-bse-shortcode-catalog.php:24 +msgid "Post ID (optional)" +msgstr "Beitrags-ID (optional)" + +#: includes/class-bse-shortcode-catalog.php:27 +msgid "Post author" +msgstr "Beitragsautor" + +#: includes/class-bse-shortcode-catalog.php:31 +msgid "Post categories" +msgstr "Beitragskategorien" + +#: includes/class-bse-shortcode-catalog.php:21 +msgid "Post count" +msgstr "Beitragsanzahl" + +#: includes/class-bse-shortcode-catalog.php:30 +msgid "Post tags" +msgstr "Beitrags-Schlagwörter" + +#: includes/class-bse-shortcode-catalog.php:22 +msgid "Publication date" +msgstr "Veröffentlichungsdatum" + +#: includes/class-bse-shortcode-catalog.php:23 +msgid "Publication time" +msgstr "Veröffentlichungszeit" + +#: includes/class-bse-shortcode-catalog.php:24 +msgid "Relative date" +msgstr "Relatives Datum" + +#: includes/class-bse-shortcode-catalog.php:10 +msgid "Reuse a published item by ID; native blocks and supported builders are rendered." +msgstr "Einen veröffentlichten Inhalt per ID wiederverwenden; native Blöcke und unterstützte Builder werden gerendert." + +#: includes/admin/class-bse-tools.php:66 +msgid "Search by task, name or attribute" +msgstr "Nach Aufgabe, Name oder Attribut suchen" + +#: includes/admin/class-bse-tools.php:17 +msgid "Shortcodes" +msgstr "Shortcodes" + +#: includes/class-bse-shortcode-catalog.php:17 +msgid "Site tagline" +msgstr "Website-Untertitel" + +#: includes/class-bse-shortcode-catalog.php:16 +msgid "Site title" +msgstr "Website-Titel" + +#: includes/class-bse-github-updates.php:32 +msgid "Small dynamic helpers for your WordPress website. Find, configure and copy useful shortcodes." +msgstr "Kleine dynamische Helfer für Ihre WordPress-Website. Nützliche Shortcodes finden, konfigurieren und kopieren Sien." + +#: includes/admin/class-bse-tools.php:93 +msgid "Small dynamic helpers. Ready when you need them." +msgstr "Kleine dynamische Helfer. Schnell eingesetzt." + +#: includes/class-bse-shortcode-catalog.php:43 +msgid "Synced pattern" +msgstr "Synchronisierte Vorlage" + +#: includes/class-bse-shortcode-catalog.php:32 +msgid "Taxonomy terms" +msgstr "Taxonomie-Begriffe" + +#: includes/class-bse-shortcode-catalog.php:42 +msgid "Technical version information for documentation and support." +msgstr "Technische Versionsinformationen für Dokumentation und Support." + +#: includes/class-bse-shortcode-catalog.php:32 +msgid "Terms from a selected taxonomy." +msgstr "Begriffe aus einer gewählten Taxonomie." + +#: includes/class-bse-shortcode-catalog.php:15 +msgid "Text after" +msgstr "Text danach" + +#: includes/class-bse-shortcode-catalog.php:15 +msgid "Text before" +msgstr "Text davor" + +#: includes/class-bse-github-updates.php:100 +msgid "The GitHub package could not be prepared. The installed version has been kept." +msgstr "Das GitHub-Paket konnte nicht vorbereitet werden. Die installierte Version wurde beibehalten." + +#: includes/class-bse-github-updates.php:99 +msgid "The GitHub package does not contain the Builder Shortcode Extras plugin file." +msgstr "Das GitHub-Paket enthält die Plugin-Datei von Builder Shortcode Extras nicht." + +#: includes/class-bse-shortcode-catalog.php:36 +msgid "The ID of the signed-in user." +msgstr "Die ID des angemeldeten Benutzers." + +#: includes/class-bse-shortcode-catalog.php:27 +msgid "The author’s display name." +msgstr "Der Anzeigename des Autors." + +#: includes/class-bse-shortcode-catalog.php:29 +msgid "The author’s name linked to their archive." +msgstr "Der Autorenname mit Link zu seinem Archiv." + +#: includes/class-bse-shortcode-catalog.php:28 +msgid "The author’s name linked to their website." +msgstr "Der Autorenname mit Link zu seiner Website." + +#: includes/class-bse-shortcode-catalog.php:14 +msgid "The breadcrumb trail provided by Genesis." +msgstr "Die von Genesis bereitgestellte Breadcrumb-Navigation." + +#: includes/class-bse-shortcode-catalog.php:31 +msgid "The categories assigned to the current post." +msgstr "Die Kategorien des aktuellen Beitrags." + +#: includes/class-bse-shortcode-catalog.php:39 +msgid "The display name of the signed-in user." +msgstr "Der Anzeigename des angemeldeten Benutzers." + +#: includes/class-bse-shortcode-catalog.php:20 +msgid "The earliest or latest modification of published content." +msgstr "Die früheste oder neueste Änderung veröffentlichter Inhalte." + +#: includes/class-bse-shortcode-catalog.php:37 +msgid "The email address of the signed-in user." +msgstr "Die E-Mail-Adresse des angemeldeten Benutzers." + +#: includes/class-bse-shortcode-catalog.php:40 +msgid "The first name of the signed-in user." +msgstr "Der Vorname des angemeldeten Benutzers." + +#: includes/class-bse-shortcode-catalog.php:13 +msgid "The footer text configured in Genesis." +msgstr "Der in Genesis eingestellte Footer-Text." + +#: includes/admin/class-bse-tools.php:62 +msgid "The generator needs JavaScript. You can still select and copy examples from the help below." +msgstr "Der Generator benötigt JavaScript. Die Beispiele in der Hilfe können Sie weiterhin markieren und kopieren Sien." + +#: includes/class-bse-shortcode-catalog.php:41 +msgid "The last name of the signed-in user." +msgstr "Der Nachname des angemeldeten Benutzers." + +#: includes/class-bse-shortcode-catalog.php:38 +msgid "The login name of the signed-in user." +msgstr "Der Anmeldename des angemeldeten Benutzers." + +#: includes/class-bse-shortcode-catalog.php:24 +msgid "The modification date of the current or selected post." +msgstr "Das Änderungsdatum des aktuellen oder gewählten Beitrags." + +#: includes/class-bse-shortcode-catalog.php:26 +msgid "The modification time of the current or selected post." +msgstr "Die Änderungszeit des aktuellen oder gewählten Beitrags." + +#: includes/class-bse-shortcode-catalog.php:16 +msgid "The name of your website." +msgstr "Der Name Ihrer Website." + +#: includes/class-bse-github-updates.php:122 +msgid "The package identity or version does not match a newer Builder Shortcode Extras release." +msgstr "Die Paketidentität oder Version entspricht keiner neueren Version von Builder Shortcode Extras." + +#: includes/class-bse-github-updates.php:127 +msgid "The package version differs from the offered update. Please check for updates again." +msgstr "Die Paketversion weicht vom angebotenen Update ab. Bitte prüfen Sie erneut auf Updates." + +#: includes/class-bse-shortcode-catalog.php:22 +msgid "The publication date of the current or selected post." +msgstr "Das Veröffentlichungsdatum des aktuellen oder gewählten Beitrags." + +#: includes/class-bse-shortcode-catalog.php:23 +msgid "The publication time of the current or selected post." +msgstr "Die Veröffentlichungszeit des aktuellen oder gewählten Beitrags." + +#: includes/class-bse-shortcode-catalog.php:17 +msgid "The tagline of your website." +msgstr "Der Untertitel Ihrer Website." + +#: includes/class-bse-shortcode-catalog.php:30 +msgid "The tags assigned to the current post." +msgstr "Die Schlagwörter des aktuellen Beitrags." + +#: includes/class-bse-github-updates.php:98 +msgid "The update filesystem is unavailable. Please try again." +msgstr "Das Dateisystem für das Update ist nicht verfügbar. Bitte versuchen Sie es erneut." + +#: includes/class-bse-github-updates.php:115 +msgid "The update package could not be checked." +msgstr "Das Update-Paket konnte nicht geprüft werden." + +#: includes/class-bse-github-updates.php:131 +msgid "The update package has missing or invalid WordPress/PHP requirements." +msgstr "Die WordPress/PHP-Anforderungen im Update-Paket fehlen oder sind ungültig." + +#: includes/class-bse-github-updates.php:135 +msgid "This release requires a newer WordPress or PHP version. The installed version has been kept." +msgstr "Diese Version benötigt eine neuere WordPress- oder PHP-Version. Die installierte Version wurde beibehalten." + +#: includes/admin/class-bse-tools.php:67 +msgid "Try: copyright, date, author…" +msgstr "Zum Beispiel: Copyright, Datum, Autor …" + +#: includes/admin/class-bse-tools.php:33 +msgid "Use a whole number greater than zero." +msgstr "Verwenden Sie eine ganze Zahl größer als null." + +#: includes/class-bse-shortcode-catalog.php:35 +msgid "User information" +msgstr "Benutzerinformationen" + +#: includes/admin/class-bse-tools.php:93 +msgid "Version" +msgstr "Version" + +#: includes/class-bse-shortcode-catalog.php:42 +msgid "Version information" +msgstr "Versionsinformationen" + +#: includes/class-bse-shortcode-catalog.php:24 +msgid "Website default" +msgstr "Website-Standard" + +#: includes/class-bse-shortcode-catalog.php:20 +msgid "Website updated" +msgstr "Website aktualisiert" + +#: includes/admin/class-bse-tools.php:50 +msgid "What would you like to add?" +msgstr "Was möchten Sie hinzufügen Sien?" + +#: includes/integrations/block-editor.php:27 +msgid "WordPress Synced Patterns" +msgstr "Synchronisierte WordPress-Vorlagen" + +#: includes/class-bse-shortcode-catalog.php:34 +msgid "Wrapper" +msgstr "HTML-Element" + +#: includes/class-bse-shortcode-catalog.php:10 +msgid "Yes" +msgstr "Ja" + +#: includes/admin/class-bse-tools.php:57 +msgid "Your shortcode" +msgstr "Ihr Shortcode" + +#: includes/class-bse-shortcode-catalog.php:24 +msgid "2026-12-31" +msgstr "2026-12-31" + +#: includes/class-bse-shortcode-catalog.php:24 +msgid "31.12.2026" +msgstr "31.12.2026" + +#: includes/shortcodes/user.php:54 +msgid "This user field is not allowed" +msgstr "Dieses Benutzerfeld ist nicht erlaubt" diff --git a/languages/builder-shortcode-extras.pot b/languages/builder-shortcode-extras.pot index 863dbe7..bd8185d 100644 --- a/languages/builder-shortcode-extras.pot +++ b/languages/builder-shortcode-extras.pot @@ -1,95 +1,41 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the Builder Shortcode Extras package. +# FIRST AUTHOR , YEAR. +# #, fuzzy msgid "" msgstr "" -"Project-Id-Version: Builder Shortcode Extras\n" -"POT-Creation-Date: 2025-03-15 09:53+0100\n" -"PO-Revision-Date: 2019-09-05 11:58+0200\n" -"Last-Translator: David Decker DECKERWEB.de\n" -"Language-Team: David Decker DECKERWEB.de\n" +"Project-Id-Version: Builder Shortcode Extras 1.2.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2026-10-01 17:31+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" -"X-Generator: Poedit 3.5\n" -"X-Poedit-Basepath: ..\n" -"X-Poedit-Flags-xgettext: --add-comments=translators:\n" -"X-Poedit-WPHeader: builder-shortcode-extras.php\n" -"X-Poedit-SourceCharset: UTF-8\n" -"X-Poedit-KeywordsList: __;_e;_n:1,2;_x:1,2c;_ex:1,2c;_nx:4c,1,2;esc_attr__;" -"esc_attr_e;esc_attr_x:1,2c;esc_html__;esc_html_e;esc_html_x:1,2c;" -"_n_noop:1,2;_nx_noop:3c,1,2;__ngettext_noop:1,2\n" -"X-Poedit-SearchPath-0: .\n" -"X-Poedit-SearchPathExcluded-0: *.min.js\n" - -#: includes/admin/admin-extras.php:61 -msgctxt "Plugins page listing" -msgid "Support" -msgstr "" - -#: includes/admin/admin-extras.php:68 -msgctxt "Plugins page listing" -msgid "Code Snippets" -msgstr "" - -#: includes/admin/admin-extras.php:75 -msgctxt "Plugins page listing" -msgid "Donate" -msgstr "" -#: includes/admin/admin-extras.php:82 -msgctxt "Plugins page listing" -msgid "Join our Newsletter" -msgstr "" - -#: includes/admin/admin-extras.php:155 -msgid "Enabled" -msgstr "" - -#: includes/admin/admin-extras.php:156 -msgid "Disabled" -msgstr "" - -#. Plugin Name of the plugin/theme -#: includes/admin/admin-extras.php:160 -msgid "Builder Shortcode Extras" -msgstr "" - -#: includes/admin/admin-extras.php:160 -msgid "Plugin" -msgstr "" - -#: includes/admin/admin-extras.php:163 -msgid "Plugin version" -msgstr "" - -#: includes/admin/admin-extras.php:167 -msgid "Elementor free" -msgstr "" - -#: includes/admin/admin-extras.php:171 -msgid "Elementor Pro" -msgstr "" - -#: includes/functions-global.php:30 +#: work/repo/includes/functions-global.php:30 msgid "No Integration registered" msgstr "" -#. Author of the plugin/theme -#: includes/functions-global.php:85 +#: work/repo/includes/functions-global.php:91 msgid "David Decker - DECKERWEB" msgstr "" -#: includes/functions-global.php:213 +#: work/repo/includes/functions-global.php:219 msgid "Shortcode" msgstr "" -#: includes/functions-global.php:259 +#: work/repo/includes/functions-global.php:265 msgctxt "time difference" msgid "seconds" msgstr "" -#. translators: %s: Number of year(s). -#: includes/functions-global.php:265 +#: work/repo/includes/functions-global.php:271 #, php-format msgctxt "Time difference" msgid "%s year" @@ -97,8 +43,7 @@ msgid_plural "%s years" msgstr[0] "" msgstr[1] "" -#. translators: %s: Number of month(s). -#: includes/functions-global.php:267 +#: work/repo/includes/functions-global.php:273 #, php-format msgctxt "Time difference" msgid "%s month" @@ -106,8 +51,7 @@ msgid_plural "%s months" msgstr[0] "" msgstr[1] "" -#. translators: %s: Number of week(s). -#: includes/functions-global.php:269 +#: work/repo/includes/functions-global.php:275 #, php-format msgctxt "Time difference" msgid "%s week" @@ -115,8 +59,7 @@ msgid_plural "%s weeks" msgstr[0] "" msgstr[1] "" -#. translators: %s: Number of day(s). -#: includes/functions-global.php:271 +#: work/repo/includes/functions-global.php:277 #, php-format msgctxt "Time difference" msgid "%s day" @@ -124,8 +67,7 @@ msgid_plural "%s days" msgstr[0] "" msgstr[1] "" -#. translators: %s: Number of hour(s). -#: includes/functions-global.php:273 +#: work/repo/includes/functions-global.php:279 #, php-format msgctxt "Time difference" msgid "%s hour" @@ -133,8 +75,7 @@ msgid_plural "%s hours" msgstr[0] "" msgstr[1] "" -#. translators: %s: Number of minute(s). -#: includes/functions-global.php:275 +#: work/repo/includes/functions-global.php:281 #, php-format msgctxt "Time difference" msgid "%s minute" @@ -142,8 +83,7 @@ msgid_plural "%s minutes" msgstr[0] "" msgstr[1] "" -#. translators: %s: Number of second(s). -#: includes/functions-global.php:277 +#: work/repo/includes/functions-global.php:283 #, php-format msgctxt "Time difference" msgid "%s second" @@ -151,123 +91,721 @@ msgid_plural "%s seconds" msgstr[0] "" msgstr[1] "" -#: includes/functions-global.php:325 +#: work/repo/includes/functions-global.php:331 msgctxt "Separator in time difference" msgid "and" msgstr "" -#: includes/integrations/astra.php:27 -msgid "Astra Custom Layouts" +#: work/repo/includes/class-bse-shortcode-catalog.php:9 +msgid "Navigation menu" msgstr "" -#: includes/integrations/block-editor.php:27 -msgid "WordPress Reusable Blocks" +#: work/repo/includes/class-bse-shortcode-catalog.php:9 +msgid "Output an existing WordPress navigation menu." msgstr "" -#: includes/integrations/block-editor.php:62 -msgctxt "Admin page title" -msgid "Reusable Blocks" +#: work/repo/includes/class-bse-shortcode-catalog.php:9 +#: work/repo/includes/class-bse-shortcode-catalog.php:11 +#: work/repo/includes/class-bse-shortcode-catalog.php:19 +#: work/repo/includes/class-bse-shortcode-catalog.php:21 +#: work/repo/includes/class-bse-shortcode-catalog.php:23 +#: work/repo/includes/class-bse-shortcode-catalog.php:25 +#: work/repo/includes/class-bse-shortcode-catalog.php:26 +#: work/repo/includes/class-bse-shortcode-catalog.php:27 +#: work/repo/includes/class-bse-shortcode-catalog.php:28 +#: work/repo/includes/class-bse-shortcode-catalog.php:29 +#: work/repo/includes/class-bse-shortcode-catalog.php:30 +#: work/repo/includes/class-bse-shortcode-catalog.php:31 +#: work/repo/includes/class-bse-shortcode-catalog.php:32 +#: work/repo/includes/class-bse-shortcode-catalog.php:33 +#: work/repo/includes/class-bse-shortcode-catalog.php:35 +#: work/repo/includes/class-bse-shortcode-catalog.php:36 +#: work/repo/includes/class-bse-shortcode-catalog.php:37 +#: work/repo/includes/class-bse-shortcode-catalog.php:38 +#: work/repo/includes/class-bse-shortcode-catalog.php:39 +#: work/repo/includes/class-bse-shortcode-catalog.php:40 +#: work/repo/includes/class-bse-shortcode-catalog.php:41 +#: work/repo/includes/class-bse-shortcode-catalog.php:42 +msgid "More helpers" msgstr "" -#: includes/integrations/block-editor.php:63 -msgctxt "Admin menu label" -msgid "Blocks" +#: work/repo/includes/class-bse-shortcode-catalog.php:10 +msgid "Embed content" msgstr "" -#: includes/integrations/block-editor.php:74 -msgctxt "Admin page title" -msgid "Add New Reusable Block" +#: work/repo/includes/class-bse-shortcode-catalog.php:10 +msgid "" +"Reuse a published item by ID; native blocks and supported builders are " +"rendered." msgstr "" -#: includes/integrations/block-editor.php:75 -msgctxt "Admin menu label" -msgid "Add New" +#: work/repo/includes/class-bse-shortcode-catalog.php:10 +#: work/repo/includes/class-bse-shortcode-catalog.php:15 +#: work/repo/includes/class-bse-shortcode-catalog.php:16 +#: work/repo/includes/class-bse-shortcode-catalog.php:17 +#: work/repo/includes/class-bse-shortcode-catalog.php:18 +#: work/repo/includes/class-bse-shortcode-catalog.php:20 +#: work/repo/includes/class-bse-shortcode-catalog.php:22 +#: work/repo/includes/class-bse-shortcode-catalog.php:24 +#: work/repo/includes/class-bse-shortcode-catalog.php:34 +msgid "Essentials" msgstr "" -#: includes/shortcodes/content.php:223 -msgctxt "Label for Comment Form" -msgid "Leave a Reply" +#: work/repo/includes/class-bse-shortcode-catalog.php:10 +msgid "Content ID" msgstr "" -#. translators: %s - name of the author of the comment being replied to -#: includes/shortcodes/content.php:225 -#, php-format -msgctxt "Label for Comment Form" -msgid "Leave a Reply to %s" +#: work/repo/includes/class-bse-shortcode-catalog.php:10 +msgid "Include Elementor CSS" msgstr "" -#: includes/shortcodes/content.php:226 -msgctxt "Label for Comment Form" -msgid "Cancel reply" +#: work/repo/includes/class-bse-shortcode-catalog.php:10 +msgid "No" msgstr "" -#: includes/shortcodes/content.php:227 -msgctxt "Label for Comment Form" -msgid "Post Comment" +#: work/repo/includes/class-bse-shortcode-catalog.php:10 +msgid "Yes" msgstr "" -#: includes/shortcodes/info.php:284 -msgid "Log in" +#: work/repo/includes/class-bse-shortcode-catalog.php:11 +msgid "Comment form" msgstr "" -#: includes/shortcodes/info.php:285 -msgid "Log out" +#: work/repo/includes/class-bse-shortcode-catalog.php:11 +msgid "Output the WordPress comment form." msgstr "" -#: includes/shortcodes/info.php:419 -#, php-format -msgctxt "Translators: shortcode, first published content time string" -msgid "%s" +#: work/repo/includes/class-bse-shortcode-catalog.php:12 +msgid "Elementor template" msgstr "" -#: includes/shortcodes/info.php:429 -#, php-format -msgctxt "Translators: shortcode, last updated time string" -msgid "%s" +#: work/repo/includes/class-bse-shortcode-catalog.php:12 +msgid "Embed a saved Elementor template; offered with Elementor Free." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:12 +#: work/repo/includes/class-bse-shortcode-catalog.php:13 +#: work/repo/includes/class-bse-shortcode-catalog.php:14 +#: work/repo/includes/class-bse-shortcode-catalog.php:43 +#: work/repo/includes/class-bse-shortcode-catalog.php:44 +msgid "Integrations" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:13 +msgid "Genesis footer" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:13 +msgid "The footer text configured in Genesis." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:14 +msgid "Genesis breadcrumbs" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:14 +msgid "The breadcrumb trail provided by Genesis." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:15 +msgid "Copyright" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:15 +msgid "A copyright notice with the current year." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:15 +msgid "First year" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:15 +msgid "Text before" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:15 +msgid "Text after" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:15 +#: work/repo/includes/class-bse-shortcode-catalog.php:22 +#: work/repo/includes/class-bse-shortcode-catalog.php:24 +#: work/repo/includes/class-bse-shortcode-catalog.php:34 +msgid "CSS classes (optional)" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:15 +#: work/repo/includes/class-bse-shortcode-catalog.php:22 +#: work/repo/includes/class-bse-shortcode-catalog.php:24 +#: work/repo/includes/class-bse-shortcode-catalog.php:34 +msgid "Wrapper" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:16 +msgid "Site title" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:16 +msgid "The name of your website." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:17 +msgid "Site tagline" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:17 +msgid "The tagline of your website." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:18 +msgid "Homepage link" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:18 +msgid "A link to your homepage." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:19 +msgid "Login / logout link" msgstr "" -#: includes/shortcodes/post.php:117 includes/shortcodes/post.php:255 +#: work/repo/includes/class-bse-shortcode-catalog.php:19 +msgid "A link matching the visitor’s login state." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:20 +msgid "Website updated" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:20 +msgid "The earliest or latest modification of published content." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:21 +msgid "Post count" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:21 +msgid "Count content by post type and status." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:22 +msgid "Publication date" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:22 +msgid "The publication date of the current or selected post." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:22 +#: work/repo/includes/class-bse-shortcode-catalog.php:24 +msgid "Post ID (optional)" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:22 +#: work/repo/includes/class-bse-shortcode-catalog.php:24 +msgid "Date format" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:22 +#: work/repo/includes/class-bse-shortcode-catalog.php:24 +msgid "Website default" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:22 +#: work/repo/includes/class-bse-shortcode-catalog.php:24 +msgid "31.12.2026" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:22 +#: work/repo/includes/class-bse-shortcode-catalog.php:24 +msgid "December 31, 2026" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:22 +#: work/repo/includes/class-bse-shortcode-catalog.php:24 +msgid "2026-12-31" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:22 +#: work/repo/includes/class-bse-shortcode-catalog.php:24 +msgid "Relative date" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:22 +#: work/repo/includes/class-bse-shortcode-catalog.php:24 +msgid "Label" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:23 +msgid "Publication time" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:23 +msgid "The publication time of the current or selected post." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:24 +msgid "Last updated" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:24 +msgid "The modification date of the current or selected post." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:25 +msgid "Last updated (alias)" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:25 +msgid "An alias for bse-post-modified-date." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:26 +msgid "Last updated time" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:26 +msgid "The modification time of the current or selected post." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:27 +msgid "Post author" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:27 +msgid "The author’s display name." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:28 +msgid "Author website link" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:28 +msgid "The author’s name linked to their website." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:29 +msgid "Author archive link" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:29 +msgid "The author’s name linked to their archive." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:30 +msgid "Post tags" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:30 +msgid "The tags assigned to the current post." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:31 +msgid "Post categories" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:31 +msgid "The categories assigned to the current post." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:32 +msgid "Taxonomy terms" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:32 +msgid "Terms from a selected taxonomy." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:33 +msgid "Edit post link" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:33 +msgid "An edit link for visitors with editing permission." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:34 +msgid "Post / page link" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:34 +msgid "Link to an item by ID or slug, or to the privacy policy." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:34 +msgid "Post ID" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:34 +msgid "Link text (optional)" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:35 +msgid "User information" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:35 +msgid "A selected user field with an optional fallback." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:36 +msgid "Current user ID" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:36 +msgid "The ID of the signed-in user." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:37 +msgid "Current user email" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:37 +msgid "The email address of the signed-in user." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:38 +msgid "Current user login" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:38 +msgid "The login name of the signed-in user." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:39 +msgid "Current user name" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:39 +msgid "The display name of the signed-in user." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:40 +msgid "Current user first name" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:40 +msgid "The first name of the signed-in user." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:41 +msgid "Current user last name" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:41 +msgid "The last name of the signed-in user." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:42 +msgid "Version information" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:42 +msgid "Technical version information for documentation and support." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:43 +msgid "Synced pattern" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:43 +msgid "Embed a WordPress synced pattern by ID." +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:44 +msgid "Astra layout" +msgstr "" + +#: work/repo/includes/class-bse-shortcode-catalog.php:44 +msgid "Embed an Astra Pro custom layout by ID." +msgstr "" + +#: work/repo/includes/class-bse-changelog.php:78 +msgid "Close" +msgstr "" + +#: work/repo/includes/class-bse-changelog.php:79 +#: work/repo/includes/admin/class-bse-tools.php:93 +msgid "Changelog" +msgstr "" + +#: work/repo/includes/class-bse-github-updates.php:32 +msgid "" +"Small dynamic helpers for your WordPress website. Find, configure and copy " +"useful shortcodes." +msgstr "" + +#: work/repo/includes/class-bse-github-updates.php:98 +msgid "The update filesystem is unavailable. Please try again." +msgstr "" + +#: work/repo/includes/class-bse-github-updates.php:99 +msgid "" +"The GitHub package does not contain the Builder Shortcode Extras plugin file." +msgstr "" + +#: work/repo/includes/class-bse-github-updates.php:100 +msgid "" +"The GitHub package could not be prepared. The installed version has been " +"kept." +msgstr "" + +#: work/repo/includes/class-bse-github-updates.php:107 +#: work/repo/includes/class-bse-github-updates.php:111 +#: work/repo/includes/class-bse-github-updates.php:115 +msgid "The update package could not be checked." +msgstr "" + +#: work/repo/includes/class-bse-github-updates.php:122 +msgid "" +"The package identity or version does not match a newer Builder Shortcode " +"Extras release." +msgstr "" + +#: work/repo/includes/class-bse-github-updates.php:127 +msgid "" +"The package version differs from the offered update. Please check for " +"updates again." +msgstr "" + +#: work/repo/includes/class-bse-github-updates.php:131 +msgid "The update package has missing or invalid WordPress/PHP requirements." +msgstr "" + +#: work/repo/includes/class-bse-github-updates.php:135 +msgid "" +"This release requires a newer WordPress or PHP version. The installed " +"version has been kept." +msgstr "" + +#: work/repo/includes/admin/class-bse-tools.php:13 +#: work/repo/includes/admin/admin-extras.php:139 +msgid "Builder Shortcode Extras" +msgstr "" + +#: work/repo/includes/admin/class-bse-tools.php:17 +msgid "Shortcodes" +msgstr "" + +#: work/repo/includes/admin/class-bse-tools.php:30 +msgid "Copied. Paste this shortcode into a shortcode-enabled field." +msgstr "" + +#: work/repo/includes/admin/class-bse-tools.php:31 +msgid "Copy the selected shortcode manually." +msgstr "" + +#: work/repo/includes/admin/class-bse-tools.php:32 +msgid "Enter a valid content ID greater than zero." +msgstr "" + +#: work/repo/includes/admin/class-bse-tools.php:33 +msgid "Use a whole number greater than zero." +msgstr "" + +#: work/repo/includes/admin/class-bse-tools.php:34 +msgid "" +"Attribute values cannot contain quotation marks, square brackets or line " +"breaks." +msgstr "" + +#: work/repo/includes/admin/class-bse-tools.php:45 +#: work/repo/includes/admin/class-bse-tools.php:93 +msgid "Small dynamic helpers. Ready when you need them." +msgstr "" + +#: work/repo/includes/admin/class-bse-tools.php:46 +msgid "" +"Find a helper, copy its shortcode and paste it into a Shortcode block or a " +"shortcode-enabled builder field. No settings to save." +msgstr "" + +#: work/repo/includes/admin/class-bse-tools.php:49 +msgid "Build your shortcode" +msgstr "" + +#: work/repo/includes/admin/class-bse-tools.php:50 +msgid "What would you like to add?" +msgstr "" + +#: work/repo/includes/admin/class-bse-tools.php:57 +msgid "Your shortcode" +msgstr "" + +#: work/repo/includes/admin/class-bse-tools.php:60 +#: work/repo/includes/admin/class-bse-tools.php:77 +msgid "Copy shortcode" +msgstr "" + +#: work/repo/includes/admin/class-bse-tools.php:61 +msgid "" +"For dates, leave the post ID empty to use the current post. Insert generated " +"shortcodes on your website to see their output." +msgstr "" + +#: work/repo/includes/admin/class-bse-tools.php:62 +msgid "" +"The generator needs JavaScript. You can still select and copy examples from " +"the help below." +msgstr "" + +#: work/repo/includes/admin/class-bse-tools.php:65 +msgid "Find a shortcode" +msgstr "" + +#: work/repo/includes/admin/class-bse-tools.php:66 +msgid "Search by task, name or attribute" +msgstr "" + +#: work/repo/includes/admin/class-bse-tools.php:67 +msgid "Try: copyright, date, author…" +msgstr "" + +#: work/repo/includes/admin/class-bse-tools.php:68 +msgid "No matching shortcode. Try another search term." +msgstr "" + +#: work/repo/includes/admin/class-bse-tools.php:77 +msgid "Copy" +msgstr "" + +#: work/repo/includes/admin/class-bse-tools.php:78 +msgid "Configure in generator" +msgstr "" + +#: work/repo/includes/admin/class-bse-tools.php:79 +msgid "Available attributes" +msgstr "" + +#: work/repo/includes/admin/class-bse-tools.php:93 +msgid "Plugin information" +msgstr "" + +#: work/repo/includes/admin/class-bse-tools.php:93 +msgid "Version" +msgstr "" + +#: work/repo/includes/admin/class-bse-tools.php:93 +msgid "Documentation" +msgstr "" + +#: work/repo/includes/admin/class-bse-tools.php:93 +msgid "Plugin website" +msgstr "" + +#: work/repo/includes/admin/admin-extras.php:40 +msgctxt "Plugins page listing" +msgid "Support" +msgstr "" + +#: work/repo/includes/admin/admin-extras.php:47 +msgctxt "Plugins page listing" +msgid "Code Snippets" +msgstr "" + +#: work/repo/includes/admin/admin-extras.php:54 +msgctxt "Plugins page listing" +msgid "Donate" +msgstr "" + +#: work/repo/includes/admin/admin-extras.php:61 +msgctxt "Plugins page listing" +msgid "Join our Newsletter" +msgstr "" + +#: work/repo/includes/admin/admin-extras.php:134 +msgid "Enabled" +msgstr "" + +#: work/repo/includes/admin/admin-extras.php:135 +msgid "Disabled" +msgstr "" + +#: work/repo/includes/admin/admin-extras.php:139 +msgid "Plugin" +msgstr "" + +#: work/repo/includes/admin/admin-extras.php:142 +msgid "Plugin version" +msgstr "" + +#: work/repo/includes/admin/admin-extras.php:146 +msgid "Elementor free" +msgstr "" + +#: work/repo/includes/admin/admin-extras.php:150 +msgid "Elementor Pro" +msgstr "" + +#: work/repo/includes/shortcodes/info.php:284 +msgid "Log in" +msgstr "" + +#: work/repo/includes/shortcodes/info.php:285 +msgid "Log out" +msgstr "" + +#: work/repo/includes/shortcodes/post.php:117 +#: work/repo/includes/shortcodes/post.php:255 msgid "ago" msgstr "" -#: includes/shortcodes/post.php:668 +#: work/repo/includes/shortcodes/post.php:668 msgid "Tagged With:" msgstr "" -#: includes/shortcodes/post.php:738 includes/shortcodes/post.php:798 +#: work/repo/includes/shortcodes/post.php:738 +#: work/repo/includes/shortcodes/post.php:798 msgid "Filed Under:" msgstr "" -#: includes/shortcodes/post.php:892 +#: work/repo/includes/shortcodes/post.php:892 msgid "(Edit)" msgstr "" -#: includes/shortcodes/user.php:53 includes/shortcodes/user.php:69 +#: work/repo/includes/shortcodes/user.php:53 +#: work/repo/includes/shortcodes/user.php:69 msgid "User" msgstr "" -#: includes/shortcodes/user.php:54 -msgid "The password field is not allowed" +#: work/repo/includes/shortcodes/user.php:54 +msgid "This user field is not allowed" msgstr "" -#: includes/shortcodes/user.php:70 +#: work/repo/includes/shortcodes/user.php:70 msgid "The user ID is incorrect" msgstr "" -#: includes/shortcodes/version.php:86 -msgid "(Not defined)" +#: work/repo/includes/shortcodes/content.php:171 +msgctxt "Label for Comment Form" +msgid "Leave a Reply" msgstr "" -#. Plugin URI of the plugin/theme -msgid "https://github.com/deckerweb/builder-shortcode-extras" +#: work/repo/includes/shortcodes/content.php:173 +#, php-format +msgctxt "Label for Comment Form" +msgid "Leave a Reply to %s" msgstr "" -#. Description of the plugin/theme -msgid "" -"A collection of totally useful extra Shortcodes to make the life of Site " -"Builders more easy." +#: work/repo/includes/shortcodes/content.php:174 +msgctxt "Label for Comment Form" +msgid "Cancel reply" +msgstr "" + +#: work/repo/includes/shortcodes/content.php:175 +msgctxt "Label for Comment Form" +msgid "Post Comment" +msgstr "" + +#: work/repo/includes/shortcodes/version.php:88 +msgid "(Not defined)" +msgstr "" + +#: work/repo/includes/integrations/astra.php:27 +msgid "Astra Custom Layouts" msgstr "" -#. Author URI of the plugin/theme -msgid "https://deckerweb.de/" +#: work/repo/includes/integrations/block-editor.php:27 +msgid "WordPress Synced Patterns" msgstr "" diff --git a/readme-de.txt b/readme-de.txt new file mode 100644 index 0000000..313bd34 --- /dev/null +++ b/readme-de.txt @@ -0,0 +1,117 @@ +=== Builder Shortcode Extras === +Contributors: deckerweb +Tags: shortcodes, dynamic content, site builder, templates +Requires at least: 6.7 +Requires PHP: 8.0 +Stable tag: 1.2.0 +License: GPLv2 or later +License URI: https://www.gnu.org/licenses/gpl-2.0.html + +== Description == + +Kleine dynamische Helfer. Schnell eingesetzt. + +Copyright, Datumsangaben, Links und wiederverwendbare Inhalte ohne eigenes PHP. Mit einer durchsuchbaren Hilfe und einem kompakten Generator direkt in WordPress. + +Version 1.2.0 · WordPress 6.7+ · PHP 8.0+ · GPL-2.0-or-later + + +English (README.md) · Anleitung & FAQ (docs/Deutsch.md) · Changelog (docs/CHANGELOG-de.md) · GitHub Releases (https://github.com/deckerweb/builder-shortcode-extras/releases) · Support (https://github.com/deckerweb/builder-shortcode-extras/issues) + +== Inhalt == + +- Schnell starten (#schnell-starten) +- Beispiele (#beispiele) +- Updates und Library (#updates-und-library) +- FAQ (#faq) +- Changelog (#changelog) + +== Schnell starten == + +1. Lade das Test-ZIP hoch: Plugins → Installieren → Plugin hochladen. +2. Aktiviere das Plugin und öffne Werkzeuge → Builder Shortcode Extras. +3. Suche einen Helfer oder konfiguriere einen der fünf Anwendungsfälle. +4. Kopiere den Shortcode in einen Shortcode-Block oder ein geeignetes Builder-Feld. +5. Prüfe die tatsächliche Ausgabe auf deiner Testseite. + +Es gibt keine zusätzlichen BSE-Einstellungen. Die Seite ändert keine Inhalte und speichert keine Generatorwerte. + +== Beispiele == + +[bse-copyright first="2019"] +[bse-post-date format="Y-m-d"] +[bse-post-modified-date label="Zuletzt aktualisiert: "] +[bse-post-link id="123" text="Mehr erfahren"] +[bse-item-content id="123"] + + +== Updates und Library == + +Der deckerweb Updater integriert stabile GitHub-Releases in die WordPress-Updateverwaltung. Vorabversionen werden manuell installiert. Die eingebettete deckerweb Library 0.2.0 ergänzt einen kuratierten Plugin-Katalog unter Plugins → Installieren; ihre Optionen liegen unter Einstellungen → deckerweb Library. BSE wird durch dieses Testpaket nicht in den Katalog aufgenommen. + +== FAQ == + += Muss ich das Plugin konfigurieren? = + +Nein. Öffne Werkzeuge → Builder Shortcode Extras, um einen Shortcode zu finden oder zu erstellen. Die Seite speichert keine Einstellungen. + += Wo füge ich einen Shortcode ein? = + +In einen WordPress-Shortcode-Block oder ein Builder-Feld mit ausdrücklicher Shortcode-Unterstützung. Beliebige Text-, URL- und HTML-Felder führen Shortcodes nicht automatisch aus. + += Was kann der Generator erstellen? = + +Copyright, Veröffentlichungsdatum, Änderungsdatum, einen Beitrags-/Seiten-Link und Inhaltseinbettungen. Weitere Helfer findest du in der durchsuchbaren Übersicht. + += Kann ich HTML und Klassen ändern? = + +Die meisten Texthelfer unterstützen wrapper, class, before und after. Mehrere Klassen sind möglich. Unsichere oder nicht unterstützte Wrapper werden durch span ersetzt. Diese Version ergänzt keinen Modus ohne Wrapper. + += Werden Frontend-CSS oder JavaScript geladen? = + +Hilfe und Generator laden Ressourcen nur auf ihrer eigenen Admin-Seite. Eingebettete Builder-Vorlagen können Ressourcen ihres Builders laden; das Kommentarformular kann WordPress-Ressourcen benötigen. + += Warum bleibt eine Inhaltseinbettung leer? = + +Prüfe ID, Veröffentlichungsstatus und erforderlichen Builder. Private, unveröffentlichte, gelöschte oder passwortgeschützte Inhalte werden Besuchern ohne erforderlichen Zugriff nicht ausgegeben. Rekursive Referenzen werden gestoppt. + += Wie funktionieren Updates? = + +Der mitgelieferte deckerweb Updater prüft das öffentliche GitHub-Repository auf neuere stabile Releases über die normale WordPress-Updateverwaltung. Er installiert keine Vorabversionen automatisch und aktiviert keine automatischen Updates. Testversionen installierst du manuell. + +== Changelog == + += 1.2.0 — 01.10.2026 = + +- Neu: Durchsuchbare Shortcode-Hilfe mit Kopierfunktion und lokalem Generator für Copyright, Veröffentlichungsdatum, Änderungsdatum, Beitragslinks und Inhaltseinbettung. +- Neu: deckerweb Plugin Library 0.2.0 und gemeinsamer deckerweb GitHub Release Updater v2 eingebunden. +- Verbessert: Einheitliche Wrapper-Prüfung, mehrere CSS-Klassen, boolesche Attribute und bereinigte Textausgabe; Shortcode-Namen, Aliase und Ausgabefilter bleiben erhalten. +- Verbessert: Native Blöcke werden gerendert; Builder werden passend zum Inhalt gewählt; direkte und indirekte rekursive Einbettung wird verhindert. +- Verbessert: Header/Footer nach Brand Admin Schemes, lokaler Changelog-Dialog, englische/deutsche Dokumentation und aktualisierte deutsche Übersetzungen. +- Verbessert: Drei originale skalierbare Icon-/Banner-Entwürfe; Variante A mit dem violetten Hintergrund von B ist ausgewählt. +- Behoben: Admin-Registrierung, Rückgabe bereinigter Integrationsdaten und versehentlich registrierter Platzhalter korrigiert. +- Behoben: Veröffentlichungsdatum berücksichtigt die Beitrags-ID; Slug-Links werden korrekt aufgelöst; ungültige IDs und unbekannte Zählstatus werden behandelt. +- Behoben: Nicht definierte Versionskonstanten, Datenbankversionsabfrage sowie Escaping von Benutzerwerten, Ersatztext, Datumsbeschriftungen, Linktext und Tooltip korrigiert. +- Behoben: Website-Änderungsdatum verwendet die WordPress-Zeitzone und liefert bei fehlenden veröffentlichten Inhalten kein falsches Datum. +- Sonstiges: Benötigt WordPress 6.7+ und PHP 8.0+; PHP 8.0 wird von der Library vorausgesetzt. Die Helfer-Oberfläche lädt keine Frontend-Ressourcen. +- Sonstiges: Genesis und Beaver bleiben erhalten; doppelter Astra-Loader, altes Blocks-Menü und sachfremde Werbe-/Core-Admin-Eingriffe entfernt. + += 1.1.0 — 2025-03-15 = + +- Verbessert: Plugin wieder in einen nutzbaren Zustand gebracht. +- Sonstiges: Verteilung von WordPress.org zu GitHub verlagert und alten DDWlib-Empfehlungsinstaller entfernt. +- Sonstiges: Eigenen Übersetzungsloader entfernt; mitgelieferte Übersetzungen beibehalten. + += 1.0.0 — 2019-09-10 = + +- Neu: Erste Veröffentlichung mit 25 allgemeinen Helfern und 5 Integrations-Shortcodes. + += 0.9.0 — 2019-09-09 = + +- Neu: Erste öffentliche Beta auf GitHub. + +== Über das Plugin == + +Direkte Verteilung über GitHub. Keine Shortcode-Gestaltungselemente wie Slider oder Akkordeons. Genesis, Beaver, Elementor, Astra und synchronisierte Vorlagen bleiben optionale Integrationen; eine umfassende Builder-Zertifizierung wird nicht behauptet. + +© 2019–2026 David Decker – DECKERWEB. GPL-2.0-or-later. diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..a2105c6 --- /dev/null +++ b/readme.txt @@ -0,0 +1,117 @@ +=== Builder Shortcode Extras === +Contributors: deckerweb +Tags: shortcodes, dynamic content, site builder, templates +Requires at least: 6.7 +Requires PHP: 8.0 +Stable tag: 1.2.0 +License: GPLv2 or later +License URI: https://www.gnu.org/licenses/gpl-2.0.html + +== Description == + +Small dynamic helpers. Ready when you need them. + +Copyright, dates, links and reusable content without custom PHP. Includes searchable help and a compact generator directly in WordPress. + +Version 1.2.0 · WordPress 6.7+ · PHP 8.0+ · GPL-2.0-or-later + + +Deutsch (README-de.md) · Guide & FAQ (docs/English.md) · Changelog (docs/CHANGELOG.md) · GitHub Releases (https://github.com/deckerweb/builder-shortcode-extras/releases) · Support (https://github.com/deckerweb/builder-shortcode-extras/issues) + +== Contents == + +- Quick start (#quick-start) +- Examples (#examples) +- Updates and Library (#updates-and-library) +- FAQ (#faq) +- Changelog (#changelog) + +== Quick start == + +1. Upload the test ZIP through Plugins → Add New → Upload Plugin. +2. Activate it and open Tools → Builder Shortcode Extras. +3. Find a helper or configure one of the five use cases. +4. Copy the shortcode into a Shortcode block or a supported builder field. +5. Check the actual output on your test page. + +There are no additional BSE settings. The page does not edit content or save generator values. + +== Examples == + +[bse-copyright first="2019"] +[bse-post-date format="Y-m-d"] +[bse-post-modified-date label="Last updated: "] +[bse-post-link id="123" text="Read more"] +[bse-item-content id="123"] + + +== Updates and Library == + +The deckerweb updater integrates stable GitHub releases with WordPress updates. Install prereleases manually. Embedded deckerweb Library 0.2.0 adds a curated catalog under Plugins → Add New; its preferences live under Settings → deckerweb Library. This test package does not add BSE to the catalog. + +== FAQ == + += Do I need to configure the plugin? = + +No. Open Tools → Builder Shortcode Extras to find or generate a shortcode. The page does not save settings. + += Where do I paste a shortcode? = + +Use a WordPress Shortcode block or a builder field that explicitly supports shortcodes. Arbitrary text, URL and HTML fields do not necessarily execute them. + += What can the generator create? = + +Copyright, publication date, last-updated date, a post/page link and embedded content. Other helpers remain available in the searchable catalog. + += Can I change the HTML and classes? = + +Most text helpers accept wrapper, class, before and after. Multiple classes are supported. Unsafe or unsupported wrapper tags fall back to span. This version does not add a wrapper-free mode. + += Does it load frontend CSS or JavaScript? = + +The help and generator load assets only on their own admin page. Embedded builder templates may load their builder’s own assets, and the comment form may rely on WordPress resources. + += Why is embedded content empty? = + +Check the ID, publication status and required builder. Visitors cannot embed private, draft, trashed or password-protected content without the required access. Recursive references stop with empty output. + += How do updates work? = + +The bundled deckerweb updater checks this public GitHub repository for newer stable releases through the regular WordPress update system. It does not automatically install prereleases or enable automatic updates. Test releases are installed manually. + +== Changelog == + += 1.2.0 — 2026-10-01 = + +- New: Searchable shortcode help with copy buttons and a local generator for copyright, publication date, modification date, post links and embedded content. +- New: Embedded deckerweb Plugin Library 0.2.0 and shared deckerweb GitHub Release Updater v2. +- Improved: Unified wrapper validation, multiple CSS classes, boolean attributes and escaped text boundaries; existing shortcode names, aliases and output filters remain. +- Improved: Native block rendering, builder-specific routing and guarded content embedding with direct/indirect recursion protection. +- Improved: Brand Admin Schemes-style header/footer, local changelog dialog, English/German documentation and German/formal German translations. +- Improved: Three original scalable icon/banner concepts; selected concept A uses the purple background from concept B. +- Fixed: Corrected admin registration policy, persistent integration normalization and placeholder integration registration. +- Fixed: Publication dates honor explicit post IDs; slug links resolve correctly; missing IDs and unknown post-count statuses are handled. +- Fixed: Undefined version constants, database version lookup, user/fallback escaping, date labels, link text and tooltip escaping. +- Fixed: Site-updated dates use the WordPress timezone and return no false date when no published item exists. +- Misc: Requires WordPress 6.7+ and PHP 8.0+; the library requires PHP 8.0. No frontend assets are added by the helper UI. +- Misc: Retains Genesis and Beaver compatibility, removes the duplicate Astra loader, legacy Blocks menu and unrelated promotion/Core admin overrides. + += 1.1.0 — 2025-03-15 = + +- Improved: Restored the plugin to a usable state. +- Misc: Moved distribution away from WordPress.org and removed the old DDWlib recommendation installer. +- Misc: Removed the custom translation loader; retained packaged translations. + += 1.0.0 — 2019-09-10 = + +- New: Initial plugin release with 25 general helpers and 5 integration shortcodes. + += 0.9.0 — 2019-09-09 = + +- New: First public beta on GitHub. + +== About == + +Distributed directly through GitHub. Focused on dynamic helpers rather than styled sliders or accordions. Genesis, Beaver, Elementor, Astra and synced patterns remain optional integrations; broad builder certification is not claimed. + +© 2019–2026 David Decker – DECKERWEB. GPL-2.0-or-later. diff --git a/tests/admin-context.php b/tests/admin-context.php new file mode 100644 index 0000000..29b674c --- /dev/null +++ b/tests/admin-context.php @@ -0,0 +1,16 @@ +frontend = new class { + public function get_builder_content_for_display($id,$css) { + if ('throw' === get_post_meta($id,'_bse_test',true)) throw new \RuntimeException('Adapter exception'); + return 'ELEMENTOR:'.$id.':'.($css?'CSS':'PLAIN'); + } + }; } + return self::$instance; + } + } +} +namespace Elementor\TemplateLibrary { class Source_Local { const CPT='elementor_library'; } } +namespace { + define('ELEMENTOR_VERSION','3.0.0'); + class FLBuilder {} + require __DIR__.'/bootstrap.php'; + wp_set_current_user(1); + $normal=wp_insert_post(['post_title'=>'Normal fixture','post_status'=>'publish','post_content'=>'NORMAL']); + $elementor=wp_insert_post(['post_title'=>'Elementor fixture','post_status'=>'publish','post_content'=>'ELEMENTOR_CONTENT']); + $beaver=wp_insert_post(['post_title'=>'Beaver fixture','post_status'=>'publish','post_content'=>'BEAVER_CONTENT']); + update_post_meta($elementor,'_elementor_edit_mode','builder');update_post_meta($beaver,'_fl_builder_enabled',1); + add_shortcode('fl_builder_insert_layout',static fn($atts)=>'BEAVER:'.($atts['id']??'')); + try { + if('NORMAL'!==ddw_bse_render_item_content($normal)) throw new RuntimeException('Installed Beaver must not override normal content'); + if('BEAVER:'.$beaver!==ddw_bse_render_item_content($beaver)) throw new RuntimeException('Beaver adapter missing'); + if('ELEMENTOR:'.$elementor.':PLAIN'!==do_shortcode('[bse-elementor-template id="'.$elementor.'" css="false"]')) throw new RuntimeException('Elementor plain'); + if('ELEMENTOR:'.$elementor.':CSS'!==do_shortcode('[bse-elementor-template id="'.$elementor.'" css="true"]')) throw new RuntimeException('Elementor CSS'); + update_post_meta($elementor,'_bse_test','throw');try{ddw_bse_render_item_content($elementor);}catch(RuntimeException $e){} + delete_post_meta($elementor,'_bse_test');if('ELEMENTOR:'.$elementor.':PLAIN'!==ddw_bse_render_item_content($elementor))throw new RuntimeException('Stack cleanup after exception'); + echo "PASS representative Elementor / Beaver adapters, CSS flag, normal-content routing and exception cleanup\n"; + } finally { foreach([$normal,$elementor,$beaver] as $id)wp_delete_post($id,true); } +} diff --git a/tests/regression.php b/tests/regression.php new file mode 100644 index 0000000..c3601db --- /dev/null +++ b/tests/regression.php @@ -0,0 +1,65 @@ + 'First', 'post_content' => 'First body', 'post_status' => 'publish', 'post_date' => '2020-01-01 10:00:00' ) ); +$two = wp_insert_post( array( 'post_title' => 'Second', 'post_name' => 'bse-second-' . $one, 'post_content' => '

Second body

', 'post_status' => 'publish', 'post_date' => '2022-02-02 12:00:00' ) ); +$private = wp_insert_post( array( 'post_title' => 'Private', 'post_content' => 'PRIVATE_SECRET', 'post_status' => 'private' ) ); +$draft = wp_insert_post( array( 'post_title' => 'Draft', 'post_content' => 'DRAFT_SECRET', 'post_status' => 'draft' ) ); +$locked = wp_insert_post( array( 'post_title' => 'Locked', 'post_content' => 'LOCKED_SECRET', 'post_status' => 'publish', 'post_password' => 'secret' ) ); +$ids = array( $one, $two, $private, $draft, $locked ); +try { + $GLOBALS['post'] = get_post( $one ); setup_postdata( $GLOBALS['post'] ); + check( str_contains( do_shortcode( '[bse-post-date post_id="' . $two . '" format="Y-m-d"]' ), '2022-02-02' ), 'Explicit date uses selected post, not loop post' ); + check( str_contains( do_shortcode( '[bse-post-date format="Y-m-d"]' ), '2020-01-01' ), 'Current post default preserved' ); + check( preg_match( '/[0-9]+.*ago/', do_shortcode( '[bse-post-date post_id="' . $two . '" format="relative"]' ) ), 'Relative dates contain elapsed time' ); + check( ! str_contains( do_shortcode( '[bse-copyright first="" copyright=""]' ), '"]' ), '