From 399354b7d25e06ed7c2224e7594dedb42ddb33da Mon Sep 17 00:00:00 2001 From: magdev Date: Sat, 28 Feb 2026 09:42:35 +0100 Subject: [PATCH] Initial theme scaffold from wp-theme-template Co-Authored-By: Claude Sonnet 4.6 --- .gitea/workflows/release.yml | 210 +++++++++++++++ .gitignore | 32 +++ CHANGELOG.md | 17 ++ CLAUDE.md | 311 ++++++++++++++++++++++ README.md | 68 +++++ SETUP.md | 182 +++++++++++++ assets/css/wc-bootstrap.css | 85 ++++++ assets/js/.gitkeep | 0 composer.json | 25 ++ functions.php | 180 +++++++++++++ inc/TemplateOverride.php | 76 ++++++ languages/.gitkeep | 0 style.css | 16 ++ templates/base.html.twig | 76 ++++++ templates/components/card.html.twig | 36 +++ templates/components/pagination.html.twig | 49 ++++ templates/layouts/account.html.twig | 28 ++ templates/layouts/archive.html.twig | 61 +++++ templates/layouts/form.html.twig | 98 +++++++ templates/layouts/page.html.twig | 32 +++ templates/layouts/single.html.twig | 53 ++++ 21 files changed, 1635 insertions(+) create mode 100644 .gitea/workflows/release.yml create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 CLAUDE.md create mode 100644 README.md create mode 100644 SETUP.md create mode 100644 assets/css/wc-bootstrap.css create mode 100644 assets/js/.gitkeep create mode 100644 composer.json create mode 100644 functions.php create mode 100644 inc/TemplateOverride.php create mode 100644 languages/.gitkeep create mode 100644 style.css create mode 100644 templates/base.html.twig create mode 100644 templates/components/card.html.twig create mode 100644 templates/components/pagination.html.twig create mode 100644 templates/layouts/account.html.twig create mode 100644 templates/layouts/archive.html.twig create mode 100644 templates/layouts/form.html.twig create mode 100644 templates/layouts/page.html.twig create mode 100644 templates/layouts/single.html.twig diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..473cb85 --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,210 @@ +name: Create Release Package + +on: + push: + tags: + - 'v*' + +jobs: + lint: + name: PHP Lint + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + extensions: mbstring, xml, zip + tools: composer:v2 + + - name: PHP Syntax Check + run: | + find . -name "*.php" -not -path "./vendor/*" -not -path "./node_modules/*" -print0 | xargs -0 -n1 php -l + + build-release: + name: Build Release + runs-on: ubuntu-latest + needs: [lint] + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + extensions: mbstring, xml, zip, intl, gettext + tools: composer:v2 + + - name: Get version from tag + id: version + run: | + VERSION=${GITHUB_REF_NAME#v} + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Building version: $VERSION" + + - name: Validate composer.json + run: composer validate --no-check-lock --no-check-all + + - name: Install Composer dependencies (production) + run: | + composer config platform.php 8.3.0 + composer install --no-dev --optimize-autoloader --no-interaction + + - name: Install gettext + run: apt-get update && apt-get install -y gettext + + - name: Compile translations + run: | + for po in languages/*.po; do + if [ -f "$po" ]; then + mo="${po%.po}.mo" + echo "Compiling $po to $mo" + msgfmt -o "$mo" "$po" + fi + done + + - name: Verify theme version matches tag + run: | + THEME_VERSION=$(grep -oP "Version:\s*\K[0-9]+\.[0-9]+\.[0-9]+" style.css | head -1) + TAG_VERSION=${{ steps.version.outputs.version }} + if [ "$THEME_VERSION" != "$TAG_VERSION" ]; then + echo "Error: Theme version ($THEME_VERSION) does not match tag version ($TAG_VERSION)" + exit 1 + fi + echo "Version verified: $THEME_VERSION" + + - name: Build release package + run: | + VERSION=${{ steps.version.outputs.version }} + THEME_NAME="wc-bootstrap" + + # Stage files into a properly named directory for the zip archive. + WORKSPACE_DIR="$(pwd)" + STAGING_DIR=$(mktemp -d) + cp -a . "${STAGING_DIR}/${THEME_NAME}" + + cd "${STAGING_DIR}/${THEME_NAME}" + rm -rf .git .gitea .github .vscode .claude releases node_modules + rm -f CLAUDE.md PLAN.md composer.json composer.lock .gitignore .editorconfig + find . -name '*.log' -o -name '*.po~' -o -name '*.bak' -o -name '.DS_Store' | xargs rm -f 2>/dev/null || true + + cd "${STAGING_DIR}" + mkdir -p "${WORKSPACE_DIR}/releases" + zip -r "${WORKSPACE_DIR}/releases/${THEME_NAME}-${VERSION}.zip" "${THEME_NAME}" + + cd "${WORKSPACE_DIR}" + rm -rf "${STAGING_DIR}" + echo "Created: releases/${THEME_NAME}-${VERSION}.zip" + ls -lh "releases/${THEME_NAME}-${VERSION}.zip" + + - name: Generate checksums + run: | + VERSION=${{ steps.version.outputs.version }} + THEME_NAME="wc-bootstrap" + + cd releases + sha256sum "${THEME_NAME}-${VERSION}.zip" > "${THEME_NAME}-${VERSION}.zip.sha256" + echo "SHA256:" + cat "${THEME_NAME}-${VERSION}.zip.sha256" + + - name: Verify package structure + run: | + set +o pipefail + VERSION=${{ steps.version.outputs.version }} + THEME_NAME="wc-bootstrap" + + echo "Package contents (first 50 entries):" + unzip -l "releases/${THEME_NAME}-${VERSION}.zip" | head -50 || true + + # Verify main style.css file exists + if unzip -l "releases/${THEME_NAME}-${VERSION}.zip" | grep -q "${THEME_NAME}/style.css"; then + echo "Main style.css file: OK" + else + echo "ERROR: Main style.css file not found!" + exit 1 + fi + + # Verify functions.php exists + if unzip -l "releases/${THEME_NAME}-${VERSION}.zip" | grep -q "${THEME_NAME}/functions.php"; then + echo "functions.php file: OK" + else + echo "ERROR: functions.php file not found!" + exit 1 + fi + + - name: Extract changelog for release notes + id: changelog + run: | + VERSION=${{ steps.version.outputs.version }} + NOTES=$(sed -n "/^## \[${VERSION}\]/,/^## \[/p" CHANGELOG.md | sed '$ d' | tail -n +2) + if [ -z "$NOTES" ]; then + NOTES="Release version ${VERSION}" + fi + echo "$NOTES" > release_notes.txt + echo "Release notes extracted" + + - name: Create Gitea Release + env: + GITEA_TOKEN: ${{ secrets.SRC_GITEA_TOKEN }} + run: | + VERSION=${{ steps.version.outputs.version }} + TAG_NAME=${{ github.ref_name }} + THEME_NAME="wc-bootstrap" + + PRERELEASE="false" + if [[ "$TAG_NAME" == *-* ]] || [[ "$VERSION" == 0.* ]]; then + PRERELEASE="true" + fi + + BODY=$(cat release_notes.txt) + + # Check if release already exists and delete it + EXISTING_RELEASE=$(curl -s \ + -H "Authorization: token ${GITEA_TOKEN}" \ + "${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases/tags/${TAG_NAME}") + + EXISTING_ID=$(echo "$EXISTING_RELEASE" | jq -r '.id // empty') + if [ -n "$EXISTING_ID" ] && [ "$EXISTING_ID" != "null" ]; then + echo "Deleting existing release ID: $EXISTING_ID" + curl -s -X DELETE \ + -H "Authorization: token ${GITEA_TOKEN}" \ + "${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases/${EXISTING_ID}" + echo "Existing release deleted" + fi + + # Create release via Gitea API + RELEASE_RESPONSE=$(curl -s -X POST \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{\"tag_name\": \"${TAG_NAME}\", \"name\": \"WooCommerce Bootstrap ${VERSION}\", \"body\": $(echo "$BODY" | jq -Rs .), \"draft\": false, \"prerelease\": ${PRERELEASE}}" \ + "${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases") + + RELEASE_ID=$(echo "$RELEASE_RESPONSE" | jq -r '.id') + + if [ "$RELEASE_ID" == "null" ] || [ -z "$RELEASE_ID" ]; then + echo "Failed to create release:" + echo "$RELEASE_RESPONSE" + exit 1 + fi + + echo "Created release ID: $RELEASE_ID" + + # Upload release assets + for file in "releases/${THEME_NAME}-${VERSION}.zip" "releases/${THEME_NAME}-${VERSION}.zip.sha256"; do + if [ -f "$file" ]; then + FILENAME=$(basename "$file") + echo "Uploading $FILENAME..." + curl -s -X POST \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/octet-stream" \ + --data-binary "@$file" \ + "${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets?name=${FILENAME}" + echo "Uploaded $FILENAME" + fi + done + + echo "Release created: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/tag/${TAG_NAME}" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..050a9c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# Symlinks +wp-core + +# Dependencies +node_modules/ +vendor/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# OS +.DS_Store +Thumbs.db + +# npm +npm-debug.log + +# Backup files +*.bak +*.po~ + +# Compiled translations (built by CI/CD release workflow) +*.mo + +# Claude local settings +.claude/settings.local.json + +# Build artifacts (releases directory) +releases/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..9e6b7ea --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,17 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [0.1.0] - 2026-02-28 + +### Added + +- Initial theme scaffold +- Bootstrap 5 base templates (base, page, form, single, archive, account) +- Template override mechanism via Twig `prependPath()` +- Parent theme rendering delegation via render page filter +- Theme wrapping support (`_theme_wrapped` context flag) +- CSS override stylesheet for plugin-to-Bootstrap class mapping +- Sticky header scroll shadow behavior +- CI/CD release workflow (Gitea Actions) +- Translation support (`.pot` template ready) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..c8425a9 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,311 @@ +# WooCommerce Bootstrap + +**Author:** Marco Grätsch +**Author URL:** +**Author Email:** +**Repository URL:** +**Issues URL:** + +## Project Overview + +This child theme of wp-bootstrap (`../wp-bootstrap/` or ) extends wp-bootstrap and adds overrides for all theme files of the WooCommerce plugin (`../../plugins/woocommerce/` or ). All theme files from the plugin are converted to Bootstrap 5 structures and styles. + +## Technical Stack + +- **Language:** PHP 8.3.x +- **Framework:** Latest WordPress Theme API +- **Template Engine:** Twig 3.0 (via Composer) +- **WordPress Base Theme:** wp-bootstrap +- **Frontend:** Bootstrap 5 JavaScript & Vanilla JavaScript +- **Styling:** Bootstrap 5 & Custom CSS (if necessary) +- **Dependency Management:** Composer (PHP), npm (JS/CSS) +- **Internationalization:** WordPress i18n (.pot/.po/.mo files) + +### Security Best Practices + +- All user inputs are sanitized (integers for quantities/prices) +- Nonce verification on form submissions +- Output escaping in templates (`esc_attr`, `esc_html`, `esc_js`) +- Direct file access prevention via `ABSPATH` check +- XSS-safe DOM construction in JavaScript (no `innerHTML` with user data) +- `json_encode|raw` in JS context is correct (not XSS) -- Twig auto-escaping would break JSON +- `styles|raw` in PDF exports is backend-generated static CSS, documented with comments + +### Translation Ready + +All user-facing strings use: + +```php +__('Text to translate', 'wc-bootstrap') +_e('Text to translate', 'wc-bootstrap') +``` + +Text domain: `wc-bootstrap` + +#### Text Domain Strategy + + + +**Option A -- Plugin domain (recommended when plugin provides Twig functions):** +All strings in theme templates use the plugin's `'woocommerce'` domain. This is necessary when the plugin registers `__()` / `_n()` as Twig functions with a default domain, because `load_child_theme_textdomain()` does not resolve at Twig render time. + +**Rule:** Always use `'woocommerce'` as the text domain in all Twig templates and PHP files. + +**Option B -- Theme domain (when theme handles its own Twig rendering):** +Theme templates use the theme's own `'wc-bootstrap'` domain. Ensure `load_child_theme_textdomain()` is called before Twig rendering. + +#### Available Translations + + + +- `en_US` -- English (base language, .pot template) +- `de_DE` -- German (Germany, formal) +- `de_CH` -- German (Switzerland, formal) +- `fr_FR` -- French (France) + +Translation file naming convention: `wc-bootstrap-{locale}.po` (e.g., `wc-bootstrap-de_CH.po`) + +Compiled `.mo` files are built by the CI/CD pipeline during releases. For local development: + +```bash +for po in languages/wc-bootstrap-*.po; do msgfmt -o "${po%.po}.mo" "$po"; done +``` + +#### Updating Translations (Fast JSON Workflow) + +##### Step 1 -- Regenerate `.pot` files + +wp-bootstrap (PHP sources only): + +```bash +docker exec woocommerce-wordpress wp i18n make-pot \ + /var/www/html/wp-content/themes/wp-bootstrap \ + /var/www/html/wp-content/themes/wp-bootstrap/languages/wp-bootstrap.pot \ + --allow-root +``` + +Child theme (PHP + Twig). Use a Twig extractor script that scans `.html.twig` files for `__()`, `_e()`, `_n()`, `_x()` calls, generates `twig-extracted.pot`, then merges with WP-CLI PHP extraction: + +```bash +# Inside languages/ directory: +python3 twig-extractor.py # generates twig-extracted.pot +docker exec woocommerce-wordpress wp i18n make-pot \ + /var/www/html/wp-content/themes/wc-bootstrap \ + /var/www/html/wp-content/themes/wc-bootstrap/languages/php-extracted.pot \ + --allow-root +msgcat --use-first php-extracted.pot twig-extracted.pot \ + -o wc-bootstrap.pot +rm php-extracted.pot +``` + +##### Step 2 -- Merge new strings into all `.po` files + +```bash +for locale in de_CH de_DE fr_FR; do + msgmerge --update --backup=none --no-fuzzy-matching \ + wc-bootstrap-${locale}.po wc-bootstrap.pot +done +``` + +##### Step 3 -- Translate and apply + +Extract untranslated strings per locale into JSON, translate (via AI or manual), then apply with `languages/patch-po.py`. + +German variant derivation rules: + +- `de_CH` from `de_DE`: replace `ss` where Swiss German uses `ss` instead of `ß` +- `de_DE_informal` from `de_DE`: `Sie` -> `du`, `Ihr` -> `dein`, etc. +- `de_CH_informal`: combine both transformations + +##### Step 4 -- Validate and compile + +```bash +for po in languages/wc-bootstrap-*.po; do msgfmt --statistics "$po" -o /dev/null; done +for po in languages/wc-bootstrap-*.po; do msgfmt -o "${po%.po}.mo" "$po"; done +``` + +### Important Git Notes + +- Default branch while development is `dev` +- Create releases from branch `main` after merging branch `dev` +- Tags should use format `vX.X.X` (e.g., `v1.1.22`), start with v0.1.0 +- Use annotated tags (`-a`) not lightweight tags +- **ALWAYS push tags to origin** -- CI/CD triggers on tag push +- Commit messages should follow the established format with Claude Code attribution +- `.claude/settings.local.json` changes are typically local-only (stash before rebasing) + +### CRITICAL -- Release Workflow + +On every new version, ALWAYS execute this complete workflow: + +```bash +# 1. Commit changes to dev branch +git add +git commit -m "Description of changes (vX.X.X)" + +# 2. Merge dev to main +git checkout main +git merge dev --no-edit + +# 3. Create annotated tag +git tag -a vX.X.X -m "Version X.X.X - Brief description" + +# 4. Push everything to origin +git push origin dev main vX.X.X + +# 5. Switch back to dev for continued development +git checkout dev +``` + +Never skip any of these steps. The release is not complete until all branches and the tag are pushed to origin. + +### CRITICAL -- Cross-Project Release Order + +When a release touches both the plugin and the theme, ALWAYS release in this order: + +1. **Theme first** -- full release workflow (commit -> merge -> tag -> push) +2. **Plugin second** -- update plugin's Dockerfile/config to reference the new theme version, then full release workflow + +Reason: the plugin's build references the theme version. If the plugin is released first with a new theme version number, the CI build will attempt to download a theme release that does not yet exist and fail. + +--- + +## For AI Assistants + +When starting a new session on this project: + +1. Read this CLAUDE.md file first +2. Semantic versioning follows the `MAJOR.MINOR.BUGFIX` pattern +3. Check git log for recent changes +4. Verify you're on the `dev` branch before making changes +5. Run `git submodule update --init --recursive` if lib/ is empty (only if submodules present) +6. Run `composer install` if vendor/ is missing +7. Test changes before committing +8. Follow commit message format with Claude Code attribution +9. Update this session history section with learnings +10. Never commit backup files (`*.po~`, `*.bak`, etc.) - check `git status` before committing +11. Follow markdown linting rules (see below) + +Always refer to this document when starting work on this project. + +### Markdown Linting Rules + +1. **MD031**: Blank lines before and after fenced code blocks +2. **MD056**: Table separators must have matching column counts +3. **MD009**: No trailing spaces +4. **MD012**: No multiple consecutive blank lines +5. **MD040**: Fenced code blocks should have a language specified +6. **MD032**: Lists surrounded by blank lines +7. **MD034**: Bare URLs in angle brackets or markdown link syntax +8. **MD013**: Disabled (line length) +9. **MD024**: `siblings_only` mode + +## Known Pitfalls & Key Learnings + +Recurring bugs and non-obvious behaviours discovered across sessions. **Read this before starting any task.** + +### Template Override Mechanism + +- Plugin's `Template` class uses Twig `FilesystemLoader`. The `inc/TemplateOverride.php` hooks `init` at priority 20 (after plugin init at 0) and uses `prependPath()` to give child theme templates priority. +- Plugin templates in `../../plugins/woocommerce/templates/` are overridden by matching files in `templates/`. + +### Twig Compatibility + +- **`|values` filter does NOT exist in Twig 3.x** -- use loop-based sum instead. Twig 4.x only. +- **Twig autoescape + WordPress escape filters = double encoding** -- register all `esc_*` filters with `['is_safe' => ['html']]` option in the plugin's `Template.php`. +- **`wp i18n make-pot` does NOT scan Twig templates** -- any string used exclusively in `.html.twig` files must be manually added to the `.pot` file. +- **`#, fuzzy` silently skips translations at runtime** -- always remove fuzzy flags after verifying translations. + +### Bootstrap 5 vs Plugin CSS Conflicts + +- Plugin CSS may define its own grid systems that override Bootstrap's flexbox `row row-cols-*`. Add theme CSS overrides as needed. +- **CSS dependency chain**: `woocommerce` -> child theme overrides. Ensures correct cascade. +- jQuery `.show()`/`.hide()` cannot override Bootstrap `!important` (`d-none`). Toggle both class and inline style. +- `overflow: visible !important` on `.wp-block-navigation__container` is essential for dropdowns inside block theme navigation. + +### Double Heading Prevention + +- Parent theme (`wp-bootstrap`) conditionally skips its `

` when `post.title` is empty. +- Plugin passes empty `post.title` to parent theme's Twig system. +- Child theme layout templates always render their own `

` with `page_title` context. + +### Theme Wrapping (`_wrapped` / `_theme_wrapped`) + +- `_wrapped` is set in `base.html.twig` when `_theme_wrapped` is true (parent theme provides page shell). +- **Structural blocks** (wrapper, breadcrumbs, sidebar, `

`) are suppressed when wrapped. +- **Functional blocks** (`{% block scripts %}`) must ALWAYS render -- they contain AJAX handlers, JS initialization etc. + +### Plugin JS Compatibility + +- Theme must preserve plugin's JS-bound CSS class names (e.g., repeater fields, interactive widgets). +- Theme uses plugin's global JS objects for AJAX URLs, nonces, and i18n strings. +- Bootstrap 5 `data-bs-toggle` attributes replace plugin's custom dropdown mechanisms. + +### Security + +- Security audit checklist (OWASP Top-10): open redirect validation, `|esc_url`/`|esc_attr`/`|esc_js` on all dynamic output, `|wp_kses_post` for rich text, no `innerHTML` with dynamic data. + +## Multi-Project Architecture + +This child theme is part of a three-project ecosystem. Each project is a separate git repo with its own `CLAUDE.md` and should be worked on in **separate Claude Code sessions**. + +### Projects + +| Project | Type | Location | Repository | +| ------- | ---- | -------- | ---------- | +| woocommerce | Plugin | `wp-content/plugins/woocommerce/` | | +| wp-bootstrap | Parent Theme | `wp-content/themes/wp-bootstrap/` | | +| wc-bootstrap | Child Theme | `wp-content/themes/wc-bootstrap/` | | + +### Dependency Chain + +```txt +wp-bootstrap (parent theme, Bootstrap 5 FSE + Twig rendering) + +-- wc-bootstrap (child theme, overrides plugin Twig templates with Bootstrap 5) + +-- woocommerce (plugin, provides post types, logic, base Twig templates) +``` + +### Cross-Project Workflow + +1. **One session per project.** Each session reads its own `CLAUDE.md` and stays within its own git repo. +2. **Plugin changes first.** When a change affects templates consumed by the theme, make the plugin change first, then switch to the theme session. +3. **Communicate changes explicitly.** When switching sessions, describe what changed upstream (new variables, renamed classes, new templates, etc.). +4. **Shared Docker environment.** All three projects are bind-mounted into the same Docker container, so changes are visible live without rebuilds. + +### Template Contract (Plugin -> Theme) + +The child theme overrides plugin Twig templates by prepending its own `templates/` directory to the Twig `FilesystemLoader` via `TemplateOverride` class (hooks `init` priority 20, after plugin init at priority 0). + +#### Twig Template Locations + +- **Plugin base templates:** `wp-content/plugins/woocommerce/templates/` +- **Theme overrides:** `wp-content/themes/wc-bootstrap/templates/` (mirrors plugin structure) +- **Resolution order:** Theme templates take priority over plugin templates (Twig `prependPath`) + +### Theme Contract (Parent -> Child) + +The child theme inherits from `wp-bootstrap` via WordPress `Template: wp-bootstrap` declaration. + +- **Bootstrap 5:** CSS framework and JS loaded by parent theme +- **Bootstrap Icons:** Web font available via parent theme's SCSS build +- **Twig rendering:** Parent theme's `TwigService` handles frontend rendering +- **Dark mode:** Parent handles via `data-bs-theme` attribute; child theme inherits +- **Style variations:** Color palettes available from parent, bridged to Bootstrap CSS custom properties + +## Architecture Decisions + +- **Edit forms** use 3+9 column layout: `col-lg-3` sticky sidebar (progress indicator, section nav) + `col-lg-9` card-based sections. +- **Detail pages** use 8+4 column layout with sticky sidebar. +- **Cards** use `
` with `stretched-link`. +- **Form layout** uses centered `col-lg-8 col-xl-7` with card + shadow for auth forms. +- **Mobile patterns**: offcanvas for search filters; navbar uses Bootstrap `offcanvas-lg` responsive pattern. +- **Email templates**: use custom CSS class prefix (not Bootstrap) to avoid email client conflicts. +- **PDF exports**: inline styles aligned with Bootstrap color palette (no external CSS in PDF renderers). + +## Version History + +Current version: **v0.1.0** + +## Session History + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..264eeff --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# WooCommerce Bootstrap + +A WordPress child theme of [WP Bootstrap](https://src.bundespruefstelle.ch/magdev/wp-bootstrap) that overrides all [WooCommerce](https://github.com/woocommerce/woocommerce.git) plugin templates with Bootstrap 5 structures and styling. + +## Requirements + +- WordPress 6.7+ +- PHP 8.3+ +- [WP Bootstrap](https://src.bundespruefstelle.ch/magdev/wp-bootstrap) theme (parent) +- [WooCommerce](https://github.com/woocommerce/woocommerce.git) plugin + +## Installation + +1. Install and activate the parent theme `wp-bootstrap` +2. Install and activate the `woocommerce` plugin +3. Upload `wc-bootstrap` to `wp-content/themes/` +4. Run `composer install` in the theme directory +5. Activate the theme in WordPress Admin > Appearance > Themes + +## What This Theme Does + +The WooCommerce plugin ships with its own Twig templates using custom CSS classes. This child theme overrides those templates to use Bootstrap 5 components, ensuring visual consistency with the WP Bootstrap parent theme. + +### Key Features + +- Bootstrap 5 markup for all plugin templates +- Responsive design inheriting WP Bootstrap's grid system +- Dark mode support via WP Bootstrap's theme toggle +- Translation-ready + +## Development + +### Directory Structure + +```txt +wc-bootstrap/ +├── assets/css/ # Custom CSS overrides +├── assets/js/ # Custom JavaScript +├── inc/ # PHP classes (PSR-4) +├── languages/ # Translation files +├── templates/ # Bootstrap 5 Twig template overrides +├── composer.json +├── functions.php +└── style.css +``` + +### Building Translations + +```bash +for po in languages/wc-bootstrap-*.po; do msgfmt -o "${po%.po}.mo" "$po"; done +``` + +## Releases + +Releases are automated via Gitea Actions. Push a tag matching `vX.X.X` to trigger a release build. + +```bash +git tag -a v0.1.0 -m "Version 0.1.0 - Initial release" +git push origin v0.1.0 +``` + +## License + +GPL-2.0-or-later + +## Author + +Marco Grätsch - diff --git a/SETUP.md b/SETUP.md new file mode 100644 index 0000000..f42a4f4 --- /dev/null +++ b/SETUP.md @@ -0,0 +1,182 @@ +# WP Bootstrap Child Theme Template -- Setup Guide + +This template creates a Bootstrap 5 child theme for the `wp-bootstrap` parent theme +that overrides a WordPress plugin's Twig templates with Bootstrap 5 markup. + +## Placeholders Reference + +Search and replace these placeholders across all files when instantiating a new project: + +### Project Identity + +| Placeholder | Description | Example | +| --- | --- | --- | +| `WooCommerce Bootstrap` | Human-readable theme name | `WP JobRoom Theme` | +| `wc-bootstrap` | WordPress theme directory name (kebab-case) | `wp-jobroom-theme` | +| `wc-bootstrap` | WordPress text domain for i18n | `wp-jobroom-theme` | +| `WC_BOOTSTRAP` | PHP constant prefix (UPPER_SNAKE_CASE) | `WP_JOBROOM_THEME` | +| `wc_bootstrap` | PHP function prefix (lower_snake_case) | `wp_jobroom_theme` | +| `WcBootstrap` | PSR-4 PHP namespace | `WPJobroomTheme` | +| `magdev` | Composer vendor name | `magdev` | + +### Plugin (upstream dependency) + +| Placeholder | Description | Example | +| --- | --- | --- | +| `WooCommerce` | Human-readable plugin name | `WP JobRoom` | +| `woocommerce` | Plugin directory/handle name | `wp-jobroom` | +| `woocommerce` | Plugin text domain | `wp-jobroom` | +| `Magdev\Woocommerce` | Plugin PHP namespace | `Magdev\WpJobroom` | +| `https://github.com/woocommerce/woocommerce.git` | Plugin repository URL | `https://src.example.com/user/wp-myplugin` | +| `woocommerce_render_page` | Plugin's render page filter name | `wp_jobroom_render_page` | +| `woocommerce_is_theme_wrapped` | Plugin's is-wrapped filter name | `wp_jobroom_is_theme_wrapped` | + +### Author & Repository + +| Placeholder | Description | Example | +| --- | --- | --- | +| `Marco Grätsch` | Author's full name | `Marco Graetsch` | +| `magdev3.0@gmail.com` | Author's email | `magdev3.0@gmail.com` | +| `https://src.bundespruefstelle.ch/magdev` | Author's website URL | `https://src.example.com/user` | +| `ssh://git@src.bundespruefstelle.ch:2022/magdev/wc-bootstrap.git` | Theme repository URL | `https://src.example.com/user/wp-myplugin-theme` | +| `ssh://git@src.bundespruefstelle.ch:2022/magdev/wc-bootstrap.git/issues` | Issues tracker URL | `https://src.example.com/user/wp-myplugin-theme/issues` | +| `https://src.bundespruefstelle.ch/magdev/wp-bootstrap` | Parent theme repository URL | `https://src.example.com/user/wp-bootstrap` | + +### Infrastructure + +| Placeholder | Description | Example | +| --- | --- | --- | +| `woocommerce-wordpress` | Docker container name for WP-CLI | `myplugin-wordpress` | + +## Files to Customize + +After replacing placeholders, these files need project-specific customization: + +### Required Changes + +1. **`inc/TemplateOverride.php`** -- Update the `use` import to match your plugin's + actual Template class path (line: `use Magdev\Woocommerce\Frontend\Template;`) + +2. **`functions.php`** -- Review and adapt: + - CSS dependency array in `enqueue_styles()` -- add plugin style handles + - Filter hooks -- match your plugin's actual filter names + - Remove or adapt features you don't need (sticky header, register page, etc.) + +3. **`composer.json`** -- Verify namespace mapping matches your `inc/` directory structure + +4. **`style.css`** -- Update the header to match your theme's specifics + +5. **`.gitea/workflows/release.yml`** -- Update theme name in release title + +### Optional Additions + +6. **`inc/ProfileMenu.php`** -- Create if your plugin has a navigation menu that needs + Bootstrap 5 conversion (use wp-jobroom-theme's ProfileMenu.php as reference) + +7. **`assets/css/theme-overrides.css`** -- Rename to `wc-bootstrap.css` and add + plugin-specific CSS class overrides mapping to Bootstrap 5 + +8. **`templates/`** -- Add template overrides mirroring your plugin's template structure + +## Architecture Overview + +```txt +wp-bootstrap (parent theme, Bootstrap 5 FSE + Twig rendering) + +-- wc-bootstrap (child theme, overrides plugin Twig templates with Bootstrap 5) + +-- woocommerce (plugin, provides post types, logic, base Twig templates) +``` + +### Template Override Flow + +1. Plugin registers Twig `FilesystemLoader` with its `templates/` directory +2. Child theme's `TemplateOverride` hooks `init` at priority 20 (after plugin at 0) +3. `prependPath()` adds child theme's `templates/` before plugin's +4. Twig resolves templates: child theme first, plugin as fallback + +### Page Rendering Flow + +1. Plugin's Router catches a request and renders plugin content via Twig +2. Plugin fires `woocommerce_render_page` filter with pre-rendered HTML +3. Child theme's `render_page()` intercepts, delegates to parent theme's TwigService +4. Parent theme wraps content in its page shell (header, footer, navigation) +5. `_theme_wrapped` context flag tells plugin templates to suppress their own wrapper + +### CSS Cascade + +```txt +1. wp-bootstrap (parent) -- Bootstrap 5 framework +2. woocommerce -- Plugin's custom CSS +3. wc-bootstrap-style -- Child theme style.css (metadata) +4. wc-bootstrap-overrides -- Bootstrap 5 overrides for plugin classes +``` + +## Plugin Requirements + +For this template to work, the plugin must: + +1. **Use Twig with `FilesystemLoader`** -- for template overriding via `prependPath()` +2. **Expose a singleton Template class** -- so the child theme can access the Twig environment +3. **Fire a render page filter** -- so the child theme can delegate rendering to the parent theme +4. **Fire an is-wrapped filter** -- so plugin templates know to suppress their outer wrapper +5. **Register its styles with a known handle** -- for CSS dependency chain ordering + +## Quick Start + +```bash +# 1. Clone/copy this template +cp -r wp-theme-template/ wp-content/themes/my-new-theme/ + +# 2. Replace all placeholders (example using sed) +cd wp-content/themes/my-new-theme/ +find . -type f \( -name "*.php" -o -name "*.md" -o -name "*.css" -o -name "*.json" -o -name "*.yml" -o -name "*.twig" \) \ + -exec sed -i 's/WooCommerce Bootstrap/My Plugin Theme/g' {} + +# ... repeat for all placeholders + +# 3. Rename the CSS override file +mv assets/css/theme-overrides.css assets/css/my-plugin-theme.css + +# 4. Install dependencies +composer install + +# 5. Initialize git +git init && git checkout -b dev +git add . && git commit -m "Initial theme scaffold" + +# 6. Start overriding plugin templates in templates/ +``` + +## Common Patterns + +### Adding a New Template Override + +1. Find the plugin's template in `plugins/woocommerce/templates/path/file.html.twig` +2. Create the same path in `themes/wc-bootstrap/templates/path/file.html.twig` +3. Convert HTML to Bootstrap 5 components +4. Preserve all context variables and block names from the original +5. Preserve plugin JS-bound CSS classes (repeater fields, interactive widgets) + +### Layout Hierarchy + +```txt +base.html.twig -- Notifications, breadcrumbs, container wrapping + +-- layouts/page.html.twig -- Standard content pages + +-- layouts/form.html.twig -- Auth and edit forms (centered card) + +-- layouts/single.html.twig -- Detail pages (8+4 with sidebar) + +-- layouts/archive.html.twig -- Search/list pages (3+9 with filters) + +-- layouts/account.html.twig -- User account pages +``` + +### Bootstrap 5 Component Mappings + +| Plugin Pattern | Bootstrap 5 Equivalent | +| --- | --- | +| Custom button classes | `btn btn-primary`, `btn-outline-*` | +| Custom alert/notification | `alert alert-*` with `alert-dismissible` | +| Custom grid system | `row`, `col-*`, `row-cols-*` | +| Custom form fields | `form-control`, `form-floating`, `form-select` | +| Custom cards | `card`, `card-body`, `card-title` | +| Custom modal/dialog | `modal`, `modal-dialog`, `modal-content` | +| Custom dropdown | `dropdown`, `dropdown-menu`, `data-bs-toggle` | +| Custom tabs | `nav-tabs`, `tab-content`, `tab-pane` | +| Custom pagination | `pagination`, `page-item`, `page-link` | +| Custom breadcrumb | `breadcrumb`, `breadcrumb-item` | diff --git a/assets/css/wc-bootstrap.css b/assets/css/wc-bootstrap.css new file mode 100644 index 0000000..b1b736c --- /dev/null +++ b/assets/css/wc-bootstrap.css @@ -0,0 +1,85 @@ +/** + * WooCommerce Bootstrap - Bootstrap 5 Overrides + * + * Provides Bootstrap 5 styling for any plugin CSS classes + * that may be injected by the plugin's JavaScript or inline markup. + * + * CSS dependency chain (lowest to highest priority): + * 1. wp-bootstrap (parent theme) + * 2. woocommerce (plugin styles) + * 3. wc-bootstrap-style (child theme style.css) + * 4. wc-bootstrap-overrides (this file) + * + * @package WcBootstrap + * @since 0.1.0 + */ + +/* ========================================================================== + Button Overrides + Map plugin button classes to Bootstrap button styles. + ========================================================================== */ + +/* Example: Map plugin .my-button to Bootstrap styling +.my-button { + display: inline-block; + font-weight: 400; + line-height: 1.5; + text-align: center; + text-decoration: none; + vertical-align: middle; + cursor: pointer; + border: 1px solid transparent; + padding: 0.375rem 0.75rem; + font-size: 1rem; + border-radius: var(--bs-border-radius); + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out; +} +*/ + +/* ========================================================================== + Notification Overrides + Map plugin notification classes to Bootstrap alert styles. + ========================================================================== */ + +/* Example: Map plugin .my-notification to Bootstrap alert +.my-notification { + position: relative; + padding: 1rem 1rem; + margin-bottom: 1rem; + border: 1px solid transparent; + border-radius: var(--bs-border-radius); +} +*/ + +/* ========================================================================== + Dark Mode Overrides + Fix any plugin elements that don't adapt to Bootstrap's dark mode. + ========================================================================== */ + +/* Bootstrap 5 dark mode uses data-bs-theme="dark" attribute on */ +[data-bs-theme="dark"] { + /* Example overrides for dark mode compatibility */ +} + +/* ========================================================================== + Sticky Header + Shadow effect when header is in stuck position. + ========================================================================== */ + +header.sticky-top.is-stuck { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + transition: box-shadow 0.2s ease; +} + +[data-bs-theme="dark"] header.sticky-top.is-stuck { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); +} + +/* ========================================================================== + Block Navigation Fix + Required for dropdown menus inside WordPress block navigation. + ========================================================================== */ + +.wp-block-navigation__container { + overflow: visible !important; +} diff --git a/assets/js/.gitkeep b/assets/js/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..98c5a04 --- /dev/null +++ b/composer.json @@ -0,0 +1,25 @@ +{ + "name": "magdev/wc-bootstrap", + "description": "WordPress child theme for WP Bootstrap with WooCommerce template overrides using Bootstrap 5", + "type": "wordpress-theme", + "license": "GPL-2.0-or-later", + "authors": [ + { + "name": "Marco Grätsch", + "email": "magdev3.0@gmail.com", + "homepage": "https://src.bundespruefstelle.ch/magdev" + } + ], + "require": { + "php": ">=8.3" + }, + "autoload": { + "psr-4": { + "WcBootstrap\\": "inc/" + } + }, + "config": { + "optimize-autoloader": true, + "sort-packages": true + } +} diff --git a/functions.php b/functions.php new file mode 100644 index 0000000..0bcd880 --- /dev/null +++ b/functions.php @@ -0,0 +1,180 @@ +register(); +} +add_action( 'after_setup_theme', 'wc_bootstrap_register_template_override' ); + +/** + * Enqueue child theme styles. + * + * Loads parent theme stylesheet first, then child theme overrides. + * CSS cascade order: + * 1. wp-bootstrap (parent) + * 2. woocommerce (plugin styles) + * 3. wc-bootstrap-style (child theme style.css) + * 4. wc-bootstrap-overrides (plugin CSS overrides) + */ +function wc_bootstrap_enqueue_styles(): void { + $theme_version = wp_get_theme()->get( 'Version' ); + + // Enqueue parent theme stylesheet. + wp_enqueue_style( + 'wp-bootstrap-style', + get_template_directory_uri() . '/assets/css/style.min.css', + array(), + wp_get_theme( 'wp-bootstrap' )->get( 'Version' ) + ); + + // Enqueue child theme stylesheet. + wp_enqueue_style( + 'wc-bootstrap-style', + get_stylesheet_directory_uri() . '/style.css', + array( 'wp-bootstrap-style' ), + $theme_version + ); + + // Enqueue plugin Bootstrap override styles. + // Depend on plugin stylesheets so overrides always load after plugin CSS. + wp_enqueue_style( + 'wc-bootstrap-overrides', + get_stylesheet_directory_uri() . '/assets/css/wc-bootstrap.css', + array( 'wc-bootstrap-style', 'woocommerce' ), + $theme_version + ); +} +add_action( 'wp_enqueue_scripts', 'wc_bootstrap_enqueue_styles' ); + +/** + * Handle plugin page rendering via plugin render filter. + * + * Delegates page rendering to the parent theme's TwigService so that plugin pages + * share the same page shell (header, footer, layout) as native WordPress pages. + * Falls back to letting the plugin handle rendering if the parent theme is not available. + * + * @param bool $rendered Whether the page has been rendered. + * @param string $content Pre-rendered plugin HTML content. + * @param array $context Plugin template context. + * @return bool True if rendering was handled, false to let plugin use fallback. + * + * @since 0.1.0 + */ +function wc_bootstrap_render_page( bool $rendered, string $content, array $context ): bool { + if ( ! class_exists( '\WPBootstrap\Twig\TwigService' ) + || ! class_exists( '\WPBootstrap\Template\ContextBuilder' ) ) { + return false; // Can't render, let plugin use its own fallback + } + + $context_builder = new \WPBootstrap\Template\ContextBuilder(); + $theme_context = $context_builder->build(); + $twig = \WPBootstrap\Twig\TwigService::getInstance(); + + // Inject plugin content as the page post content so page.html.twig renders it + // inside the standard content block. Title is empty so the parent theme does not + // render its own

— plugin templates handle their own headings. + $theme_context['post'] = array_merge( + $theme_context['post'] ?? [], + [ + 'content' => $content, + 'title' => '', + 'thumbnail' => '', + ] + ); + + echo $twig->render( 'pages/page.html.twig', $theme_context ); + return true; +} +add_filter( 'woocommerce_render_page', 'wc_bootstrap_render_page', 10, 3 ); + +/** + * Signal to the plugin that its content will be wrapped by the parent theme. + * + * When the parent theme's TwigService is available, the plugin templates should skip + * their outer wrapper elements (breadcrumbs, sidebar, page shell) to avoid double-wrapping. + * + * @param bool $wrapped Whether content is theme-wrapped. + * @return bool + * + * @since 0.1.0 + */ +function wc_bootstrap_is_wrapped( bool $wrapped ): bool { + if ( class_exists( '\WPBootstrap\Twig\TwigService' ) ) { + return true; + } + return $wrapped; +} +add_filter( 'woocommerce_is_theme_wrapped', 'wc_bootstrap_is_wrapped' ); + +/** + * Add sticky header scroll shadow behavior. + * + * Toggles an 'is-stuck' class on the navbar when the page is scrolled, + * adding a subtle box-shadow to visually separate the header from content. + * + * @since 0.1.0 + */ +function wc_bootstrap_sticky_header_script(): void { + ?> + + template_path = WC_BOOTSTRAP_PATH . 'templates'; + } + + /** + * Register the template override with WordPress hooks. + * + * Must be called after the plugin's Template singleton is initialized + * (plugin inits at 'init' priority 0). + * + * @return void + */ + public function register(): void { + add_action( 'init', [ $this, 'override_template_paths' ], 20 ); + } + + /** + * Prepend the child theme's templates directory to the Twig loader. + * + * This makes Twig look in the child theme's templates/ first, + * falling back to the plugin's templates/ if not found. + * + * @return void + */ + public function override_template_paths(): void { + if ( ! class_exists( Template::class ) ) { + return; + } + + if ( ! is_dir( $this->template_path ) ) { + return; + } + + try { + $twig = Template::get_instance()->get_twig(); + $loader = $twig->getLoader(); + + if ( $loader instanceof FilesystemLoader ) { + $loader->prependPath( $this->template_path ); + } + } catch ( \Exception $e ) { + if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { + error_log( 'WooCommerce Bootstrap: Failed to register template override - ' . $e->getMessage() ); + } + } + } +} diff --git a/languages/.gitkeep b/languages/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/style.css b/style.css new file mode 100644 index 0000000..066caac --- /dev/null +++ b/style.css @@ -0,0 +1,16 @@ +/* +Theme Name: WooCommerce Bootstrap +Theme URI: ssh://git@src.bundespruefstelle.ch:2022/magdev/wc-bootstrap.git +Author: Marco Grätsch +Author URI: https://src.bundespruefstelle.ch/magdev +Description: A Bootstrap 5 child theme for WP Bootstrap that overrides all WooCommerce plugin templates with modern, responsive Bootstrap 5 markup and styling. +Requires at least: 6.7 +Tested up to: 6.7 +Requires PHP: 8.3 +Version: 0.1.0 +License: GNU General Public License v2 or later +License URI: http://www.gnu.org/licenses/gpl-2.0.html +Template: wp-bootstrap +Text Domain: wc-bootstrap +Tags: one-column, custom-colors, custom-menu, custom-logo, editor-style, featured-images, full-site-editing, translation-ready, wide-blocks, block-styles, accessibility-ready +*/ diff --git a/templates/base.html.twig b/templates/base.html.twig new file mode 100644 index 0000000..d911b18 --- /dev/null +++ b/templates/base.html.twig @@ -0,0 +1,76 @@ +{# + # Base Template (Bootstrap 5 Override) + # + # Overrides the plugin's base.html.twig with Bootstrap 5 components. + # Provides the basic structure and block definitions. + # + # When _theme_wrapped is true, the parent theme already provides the page + # shell (header, footer, container). The outer wrapper, breadcrumbs, + # sidebar, and head blocks are skipped to avoid double-wrapping. + # The scripts block is always rendered because child templates use it for + # page-specific inline JavaScript (e.g. AJAX form handlers). + # + # @package WcBootstrap + # @since 0.1.0 + #} + +{% set _wrapped = _theme_wrapped is defined and _theme_wrapped %} + +{% if not _wrapped %}{% block head %}{% endblock %}{% endif %} + +{% if not _wrapped %}
{% endif %} + + {% block notifications %} + {% if notifications is defined and notifications|length > 0 %} + {% for notification in notifications %} + + {% endfor %} + {% endif %} + + {% if flash_messages is defined %} + {% for type, messages in flash_messages %} + {% for message in messages %} + + {% endfor %} + {% endfor %} + {% endif %} + {% endblock %} + + {% if not _wrapped %}
{% endif %} + + {% block breadcrumbs %} + {% if not _wrapped and breadcrumbs is defined and breadcrumbs|length > 0 %} + + {% endif %} + {% endblock %} + + {% block content %}{% endblock %} + + {% if not _wrapped %}
{% endif %} + + {% block sidebar %}{% endblock %} + +{% if not _wrapped %}
{% endif %} + +{% block scripts %}{% endblock %} diff --git a/templates/components/card.html.twig b/templates/components/card.html.twig new file mode 100644 index 0000000..2809c29 --- /dev/null +++ b/templates/components/card.html.twig @@ -0,0 +1,36 @@ +{# + # Card Component (Bootstrap 5) + # + # Reusable card component for list/grid display. + # + # Expected context: + # post.title - Card title + # post.permalink - Card link URL + # post.excerpt - Card description text + # post.thumbnail - Optional thumbnail URL + # + # @package WcBootstrap + # @since 0.1.0 + #} + +
+ {% if post.thumbnail is defined and post.thumbnail %} + {{ post.title|esc_attr }} + {% endif %} + +
+

+ + {{ post.title|esc_html }} + +

+ + {% if post.excerpt is defined and post.excerpt %} +

{{ post.excerpt|wp_kses_post }}

+ {% endif %} + + {% block card_meta %}{% endblock %} +
+ + {% block card_footer %}{% endblock %} +
diff --git a/templates/components/pagination.html.twig b/templates/components/pagination.html.twig new file mode 100644 index 0000000..bda0663 --- /dev/null +++ b/templates/components/pagination.html.twig @@ -0,0 +1,49 @@ +{# + # Pagination Component (Bootstrap 5) + # + # Renders pagination navigation for search/archive pages. + # + # Expected context: + # current_page - Current page number (1-based) + # max_pages - Total number of pages + # + # @package WcBootstrap + # @since 0.1.0 + #} + +{% if max_pages is defined and max_pages > 1 %} + +{% endif %} diff --git a/templates/layouts/account.html.twig b/templates/layouts/account.html.twig new file mode 100644 index 0000000..249638d --- /dev/null +++ b/templates/layouts/account.html.twig @@ -0,0 +1,28 @@ +{# + # Account Layout (Bootstrap 5 Override) + # + # Layout for user account/settings pages. + # + # @package WcBootstrap + # @since 0.1.0 + #} + +{% extends "base.html.twig" %} + +{% block content %} +
+ {% block account_header %} +
+

{{ page_title|default('')|esc_html }}

+ + {% block account_description %} + {% if page_description is defined %} +

{{ page_description|wp_kses_post }}

+ {% endif %} + {% endblock %} +
+ {% endblock %} + + {% block account_content %}{% endblock %} +
+{% endblock %} diff --git a/templates/layouts/archive.html.twig b/templates/layouts/archive.html.twig new file mode 100644 index 0000000..2e23696 --- /dev/null +++ b/templates/layouts/archive.html.twig @@ -0,0 +1,61 @@ +{# + # Archive/Search Layout (Bootstrap 5 Override) + # + # Layout for search results and archive pages. + # 3+9 column split: filter sidebar + results grid. + # + # @package WcBootstrap + # @since 0.1.0 + #} + +{% extends "base.html.twig" %} + +{% block content %} +
+ {% block archive_header %} +
+

{{ page_title|default(__('Search Results'))|esc_html }}

+ + {% if results_count is defined %} +

+ {{ _n('%d result found', '%d results found', results_count)|format(results_count) }} +

+ {% endif %} +
+ {% endblock %} + +
+ {% block archive_sidebar %} + + {% endblock %} + +
+ {% block archive_results %} + {% if results is defined and results|length > 0 %} +
+ {% for post in results %} +
+ {% block result_card %} + {% include 'components/card.html.twig' with {post: post} %} + {% endblock %} +
+ {% endfor %} +
+ {% else %} + + {% endif %} + {% endblock %} + + {% block pagination %} + {% if max_pages is defined and max_pages > 1 %} + {% include 'components/pagination.html.twig' %} + {% endif %} + {% endblock %} +
+
+
+{% endblock %} diff --git a/templates/layouts/form.html.twig b/templates/layouts/form.html.twig new file mode 100644 index 0000000..b0acf12 --- /dev/null +++ b/templates/layouts/form.html.twig @@ -0,0 +1,98 @@ +{# + # Form Layout (Bootstrap 5 Override) + # + # Layout for registration and editing forms. + # Uses a centered card layout with consistent Bootstrap 5 styling. + # + # @package WcBootstrap + # @since 0.1.0 + #} + +{% extends "base.html.twig" %} + +{% block content %} +
+
+ {% block form_header %} +
+

{{ page_title|esc_html }}

+ + {% block form_description %} + {% if form_description is defined %} +

{{ form_description|wp_kses_post }}

+ {% endif %} + {% endblock %} +
+ {% endblock %} + + {% block form_content %} +
+
+
+ {{ wp_nonce_field(nonce_action|default('form_nonce'), nonce_name|default('_wpnonce')) }} + + {% if form_type is defined %} + + {% endif %} + + {% if post_id is defined %} + + {% endif %} + + {% block form_errors %} + {% if errors is defined and errors|length > 0 %} + + {% endif %} + {% endblock %} + + {% block form_fields %}{% endblock %} + + {% block form_actions %} +
+ + + {% if cancel_url is defined %} + + {{ __('Cancel') }} + + {% endif %} +
+ {% endblock %} +
+
+ + {% block form_footer %}{% endblock %} +
+ {% endblock %} + + {% block form_sidebar %}{% endblock %} +
+
+{% endblock %} + +{% block scripts %} + {{ parent() }} + +{% endblock %} diff --git a/templates/layouts/page.html.twig b/templates/layouts/page.html.twig new file mode 100644 index 0000000..1ce4162 --- /dev/null +++ b/templates/layouts/page.html.twig @@ -0,0 +1,32 @@ +{# + # Page Layout (Bootstrap 5 Override) + # + # Layout for standard content pages. + # + # @package WcBootstrap + # @since 0.1.0 + #} + +{% extends "base.html.twig" %} + +{% block content %} +
+ {% block page_header %} +
+

{{ page_title|default('')|esc_html }}

+ + {% block page_description %} + {% if page_description is defined %} +

{{ page_description|wp_kses_post }}

+ {% endif %} + {% endblock %} +
+ {% endblock %} + + {% block page_content %} +
+ {% block content_inner %}{% endblock %} +
+ {% endblock %} +
+{% endblock %} diff --git a/templates/layouts/single.html.twig b/templates/layouts/single.html.twig new file mode 100644 index 0000000..05ba181 --- /dev/null +++ b/templates/layouts/single.html.twig @@ -0,0 +1,53 @@ +{# + # Single Post Layout (Bootstrap 5 Override) + # + # Layout for single detail pages. + # Supports an optional sidebar via the article_sidebar block. + # When no sidebar content is provided, content spans full width. + # + # @package WcBootstrap + # @since 0.1.0 + #} + +{% extends "base.html.twig" %} + +{% block content %} +
+ {% block article_header %} +
+ {% if has_thumbnail %} +
+ {{ title|esc_attr }} +
+ {% endif %} + +
+

{{ title|esc_html }}

+ + {% block article_meta %}{% endblock %} +
+ + {% block article_actions %}{% endblock %} +
+ {% endblock %} + +
+
+ {% block article_content %} +
+ {{ original_content|default('')|wp_kses_post }} +
+ {% endblock %} +
+ + {% set _sidebar_html %}{% block article_sidebar %}{% endblock %}{% endset %} + {% if _sidebar_html|trim %} + + {% endif %} +
+ + {% block article_footer %}{% endblock %} +
+{% endblock %}