Merge dev into main for v03.09.01 release
Merges all dev branch work including: - Accessibility toolbar (6 toggleable options) - Complete module overrides with showtitle (24 modules) - IcoMoon to Font Awesome 7 compatibility layer - Sidebar accordion (open desktop, collapsed mobile) - TOC scoped to article body, multi-level heading support - Bootstrap collapse for mobile menu - Search module full-width in header - Blog equal-height cards - Footer padding and dynamic floating control offsets - Auto dev mode when Joomla debug enabled - mod_login count() null fix - Main menu link color fixes - Back-to-top FA icon and anchor Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
18
.github/CODEOWNERS
vendored
18
.github/CODEOWNERS
vendored
@@ -8,20 +8,8 @@
|
||||
# Combined with branch protection (require PR reviews), this prevents
|
||||
# unauthorized modifications to workflows, configs, and governance files.
|
||||
|
||||
# ── Synced workflows (managed by MokoStandards — do not edit manually) ────
|
||||
/.github/workflows/deploy-dev.yml @jmiller-moko
|
||||
/.github/workflows/deploy-demo.yml @jmiller-moko
|
||||
/.github/workflows/deploy-rs.yml @jmiller-moko
|
||||
/.github/workflows/auto-release.yml @jmiller-moko
|
||||
/.github/workflows/auto-dev-issue.yml @jmiller-moko
|
||||
/.github/workflows/auto-assign.yml @jmiller-moko
|
||||
/.github/workflows/sync-version-on-merge.yml @jmiller-moko
|
||||
/.github/workflows/enterprise-firewall-setup.yml @jmiller-moko
|
||||
/.github/workflows/repository-cleanup.yml @jmiller-moko
|
||||
/.github/workflows/standards-compliance.yml @jmiller-moko
|
||||
/.github/workflows/codeql-analysis.yml @jmiller-moko
|
||||
/.github/workflows/repo_health.yml @jmiller-moko
|
||||
# Custom workflows in .github/workflows/ not listed above are repo-owned.
|
||||
# ── Workflows (synced from MokoStandards — must not be manually edited) ──
|
||||
/.github/workflows/ @jmiller-moko
|
||||
|
||||
# ── GitHub configuration ─────────────────────────────────────────────────
|
||||
/.github/ISSUE_TEMPLATE/ @jmiller-moko
|
||||
@@ -35,7 +23,7 @@
|
||||
/composer.json @jmiller-moko
|
||||
/phpstan.neon @jmiller-moko
|
||||
/Makefile @jmiller-moko
|
||||
/.ftpignore @jmiller-moko
|
||||
/.ftp_ignore @jmiller-moko
|
||||
/.gitignore @jmiller-moko
|
||||
/.gitattributes @jmiller-moko
|
||||
/.editorconfig @jmiller-moko
|
||||
|
||||
164
.github/workflows/auto-dev-issue.yml
vendored
164
.github/workflows/auto-dev-issue.yml
vendored
@@ -9,22 +9,14 @@
|
||||
# INGROUP: MokoStandards.Automation
|
||||
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
||||
# PATH: /templates/workflows/shared/auto-dev-issue.yml.template
|
||||
# VERSION: 04.05.13
|
||||
# BRIEF: Auto-create tracking issue with sub-issues for dev/rc branch workflow
|
||||
# VERSION: 04.05.00
|
||||
# BRIEF: Auto-create tracking issue when a dev/** or rc/** branch is pushed
|
||||
# NOTE: Synced via bulk-repo-sync to .github/workflows/auto-dev-issue.yml in all governed repos.
|
||||
|
||||
name: Dev/RC Branch Issue
|
||||
name: Auto Dev Branch Issue
|
||||
|
||||
on:
|
||||
# Auto-create on RC branch creation
|
||||
create:
|
||||
# Manual trigger for dev branches
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
branch:
|
||||
description: 'Branch name (e.g., dev/my-feature or dev/04.06)'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
@@ -38,20 +30,15 @@ jobs:
|
||||
name: Create version tracking issue
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
(github.event_name == 'workflow_dispatch') ||
|
||||
(github.event.ref_type == 'branch' && startsWith(github.event.ref, 'rc/'))
|
||||
github.event.ref_type == 'branch' &&
|
||||
(startsWith(github.event.ref, 'dev/') || startsWith(github.event.ref, 'rc/'))
|
||||
|
||||
steps:
|
||||
- name: Create tracking issue and sub-issues
|
||||
- name: Create tracking issue
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
||||
run: |
|
||||
# For manual dispatch, use input; for auto, use event ref
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
BRANCH="${{ inputs.branch }}"
|
||||
else
|
||||
BRANCH="${{ github.event.ref }}"
|
||||
fi
|
||||
REPO="${{ github.repository }}"
|
||||
ACTOR="${{ github.actor }}"
|
||||
NOW=$(date -u '+%Y-%m-%d %H:%M UTC')
|
||||
@@ -71,122 +58,45 @@ jobs:
|
||||
|
||||
TITLE="${TITLE_PREFIX}(${VERSION}): ${BRANCH_TYPE} tracking for ${BRANCH}"
|
||||
|
||||
BODY="## ${BRANCH_TYPE} Branch Created
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| **Branch** | \`${BRANCH}\` |
|
||||
| **Version** | \`${VERSION}\` |
|
||||
| **Type** | ${BRANCH_TYPE} |
|
||||
| **Created by** | @${ACTOR} |
|
||||
| **Created at** | ${NOW} |
|
||||
| **Repository** | \`${REPO}\` |
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] Feature development complete
|
||||
- [ ] Tests passing
|
||||
- [ ] README.md version bumped to \`${VERSION}\`
|
||||
- [ ] CHANGELOG.md updated
|
||||
- [ ] PR created targeting \`main\`
|
||||
- [ ] Code reviewed and approved
|
||||
- [ ] Merged to \`main\`
|
||||
|
||||
---
|
||||
*Auto-created by [auto-dev-issue.yml](.github/workflows/auto-dev-issue.yml) on branch creation.*"
|
||||
|
||||
# Dedent heredoc
|
||||
BODY=$(echo "$BODY" | sed 's/^ //')
|
||||
|
||||
# Check for existing issue with same title prefix
|
||||
EXISTING=$(gh api "repos/${REPO}/issues?state=open&per_page=10" \
|
||||
EXISTING=$(gh api "repos/${REPO}/issues?state=open&per_page=5" \
|
||||
--jq ".[] | select(.title | startswith(\"${TITLE_PREFIX}(${VERSION})\")) | .number" 2>/dev/null | head -1)
|
||||
|
||||
if [ -n "$EXISTING" ]; then
|
||||
echo "ℹ️ Issue #${EXISTING} already exists for ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# ── Define sub-issues for the dev workflow ────────────────────────
|
||||
if [[ "$BRANCH" == rc/* ]]; then
|
||||
SUB_ISSUES=(
|
||||
"RC Testing|Verify all features work on rc branch|type: test,release-candidate"
|
||||
"Regression Testing|Run full regression suite before merge to main|type: test,release-candidate"
|
||||
"Version Bump|Bump version in README.md and all headers|type: version,release-candidate"
|
||||
"Changelog Update|Update CHANGELOG.md with release notes|documentation,release-candidate"
|
||||
"Merge to Main|Create PR from rc branch to main|type: release,needs-review"
|
||||
)
|
||||
else
|
||||
SUB_ISSUES=(
|
||||
"Development|Implement feature/fix on dev branch|type: feature,status: in-progress"
|
||||
"Unit Testing|Write and pass unit tests|type: test,status: pending"
|
||||
"Code Review|Request and complete code review|needs-review,status: pending"
|
||||
"Version Bump|Bump version in README.md and all headers|type: version,status: pending"
|
||||
"Changelog Update|Update CHANGELOG.md with release notes|documentation,status: pending"
|
||||
"Create RC Branch|Promote dev to rc branch for final testing|type: release,status: pending"
|
||||
"Merge to Main|Create PR from rc/dev to main|type: release,needs-review,status: pending"
|
||||
)
|
||||
fi
|
||||
|
||||
# ── Create sub-issues first ───────────────────────────────────────
|
||||
SUB_LIST=""
|
||||
SUB_NUMBERS=""
|
||||
for SUB in "${SUB_ISSUES[@]}"; do
|
||||
IFS='|' read -r SUB_TITLE SUB_DESC SUB_LABELS <<< "$SUB"
|
||||
SUB_FULL_TITLE="${TITLE_PREFIX}(${VERSION}): ${SUB_TITLE}"
|
||||
|
||||
SUB_BODY=$(printf '### %s\n\n%s\n\n| Field | Value |\n|-------|-------|\n| **Parent Branch** | `%s` |\n| **Version** | `%s` |\n\n---\n*Sub-issue of the %s tracking issue for `%s`.*' \
|
||||
"$SUB_TITLE" "$SUB_DESC" "$BRANCH" "$VERSION" "$BRANCH_TYPE" "$BRANCH")
|
||||
|
||||
SUB_URL=$(gh issue create \
|
||||
--repo "$REPO" \
|
||||
--title "$SUB_FULL_TITLE" \
|
||||
--body "$SUB_BODY" \
|
||||
--label "${SUB_LABELS}" \
|
||||
--assignee "jmiller-moko" 2>&1)
|
||||
|
||||
SUB_NUM=$(echo "$SUB_URL" | grep -oE '[0-9]+$')
|
||||
if [ -n "$SUB_NUM" ]; then
|
||||
SUB_LIST="${SUB_LIST}\n- [ ] ${SUB_TITLE} (#${SUB_NUM})"
|
||||
SUB_NUMBERS="${SUB_NUMBERS} #${SUB_NUM}"
|
||||
fi
|
||||
sleep 0.3
|
||||
done
|
||||
|
||||
# ── Create parent tracking issue ──────────────────────────────────
|
||||
PARENT_BODY=$(printf '## %s Branch Created\n\n| Field | Value |\n|-------|-------|\n| **Branch** | `%s` |\n| **Version** | `%s` |\n| **Type** | %s |\n| **Created by** | @%s |\n| **Created at** | %s |\n| **Repository** | `%s` |\n\n## Workflow Sub-Issues\n\n%b\n\n---\n*Auto-created by [auto-dev-issue.yml](.github/workflows/auto-dev-issue.yml) on branch creation.*' \
|
||||
"$BRANCH_TYPE" "$BRANCH" "$VERSION" "$BRANCH_TYPE" "$ACTOR" "$NOW" "$REPO" "$SUB_LIST")
|
||||
|
||||
PARENT_URL=$(gh issue create \
|
||||
ISSUE_URL=$(gh issue create \
|
||||
--repo "$REPO" \
|
||||
--title "$TITLE" \
|
||||
--body "$PARENT_BODY" \
|
||||
--body "$BODY" \
|
||||
--label "${LABEL_TYPE},version" \
|
||||
--assignee "jmiller-moko" 2>&1)
|
||||
|
||||
PARENT_NUM=$(echo "$PARENT_URL" | grep -oE '[0-9]+$')
|
||||
|
||||
# ── Link sub-issues back to parent ────────────────────────────────
|
||||
if [ -n "$PARENT_NUM" ]; then
|
||||
for SUB in "${SUB_ISSUES[@]}"; do
|
||||
IFS='|' read -r SUB_TITLE _ _ <<< "$SUB"
|
||||
SUB_FULL_TITLE="${TITLE_PREFIX}(${VERSION}): ${SUB_TITLE}"
|
||||
SUB_NUM=$(gh api "repos/${REPO}/issues?state=open&per_page=20" \
|
||||
--jq ".[] | select(.title == \"${SUB_FULL_TITLE}\") | .number" 2>/dev/null | head -1)
|
||||
if [ -n "$SUB_NUM" ]; then
|
||||
gh api "repos/${REPO}/issues/${SUB_NUM}" -X PATCH \
|
||||
-f body="$(gh api "repos/${REPO}/issues/${SUB_NUM}" --jq '.body' 2>/dev/null)
|
||||
|
||||
> **Parent Issue:** #${PARENT_NUM}" --silent 2>/dev/null || true
|
||||
echo "✅ Created tracking issue: ${ISSUE_URL}" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
sleep 0.2
|
||||
done
|
||||
fi
|
||||
|
||||
# ── RC: Create or update draft release ────────────────────────────
|
||||
if [[ "$BRANCH" == rc/* ]]; then
|
||||
MAJOR=$(echo "$VERSION" | awk -F. '{print $1}')
|
||||
RELEASE_TAG="v${MAJOR}"
|
||||
DRAFT_EXISTS=$(gh release view "$RELEASE_TAG" --json isDraft -q .isDraft 2>/dev/null || true)
|
||||
|
||||
if [ -z "$DRAFT_EXISTS" ]; then
|
||||
# No release exists — create draft
|
||||
gh release create "$RELEASE_TAG" \
|
||||
--title "v${MAJOR} (RC: ${VERSION})" \
|
||||
--notes "## Release Candidate ${VERSION}\n\nRC branch: \`${BRANCH}\`\nTracking issue: ${PARENT_URL}" \
|
||||
--draft \
|
||||
--target main 2>/dev/null || true
|
||||
echo "Draft release created: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY
|
||||
elif [ "$DRAFT_EXISTS" = "true" ]; then
|
||||
# Draft exists — update title
|
||||
gh release edit "$RELEASE_TAG" \
|
||||
--title "v${MAJOR} (RC: ${VERSION})" --draft 2>/dev/null || true
|
||||
echo "Draft release updated: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
# Release exists and is published — set back to draft for RC
|
||||
gh release edit "$RELEASE_TAG" \
|
||||
--title "v${MAJOR} (RC: ${VERSION})" --draft 2>/dev/null || true
|
||||
echo "Release ${RELEASE_TAG} set to draft for RC" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
fi
|
||||
|
||||
# ── Summary ───────────────────────────────────────────────────────
|
||||
echo "## Dev Workflow Issues Created" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Item | Issue |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| **Parent** | ${PARENT_URL} |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| **Sub-issues** |${SUB_NUMBERS} |" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
371
.github/workflows/auto-release.yml
vendored
371
.github/workflows/auto-release.yml
vendored
@@ -6,31 +6,29 @@
|
||||
# DEFGROUP: GitHub.Workflow
|
||||
# INGROUP: MokoStandards.Release
|
||||
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
||||
# PATH: /templates/workflows/joomla/auto-release.yml.template
|
||||
# VERSION: 04.05.13
|
||||
# BRIEF: Joomla build & release — ZIP package, update.xml, SHA-256 checksum
|
||||
# PATH: /templates/workflows/shared/auto-release.yml.template
|
||||
# VERSION: 04.05.00
|
||||
# BRIEF: Unified build & release pipeline — version branch, platform version, badges, tag, release
|
||||
#
|
||||
# +========================================================================+
|
||||
# | BUILD & RELEASE PIPELINE (JOOMLA) |
|
||||
# +========================================================================+
|
||||
# | |
|
||||
# | Triggers on push to main (skips bot commits + [skip ci]): |
|
||||
# | |
|
||||
# | Every push: |
|
||||
# | 1. Read version from README.md |
|
||||
# | 3. Set platform version (Joomla <version>) |
|
||||
# | 4. Update [VERSION: XX.YY.ZZ] badges in markdown files |
|
||||
# | 5. Write update.xml (Joomla update server XML) |
|
||||
# | 6. Create git tag vXX.YY.ZZ |
|
||||
# | 7a. Patch: update existing GitHub Release for this minor |
|
||||
# | 8. Build ZIP, upload asset, write SHA-256 to update.xml |
|
||||
# | |
|
||||
# | Every version change: archives main -> version/XX.YY branch |
|
||||
# | Patch 00 = development (no release). First release = patch 01. |
|
||||
# | First release only (patch == 01): |
|
||||
# | 7b. Create new GitHub Release |
|
||||
# | |
|
||||
# +========================================================================+
|
||||
# ╔════════════════════════════════════════════════════════════════════════╗
|
||||
# ║ BUILD & RELEASE PIPELINE ║
|
||||
# ╠════════════════════════════════════════════════════════════════════════╣
|
||||
# ║ ║
|
||||
# ║ Triggers on push to main (skips bot commits + [skip ci]): ║
|
||||
# ║ ║
|
||||
# ║ Every push: ║
|
||||
# ║ 1. Read version from README.md ║
|
||||
# ║ 3. Set platform version (Dolibarr $this->version, Joomla <version>)║
|
||||
# ║ 4. Update [VERSION: XX.YY.ZZ] badges in markdown files ║
|
||||
# ║ 5. Write update.txt / update.xml ║
|
||||
# ║ 6. Create git tag vXX.YY.ZZ ║
|
||||
# ║ 7a. Patch: update existing GitHub Release for this minor ║
|
||||
# ║ ║
|
||||
# ║ Minor releases only (patch == 00): ║
|
||||
# ║ 2. Create/update version/XX.YY branch (patches update in-place) ║
|
||||
# ║ 7b. Create new GitHub Release ║
|
||||
# ║ ║
|
||||
# ╚════════════════════════════════════════════════════════════════════════╝
|
||||
|
||||
name: Build & Release
|
||||
|
||||
@@ -72,13 +70,13 @@ jobs:
|
||||
cd /tmp/mokostandards
|
||||
composer install --no-dev --no-interaction --quiet
|
||||
|
||||
# -- STEP 1: Read version -----------------------------------------------
|
||||
# ── STEP 1: Read version ───────────────────────────────────────────
|
||||
- name: "Step 1: Read version from README.md"
|
||||
id: version
|
||||
run: |
|
||||
VERSION=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null)
|
||||
if [ -z "$VERSION" ]; then
|
||||
echo "No VERSION in README.md — skipping release"
|
||||
echo "⏭️ No VERSION in README.md — skipping release"
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
@@ -86,34 +84,24 @@ jobs:
|
||||
MINOR=$(echo "$VERSION" | awk -F. '{printf "%s.%s", $1, $2}')
|
||||
PATCH=$(echo "$VERSION" | awk -F. '{print $3}')
|
||||
|
||||
MAJOR=$(echo "$VERSION" | awk -F. '{print $1}')
|
||||
MINOR_NUM=$(echo "$VERSION" | awk -F. '{print $2}')
|
||||
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=v${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "branch=version/${MINOR}" >> "$GITHUB_OUTPUT"
|
||||
echo "minor=$MINOR" >> "$GITHUB_OUTPUT"
|
||||
echo "major=$MAJOR" >> "$GITHUB_OUTPUT"
|
||||
echo "release_tag=v${MAJOR}" >> "$GITHUB_OUTPUT"
|
||||
if [ "$PATCH" = "00" ]; then
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
echo "is_minor=false" >> "$GITHUB_OUTPUT"
|
||||
echo "Version: $VERSION (patch 00 = development — skipping release)"
|
||||
else
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
if [ "$PATCH" = "01" ]; then
|
||||
if [ "$PATCH" = "00" ]; then
|
||||
echo "is_minor=true" >> "$GITHUB_OUTPUT"
|
||||
echo "Version: $VERSION (first release — full pipeline)"
|
||||
echo "✅ Version: $VERSION (minor release — full pipeline)"
|
||||
else
|
||||
echo "is_minor=false" >> "$GITHUB_OUTPUT"
|
||||
echo "Version: $VERSION (patch — platform version + badges only)"
|
||||
fi
|
||||
echo "✅ Version: $VERSION (patch — platform version + badges only)"
|
||||
fi
|
||||
|
||||
- name: Check if already released
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
id: check
|
||||
run: |
|
||||
TAG="${{ steps.version.outputs.release_tag }}"
|
||||
TAG="${{ steps.version.outputs.tag }}"
|
||||
BRANCH="${{ steps.version.outputs.branch }}"
|
||||
|
||||
TAG_EXISTS=false
|
||||
@@ -131,109 +119,102 @@ jobs:
|
||||
echo "already_released=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
# -- SANITY CHECKS -------------------------------------------------------
|
||||
- name: "Sanity: Pre-release validation"
|
||||
# ── SANITY CHECKS ────────────────────────────────────────────────────
|
||||
- name: "Sanity: Platform-specific validation"
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
steps.check.outputs.already_released != 'true'
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
PLATFORM=$(php /tmp/mokostandards/api/cli/platform_detect.php --path . 2>/dev/null)
|
||||
ERRORS=0
|
||||
|
||||
echo "## Pre-Release Sanity Checks (Joomla)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "## 🔍 Pre-Release Sanity Checks" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Platform: \`${PLATFORM}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# -- Version drift check (must pass before release) --------
|
||||
README_VER=$(grep -oP 'VERSION:\s*\K[\d.]+' README.md 2>/dev/null | head -1)
|
||||
if [ "$README_VER" != "$VERSION" ]; then
|
||||
echo "- Version drift: README says \`${README_VER}\` but releasing \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS+1))
|
||||
else
|
||||
echo "- Version consistent: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# Check CHANGELOG version matches
|
||||
CL_VER=$(grep -oP 'VERSION:\s*\K[\d.]+' CHANGELOG.md 2>/dev/null | head -1)
|
||||
if [ -n "$CL_VER" ] && [ "$CL_VER" != "$VERSION" ]; then
|
||||
echo "- CHANGELOG drift: \`${CL_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS+1))
|
||||
fi
|
||||
|
||||
# Check composer.json version if present
|
||||
if [ -f "composer.json" ]; then
|
||||
COMP_VER=$(grep -oP '"version"\s*:\s*"\K[^"]+' composer.json 2>/dev/null | head -1)
|
||||
if [ -n "$COMP_VER" ] && [ "$COMP_VER" != "$VERSION" ]; then
|
||||
echo "- composer.json drift: \`${COMP_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS+1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# Common checks
|
||||
if [ ! -f "LICENSE" ]; then
|
||||
echo "- Missing LICENSE file" >> $GITHUB_STEP_SUMMARY
|
||||
echo "❌ Missing LICENSE file" >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS+1))
|
||||
else
|
||||
echo "- LICENSE present" >> $GITHUB_STEP_SUMMARY
|
||||
echo "✅ LICENSE" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [ ! -d "src" ] && [ ! -d "htdocs" ]; then
|
||||
echo "- Warning: No src/ or htdocs/ directory" >> $GITHUB_STEP_SUMMARY
|
||||
if [ ! -d "src" ]; then
|
||||
echo "⚠️ No src/ directory" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "- Source directory present" >> $GITHUB_STEP_SUMMARY
|
||||
echo "✅ src/ directory" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# -- Joomla: manifest version drift --------
|
||||
# Dolibarr-specific checks
|
||||
if [ "$PLATFORM" = "crm-module" ]; then
|
||||
MOD_FILE=$(find src htdocs -path "*/core/modules/mod*.class.php" -print -quit 2>/dev/null)
|
||||
if [ -z "$MOD_FILE" ]; then
|
||||
echo "❌ No module descriptor (src/core/modules/mod*.class.php)" >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS+1))
|
||||
else
|
||||
echo "✅ Module descriptor: \`${MOD_FILE}\`" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Check module number
|
||||
NUMERO=$(grep -oP '\$this->numero\s*=\s*\K\d+' "$MOD_FILE" 2>/dev/null || echo "0")
|
||||
if [ "$NUMERO" = "0" ] || [ -z "$NUMERO" ]; then
|
||||
echo "❌ Module number (\$this->numero) is 0 or not set" >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS+1))
|
||||
else
|
||||
echo "✅ Module number: ${NUMERO}" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# Check url_last_version exists
|
||||
if grep -q 'url_last_version' "$MOD_FILE" 2>/dev/null; then
|
||||
echo "✅ url_last_version is set" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "⚠️ url_last_version not set — update checks won't work" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Joomla-specific checks
|
||||
if [ "$PLATFORM" = "waas-component" ]; then
|
||||
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
||||
if [ -n "$MANIFEST" ]; then
|
||||
XML_VER=$(grep -oP '<version>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1)
|
||||
if [ -n "$XML_VER" ] && [ "$XML_VER" != "$VERSION" ]; then
|
||||
echo "- Manifest drift: \`${XML_VER}\` != \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS+1))
|
||||
else
|
||||
echo "- Manifest version: \`${VERSION}\`" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
fi
|
||||
|
||||
# -- Joomla: XML manifest existence --------
|
||||
if [ -z "$MANIFEST" ]; then
|
||||
echo "- No Joomla XML manifest found" >> $GITHUB_STEP_SUMMARY
|
||||
echo "❌ No Joomla XML manifest found" >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS+1))
|
||||
else
|
||||
echo "- Manifest: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "✅ Manifest: \`${MANIFEST}\`" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# -- Joomla: extension type check --------
|
||||
# Check extension type
|
||||
TYPE=$(grep -oP '<extension[^>]+type="\K[^"]+' "$MANIFEST" 2>/dev/null)
|
||||
echo "- Extension type: ${TYPE:-unknown}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "✅ Extension type: ${TYPE:-unknown}" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
if [ "$ERRORS" -gt 0 ]; then
|
||||
echo "**${ERRORS} error(s) — release may be incomplete**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**❌ ${ERRORS} error(s) — release may be incomplete**" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "**All sanity checks passed**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**✅ All sanity checks passed**" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# -- STEP 2: Create or update version/XX.YY archive branch ---------------
|
||||
# Always runs — every version change on main archives to version/XX.YY
|
||||
- name: "Step 2: Version archive branch"
|
||||
if: steps.check.outputs.already_released != 'true'
|
||||
# ── STEP 2: Create or update version/XX.YY branch ──────────────────
|
||||
- name: "Step 2: Version branch"
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
steps.check.outputs.already_released != 'true'
|
||||
run: |
|
||||
BRANCH="${{ steps.version.outputs.branch }}"
|
||||
IS_MINOR="${{ steps.version.outputs.is_minor }}"
|
||||
PATCH="${{ steps.version.outputs.version }}"
|
||||
PATCH_NUM=$(echo "$PATCH" | awk -F. '{print $3}')
|
||||
|
||||
# Check if branch exists
|
||||
if git ls-remote --heads origin "$BRANCH" | grep -q "$BRANCH"; then
|
||||
git push origin HEAD:"$BRANCH" --force
|
||||
echo "Updated archive branch: ${BRANCH} (patch ${PATCH_NUM})" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
if [ "$IS_MINOR" = "true" ]; then
|
||||
git checkout -b "$BRANCH" 2>/dev/null || git checkout "$BRANCH"
|
||||
git push origin "$BRANCH" --force
|
||||
echo "Created archive branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "🌿 Created branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
git push origin HEAD:"$BRANCH" --force
|
||||
echo "📝 Updated branch: ${BRANCH} (patch)" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# -- STEP 3: Set platform version ----------------------------------------
|
||||
# ── STEP 3: Set platform version ───────────────────────────────────
|
||||
- name: "Step 3: Set platform version"
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
@@ -243,7 +224,7 @@ jobs:
|
||||
php /tmp/mokostandards/api/cli/version_set_platform.php \
|
||||
--path . --version "$VERSION" --branch main
|
||||
|
||||
# -- STEP 4: Update version badges ----------------------------------------
|
||||
# ── STEP 4: Update version badges ──────────────────────────────────
|
||||
- name: "Step 4: Update version badges"
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
@@ -256,22 +237,27 @@ jobs:
|
||||
fi
|
||||
done
|
||||
|
||||
# -- STEP 5: Write update.xml (Joomla update server) ---------------------
|
||||
- name: "Step 5: Write update.xml"
|
||||
# ── STEP 5: Write update files (Dolibarr: update.txt / Joomla: update.xml)
|
||||
- name: "Step 5: Write update files"
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
steps.check.outputs.already_released != 'true'
|
||||
run: |
|
||||
PLATFORM=$(php /tmp/mokostandards/api/cli/platform_detect.php --path . 2>/dev/null)
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
REPO="${{ github.repository }}"
|
||||
|
||||
# -- Parse extension metadata from XML manifest ----------------
|
||||
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
||||
if [ -z "$MANIFEST" ]; then
|
||||
echo "Warning: No Joomla XML manifest found — skipping update.xml" >> $GITHUB_STEP_SUMMARY
|
||||
exit 0
|
||||
if [ "$PLATFORM" = "crm-module" ]; then
|
||||
printf '%s' "$VERSION" > update.txt
|
||||
echo "📦 update.txt: ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [ "$PLATFORM" = "waas-component" ]; then
|
||||
# ── Parse extension metadata from XML manifest ──────────────
|
||||
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1)
|
||||
if [ -z "$MANIFEST" ]; then
|
||||
echo "⚠️ No Joomla XML manifest found — skipping update.xml" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
EXT_NAME=$(grep -oP '<name>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "${{ github.event.repository.name }}")
|
||||
EXT_TYPE=$(grep -oP '<extension[^>]+type="\K[^"]+' "$MANIFEST" 2>/dev/null || echo "component")
|
||||
EXT_ELEMENT=$(grep -oP '<element>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || echo "")
|
||||
@@ -299,7 +285,7 @@ jobs:
|
||||
FOLDER_TAG="<folder>${EXT_FOLDER}</folder>"
|
||||
fi
|
||||
|
||||
# Build targetplatform (fallback to Joomla 5 if not in manifest)
|
||||
# Build targetplatform (fallback to Joomla 5+6 if not in manifest)
|
||||
if [ -z "$TARGET_PLATFORM" ]; then
|
||||
TARGET_PLATFORM=$(printf '<targetplatform name="joomla" version="5.*" %s>' "/")
|
||||
fi
|
||||
@@ -313,7 +299,7 @@ jobs:
|
||||
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/v${VERSION}/${EXT_ELEMENT}-${VERSION}.zip"
|
||||
INFO_URL="https://github.com/${REPO}/releases/tag/v${VERSION}"
|
||||
|
||||
# -- Write update.xml (stable release) --------------------------
|
||||
# ── Write update.xml (stable release) ───────────────────────
|
||||
{
|
||||
printf '%s\n' '<?xml version="1.0" encoding="utf-8"?>'
|
||||
printf '%s\n' '<updates>'
|
||||
@@ -340,16 +326,18 @@ jobs:
|
||||
printf '%s\n' '</updates>'
|
||||
} > update.xml
|
||||
|
||||
echo "update.xml: ${VERSION} (stable) — ${EXT_TYPE}/${EXT_ELEMENT}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "📦 update.xml: ${VERSION} (stable) — ${EXT_TYPE}/${EXT_ELEMENT}" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
fi
|
||||
|
||||
# -- Commit all changes ---------------------------------------------------
|
||||
# ── Commit all changes ─────────────────────────────────────────────
|
||||
- name: Commit release changes
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
steps.check.outputs.already_released != 'true'
|
||||
run: |
|
||||
if git diff --quiet && git diff --cached --quiet; then
|
||||
echo "No changes to commit"
|
||||
echo "ℹ️ No changes to commit"
|
||||
exit 0
|
||||
fi
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
@@ -360,25 +348,18 @@ jobs:
|
||||
--author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
|
||||
git push
|
||||
|
||||
# -- STEP 6: Create tag ---------------------------------------------------
|
||||
# ── STEP 6: Create tag ─────────────────────────────────────────────
|
||||
- name: "Step 6: Create git tag"
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
steps.check.outputs.tag_exists != 'true' &&
|
||||
steps.version.outputs.is_minor == 'true'
|
||||
steps.check.outputs.tag_exists != 'true'
|
||||
run: |
|
||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||
# Only create the major release tag if it doesn't exist yet
|
||||
if ! git rev-parse "$RELEASE_TAG" >/dev/null 2>&1; then
|
||||
git tag "$RELEASE_TAG"
|
||||
git push origin "$RELEASE_TAG"
|
||||
echo "Tag created: ${RELEASE_TAG}" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "Tag ${RELEASE_TAG} already exists" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
echo "Tag: ${TAG}" >> $GITHUB_STEP_SUMMARY
|
||||
TAG="${{ steps.version.outputs.tag }}"
|
||||
git tag "$TAG"
|
||||
git push origin "$TAG"
|
||||
echo "🏷️ Tag: ${TAG}" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# -- STEP 7: Create or update GitHub Release ------------------------------
|
||||
# ── STEP 7: Create or update GitHub Release ──────────────────────────
|
||||
- name: "Step 7: GitHub Release"
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true' &&
|
||||
@@ -387,129 +368,67 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||
TAG="${{ steps.version.outputs.tag }}"
|
||||
BRANCH="${{ steps.version.outputs.branch }}"
|
||||
MAJOR="${{ steps.version.outputs.major }}"
|
||||
IS_MINOR="${{ steps.version.outputs.is_minor }}"
|
||||
|
||||
# Derive the minor version base (XX.YY.00)
|
||||
MINOR_BASE=$(echo "$VERSION" | sed 's/\.[0-9]*$/.00/')
|
||||
MINOR_TAG="v${MINOR_BASE}"
|
||||
|
||||
NOTES=$(php /tmp/mokostandards/api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null)
|
||||
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
|
||||
echo "$NOTES" > /tmp/release_notes.md
|
||||
|
||||
# Check if the major release already exists
|
||||
EXISTING=$(gh release view "$RELEASE_TAG" --json tagName -q .tagName 2>/dev/null || true)
|
||||
|
||||
if [ -z "$EXISTING" ]; then
|
||||
# First release for this major
|
||||
gh release create "$RELEASE_TAG" \
|
||||
--title "v${MAJOR} (latest: ${VERSION})" \
|
||||
if [ "$IS_MINOR" = "true" ]; then
|
||||
# Minor release: create new GitHub Release
|
||||
gh release create "$TAG" \
|
||||
--title "${VERSION}" \
|
||||
--notes-file /tmp/release_notes.md \
|
||||
--target "$BRANCH"
|
||||
echo "Release created: ${RELEASE_TAG} (${VERSION})" >> $GITHUB_STEP_SUMMARY
|
||||
echo "🚀 Release created: ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
# Append version notes to existing major release
|
||||
CURRENT_NOTES=$(gh release view "$RELEASE_TAG" --json body -q .body 2>/dev/null || true)
|
||||
# Patch release: update the existing minor release with new tag
|
||||
# Find the latest release for this minor version
|
||||
EXISTING=$(gh release view "$MINOR_TAG" --json tagName -q .tagName 2>/dev/null || true)
|
||||
if [ -n "$EXISTING" ]; then
|
||||
# Update existing release body with patch info
|
||||
CURRENT_NOTES=$(gh release view "$MINOR_TAG" --json body -q .body 2>/dev/null || true)
|
||||
{
|
||||
echo "$CURRENT_NOTES"
|
||||
echo ""
|
||||
echo "---"
|
||||
echo "### ${VERSION}"
|
||||
echo "### Patch ${VERSION}"
|
||||
echo ""
|
||||
cat /tmp/release_notes.md
|
||||
} > /tmp/updated_notes.md
|
||||
|
||||
gh release edit "$RELEASE_TAG" \
|
||||
--title "v${MAJOR} (latest: ${VERSION})" \
|
||||
gh release edit "$MINOR_TAG" \
|
||||
--title "${MINOR_BASE} (latest: ${VERSION})" \
|
||||
--notes-file /tmp/updated_notes.md
|
||||
echo "Release updated: ${RELEASE_TAG} -> ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# -- STEP 8: Build Joomla install ZIP + SHA-256 checksum ------------------
|
||||
# Every patch builds an install-ready ZIP and uploads it to the minor release.
|
||||
# Result: one Release per minor version with a ZIP for each patch.
|
||||
- name: "Step 8: Build Joomla package and update checksum"
|
||||
if: >-
|
||||
steps.version.outputs.skip != 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||
REPO="${{ github.repository }}"
|
||||
|
||||
# All ZIPs upload to the major release tag (vXX)
|
||||
gh release view "$RELEASE_TAG" --json tagName > /dev/null 2>&1 || {
|
||||
echo "No release ${RELEASE_TAG} found — skipping ZIP upload"
|
||||
exit 0
|
||||
}
|
||||
|
||||
# Find extension element name from manifest
|
||||
MANIFEST=$(find . -maxdepth 2 -name "*.xml" -exec grep -l '<extension' {} \; 2>/dev/null | head -1 || true)
|
||||
[ -z "$MANIFEST" ] && exit 0
|
||||
|
||||
EXT_ELEMENT=$(grep -oP '<element>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1 || basename "$MANIFEST" .xml)
|
||||
PACKAGE_NAME="${EXT_ELEMENT}-${VERSION}.zip"
|
||||
|
||||
# -- Build install-ready ZIP from src/ ----------------------------
|
||||
SOURCE_DIR="src"
|
||||
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
||||
[ ! -d "$SOURCE_DIR" ] && { echo "No src/ or htdocs/ — skipping package"; exit 0; }
|
||||
|
||||
cd "$SOURCE_DIR"
|
||||
zip -r "/tmp/${PACKAGE_NAME}" . -x '*.git*' '*.DS_Store' 'Thumbs.db' '*.log'
|
||||
cd ..
|
||||
|
||||
FILESIZE=$(stat -c%s "/tmp/${PACKAGE_NAME}" 2>/dev/null || stat -f%z "/tmp/${PACKAGE_NAME}" 2>/dev/null || echo "unknown")
|
||||
|
||||
# -- Calculate SHA-256 -------------------------------------------
|
||||
SHA256=$(sha256sum "/tmp/${PACKAGE_NAME}" | cut -d' ' -f1)
|
||||
|
||||
# -- Upload ZIP to the minor release tag -------------------------
|
||||
gh release upload "$RELEASE_TAG" "/tmp/${PACKAGE_NAME}" --clobber 2>/dev/null || {
|
||||
echo "Could not upload with --clobber, retrying..."
|
||||
gh release upload "$RELEASE_TAG" "/tmp/${PACKAGE_NAME}" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# -- Update update.xml with SHA-256 for latest patch -------------
|
||||
if [ -f "update.xml" ]; then
|
||||
if grep -q '<sha256>' update.xml; then
|
||||
sed -i "s|<sha256>.*</sha256>|<sha256>sha256:${SHA256}</sha256>|" update.xml
|
||||
echo "📝 Release updated: ${MINOR_BASE} → patch ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
sed -i "s|</downloads>|</downloads>\n <sha256>sha256:${SHA256}</sha256>|" update.xml
|
||||
# No existing minor release found — create one for this patch
|
||||
gh release create "$TAG" \
|
||||
--title "${VERSION}" \
|
||||
--notes-file /tmp/release_notes.md
|
||||
echo "🚀 Release created: ${VERSION} (no minor release found)" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
fi
|
||||
|
||||
# Also update the download URL to point to this patch's ZIP
|
||||
DOWNLOAD_URL="https://github.com/${REPO}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}"
|
||||
sed -i "s|<downloadurl[^>]*>[^<]*</downloadurl>|<downloadurl type=\"full\" format=\"zip\">${DOWNLOAD_URL}</downloadurl>|" update.xml
|
||||
|
||||
git add update.xml
|
||||
git commit -m "chore(release): SHA-256 + download URL for ${VERSION} [skip ci]" \
|
||||
--author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>" || true
|
||||
git push || true
|
||||
fi
|
||||
|
||||
echo "### Joomla Package" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Package | \`${PACKAGE_NAME}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Size | ${FILESIZE} bytes |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| SHA-256 | \`${SHA256}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Release | \`${RELEASE_TAG}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Download | [${PACKAGE_NAME}](https://github.com/${REPO}/releases/download/${RELEASE_TAG}/${PACKAGE_NAME}) |" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# -- Summary --------------------------------------------------------------
|
||||
# ── Summary ────────────────────────────────────────────────────────
|
||||
- name: Pipeline Summary
|
||||
if: always()
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
|
||||
echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY
|
||||
echo "## ⏭️ Release Skipped" >> $GITHUB_STEP_SUMMARY
|
||||
echo "No VERSION in README.md" >> $GITHUB_STEP_SUMMARY
|
||||
elif [ "${{ steps.check.outputs.already_released }}" = "true" ]; then
|
||||
echo "## Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "## ℹ️ Already Released — ${VERSION}" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "## Build & Release Complete (Joomla)" >> $GITHUB_STEP_SUMMARY
|
||||
echo "## ✅ Build & Release Complete" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Step | Result |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|------|--------|" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
19
.github/workflows/deploy-demo.yml
vendored
19
.github/workflows/deploy-demo.yml
vendored
@@ -22,7 +22,7 @@
|
||||
# INGROUP: MokoStandards.Deploy
|
||||
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
||||
# PATH: /templates/workflows/shared/deploy-demo.yml.template
|
||||
# VERSION: 04.05.13
|
||||
# VERSION: 04.05.00
|
||||
# BRIEF: SFTP deployment workflow for demo server — synced to all governed repos
|
||||
# NOTE: Synced via bulk-repo-sync to .github/workflows/deploy-demo.yml in all governed repos.
|
||||
# Port is resolved in order: DEMO_FTP_PORT variable → :port suffix in DEMO_FTP_HOST → 22.
|
||||
@@ -296,12 +296,6 @@ jobs:
|
||||
HOST="$HOST_RAW"
|
||||
PORT="$PORT_VAR"
|
||||
|
||||
if [ -z "$HOST" ]; then
|
||||
echo "⏭️ DEMO_FTP_HOST not configured — skipping demo deployment."
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Priority 1 — explicit DEMO_FTP_PORT variable
|
||||
if [ -n "$PORT" ]; then
|
||||
echo "ℹ️ Using explicit DEMO_FTP_PORT=${PORT}"
|
||||
@@ -323,7 +317,7 @@ jobs:
|
||||
echo "SFTP target: ${HOST}:${PORT}"
|
||||
|
||||
- name: Build remote path
|
||||
if: steps.source.outputs.skip == 'false' && steps.conn.outputs.skip != 'true'
|
||||
if: steps.source.outputs.skip == 'false'
|
||||
id: remote
|
||||
env:
|
||||
DEMO_FTP_PATH: ${{ vars.DEMO_FTP_PATH }}
|
||||
@@ -332,9 +326,10 @@ jobs:
|
||||
BASE="$DEMO_FTP_PATH"
|
||||
|
||||
if [ -z "$BASE" ]; then
|
||||
echo "⏭️ DEMO_FTP_PATH not configured — skipping demo deployment."
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
echo "❌ DEMO_FTP_PATH is not set."
|
||||
echo " Configure it as an org-level variable (Settings → Variables) and"
|
||||
echo " ensure this repository has been granted access to it."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# DEMO_FTP_SUFFIX is required — it identifies the remote subdirectory for this repo.
|
||||
@@ -645,7 +640,7 @@ jobs:
|
||||
rm -f /tmp/deploy_key /tmp/sftp-config.json
|
||||
|
||||
- name: Create or update failure issue
|
||||
if: failure() && steps.remote.outputs.skip != 'true' && steps.conn.outputs.skip != 'true'
|
||||
if: failure() && steps.remote.outputs.skip != 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
||||
run: |
|
||||
|
||||
2
.github/workflows/deploy-dev.yml
vendored
2
.github/workflows/deploy-dev.yml
vendored
@@ -22,7 +22,7 @@
|
||||
# INGROUP: MokoStandards.Deploy
|
||||
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
||||
# PATH: /templates/workflows/shared/deploy-dev.yml.template
|
||||
# VERSION: 04.05.13
|
||||
# VERSION: 04.05.00
|
||||
# BRIEF: SFTP deployment workflow for development server — synced to all governed repos
|
||||
# NOTE: Synced via bulk-repo-sync to .github/workflows/deploy-dev.yml in all governed repos.
|
||||
# Port is resolved in order: DEV_FTP_PORT variable → :port suffix in DEV_FTP_HOST → 22.
|
||||
|
||||
19
.github/workflows/deploy-rs.yml
vendored
19
.github/workflows/deploy-rs.yml
vendored
@@ -22,7 +22,7 @@
|
||||
# INGROUP: MokoStandards.Deploy
|
||||
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
||||
# PATH: /templates/workflows/shared/deploy-rs.yml.template
|
||||
# VERSION: 04.05.13
|
||||
# VERSION: 04.05.00
|
||||
# BRIEF: SFTP deployment workflow for release staging server — synced to all governed repos
|
||||
# NOTE: Synced via bulk-repo-sync to .github/workflows/deploy-rs.yml in all governed repos.
|
||||
# Port is resolved in order: RS_FTP_PORT variable → :port suffix in RS_FTP_HOST → 22.
|
||||
@@ -296,12 +296,6 @@ jobs:
|
||||
HOST="$HOST_RAW"
|
||||
PORT="$PORT_VAR"
|
||||
|
||||
if [ -z "$HOST" ]; then
|
||||
echo "⏭️ RS_FTP_HOST not configured — skipping RS deployment."
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Priority 1 — explicit RS_FTP_PORT variable
|
||||
if [ -n "$PORT" ]; then
|
||||
echo "ℹ️ Using explicit RS_FTP_PORT=${PORT}"
|
||||
@@ -323,7 +317,7 @@ jobs:
|
||||
echo "SFTP target: ${HOST}:${PORT}"
|
||||
|
||||
- name: Build remote path
|
||||
if: steps.source.outputs.skip == 'false' && steps.conn.outputs.skip != 'true'
|
||||
if: steps.source.outputs.skip == 'false'
|
||||
id: remote
|
||||
env:
|
||||
RS_FTP_PATH: ${{ vars.RS_FTP_PATH }}
|
||||
@@ -332,9 +326,10 @@ jobs:
|
||||
BASE="$RS_FTP_PATH"
|
||||
|
||||
if [ -z "$BASE" ]; then
|
||||
echo "⏭️ RS_FTP_PATH not configured — skipping RS deployment."
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
echo "❌ RS_FTP_PATH is not set."
|
||||
echo " Configure it as an org-level variable (Settings → Variables) and"
|
||||
echo " ensure this repository has been granted access to it."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# RS_FTP_SUFFIX is required — it identifies the remote subdirectory for this repo.
|
||||
@@ -572,7 +567,7 @@ jobs:
|
||||
rm -f /tmp/deploy_key /tmp/sftp-config.json
|
||||
|
||||
- name: Create or update failure issue
|
||||
if: failure() && steps.remote.outputs.skip != 'true' && steps.conn.outputs.skip != 'true'
|
||||
if: failure() && steps.remote.outputs.skip != 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
|
||||
run: |
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
# INGROUP: MokoStandards.Firewall
|
||||
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
||||
# PATH: /templates/workflows/shared/enterprise-firewall-setup.yml.template
|
||||
# VERSION: 04.05.13
|
||||
# VERSION: 04.05.00
|
||||
# BRIEF: Enterprise firewall configuration — generates outbound allow-rules including SFTP deployment server
|
||||
# NOTE: Reads DEV_FTP_HOST / DEV_FTP_PORT variables to include SFTP egress rules alongside HTTPS rules.
|
||||
|
||||
|
||||
2
.github/workflows/repository-cleanup.yml
vendored
2
.github/workflows/repository-cleanup.yml
vendored
@@ -9,7 +9,7 @@
|
||||
# INGROUP: MokoStandards.Maintenance
|
||||
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
||||
# PATH: /templates/workflows/shared/repository-cleanup.yml.template
|
||||
# VERSION: 04.05.13
|
||||
# VERSION: 04.05.00
|
||||
# BRIEF: Recurring repository maintenance — labels, branches, workflows, logs, doc indexes
|
||||
# NOTE: Synced via bulk-repo-sync to .github/workflows/repository-cleanup.yml in all governed repos.
|
||||
# Runs on the 1st and 15th of each month at 6:00 AM UTC, and on manual dispatch.
|
||||
|
||||
2
.github/workflows/sync-version-on-merge.yml
vendored
2
.github/workflows/sync-version-on-merge.yml
vendored
@@ -9,7 +9,7 @@
|
||||
# INGROUP: MokoStandards.Automation
|
||||
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
|
||||
# PATH: /templates/workflows/shared/sync-version-on-merge.yml.template
|
||||
# VERSION: 04.05.13
|
||||
# VERSION: 04.05.00
|
||||
# BRIEF: Auto-bump patch version on every push to main and propagate to all file headers
|
||||
# NOTE: Synced via bulk-repo-sync to .github/workflows/sync-version-on-merge.yml in all governed repos.
|
||||
# README.md is the single source of truth for the repository version.
|
||||
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -198,9 +198,5 @@ venv/
|
||||
*.coverage
|
||||
hypothesis/
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Cassiopeia custom theme overrides
|
||||
# ============================================================
|
||||
/src/media/css/theme/dark.custom.css
|
||||
/src/media/css/theme/light.custom.css
|
||||
src/media/css/theme/dark.custom.css
|
||||
src/media/css/theme/light.custom.css
|
||||
|
||||
58
CHANGELOG.md
58
CHANGELOG.md
@@ -12,13 +12,48 @@
|
||||
BRIEF: Changelog file documenting version history of MokoCassiopeia
|
||||
-->
|
||||
|
||||
# Changelog — MokoCassiopeia (VERSION: 03.08.03)
|
||||
# Changelog — MokoCassiopeia (VERSION: 03.09.02)
|
||||
|
||||
All notable changes to the MokoCassiopeia Joomla template are documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Unreleased] - 2026-04-02
|
||||
|
||||
### Added
|
||||
|
||||
- **Favicon configuration** — New "Favicon" tab in template config; upload a PNG and all favicon sizes are auto-generated via PHP GD (ICO, Apple Touch Icon 180px, Android Chrome 192/512px, site.webmanifest)
|
||||
- **Module overrides** — 11 new `default.php` layout overrides for Joomla core modules: `mod_custom`, `mod_articles_latest`, `mod_articles_popular`, `mod_articles_news`, `mod_articles_category`, `mod_breadcrumbs`, `mod_footer`, `mod_login`, `mod_finder`, `mod_tags_popular`, `mod_tags_similar`, `mod_related_items`
|
||||
- **Module title support** — All module overrides respect `$module->showtitle`, `header_tag`, `header_class`, and `moduleclass_sfx` parameters
|
||||
- **Module CSS** — BEM-scoped styles for module titles, article lists, tag badges, search forms, login forms, breadcrumbs, and footer content
|
||||
- **Hero card variables** — Full variable-driven hero system: `--hero-card-bg`, `--hero-card-color`, `--hero-card-overlay`, `--hero-card-border-radius`, `--hero-card-padding-x/y`, `--hero-card-max-width`, plus `--hero-alt-card-*` for secondary variant
|
||||
- **Hero mobile breakpoint** — Photo background hidden on mobile (≤767.98px), hero card becomes full-bleed (100dvh, no border-radius)
|
||||
- **CSS fallback values** — 1365 `var()` calls in template.css now include inline fallback values
|
||||
- **Card border-radius** — `.card` now has `.25rem` fallback on `var(--card-border-radius)`
|
||||
- **Usage section in README** — Added missing "Usage" section required by MokoStandards
|
||||
|
||||
### Changed
|
||||
|
||||
- **Button backgrounds** — `--btn-bg: transparent` changed to `var(--body-bg)` in dark and light themes
|
||||
- **Offcanvas close button** — `.offcanvas-header .btn-close` now gets `background-color` from `--offcanvas-bg`
|
||||
- **Custom template sync** — Both `dark.custom.css` and `light.custom.css` now contain all variables from their standard counterparts (was missing 223 variables)
|
||||
- **Overlay layer** — Added `--hero-overlay-bg-position` and `--hero-overlay-bg-size` variables
|
||||
- **Legacy CSS cleanup** — Removed vendor prefixes (`-webkit-box`, `-ms-flexbox`) from `.overlay` rules, replaced with modern flexbox
|
||||
|
||||
### Removed
|
||||
|
||||
- **FILE INFORMATION headers** — Stripped DEFGROUP/INGROUP/PATH/VERSION/BRIEF metadata from all PHP, CSS, JS, INI, and HTML files (kept in XML and README per policy)
|
||||
- **Mobile overrides** — Deleted 26 `mobile.php` layout files and their empty parent directories
|
||||
- **Joomla-specific gitignore entries** — Removed ~700 lines of Joomla CMS core paths from `.gitignore` (not applicable to a template repository)
|
||||
|
||||
### Fixed
|
||||
|
||||
- **CI: composer install** — Workflow `standards-compliance.yml` now conditionally runs `composer install` only when `composer.json` exists
|
||||
- **CI: YAML syntax** — Fixed invalid YAML in `auto-update-sha.yml` caused by multiline commit message in run block
|
||||
|
||||
---
|
||||
|
||||
## [03.09.02] - 2026-03-26
|
||||
|
||||
### Added - Hero Variant System & Block Color System
|
||||
@@ -37,13 +72,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
#### Files Modified
|
||||
- `src/media/css/template.css` — hero variant rules, block color `:nth-child()` rules, named override rules
|
||||
- `src/templates/light.custom.css` — hero and block color variables (light mode)
|
||||
- `src/templates/dark.custom.css` — hero and block color variables (dark mode)
|
||||
- `docs/CSS_VARIABLES.md` — full variable reference for both systems
|
||||
- `src/media/css/theme/light.standard.css` — hero and block color variables (light standard)
|
||||
- `src/media/css/theme/dark.standard.css` — hero and block color variables (dark standard)
|
||||
- `src/templates/light.custom.css` — hero and block color variables (light custom starter)
|
||||
- `src/templates/dark.custom.css` — hero and block color variables (dark custom starter)
|
||||
- `src/templateDetails.xml` — Theme Preview tab, hero/block note fields, scriptfile registration, version bump to 03.09.02
|
||||
- `src/language/en-GB/tpl_mokocassiopeia.ini` — language strings for new admin fields (British English)
|
||||
- `src/language/en-US/tpl_mokocassiopeia.ini` — language strings for new admin fields (American English)
|
||||
- `docs/CSS_VARIABLES.md` — full variable reference for both systems, sync script documentation
|
||||
- `CHANGELOG.md` — this entry
|
||||
|
||||
#### Files Added
|
||||
- `src/templates/theme-test.html` — Bootstrap-style test page showing all CSS variables and new features
|
||||
- `src/templates/theme-test.html` — Bootstrap-style test page with branded showcase, CSS variable swatches, hero demos, block color demos, and color test image
|
||||
- `src/script.php` — Joomla install/update lifecycle script (runs CSS variable sync on upgrade, checks PHP/Joomla minimum versions)
|
||||
- `src/sync_custom_vars.php` — CLI/library utility that detects missing CSS variables in user custom palettes and injects them
|
||||
- `src/templates/brand-showcase.html` — Interactive color system gradients with hover pixel sampler, Bootstrap component showcase
|
||||
|
||||
#### Variable Audit
|
||||
- All 20 hero/block variables confirmed present in all 4 theme files (light/dark standard + custom)
|
||||
- No duplicate variable declarations found across any theme file
|
||||
- `--gutter-x` references in template.css are self-scoped to grid containers (standard Bootstrap 5 behavior, not a `:root` variable)
|
||||
|
||||
---
|
||||
|
||||
|
||||
14
README.md
14
README.md
@@ -146,6 +146,20 @@ The template includes a dark mode toggle. Test it by:
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Once installed and set as the default site template, MokoCassiopeia works out of the box with Joomla's standard content and module system. Key usage points:
|
||||
|
||||
- **Template Options** — Configure via **System → Site Templates → MokoCassiopeia** (theme colours, layout, analytics, favicon, drawers)
|
||||
- **Custom Colour Schemes** — Copy `templates/mokocassiopeia/templates/light.custom.css` or `dark.custom.css` to `media/templates/site/mokocassiopeia/css/theme/` and select "Custom" in the Theme tab
|
||||
- **Custom CSS/JS** — Create `media/templates/site/mokocassiopeia/css/user.css` or `js/user.js` for site-specific overrides that survive template updates
|
||||
- **Module Overrides** — The template includes overrides for common Joomla modules with consistent title rendering, Bootstrap 5 styling, and Font Awesome 7 icons
|
||||
- **Dark Mode** — Enabled by default with a floating toggle button; respects system preference and persists via localStorage
|
||||
|
||||
See [Configuration](#️-configuration) below for detailed parameter reference.
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Configuration
|
||||
|
||||
### Global Parameters
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
INGROUP: MokoCassiopeia.Documentation
|
||||
REPO: https://github.com/mokoconsulting-tech/MokoCassiopeia
|
||||
FILE: docs/CSS_VARIABLES.md
|
||||
VERSION: 03.06.03
|
||||
VERSION: 03.09.02
|
||||
BRIEF: Complete CSS variable reference for MokoCassiopeia template
|
||||
-->
|
||||
|
||||
@@ -60,6 +60,12 @@ To create custom color schemes:
|
||||
|
||||
4. **Note**: Custom files are gitignored and won't be committed to the repository
|
||||
|
||||
5. **On upgrade**: When the template is updated, `script.php` automatically runs `sync_custom_vars.php` to detect any new variables added to the starter templates and inject them into your existing custom palette files. Your existing values are never overwritten — only genuinely new variables are added. You can also run this manually:
|
||||
```bash
|
||||
php templates/mokocassiopeia/sync_custom_vars.php --dry-run # preview what would be added
|
||||
php templates/mokocassiopeia/sync_custom_vars.php # apply missing variables
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Primary Brand Colors
|
||||
@@ -1440,9 +1446,9 @@ These ensure optimal readability for links within alert boxes.
|
||||
* Repository: [https://github.com/mokoconsulting-tech/MokoCassiopeia](https://github.com/mokoconsulting-tech/MokoCassiopeia)
|
||||
* Path: /docs/CSS_VARIABLES.md
|
||||
* Owner: Moko Consulting
|
||||
* Version: 03.06.03
|
||||
* Version: 03.09.02
|
||||
* Status: Active
|
||||
* Effective Date: 2026-01-30
|
||||
* Effective Date: 2026-03-26
|
||||
* Classification: Public Open Source Documentation
|
||||
|
||||
## Revision History
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
<?php
|
||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
/* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
||||
|
||||
# FILE INFORMATION
|
||||
DEFGROUP: Joomla.Template.Site
|
||||
INGROUP: MokoCassiopeia
|
||||
REPO: https://github.com/mokoconsulting-tech/MokoCassiopeia
|
||||
PATH: ./templates/mokocassiopeia/component.php
|
||||
VERSION: 03.06.02
|
||||
BRIEF: Main template index file for MokoCassiopeia rendering site layout
|
||||
*/
|
||||
|
||||
|
||||
@@ -54,15 +44,6 @@ $sitename = htmlspecialchars($sitenameR, ENT_QUOTES, 'UTF-8');
|
||||
$menu = $app->getMenu()->getActive();
|
||||
$pageclass = $menu !== null ? $menu->getParams()->get('pageclass_sfx', '') : '';
|
||||
|
||||
// Respect “Site Name in Page Titles” (0:none, 1:before, 2:after)
|
||||
$mode = (int) $app->get('sitename_pagetitles', 0);
|
||||
$pageTitle = trim($this->getTitle());
|
||||
$final = $pageTitle !== ''
|
||||
? ($mode === 1 ? $sitenameR . ' - ' . $pageTitle
|
||||
: ($mode === 2 ? $pageTitle . ' - ' . $sitenameR : $pageTitle))
|
||||
: $sitenameR;
|
||||
$this->setTitle($final);
|
||||
|
||||
// Template/Media path
|
||||
$templatePath = 'media/templates/site/mokocassiopeia';
|
||||
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
<?php
|
||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
/* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
||||
|
||||
# FILE INFORMATION
|
||||
DEFGROUP: Joomla.Template.Site
|
||||
INGROUP: MokoCassiopeia
|
||||
REPO: https://github.com/mokoconsulting-tech/MokoCassiopeia
|
||||
PATH: ./templates/mokocassiopeia/custom.php
|
||||
VERSION: 03.06.02
|
||||
BRIEF: MokoCassiopeia with user-defined overrides
|
||||
*/
|
||||
|
||||
function console_log($output, $with_script_tags = true) {
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
<?php
|
||||
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
/* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
||||
|
||||
# FILE INFORMATION
|
||||
DEFGROUP: Joomla.Template.Site
|
||||
INGROUP: MokoCassiopeia
|
||||
REPO: https://github.com/mokoconsulting-tech/MokoCassiopeia
|
||||
PATH: ./templates/mokocassiopeia/error.php
|
||||
VERSION: 03.06.02
|
||||
BRIEF: Error page template file for MokoCassiopeia
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
@@ -428,7 +418,7 @@ $wa->useScript('user.js'); // js/user.js
|
||||
|
||||
<?php if ($this->params->get('backTop') == 1) : ?>
|
||||
<a href="#top" id="back-top" class="back-to-top-link" aria-label="<?php echo Text::_('TPL_MOKOCASSIOPEIA_BACKTOTOP'); ?>">
|
||||
<span class="icon-arrow-up icon-fw" aria-hidden="true"></span>
|
||||
<span class="fa-solid fa-arrow-up" aria-hidden="true"></span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
|
||||
173
src/helper/favicon.php
Normal file
173
src/helper/favicon.php
Normal file
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Favicon generator — creates ICO, Apple Touch Icon, and Android icons
|
||||
* from a single source PNG uploaded via the template config.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
class MokoFaviconHelper
|
||||
{
|
||||
/**
|
||||
* Sizes to generate: filename => [width, height, format].
|
||||
* ICO embeds 16×16 and 32×32 internally.
|
||||
*/
|
||||
private const SIZES = [
|
||||
'apple-touch-icon.png' => [180, 180, 'png'],
|
||||
'favicon-32x32.png' => [32, 32, 'png'],
|
||||
'favicon-16x16.png' => [16, 16, 'png'],
|
||||
'android-chrome-192x192.png' => [192, 192, 'png'],
|
||||
'android-chrome-512x512.png' => [512, 512, 'png'],
|
||||
];
|
||||
|
||||
/**
|
||||
* Generate all favicon files from a source PNG if they don't already exist
|
||||
* or if the source has been modified since last generation.
|
||||
*
|
||||
* @param string $sourcePath Absolute path to the source PNG.
|
||||
* @param string $outputDir Absolute path to the output directory.
|
||||
*
|
||||
* @return bool True if generation succeeded or files are up to date.
|
||||
*/
|
||||
public static function generate(string $sourcePath, string $outputDir): bool
|
||||
{
|
||||
if (!is_file($sourcePath) || !extension_loaded('gd')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!is_dir($outputDir)) {
|
||||
mkdir($outputDir, 0755, true);
|
||||
}
|
||||
|
||||
$sourceTime = filemtime($sourcePath);
|
||||
$stampFile = $outputDir . '/.favicon_generated';
|
||||
|
||||
// Skip if already up to date
|
||||
if (is_file($stampFile) && filemtime($stampFile) >= $sourceTime) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$source = imagecreatefrompng($sourcePath);
|
||||
if (!$source) {
|
||||
return false;
|
||||
}
|
||||
|
||||
imagealphablending($source, false);
|
||||
imagesavealpha($source, true);
|
||||
|
||||
$srcW = imagesx($source);
|
||||
$srcH = imagesy($source);
|
||||
|
||||
// Generate PNG sizes
|
||||
foreach (self::SIZES as $filename => [$w, $h]) {
|
||||
$resized = imagecreatetruecolor($w, $h);
|
||||
imagealphablending($resized, false);
|
||||
imagesavealpha($resized, true);
|
||||
$transparent = imagecolorallocatealpha($resized, 0, 0, 0, 127);
|
||||
imagefill($resized, 0, 0, $transparent);
|
||||
|
||||
imagecopyresampled($resized, $source, 0, 0, 0, 0, $w, $h, $srcW, $srcH);
|
||||
imagepng($resized, $outputDir . '/' . $filename, 9);
|
||||
imagedestroy($resized);
|
||||
}
|
||||
|
||||
// Generate ICO (contains 16×16 and 32×32)
|
||||
self::generateIco($source, $srcW, $srcH, $outputDir . '/favicon.ico');
|
||||
|
||||
// Generate site.webmanifest
|
||||
self::generateManifest($outputDir);
|
||||
|
||||
imagedestroy($source);
|
||||
|
||||
// Write timestamp stamp
|
||||
file_put_contents($stampFile, date('c'));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a minimal ICO file containing 16×16 and 32×32 PNG entries.
|
||||
*/
|
||||
private static function generateIco(\GdImage $source, int $srcW, int $srcH, string $outPath): void
|
||||
{
|
||||
$entries = [];
|
||||
foreach ([16, 32] as $size) {
|
||||
$resized = imagecreatetruecolor($size, $size);
|
||||
imagealphablending($resized, false);
|
||||
imagesavealpha($resized, true);
|
||||
$transparent = imagecolorallocatealpha($resized, 0, 0, 0, 127);
|
||||
imagefill($resized, 0, 0, $transparent);
|
||||
imagecopyresampled($resized, $source, 0, 0, 0, 0, $size, $size, $srcW, $srcH);
|
||||
|
||||
ob_start();
|
||||
imagepng($resized, null, 9);
|
||||
$pngData = ob_get_clean();
|
||||
imagedestroy($resized);
|
||||
|
||||
$entries[] = ['size' => $size, 'data' => $pngData];
|
||||
}
|
||||
|
||||
// ICO header: 2 bytes reserved, 2 bytes type (1=ICO), 2 bytes count
|
||||
$count = count($entries);
|
||||
$ico = pack('vvv', 0, 1, $count);
|
||||
|
||||
// Calculate offset: header (6) + directory entries (16 each)
|
||||
$offset = 6 + ($count * 16);
|
||||
$imageData = '';
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
$size = $entry['size'] >= 256 ? 0 : $entry['size'];
|
||||
$dataLen = strlen($entry['data']);
|
||||
|
||||
// ICONDIRENTRY: width, height, colors, reserved, planes, bpp, size, offset
|
||||
$ico .= pack('CCCCvvVV', $size, $size, 0, 0, 1, 32, $dataLen, $offset);
|
||||
$imageData .= $entry['data'];
|
||||
$offset += $dataLen;
|
||||
}
|
||||
|
||||
file_put_contents($outPath, $ico . $imageData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a site.webmanifest for Android/PWA icon discovery.
|
||||
*/
|
||||
private static function generateManifest(string $outputDir): void
|
||||
{
|
||||
$manifest = [
|
||||
'icons' => [
|
||||
['src' => 'android-chrome-192x192.png', 'sizes' => '192x192', 'type' => 'image/png'],
|
||||
['src' => 'android-chrome-512x512.png', 'sizes' => '512x512', 'type' => 'image/png'],
|
||||
],
|
||||
];
|
||||
file_put_contents(
|
||||
$outputDir . '/site.webmanifest',
|
||||
json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the <link> tags to inject into <head>.
|
||||
*
|
||||
* @param string $basePath URL path to the favicon directory (relative to site root).
|
||||
*
|
||||
* @return string HTML link tags.
|
||||
*/
|
||||
public static function getHeadTags(string $basePath): string
|
||||
{
|
||||
$basePath = rtrim($basePath, '/');
|
||||
|
||||
return '<link rel="apple-touch-icon" sizes="180x180" href="' . $basePath . '/apple-touch-icon.png">' . "\n"
|
||||
. '<link rel="icon" type="image/png" sizes="32x32" href="' . $basePath . '/favicon-32x32.png">' . "\n"
|
||||
. '<link rel="icon" type="image/png" sizes="16x16" href="' . $basePath . '/favicon-16x16.png">' . "\n"
|
||||
. '<link rel="manifest" href="' . $basePath . '/site.webmanifest">' . "\n"
|
||||
. '<link rel="shortcut icon" href="' . $basePath . '/favicon.ico">' . "\n";
|
||||
}
|
||||
}
|
||||
162
src/helper/minify.php
Normal file
162
src/helper/minify.php
Normal file
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* CSS/JS minifier — generates .min files from source when dev mode is off,
|
||||
* deletes them when dev mode is on.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
class MokoMinifyHelper
|
||||
{
|
||||
/**
|
||||
* Files to minify: source path relative to template media root.
|
||||
* The .min variant is derived automatically (template.css → template.min.css).
|
||||
*/
|
||||
private const CSS_FILES = [
|
||||
'css/template.css',
|
||||
'css/theme/light.standard.css',
|
||||
'css/theme/dark.standard.css',
|
||||
'css/theme/light.custom.css',
|
||||
'css/theme/dark.custom.css',
|
||||
];
|
||||
|
||||
private const JS_FILES = [
|
||||
'js/template.js',
|
||||
];
|
||||
|
||||
/**
|
||||
* When dev mode is ON: delete all .min files.
|
||||
* When dev mode is OFF: regenerate .min files if source is newer.
|
||||
*
|
||||
* @param string $mediaRoot Absolute path to the template media directory.
|
||||
* @param bool $devMode Whether development mode is enabled.
|
||||
*/
|
||||
public static function sync(string $mediaRoot, bool $devMode): void
|
||||
{
|
||||
$mediaRoot = rtrim($mediaRoot, '/\\');
|
||||
|
||||
foreach (self::CSS_FILES as $relPath) {
|
||||
$source = $mediaRoot . '/' . $relPath;
|
||||
$min = self::minPath($source);
|
||||
|
||||
if ($devMode) {
|
||||
self::deleteIfExists($min);
|
||||
} else {
|
||||
self::buildIfStale($source, $min, 'css');
|
||||
}
|
||||
}
|
||||
|
||||
foreach (self::JS_FILES as $relPath) {
|
||||
$source = $mediaRoot . '/' . $relPath;
|
||||
$min = self::minPath($source);
|
||||
|
||||
if ($devMode) {
|
||||
self::deleteIfExists($min);
|
||||
} else {
|
||||
self::buildIfStale($source, $min, 'js');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the .min path from a source path.
|
||||
* template.css → template.min.css
|
||||
*/
|
||||
private static function minPath(string $path): string
|
||||
{
|
||||
$info = pathinfo($path);
|
||||
return $info['dirname'] . '/' . $info['filename'] . '.min.' . $info['extension'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a file if it exists.
|
||||
*/
|
||||
private static function deleteIfExists(string $path): void
|
||||
{
|
||||
if (is_file($path)) {
|
||||
@unlink($path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the minified file if the source is newer or the min file is missing.
|
||||
*/
|
||||
private static function buildIfStale(string $source, string $min, string $type): void
|
||||
{
|
||||
if (!is_file($source)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip if min file exists and is newer than source
|
||||
if (is_file($min) && filemtime($min) >= filemtime($source)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$content = file_get_contents($source);
|
||||
if ($content === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$minified = ($type === 'css')
|
||||
? self::minifyCss($content)
|
||||
: self::minifyJs($content);
|
||||
|
||||
file_put_contents($min, $minified);
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify CSS by stripping comments, excess whitespace, and unnecessary characters.
|
||||
*/
|
||||
private static function minifyCss(string $css): string
|
||||
{
|
||||
// Remove comments (but keep IE hacks like /*\*/)
|
||||
$css = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $css);
|
||||
|
||||
// Remove whitespace around { } : ; , > + ~
|
||||
$css = preg_replace('/\s*([{}:;,>+~])\s*/', '$1', $css);
|
||||
|
||||
// Remove remaining newlines and tabs
|
||||
$css = preg_replace('/\s+/', ' ', $css);
|
||||
|
||||
// Remove spaces around selectors
|
||||
$css = str_replace(['{ ', ' {', '; ', ' ;'], ['{', '{', ';', ';'], $css);
|
||||
|
||||
// Remove trailing semicolons before closing braces
|
||||
$css = str_replace(';}', '}', $css);
|
||||
|
||||
// Remove leading/trailing whitespace
|
||||
return trim($css);
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify JS by stripping single-line comments, multi-line comments,
|
||||
* and collapsing whitespace. Preserves string literals.
|
||||
*/
|
||||
private static function minifyJs(string $js): string
|
||||
{
|
||||
// Remove multi-line comments
|
||||
$js = preg_replace('!/\*.*?\*/!s', '', $js);
|
||||
|
||||
// Remove single-line comments (but not URLs like http://)
|
||||
$js = preg_replace('!(?<=^|[\s;{}()\[\]])//[^\n]*!m', '', $js);
|
||||
|
||||
// Collapse whitespace
|
||||
$js = preg_replace('/\s+/', ' ', $js);
|
||||
|
||||
// Remove spaces around operators and punctuation
|
||||
$js = preg_replace('/\s*([{}();,=+\-*\/<>!&|?:])\s*/', '$1', $js);
|
||||
|
||||
// Restore necessary spaces (after keywords)
|
||||
$js = preg_replace('/(var|let|const|return|typeof|instanceof|new|delete|throw|case|in|of)([^\s;})><=!&|?:,])/', '$1 $2', $js);
|
||||
|
||||
return trim($js);
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><title></title>
|
||||
@@ -1,112 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Community Builder
|
||||
* @subpackage com_comprofiler
|
||||
*
|
||||
* @copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*
|
||||
* Mobile responsive override for Community Builder login view
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
$return = $this->return ?? '';
|
||||
$showRegisterLink = $this->showRegisterLink ?? true;
|
||||
$showLostPasswordLink = $this->showLostPasswordLink ?? true;
|
||||
?>
|
||||
|
||||
<div class="cb-login-responsive cb-component">
|
||||
<div class="cb-login__container">
|
||||
<div class="cb-login__header">
|
||||
<h1 class="cb-login__title">
|
||||
<?php echo Text::_('COM_COMPROFILER_LOGIN'); ?>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<form action="<?php echo Route::_('index.php?option=com_comprofiler&view=login'); ?>"
|
||||
method="post"
|
||||
class="cb-login__form"
|
||||
aria-label="<?php echo Text::_('COM_COMPROFILER_LOGIN_FORM'); ?>">
|
||||
|
||||
<div class="cb-login__field">
|
||||
<label for="cb-username" class="cb-login__label">
|
||||
<?php echo Text::_('COM_COMPROFILER_USERNAME'); ?>
|
||||
<span class="cb-login__required" aria-label="<?php echo Text::_('COM_COMPROFILER_REQUIRED'); ?>">*</span>
|
||||
</label>
|
||||
<input type="text"
|
||||
id="cb-username"
|
||||
name="username"
|
||||
class="cb-login__input"
|
||||
required
|
||||
aria-required="true"
|
||||
autocomplete="username"
|
||||
placeholder="<?php echo Text::_('COM_COMPROFILER_USERNAME'); ?>">
|
||||
</div>
|
||||
|
||||
<div class="cb-login__field">
|
||||
<label for="cb-password" class="cb-login__label">
|
||||
<?php echo Text::_('COM_COMPROFILER_PASSWORD'); ?>
|
||||
<span class="cb-login__required" aria-label="<?php echo Text::_('COM_COMPROFILER_REQUIRED'); ?>">*</span>
|
||||
</label>
|
||||
<input type="password"
|
||||
id="cb-password"
|
||||
name="passwd"
|
||||
class="cb-login__input"
|
||||
required
|
||||
aria-required="true"
|
||||
autocomplete="current-password"
|
||||
placeholder="<?php echo Text::_('COM_COMPROFILER_PASSWORD'); ?>">
|
||||
</div>
|
||||
|
||||
<?php if ($this->showRememberMe ?? true) : ?>
|
||||
<div class="cb-login__remember">
|
||||
<div class="form-check">
|
||||
<input type="checkbox"
|
||||
id="cb-remember"
|
||||
name="remember"
|
||||
class="form-check-input cb-login__remember-checkbox"
|
||||
value="yes">
|
||||
<label for="cb-remember" class="form-check-label cb-login__remember-label">
|
||||
<?php echo Text::_('COM_COMPROFILER_REMEMBER_ME'); ?>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="cb-login__actions">
|
||||
<button type="submit" class="cb-login__btn cb-login__btn--submit btn btn-primary">
|
||||
<span class="icon-lock" aria-hidden="true"></span>
|
||||
<?php echo Text::_('COM_COMPROFILER_LOGIN'); ?>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="task" value="login">
|
||||
<input type="hidden" name="return" value="<?php echo htmlspecialchars($return, ENT_QUOTES, 'UTF-8'); ?>">
|
||||
<?php echo $this->token ?? ''; ?>
|
||||
</form>
|
||||
|
||||
<div class="cb-login__links">
|
||||
<?php if ($showRegisterLink) : ?>
|
||||
<div class="cb-login__link">
|
||||
<a href="<?php echo Route::_('index.php?option=com_comprofiler&view=registers'); ?>" class="cb-login__link-item">
|
||||
<span class="icon-user-plus" aria-hidden="true"></span>
|
||||
<?php echo Text::_('COM_COMPROFILER_REGISTER_NEW_ACCOUNT'); ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($showLostPasswordLink) : ?>
|
||||
<div class="cb-login__link">
|
||||
<a href="<?php echo Route::_('index.php?option=com_comprofiler&view=lostpassword'); ?>" class="cb-login__link-item">
|
||||
<span class="icon-question" aria-hidden="true"></span>
|
||||
<?php echo Text::_('COM_COMPROFILER_FORGOT_PASSWORD'); ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><title></title>
|
||||
@@ -1,136 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Community Builder
|
||||
* @subpackage com_comprofiler
|
||||
*
|
||||
* @copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*
|
||||
* Mobile responsive override for Community Builder registration view
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
// Get form and fields
|
||||
$form = $this->form ?? null;
|
||||
$tabs = $this->tabs ?? null;
|
||||
?>
|
||||
|
||||
<div class="cb-register-responsive cb-component">
|
||||
<div class="cb-register__header">
|
||||
<h1 class="cb-register__title">
|
||||
<?php echo Text::_('COM_COMPROFILER_REGISTER'); ?>
|
||||
</h1>
|
||||
|
||||
<?php if ($this->introduction ?? null) : ?>
|
||||
<div class="cb-register__intro">
|
||||
<?php echo $this->introduction; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($form) : ?>
|
||||
<form action="<?php echo $this->action ?? ''; ?>"
|
||||
method="post"
|
||||
class="cb-register__form"
|
||||
enctype="multipart/form-data"
|
||||
aria-label="<?php echo Text::_('COM_COMPROFILER_REGISTRATION_FORM'); ?>">
|
||||
|
||||
<?php if ($tabs) : ?>
|
||||
<?php foreach ($tabs as $tab) : ?>
|
||||
<?php if (isset($tab->fields) && !empty($tab->fields)) : ?>
|
||||
<fieldset class="cb-register__fieldset">
|
||||
<?php if ($tab->title) : ?>
|
||||
<legend class="cb-register__legend">
|
||||
<?php echo htmlspecialchars($tab->title, ENT_QUOTES, 'UTF-8'); ?>
|
||||
</legend>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($tab->description) : ?>
|
||||
<div class="cb-register__tab-description">
|
||||
<?php echo $tab->description; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="cb-register__fields">
|
||||
<?php foreach ($tab->fields as $field) : ?>
|
||||
<div class="cb-register__field<?php echo $field->required ? ' cb-register__field--required' : ''; ?>">
|
||||
<label for="<?php echo htmlspecialchars($field->name, ENT_QUOTES, 'UTF-8'); ?>"
|
||||
class="cb-register__label">
|
||||
<?php echo htmlspecialchars($field->title, ENT_QUOTES, 'UTF-8'); ?>
|
||||
<?php if ($field->required) : ?>
|
||||
<span class="cb-register__required" aria-label="<?php echo Text::_('COM_COMPROFILER_REQUIRED'); ?>">*</span>
|
||||
<?php endif; ?>
|
||||
</label>
|
||||
|
||||
<?php if ($field->description) : ?>
|
||||
<div class="cb-register__field-description" id="<?php echo htmlspecialchars($field->name, ENT_QUOTES, 'UTF-8'); ?>-desc">
|
||||
<?php echo $field->description; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="cb-register__input-wrapper">
|
||||
<?php echo $field->input; ?>
|
||||
</div>
|
||||
|
||||
<?php if (isset($field->error) && $field->error) : ?>
|
||||
<div class="cb-register__error" role="alert">
|
||||
<?php echo $field->error; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</fieldset>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($this->showCaptcha ?? false) : ?>
|
||||
<div class="cb-register__captcha">
|
||||
<?php echo $this->captcha; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($this->showTerms ?? false) : ?>
|
||||
<div class="cb-register__terms">
|
||||
<div class="form-check">
|
||||
<input type="checkbox"
|
||||
id="cb-terms"
|
||||
name="agreedToTerms"
|
||||
class="form-check-input cb-register__terms-checkbox"
|
||||
required
|
||||
aria-required="true"
|
||||
aria-describedby="cb-terms-text">
|
||||
<label for="cb-terms" class="form-check-label cb-register__terms-label" id="cb-terms-text">
|
||||
<?php echo Text::_('COM_COMPROFILER_AGREE_TO_TERMS'); ?>
|
||||
<a href="<?php echo $this->termsUrl ?? ''; ?>" target="_blank" rel="noopener noreferrer">
|
||||
<?php echo Text::_('COM_COMPROFILER_TERMS_CONDITIONS'); ?>
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="cb-register__actions">
|
||||
<button type="submit" class="cb-register__btn cb-register__btn--submit btn btn-primary">
|
||||
<span class="icon-check" aria-hidden="true"></span>
|
||||
<?php echo Text::_('COM_COMPROFILER_REGISTER_SUBMIT'); ?>
|
||||
</button>
|
||||
|
||||
<a href="<?php echo $this->loginUrl ?? ''; ?>" class="cb-register__btn cb-register__btn--cancel btn btn-secondary">
|
||||
<span class="icon-cancel" aria-hidden="true"></span>
|
||||
<?php echo Text::_('JCANCEL'); ?>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<?php echo $this->token ?? ''; ?>
|
||||
</form>
|
||||
<?php else : ?>
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<?php echo Text::_('COM_COMPROFILER_REGISTRATION_NOT_AVAILABLE'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><title></title>
|
||||
@@ -1,100 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Community Builder
|
||||
* @subpackage com_comprofiler
|
||||
*
|
||||
* @copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*
|
||||
* Mobile responsive override for Community Builder user profile view
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
// Get user object
|
||||
$user = $this->user ?? null;
|
||||
$tabs = $this->tabs ?? null;
|
||||
?>
|
||||
|
||||
<div class="cb-profile-responsive cb-component">
|
||||
<?php if ($user) : ?>
|
||||
<div class="cb-profile__header">
|
||||
<?php if ($user->getField('avatar', null, 'html', 'none', 'profile')) : ?>
|
||||
<div class="cb-profile__avatar">
|
||||
<?php echo $user->getField('avatar', null, 'html', 'none', 'profile'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="cb-profile__header-info">
|
||||
<h1 class="cb-profile__name">
|
||||
<?php echo htmlspecialchars($user->getField('formatname', null, 'html', 'none', 'profile'), ENT_QUOTES, 'UTF-8'); ?>
|
||||
</h1>
|
||||
|
||||
<?php if ($user->getField('onlinestatus', null, 'html', 'none', 'profile')) : ?>
|
||||
<div class="cb-profile__status">
|
||||
<?php echo $user->getField('onlinestatus', null, 'html', 'none', 'profile'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($tabs) : ?>
|
||||
<div class="cb-profile__tabs">
|
||||
<ul class="cb-profile__tabs-nav" role="tablist" aria-label="<?php echo Text::_('COM_COMPROFILER_PROFILE_TABS'); ?>">
|
||||
<?php foreach ($tabs as $tab) : ?>
|
||||
<?php if (isset($tab->fields) && !empty($tab->fields)) : ?>
|
||||
<li class="cb-profile__tab-item" role="presentation">
|
||||
<a href="#<?php echo htmlspecialchars($tab->id, ENT_QUOTES, 'UTF-8'); ?>"
|
||||
class="cb-profile__tab-link"
|
||||
role="tab"
|
||||
aria-controls="<?php echo htmlspecialchars($tab->id, ENT_QUOTES, 'UTF-8'); ?>"
|
||||
aria-selected="false">
|
||||
<?php echo htmlspecialchars($tab->title, ENT_QUOTES, 'UTF-8'); ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
|
||||
<div class="cb-profile__tabs-content">
|
||||
<?php foreach ($tabs as $tab) : ?>
|
||||
<?php if (isset($tab->fields) && !empty($tab->fields)) : ?>
|
||||
<div id="<?php echo htmlspecialchars($tab->id, ENT_QUOTES, 'UTF-8'); ?>"
|
||||
class="cb-profile__tab-pane"
|
||||
role="tabpanel"
|
||||
aria-labelledby="<?php echo htmlspecialchars($tab->id, ENT_QUOTES, 'UTF-8'); ?>-tab">
|
||||
|
||||
<?php if ($tab->description) : ?>
|
||||
<div class="cb-profile__tab-description">
|
||||
<?php echo $tab->description; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="cb-profile__fields">
|
||||
<?php foreach ($tab->fields as $field) : ?>
|
||||
<?php if ($field->value) : ?>
|
||||
<div class="cb-profile__field">
|
||||
<div class="cb-profile__field-label">
|
||||
<?php echo htmlspecialchars($field->title, ENT_QUOTES, 'UTF-8'); ?>
|
||||
</div>
|
||||
<div class="cb-profile__field-value">
|
||||
<?php echo $field->value; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php else : ?>
|
||||
<div class="alert alert-warning" role="alert">
|
||||
<?php echo Text::_('COM_COMPROFILER_USER_NOT_FOUND'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><title></title>
|
||||
@@ -1,123 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Community Builder
|
||||
* @subpackage com_comprofiler
|
||||
*
|
||||
* @copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*
|
||||
* Mobile responsive override for Community Builder users list view
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
// Get users list
|
||||
$users = $this->users ?? [];
|
||||
$pagination = $this->pagination ?? null;
|
||||
$search = $this->search ?? '';
|
||||
$listid = $this->listid ?? 0;
|
||||
?>
|
||||
|
||||
<div class="cb-userslist-responsive cb-component">
|
||||
<div class="cb-userslist__header">
|
||||
<h1 class="cb-userslist__title">
|
||||
<?php echo Text::_('COM_COMPROFILER_USERLIST'); ?>
|
||||
</h1>
|
||||
|
||||
<?php if ($this->showSearch ?? true) : ?>
|
||||
<div class="cb-userslist__search">
|
||||
<form action="<?php echo Route::_('index.php?option=com_comprofiler&view=userslist&listid=' . $listid); ?>"
|
||||
method="post"
|
||||
class="cb-userslist__search-form"
|
||||
role="search"
|
||||
aria-label="<?php echo Text::_('COM_COMPROFILER_SEARCH_USERS'); ?>">
|
||||
|
||||
<div class="cb-userslist__search-wrapper">
|
||||
<label for="cb-userslist-search" class="visually-hidden">
|
||||
<?php echo Text::_('COM_COMPROFILER_SEARCH'); ?>
|
||||
</label>
|
||||
<input type="search"
|
||||
id="cb-userslist-search"
|
||||
name="search"
|
||||
class="cb-userslist__search-input"
|
||||
placeholder="<?php echo Text::_('COM_COMPROFILER_SEARCH_USERS'); ?>"
|
||||
value="<?php echo htmlspecialchars($search, ENT_QUOTES, 'UTF-8'); ?>"
|
||||
aria-label="<?php echo Text::_('COM_COMPROFILER_SEARCH'); ?>">
|
||||
|
||||
<button type="submit"
|
||||
class="cb-userslist__search-btn btn btn-primary"
|
||||
aria-label="<?php echo Text::_('COM_COMPROFILER_SEARCH'); ?>">
|
||||
<span class="icon-search" aria-hidden="true"></span>
|
||||
<span class="cb-userslist__search-text"><?php echo Text::_('COM_COMPROFILER_SEARCH'); ?></span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($users)) : ?>
|
||||
<div class="cb-userslist__grid">
|
||||
<?php foreach ($users as $user) : ?>
|
||||
<div class="cb-userslist__user-card">
|
||||
<?php if ($user->getField('avatar', null, 'html', 'none', 'list')) : ?>
|
||||
<div class="cb-userslist__avatar">
|
||||
<a href="<?php echo $user->getField('canvas', null, 'html', 'none', 'profile'); ?>"
|
||||
aria-label="<?php echo Text::sprintf('COM_COMPROFILER_VIEW_PROFILE', htmlspecialchars($user->getField('formatname', null, 'html', 'none', 'list'), ENT_QUOTES, 'UTF-8')); ?>">
|
||||
<?php echo $user->getField('avatar', null, 'html', 'none', 'list'); ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="cb-userslist__user-info">
|
||||
<h3 class="cb-userslist__username">
|
||||
<a href="<?php echo $user->getField('canvas', null, 'html', 'none', 'profile'); ?>">
|
||||
<?php echo htmlspecialchars($user->getField('formatname', null, 'html', 'none', 'list'), ENT_QUOTES, 'UTF-8'); ?>
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<?php if ($user->getField('onlinestatus', null, 'html', 'none', 'list')) : ?>
|
||||
<div class="cb-userslist__status">
|
||||
<?php echo $user->getField('onlinestatus', null, 'html', 'none', 'list'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($user->fields) && !empty($user->fields)) : ?>
|
||||
<div class="cb-userslist__fields">
|
||||
<?php foreach ($user->fields as $field) : ?>
|
||||
<?php if ($field->value) : ?>
|
||||
<div class="cb-userslist__field">
|
||||
<span class="cb-userslist__field-label"><?php echo htmlspecialchars($field->title, ENT_QUOTES, 'UTF-8'); ?>:</span>
|
||||
<span class="cb-userslist__field-value"><?php echo $field->value; ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="cb-userslist__actions">
|
||||
<a href="<?php echo $user->getField('canvas', null, 'html', 'none', 'profile'); ?>"
|
||||
class="cb-userslist__btn btn btn-primary btn-sm">
|
||||
<span class="icon-user" aria-hidden="true"></span>
|
||||
<?php echo Text::_('COM_COMPROFILER_VIEW_PROFILE'); ?>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($pagination) : ?>
|
||||
<div class="cb-userslist__pagination">
|
||||
<?php echo $pagination->getPagesLinks(); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php else : ?>
|
||||
<div class="alert alert-info" role="alert">
|
||||
<?php echo Text::_('COM_COMPROFILER_NO_USERS_FOUND'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -1 +1,76 @@
|
||||
<!DOCTYPE html><title></title>
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Joomla.Site
|
||||
* @subpackage Templates.MokoCassiopeia
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* @copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 3 or later; see LICENSE.txt
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: Joomla.Template.Site
|
||||
* INGROUP: MokoCassiopeia
|
||||
* PATH: ./templates/mokocassiopeia/html/com_content/article/toc-left.php
|
||||
* VERSION: 03.06.02
|
||||
* BRIEF: Article layout with table of contents on the left side using Bootstrap TOC
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
@@ -19,6 +12,7 @@ defined('_JEXEC') or die;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Associations;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Layout\LayoutHelper;
|
||||
|
||||
// Load Bootstrap TOC assets
|
||||
@@ -43,13 +37,13 @@ $assocParam = (Associations::isEnabled() && $params->get('show_associations'));
|
||||
<div class="col-lg-3 col-md-4 order-md-1 mb-4">
|
||||
<div class="sticky-top toc-wrapper" style="top: 20px;">
|
||||
<nav id="toc" data-toggle="toc" class="toc-container">
|
||||
<h5 class="toc-title"><?php echo HTMLHelper::_('string.truncate', $this->item->title, 50); ?></h5>
|
||||
<h5 class="toc-title"><?php echo Text::_('TPL_MOKOCASSIOPEIA_TOC_TITLE'); ?></h5>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Article Content -->
|
||||
<div class="col-lg-9 col-md-8 order-md-2" data-toc-scope>
|
||||
<div class="col-lg-9 col-md-8 order-md-2">
|
||||
<meta itemprop="inLanguage" content="<?php echo ($this->item->language === '*') ? Factory::getApplication()->get('language') : $this->item->language; ?>" />
|
||||
|
||||
<?php if ($this->params->get('show_page_heading')) : ?>
|
||||
@@ -91,7 +85,7 @@ $assocParam = (Associations::isEnabled() && $params->get('show_associations'));
|
||||
<?php echo LayoutHelper::render('joomla.content.tags', $this->item->tags->itemTags); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="article-content" itemprop="articleBody">
|
||||
<div class="article-content" itemprop="articleBody" data-toc-scope>
|
||||
<?php echo $this->item->text; ?>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Joomla.Site
|
||||
* @subpackage Templates.MokoCassiopeia
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* @copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 3 or later; see LICENSE.txt
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* FILE INFORMATION
|
||||
* DEFGROUP: Joomla.Template.Site
|
||||
* INGROUP: MokoCassiopeia
|
||||
* PATH: ./templates/mokocassiopeia/html/com_content/article/toc-right.php
|
||||
* VERSION: 03.06.02
|
||||
* BRIEF: Article layout with table of contents on the right side using Bootstrap TOC
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
@@ -19,6 +12,7 @@ defined('_JEXEC') or die;
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Associations;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Layout\LayoutHelper;
|
||||
|
||||
// Load Bootstrap TOC assets
|
||||
@@ -40,7 +34,7 @@ $assocParam = (Associations::isEnabled() && $params->get('show_associations'));
|
||||
<div class="com-content-article item-page<?php echo $this->pageclass_sfx; ?>">
|
||||
<div class="row">
|
||||
<!-- Article Content -->
|
||||
<div class="col-lg-9 col-md-8 order-md-1" data-toc-scope>
|
||||
<div class="col-lg-9 col-md-8 order-md-1">
|
||||
<meta itemprop="inLanguage" content="<?php echo ($this->item->language === '*') ? Factory::getApplication()->get('language') : $this->item->language; ?>" />
|
||||
|
||||
<?php if ($this->params->get('show_page_heading')) : ?>
|
||||
@@ -82,7 +76,7 @@ $assocParam = (Associations::isEnabled() && $params->get('show_associations'));
|
||||
<?php echo LayoutHelper::render('joomla.content.tags', $this->item->tags->itemTags); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="article-content" itemprop="articleBody">
|
||||
<div class="article-content" itemprop="articleBody" data-toc-scope>
|
||||
<?php echo $this->item->text; ?>
|
||||
</div>
|
||||
|
||||
@@ -108,7 +102,7 @@ $assocParam = (Associations::isEnabled() && $params->get('show_associations'));
|
||||
<div class="col-lg-3 col-md-4 order-md-2 mb-4">
|
||||
<div class="sticky-top toc-wrapper" style="top: 20px;">
|
||||
<nav id="toc" data-toggle="toc" class="toc-container">
|
||||
<h5 class="toc-title"><?php echo HTMLHelper::_('string.truncate', $this->item->title, 50); ?></h5>
|
||||
<h5 class="toc-title"><?php echo Text::_('TPL_MOKOCASSIOPEIA_TOC_TITLE'); ?></h5>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><title></title>
|
||||
@@ -1,167 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package JEM
|
||||
* @subpackage com_jem
|
||||
*
|
||||
* @copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*
|
||||
* Mobile responsive override for JEM calendar view
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
|
||||
$events = $this->rows ?? [];
|
||||
$date = $this->date ?? null;
|
||||
$year = $this->year ?? date('Y');
|
||||
$month = $this->month ?? date('m');
|
||||
?>
|
||||
|
||||
<div class="jem-calendar-responsive jem-component">
|
||||
<div class="jem-calendar__container">
|
||||
|
||||
<!-- Calendar Header -->
|
||||
<div class="jem-calendar__header">
|
||||
<h1 class="jem-calendar__title">
|
||||
<?php echo Text::_('COM_JEM_CALENDAR'); ?>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<!-- Calendar Navigation -->
|
||||
<div class="jem-calendar__navigation">
|
||||
<a href="<?php echo Route::_('index.php?option=com_jem&view=calendar&year=' . ($month == 1 ? $year - 1 : $year) . '&month=' . ($month == 1 ? 12 : $month - 1)); ?>"
|
||||
class="jem-calendar__nav-button jem-calendar__nav-prev"
|
||||
aria-label="<?php echo Text::_('COM_JEM_PREVIOUS_MONTH'); ?>">
|
||||
<span aria-hidden="true">‹</span>
|
||||
</a>
|
||||
|
||||
<h2 class="jem-calendar__current-month">
|
||||
<?php echo HTMLHelper::_('date', $year . '-' . $month . '-01', 'F Y'); ?>
|
||||
</h2>
|
||||
|
||||
<a href="<?php echo Route::_('index.php?option=com_jem&view=calendar&year=' . ($month == 12 ? $year + 1 : $year) . '&month=' . ($month == 12 ? 1 : $month + 1)); ?>"
|
||||
class="jem-calendar__nav-button jem-calendar__nav-next"
|
||||
aria-label="<?php echo Text::_('COM_JEM_NEXT_MONTH'); ?>">
|
||||
<span aria-hidden="true">›</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Calendar Grid -->
|
||||
<div class="jem-calendar__grid">
|
||||
<!-- Weekday Headers -->
|
||||
<div class="jem-calendar__weekdays">
|
||||
<?php
|
||||
$weekDays = [
|
||||
Text::_('SUN'),
|
||||
Text::_('MON'),
|
||||
Text::_('TUE'),
|
||||
Text::_('WED'),
|
||||
Text::_('THU'),
|
||||
Text::_('FRI'),
|
||||
Text::_('SAT')
|
||||
];
|
||||
foreach ($weekDays as $day) : ?>
|
||||
<div class="jem-calendar__weekday">
|
||||
<?php echo $day; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<!-- Calendar Days -->
|
||||
<div class="jem-calendar__days">
|
||||
<?php
|
||||
// Generate calendar days
|
||||
$firstDay = mktime(0, 0, 0, $month, 1, $year);
|
||||
$daysInMonth = date('t', $firstDay);
|
||||
$dayOfWeek = date('w', $firstDay);
|
||||
|
||||
// Empty cells before first day
|
||||
for ($i = 0; $i < $dayOfWeek; $i++) : ?>
|
||||
<div class="jem-calendar__day jem-calendar__day--empty"></div>
|
||||
<?php endfor;
|
||||
|
||||
// Days with events
|
||||
for ($day = 1; $day <= $daysInMonth; $day++) :
|
||||
$currentDate = sprintf('%04d-%02d-%02d', $year, $month, $day);
|
||||
$hasEvents = false;
|
||||
$dayEvents = [];
|
||||
|
||||
// Check for events on this day
|
||||
if (!empty($events)) {
|
||||
foreach ($events as $event) {
|
||||
if (!empty($event->dates) && date('Y-m-d', strtotime($event->dates)) == $currentDate) {
|
||||
$hasEvents = true;
|
||||
$dayEvents[] = $event;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$isToday = ($currentDate == date('Y-m-d'));
|
||||
$classes = 'jem-calendar__day';
|
||||
if ($hasEvents) {
|
||||
$classes .= ' jem-calendar__day--has-events';
|
||||
}
|
||||
if ($isToday) {
|
||||
$classes .= ' jem-calendar__day--today';
|
||||
}
|
||||
?>
|
||||
<div class="<?php echo $classes; ?>" data-date="<?php echo $currentDate; ?>">
|
||||
<div class="jem-calendar__day-number">
|
||||
<?php echo $day; ?>
|
||||
</div>
|
||||
<?php if ($hasEvents) : ?>
|
||||
<div class="jem-calendar__day-events">
|
||||
<span class="jem-calendar__event-indicator"
|
||||
aria-label="<?php echo Text::sprintf('COM_JEM_EVENTS_COUNT', count($dayEvents)); ?>">
|
||||
<?php echo count($dayEvents); ?>
|
||||
</span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endfor; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Events List for Selected/Current Day -->
|
||||
<?php if (!empty($events)) : ?>
|
||||
<div class="jem-calendar__events-list">
|
||||
<h3 class="jem-calendar__events-title">
|
||||
<?php echo Text::_('COM_JEM_UPCOMING_EVENTS'); ?>
|
||||
</h3>
|
||||
<div class="jem-calendar__events">
|
||||
<?php foreach ($events as $event) : ?>
|
||||
<div class="jem-calendar__event-item">
|
||||
<div class="jem-calendar__event-date">
|
||||
<?php if (!empty($event->dates)) : ?>
|
||||
<time datetime="<?php echo $this->escape($event->dates); ?>">
|
||||
<?php echo HTMLHelper::_('date', $event->dates, Text::_('DATE_FORMAT_LC4')); ?>
|
||||
</time>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<h4 class="jem-calendar__event-title">
|
||||
<?php if (!empty($event->slug)) : ?>
|
||||
<a href="<?php echo Route::_('index.php?option=com_jem&view=event&id=' . $event->slug); ?>"
|
||||
class="jem-calendar__event-link">
|
||||
<?php echo $this->escape($event->title); ?>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<?php echo $this->escape($event->title); ?>
|
||||
<?php endif; ?>
|
||||
</h4>
|
||||
<?php if (!empty($event->venue)) : ?>
|
||||
<div class="jem-calendar__event-venue">
|
||||
📍 <?php echo $this->escape($event->venue); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><title></title>
|
||||
@@ -1,111 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package JEM
|
||||
* @subpackage com_jem
|
||||
*
|
||||
* @copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*
|
||||
* Mobile responsive override for JEM categories view
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
$categories = $this->categories ?? [];
|
||||
?>
|
||||
|
||||
<div class="jem-categories-responsive jem-component">
|
||||
<div class="jem-categories__container">
|
||||
|
||||
<!-- Categories Header -->
|
||||
<div class="jem-categories__header">
|
||||
<h1 class="jem-categories__title">
|
||||
<?php echo Text::_('COM_JEM_CATEGORIES'); ?>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($categories)) : ?>
|
||||
<div class="jem-categories__list">
|
||||
<?php foreach ($categories as $category) : ?>
|
||||
<div class="jem-categories__item">
|
||||
<div class="jem-categories__item-inner">
|
||||
|
||||
<!-- Category Image -->
|
||||
<?php if (!empty($category->image)) : ?>
|
||||
<div class="jem-categories__image-wrapper">
|
||||
<img src="<?php echo $this->escape($category->image); ?>"
|
||||
alt="<?php echo $this->escape($category->catname); ?>"
|
||||
class="jem-categories__image"
|
||||
loading="lazy">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Category Content -->
|
||||
<div class="jem-categories__content">
|
||||
|
||||
<!-- Category Title -->
|
||||
<h2 class="jem-categories__category-title">
|
||||
<?php if (!empty($category->slug)) : ?>
|
||||
<a href="<?php echo Route::_('index.php?option=com_jem&view=category&id=' . $category->slug); ?>"
|
||||
class="jem-categories__link">
|
||||
<?php echo $this->escape($category->catname); ?>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<?php echo $this->escape($category->catname); ?>
|
||||
<?php endif; ?>
|
||||
</h2>
|
||||
|
||||
<!-- Category Description -->
|
||||
<?php if (!empty($category->catdescription)) : ?>
|
||||
<div class="jem-categories__description">
|
||||
<?php echo $category->catdescription; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Event Count -->
|
||||
<?php if (isset($category->eventcount)) : ?>
|
||||
<div class="jem-categories__meta">
|
||||
<span class="jem-categories__event-count">
|
||||
<?php echo Text::sprintf('COM_JEM_EVENTS_COUNT_FULL', (int) $category->eventcount); ?>
|
||||
</span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- View Category Button -->
|
||||
<?php if (!empty($category->slug)) : ?>
|
||||
<div class="jem-categories__actions">
|
||||
<a href="<?php echo Route::_('index.php?option=com_jem&view=category&id=' . $category->slug); ?>"
|
||||
class="jem-categories__button btn btn-primary"
|
||||
aria-label="<?php echo Text::sprintf('COM_JEM_VIEW_CATEGORY', $this->escape($category->catname)); ?>">
|
||||
<?php echo Text::_('COM_JEM_VIEW_CATEGORY'); ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<?php if (!empty($this->pagination)) : ?>
|
||||
<div class="jem-categories__pagination">
|
||||
<?php echo $this->pagination->getPagesLinks(); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php else : ?>
|
||||
<div class="jem-categories__empty">
|
||||
<p class="jem-categories__empty-message">
|
||||
<?php echo Text::_('COM_JEM_NO_CATEGORIES'); ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><title></title>
|
||||
@@ -1,212 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package JEM
|
||||
* @subpackage com_jem
|
||||
*
|
||||
* @copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*
|
||||
* Mobile responsive override for JEM event details view
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
|
||||
$item = $this->item ?? null;
|
||||
$params = $this->params ?? null;
|
||||
|
||||
if (!$item) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="jem-event-responsive jem-component">
|
||||
<div class="jem-event__container">
|
||||
|
||||
<!-- Event Header -->
|
||||
<div class="jem-event__header">
|
||||
<h1 class="jem-event__title">
|
||||
<?php echo $this->escape($item->title); ?>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<!-- Event Image -->
|
||||
<?php if (!empty($item->datimage)) : ?>
|
||||
<div class="jem-event__image-wrapper">
|
||||
<img src="<?php echo $this->escape($item->datimage); ?>"
|
||||
alt="<?php echo $this->escape($item->title); ?>"
|
||||
class="jem-event__image"
|
||||
loading="lazy">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Event Meta Information -->
|
||||
<div class="jem-event__meta">
|
||||
|
||||
<!-- Date and Time -->
|
||||
<div class="jem-event__meta-item jem-event__date">
|
||||
<span class="jem-event__meta-icon" aria-hidden="true">📅</span>
|
||||
<div class="jem-event__meta-content">
|
||||
<strong class="jem-event__meta-label">
|
||||
<?php echo Text::_('COM_JEM_DATE'); ?>:
|
||||
</strong>
|
||||
<?php if (!empty($item->dates)) : ?>
|
||||
<time datetime="<?php echo $this->escape($item->dates); ?>"
|
||||
class="jem-event__datetime">
|
||||
<?php echo HTMLHelper::_('date', $item->dates, Text::_('DATE_FORMAT_LC3')); ?>
|
||||
</time>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($item->enddates) && $item->enddates != $item->dates) : ?>
|
||||
<span class="jem-event__date-separator"> - </span>
|
||||
<time datetime="<?php echo $this->escape($item->enddates); ?>"
|
||||
class="jem-event__datetime">
|
||||
<?php echo HTMLHelper::_('date', $item->enddates, Text::_('DATE_FORMAT_LC3')); ?>
|
||||
</time>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Time -->
|
||||
<?php if (!empty($item->times)) : ?>
|
||||
<div class="jem-event__meta-item jem-event__time">
|
||||
<span class="jem-event__meta-icon" aria-hidden="true">🕐</span>
|
||||
<div class="jem-event__meta-content">
|
||||
<strong class="jem-event__meta-label">
|
||||
<?php echo Text::_('COM_JEM_TIME'); ?>:
|
||||
</strong>
|
||||
<span class="jem-event__time-value">
|
||||
<?php echo $this->escape($item->times); ?>
|
||||
<?php if (!empty($item->endtimes)) : ?>
|
||||
- <?php echo $this->escape($item->endtimes); ?>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Venue -->
|
||||
<?php if (!empty($item->venue)) : ?>
|
||||
<div class="jem-event__meta-item jem-event__venue">
|
||||
<span class="jem-event__meta-icon" aria-hidden="true">📍</span>
|
||||
<div class="jem-event__meta-content">
|
||||
<strong class="jem-event__meta-label">
|
||||
<?php echo Text::_('COM_JEM_VENUE'); ?>:
|
||||
</strong>
|
||||
<?php if (!empty($item->venueslug)) : ?>
|
||||
<a href="<?php echo Route::_('index.php?option=com_jem&view=venue&id=' . $item->venueslug); ?>"
|
||||
class="jem-event__venue-link">
|
||||
<?php echo $this->escape($item->venue); ?>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<span class="jem-event__venue-name">
|
||||
<?php echo $this->escape($item->venue); ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($item->street) || !empty($item->city)) : ?>
|
||||
<div class="jem-event__address">
|
||||
<?php if (!empty($item->street)) : ?>
|
||||
<span class="jem-event__street">
|
||||
<?php echo $this->escape($item->street); ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($item->city)) : ?>
|
||||
<span class="jem-event__city">
|
||||
<?php echo $this->escape($item->city); ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Categories -->
|
||||
<?php if (!empty($item->categories)) : ?>
|
||||
<div class="jem-event__meta-item jem-event__categories">
|
||||
<span class="jem-event__meta-icon" aria-hidden="true">🏷️</span>
|
||||
<div class="jem-event__meta-content">
|
||||
<strong class="jem-event__meta-label">
|
||||
<?php echo Text::_('COM_JEM_CATEGORIES'); ?>:
|
||||
</strong>
|
||||
<div class="jem-event__category-list">
|
||||
<?php foreach ($item->categories as $category) : ?>
|
||||
<span class="jem-event__category-badge">
|
||||
<?php echo $this->escape($category->catname); ?>
|
||||
</span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Event Description -->
|
||||
<?php if (!empty($item->fulltext)) : ?>
|
||||
<div class="jem-event__description">
|
||||
<h2 class="jem-event__description-title">
|
||||
<?php echo Text::_('COM_JEM_DESCRIPTION'); ?>
|
||||
</h2>
|
||||
<div class="jem-event__description-content">
|
||||
<?php echo $item->fulltext; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Event Registration -->
|
||||
<?php if (!empty($item->registra) && $item->registra == 1) : ?>
|
||||
<div class="jem-event__registration">
|
||||
<h2 class="jem-event__registration-title">
|
||||
<?php echo Text::_('COM_JEM_REGISTRATION'); ?>
|
||||
</h2>
|
||||
<?php if (!empty($item->maxplaces)) : ?>
|
||||
<p class="jem-event__capacity">
|
||||
<strong><?php echo Text::_('COM_JEM_MAX_PLACES'); ?>:</strong>
|
||||
<?php echo (int) $item->maxplaces; ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($item->waitinglist)) : ?>
|
||||
<p class="jem-event__waitinglist">
|
||||
<?php echo Text::_('COM_JEM_WAITING_LIST_ENABLED'); ?>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Event Contact -->
|
||||
<?php if (!empty($item->contactname)) : ?>
|
||||
<div class="jem-event__contact">
|
||||
<h2 class="jem-event__contact-title">
|
||||
<?php echo Text::_('COM_JEM_CONTACT'); ?>
|
||||
</h2>
|
||||
<p class="jem-event__contact-info">
|
||||
<strong><?php echo Text::_('COM_JEM_NAME'); ?>:</strong>
|
||||
<?php echo $this->escape($item->contactname); ?>
|
||||
</p>
|
||||
<?php if (!empty($item->contactemail)) : ?>
|
||||
<p class="jem-event__contact-info">
|
||||
<strong><?php echo Text::_('COM_JEM_EMAIL'); ?>:</strong>
|
||||
<a href="mailto:<?php echo $this->escape($item->contactemail); ?>"
|
||||
class="jem-event__contact-link">
|
||||
<?php echo $this->escape($item->contactemail); ?>
|
||||
</a>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Back Button -->
|
||||
<div class="jem-event__actions">
|
||||
<a href="<?php echo Route::_('index.php?option=com_jem&view=eventslist'); ?>"
|
||||
class="jem-event__button btn btn-secondary">
|
||||
<?php echo Text::_('COM_JEM_BACK_TO_EVENTS'); ?>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><title></title>
|
||||
@@ -1,147 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package JEM
|
||||
* @subpackage com_jem
|
||||
*
|
||||
* @copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*
|
||||
* Mobile responsive override for JEM events list view
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
|
||||
// Load JEM helper if available
|
||||
if (file_exists(JPATH_SITE . '/components/com_jem/helpers/helper.php')) {
|
||||
require_once JPATH_SITE . '/components/com_jem/helpers/helper.php';
|
||||
}
|
||||
|
||||
$items = $this->items ?? [];
|
||||
$params = $this->params ?? null;
|
||||
?>
|
||||
|
||||
<div class="jem-eventslist-responsive jem-component">
|
||||
<div class="jem-eventslist__container">
|
||||
|
||||
<?php if (!empty($this->pageheading)) : ?>
|
||||
<div class="jem-eventslist__header">
|
||||
<h1 class="jem-eventslist__title">
|
||||
<?php echo $this->escape($this->pageheading); ?>
|
||||
</h1>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($items)) : ?>
|
||||
<div class="jem-eventslist__list">
|
||||
<?php foreach ($items as $item) : ?>
|
||||
<div class="jem-eventslist__item">
|
||||
<div class="jem-eventslist__item-inner">
|
||||
|
||||
<!-- Event Date -->
|
||||
<div class="jem-eventslist__date">
|
||||
<?php if (!empty($item->dates)) : ?>
|
||||
<time datetime="<?php echo $this->escape($item->dates); ?>"
|
||||
class="jem-eventslist__datetime">
|
||||
<?php echo HTMLHelper::_('date', $item->dates, Text::_('DATE_FORMAT_LC4')); ?>
|
||||
</time>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($item->enddates) && $item->enddates != $item->dates) : ?>
|
||||
<span class="jem-eventslist__date-separator"> - </span>
|
||||
<time datetime="<?php echo $this->escape($item->enddates); ?>"
|
||||
class="jem-eventslist__datetime">
|
||||
<?php echo HTMLHelper::_('date', $item->enddates, Text::_('DATE_FORMAT_LC4')); ?>
|
||||
</time>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<!-- Event Title -->
|
||||
<h2 class="jem-eventslist__event-title">
|
||||
<?php if (!empty($item->slug)) : ?>
|
||||
<a href="<?php echo Route::_('index.php?option=com_jem&view=event&id=' . $item->slug); ?>"
|
||||
class="jem-eventslist__link">
|
||||
<?php echo $this->escape($item->title); ?>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<?php echo $this->escape($item->title); ?>
|
||||
<?php endif; ?>
|
||||
</h2>
|
||||
|
||||
<!-- Event Venue -->
|
||||
<?php if (!empty($item->venue)) : ?>
|
||||
<div class="jem-eventslist__venue">
|
||||
<span class="jem-eventslist__venue-icon" aria-hidden="true">📍</span>
|
||||
<?php if (!empty($item->venueslug)) : ?>
|
||||
<a href="<?php echo Route::_('index.php?option=com_jem&view=venue&id=' . $item->venueslug); ?>"
|
||||
class="jem-eventslist__venue-link">
|
||||
<?php echo $this->escape($item->venue); ?>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<span class="jem-eventslist__venue-name">
|
||||
<?php echo $this->escape($item->venue); ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($item->city)) : ?>
|
||||
<span class="jem-eventslist__city">
|
||||
, <?php echo $this->escape($item->city); ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Event Description -->
|
||||
<?php if (!empty($item->introtext)) : ?>
|
||||
<div class="jem-eventslist__description">
|
||||
<?php echo $item->introtext; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Event Categories -->
|
||||
<?php if (!empty($item->categories)) : ?>
|
||||
<div class="jem-eventslist__categories">
|
||||
<?php foreach ($item->categories as $category) : ?>
|
||||
<span class="jem-eventslist__category-badge">
|
||||
<?php echo $this->escape($category->catname); ?>
|
||||
</span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Read More Button -->
|
||||
<?php if (!empty($item->slug)) : ?>
|
||||
<div class="jem-eventslist__actions">
|
||||
<a href="<?php echo Route::_('index.php?option=com_jem&view=event&id=' . $item->slug); ?>"
|
||||
class="jem-eventslist__button btn btn-primary"
|
||||
aria-label="<?php echo Text::sprintf('COM_JEM_READ_MORE_ABOUT', $this->escape($item->title)); ?>">
|
||||
<?php echo Text::_('COM_JEM_READ_MORE'); ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
<?php if (!empty($this->pagination)) : ?>
|
||||
<div class="jem-eventslist__pagination">
|
||||
<?php echo $this->pagination->getPagesLinks(); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php else : ?>
|
||||
<div class="jem-eventslist__empty">
|
||||
<p class="jem-eventslist__empty-message">
|
||||
<?php echo Text::_('COM_JEM_NO_EVENTS'); ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><title></title>
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><title></title>
|
||||
@@ -1,188 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package JEM
|
||||
* @subpackage com_jem
|
||||
*
|
||||
* @copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*
|
||||
* Mobile responsive override for JEM venue view
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
|
||||
$venue = $this->venue ?? null;
|
||||
$events = $this->rows ?? [];
|
||||
|
||||
if (!$venue) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="jem-venue-responsive jem-component">
|
||||
<div class="jem-venue__container">
|
||||
|
||||
<!-- Venue Header -->
|
||||
<div class="jem-venue__header">
|
||||
<h1 class="jem-venue__title">
|
||||
<?php echo $this->escape($venue->venue); ?>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<!-- Venue Image -->
|
||||
<?php if (!empty($venue->locimage)) : ?>
|
||||
<div class="jem-venue__image-wrapper">
|
||||
<img src="<?php echo $this->escape($venue->locimage); ?>"
|
||||
alt="<?php echo $this->escape($venue->venue); ?>"
|
||||
class="jem-venue__image"
|
||||
loading="lazy">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Venue Information -->
|
||||
<div class="jem-venue__info">
|
||||
|
||||
<!-- Address -->
|
||||
<?php if (!empty($venue->street) || !empty($venue->city) || !empty($venue->postalCode)) : ?>
|
||||
<div class="jem-venue__info-item jem-venue__address">
|
||||
<span class="jem-venue__info-icon" aria-hidden="true">📍</span>
|
||||
<div class="jem-venue__info-content">
|
||||
<strong class="jem-venue__info-label">
|
||||
<?php echo Text::_('COM_JEM_ADDRESS'); ?>:
|
||||
</strong>
|
||||
<address class="jem-venue__address-content">
|
||||
<?php if (!empty($venue->street)) : ?>
|
||||
<div class="jem-venue__street">
|
||||
<?php echo $this->escape($venue->street); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($venue->postalCode) || !empty($venue->city)) : ?>
|
||||
<div class="jem-venue__city-line">
|
||||
<?php if (!empty($venue->postalCode)) : ?>
|
||||
<span class="jem-venue__postal">
|
||||
<?php echo $this->escape($venue->postalCode); ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($venue->city)) : ?>
|
||||
<span class="jem-venue__city">
|
||||
<?php echo $this->escape($venue->city); ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($venue->state)) : ?>
|
||||
<div class="jem-venue__state">
|
||||
<?php echo $this->escape($venue->state); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (!empty($venue->country)) : ?>
|
||||
<div class="jem-venue__country">
|
||||
<?php echo $this->escape($venue->country); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</address>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Website -->
|
||||
<?php if (!empty($venue->url)) : ?>
|
||||
<div class="jem-venue__info-item jem-venue__website">
|
||||
<span class="jem-venue__info-icon" aria-hidden="true">🌐</span>
|
||||
<div class="jem-venue__info-content">
|
||||
<strong class="jem-venue__info-label">
|
||||
<?php echo Text::_('COM_JEM_WEBSITE'); ?>:
|
||||
</strong>
|
||||
<a href="<?php echo $this->escape($venue->url); ?>"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="jem-venue__link">
|
||||
<?php echo $this->escape($venue->url); ?>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Description -->
|
||||
<?php if (!empty($venue->locdescription)) : ?>
|
||||
<div class="jem-venue__description">
|
||||
<h2 class="jem-venue__description-title">
|
||||
<?php echo Text::_('COM_JEM_DESCRIPTION'); ?>
|
||||
</h2>
|
||||
<div class="jem-venue__description-content">
|
||||
<?php echo $venue->locdescription; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Map -->
|
||||
<?php if (!empty($venue->latitude) && !empty($venue->longitude)) : ?>
|
||||
<div class="jem-venue__map">
|
||||
<h2 class="jem-venue__map-title">
|
||||
<?php echo Text::_('COM_JEM_LOCATION'); ?>
|
||||
</h2>
|
||||
<div class="jem-venue__map-container">
|
||||
<!-- Map would be rendered here by JEM's map functionality -->
|
||||
<div class="jem-venue__map-placeholder">
|
||||
<p><?php echo Text::_('COM_JEM_MAP_VIEW'); ?></p>
|
||||
<p>
|
||||
<a href="https://www.google.com/maps?q=<?php echo $venue->latitude; ?>,<?php echo $venue->longitude; ?>"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="jem-venue__map-link btn btn-primary">
|
||||
<?php echo Text::_('COM_JEM_VIEW_ON_MAP'); ?>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Events at this Venue -->
|
||||
<?php if (!empty($events)) : ?>
|
||||
<div class="jem-venue__events">
|
||||
<h2 class="jem-venue__events-title">
|
||||
<?php echo Text::_('COM_JEM_EVENTS_AT_VENUE'); ?>
|
||||
</h2>
|
||||
<div class="jem-venue__events-list">
|
||||
<?php foreach ($events as $event) : ?>
|
||||
<div class="jem-venue__event-item">
|
||||
<div class="jem-venue__event-date">
|
||||
<?php if (!empty($event->dates)) : ?>
|
||||
<time datetime="<?php echo $this->escape($event->dates); ?>">
|
||||
<?php echo HTMLHelper::_('date', $event->dates, Text::_('DATE_FORMAT_LC4')); ?>
|
||||
</time>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<h3 class="jem-venue__event-title">
|
||||
<?php if (!empty($event->slug)) : ?>
|
||||
<a href="<?php echo Route::_('index.php?option=com_jem&view=event&id=' . $event->slug); ?>"
|
||||
class="jem-venue__event-link">
|
||||
<?php echo $this->escape($event->title); ?>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<?php echo $this->escape($event->title); ?>
|
||||
<?php endif; ?>
|
||||
</h3>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Back Button -->
|
||||
<div class="jem-venue__actions">
|
||||
<a href="<?php echo Route::_('index.php?option=com_jem&view=eventslist'); ?>"
|
||||
class="jem-venue__button btn btn-secondary">
|
||||
<?php echo Text::_('COM_JEM_BACK_TO_EVENTS'); ?>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><html><head><title></title></head><body></body></html>
|
||||
@@ -1,70 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Kunena
|
||||
* @subpackage com_kunena
|
||||
*
|
||||
* @copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*
|
||||
* Mobile responsive override for Kunena category list
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
$this->document->addStyleDeclaration('
|
||||
.kunena-category-list-responsive {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.kunena-category-responsive {
|
||||
background: var(--body-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.kunena-category-responsive:hover {
|
||||
background: var(--secondary-bg);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
@media (max-width: 575.98px) {
|
||||
.kunena-category-responsive {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
}
|
||||
');
|
||||
?>
|
||||
|
||||
<div class="kunena-category-list-responsive">
|
||||
<?php if (!empty($this->categories)) : ?>
|
||||
<?php foreach ($this->categories as $category) : ?>
|
||||
<div class="kunena-category-responsive">
|
||||
<h3 class="kunena-category__title">
|
||||
<a href="<?php echo $category->getUrl(); ?>">
|
||||
<?php echo $this->escape($category->name); ?>
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<?php if ($category->description) : ?>
|
||||
<div class="kunena-category__description">
|
||||
<?php echo $category->displayField('description'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="kunena-category__meta">
|
||||
<span><?php echo Text::_('COM_KUNENA_TOPICS'); ?>: <?php echo $category->numTopics; ?></span>
|
||||
<span><?php echo Text::_('COM_KUNENA_POSTS'); ?>: <?php echo $category->numPosts; ?></span>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php else : ?>
|
||||
<div class="alert alert-info">
|
||||
<?php echo Text::_('COM_KUNENA_NO_CATEGORIES'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><html><head><title></title></head><body></body></html>
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><html><head><title></title></head><body></body></html>
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><html><head><title></title></head><body></body></html>
|
||||
@@ -1,141 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package OS Membership Pro
|
||||
* @subpackage com_osmembership
|
||||
*
|
||||
* @copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*
|
||||
* Mobile responsive override for OS Membership plans list
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
$this->document->addStyleDeclaration('
|
||||
.osmembership-plans-responsive {
|
||||
display: grid;
|
||||
gap: 2rem;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.osmembership-plan-card {
|
||||
background: var(--body-bg);
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 2rem;
|
||||
transition: all 0.3s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.osmembership-plan-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.osmembership-plan-card--featured {
|
||||
border-color: var(--color-primary);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.osmembership-plan-card--featured::before {
|
||||
content: "' . Text::_('OSM_POPULAR') . '";
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
right: 20px;
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
padding: 0.25rem 1rem;
|
||||
border-radius: 1rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.osmembership-plans-responsive {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 992px) {
|
||||
.osmembership-plans-responsive {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
.osmembership-plans-responsive.osmembership-plans--many {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
}
|
||||
');
|
||||
?>
|
||||
|
||||
<div class="osmembership-plans-responsive <?php echo count($this->items) > 3 ? 'osmembership-plans--many' : ''; ?>">
|
||||
<?php foreach ($this->items as $item) : ?>
|
||||
<div class="osmembership-plan-card <?php echo $item->featured ? 'osmembership-plan-card--featured' : ''; ?>">
|
||||
<?php if (!empty($item->image)) : ?>
|
||||
<div class="osmembership-plan__image" style="margin-bottom: 1.5rem;">
|
||||
<img src="<?php echo $item->image; ?>"
|
||||
alt="<?php echo htmlspecialchars($item->title, ENT_COMPAT, 'UTF-8'); ?>"
|
||||
style="width: 100%; height: auto; border-radius: var(--border-radius);" />
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<h2 class="osmembership-plan__title" style="margin: 0 0 1rem 0; font-size: 1.75rem; font-weight: 700;">
|
||||
<?php echo htmlspecialchars($item->title, ENT_COMPAT, 'UTF-8'); ?>
|
||||
</h2>
|
||||
|
||||
<div class="osmembership-plan__pricing" style="margin-bottom: 1.5rem;">
|
||||
<?php if ($item->price > 0) : ?>
|
||||
<div style="font-size: 2.5rem; font-weight: 700; color: var(--color-primary); line-height: 1;">
|
||||
<span style="font-size: 1.5rem; vertical-align: super;"><?php echo $this->config->currency_symbol; ?></span>
|
||||
<?php echo number_format($item->price, 0); ?>
|
||||
</div>
|
||||
<?php if ($item->subscription_length > 0) : ?>
|
||||
<div style="color: var(--gray-600); margin-top: 0.5rem;">
|
||||
<?php echo Text::_('OSM_PER') . ' ' . $item->subscription_length . ' ' . Text::_('OSM_' . strtoupper($item->subscription_length_unit)); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php else : ?>
|
||||
<div style="font-size: 2.5rem; font-weight: 700; color: var(--success);">
|
||||
<?php echo Text::_('OSM_FREE'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($item->short_description)) : ?>
|
||||
<div class="osmembership-plan__description" style="margin-bottom: 1.5rem; color: var(--gray-600); line-height: 1.6;">
|
||||
<?php echo $item->short_description; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($item->features)) : ?>
|
||||
<div class="osmembership-plan__features" style="flex: 1; margin-bottom: 1.5rem;">
|
||||
<ul style="list-style: none; padding: 0; margin: 0;">
|
||||
<?php foreach (explode("\n", $item->features) as $feature) : ?>
|
||||
<?php if (trim($feature)) : ?>
|
||||
<li style="padding: 0.5rem 0; display: flex; align-items: flex-start; gap: 0.5rem;">
|
||||
<span class="icon-check" style="color: var(--success); flex-shrink: 0; margin-top: 0.25rem;"></span>
|
||||
<span><?php echo htmlspecialchars(trim($feature), ENT_COMPAT, 'UTF-8'); ?></span>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="osmembership-plan__actions">
|
||||
<a href="<?php echo OSMembershipHelperRoute::getRegistrationRoute($item->id); ?>"
|
||||
class="btn btn-primary"
|
||||
style="width: 100%; min-height: 48px; display: inline-flex; align-items: center; justify-content: center; text-decoration: none;">
|
||||
<?php echo Text::_('OSM_SUBSCRIBE_NOW'); ?>
|
||||
<span class="icon-chevron-right" style="margin-left: 0.5rem;"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><html><head><title></title></head><body></body></html>
|
||||
@@ -1,82 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AcyMailing
|
||||
* @subpackage mod_acymailing
|
||||
*
|
||||
* @copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*
|
||||
* Mobile responsive override for mod_acymailing module
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
$moduleclass_sfx = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
|
||||
// Add responsive wrapper class
|
||||
$wrapperClass = 'mod-acymailing mod-acymailing-responsive ' . $moduleclass_sfx;
|
||||
?>
|
||||
|
||||
<div class="<?php echo $wrapperClass; ?>">
|
||||
<?php if (!empty($formDisplay)) : ?>
|
||||
<div class="mod-acymailing__form-container">
|
||||
<?php if ($params->get('intro_text')) : ?>
|
||||
<div class="mod-acymailing__intro">
|
||||
<?php echo $params->get('intro_text'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php echo $formDisplay; ?>
|
||||
|
||||
<?php if ($params->get('outro_text')) : ?>
|
||||
<div class="mod-acymailing__outro">
|
||||
<?php echo $params->get('outro_text'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<div class="mod-acymailing__empty">
|
||||
<p><?php echo Text::_('MOD_ACYMAILING_NO_FORM'); ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
/* Override AcyMailing inline styles for mobile responsiveness */
|
||||
.mod-acymailing-responsive .acymailing_module input[type="email"],
|
||||
.mod-acymailing-responsive .acymailing_module input[type="text"] {
|
||||
min-height: 44px !important;
|
||||
font-size: 1rem !important;
|
||||
padding: 0.5rem 0.75rem !important;
|
||||
border-radius: var(--border-radius, 0.375rem) !important;
|
||||
border: 1px solid var(--input-border-color, #dee2e6) !important;
|
||||
width: 100% !important;
|
||||
box-sizing: border-box !important;
|
||||
}
|
||||
|
||||
.mod-acymailing-responsive .acymailing_module button[type="submit"],
|
||||
.mod-acymailing-responsive .acymailing_module input[type="submit"] {
|
||||
min-height: 44px !important;
|
||||
padding: 0.625rem 1rem !important;
|
||||
font-size: 1rem !important;
|
||||
border-radius: var(--border-radius, 0.375rem) !important;
|
||||
cursor: pointer !important;
|
||||
}
|
||||
|
||||
@media (max-width: 575.98px) {
|
||||
.mod-acymailing-responsive .acymailing_module input[type="email"],
|
||||
.mod-acymailing-responsive .acymailing_module input[type="text"] {
|
||||
font-size: 16px !important;
|
||||
min-height: 48px !important;
|
||||
padding: 0.75rem 1rem !important;
|
||||
}
|
||||
|
||||
.mod-acymailing-responsive .acymailing_module button[type="submit"],
|
||||
.mod-acymailing-responsive .acymailing_module input[type="submit"] {
|
||||
min-height: 48px !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
36
src/html/mod_articles_archive/default.php
Normal file
36
src/html/mod_articles_archive/default.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_articles_archive.
|
||||
* Adds showtitle support.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
if (empty($list)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
?>
|
||||
<div class="mod-articles-archive<?php echo $suffix ? ' ' . $suffix : ''; ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-articles-archive__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<ul class="mod-articles-archive__list">
|
||||
<?php foreach ($list as $item) : ?>
|
||||
<li class="mod-articles-archive__item">
|
||||
<a href="<?php echo $item->link; ?>"><?php echo $item->text; ?></a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
76
src/html/mod_articles_archive/index.html
Normal file
76
src/html/mod_articles_archive/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
44
src/html/mod_articles_categories/default.php
Normal file
44
src/html/mod_articles_categories/default.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_articles_categories.
|
||||
* Adds showtitle support.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
if (empty($list)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
$showDescription = $params->get('show_description', 0);
|
||||
$numitems = $params->get('numitems', 0);
|
||||
?>
|
||||
<div class="mod-articles-categories<?php echo $suffix ? ' ' . $suffix : ''; ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-articles-categories__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<ul class="mod-articles-categories__list">
|
||||
<?php foreach ($list as $item) : ?>
|
||||
<li class="mod-articles-categories__item">
|
||||
<a href="<?php echo $item->link; ?>"><?php echo $item->title; ?></a>
|
||||
<?php if ($numitems) : ?>
|
||||
<span class="mod-articles-categories__count">(<?php echo $item->numitems; ?>)</span>
|
||||
<?php endif; ?>
|
||||
<?php if ($showDescription && !empty($item->description)) : ?>
|
||||
<p class="mod-articles-categories__description"><?php echo $item->description; ?></p>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
76
src/html/mod_articles_categories/index.html
Normal file
76
src/html/mod_articles_categories/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
78
src/html/mod_articles_category/default.php
Normal file
78
src/html/mod_articles_category/default.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_articles_category.
|
||||
* Adds showtitle support and respects module settings.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
Factory::getApplication()->getLanguage()->load('mod_articles_category', JPATH_SITE);
|
||||
|
||||
if (empty($list)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
?>
|
||||
<div class="mod-articles-category<?php echo $suffix ? ' ' . $suffix : ''; ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-articles-category__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<ul class="mod-articles-category__list">
|
||||
<?php foreach ($list as $item) : ?>
|
||||
<li class="mod-articles-category__item" itemscope itemtype="https://schema.org/Article">
|
||||
<?php if ($params->get('link_titles') == 1) : ?>
|
||||
<a class="mod-articles-category__link" href="<?php echo $item->link; ?>" itemprop="url">
|
||||
<span itemprop="name"><?php echo $item->title; ?></span>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<span itemprop="name"><?php echo $item->title; ?></span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($item->displayHits) : ?>
|
||||
<span class="mod-articles-category__hits">
|
||||
(<?php echo $item->displayHits; ?>)
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('show_author', 0)) : ?>
|
||||
<span class="mod-articles-category__author">
|
||||
<?php echo $item->displayAuthorName; ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($item->displayDate) : ?>
|
||||
<time class="mod-articles-category__date" datetime="<?php echo HTMLHelper::_('date', $item->displayDate, 'c'); ?>" itemprop="datePublished">
|
||||
<?php echo $item->displayDate; ?>
|
||||
</time>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('show_introtext', 0)) : ?>
|
||||
<div class="mod-articles-category__intro" itemprop="description">
|
||||
<?php echo $item->displayIntrotext; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('show_readmore', 0)) : ?>
|
||||
<a class="mod-articles-category__readmore" href="<?php echo $item->link; ?>" itemprop="url">
|
||||
<?php echo Text::_('MOD_ARTICLES_CATEGORY_READ_MORE_TITLE'); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
76
src/html/mod_articles_category/index.html
Normal file
76
src/html/mod_articles_category/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
40
src/html/mod_articles_latest/default.php
Normal file
40
src/html/mod_articles_latest/default.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_articles_latest.
|
||||
* Adds showtitle support and respects module settings.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
if (empty($list)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
?>
|
||||
<div class="mod-articles-latest<?php echo $suffix ? ' ' . $suffix : ''; ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-articles-latest__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<ul class="mod-articles-latest__list">
|
||||
<?php foreach ($list as $item) : ?>
|
||||
<li class="mod-articles-latest__item" itemscope itemtype="https://schema.org/Article">
|
||||
<a href="<?php echo $item->link; ?>" itemprop="url">
|
||||
<span itemprop="name"><?php echo $item->title; ?></span>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
76
src/html/mod_articles_latest/index.html
Normal file
76
src/html/mod_articles_latest/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
58
src/html/mod_articles_news/default.php
Normal file
58
src/html/mod_articles_news/default.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_articles_news (newsflash).
|
||||
* Adds showtitle support with card-based layout.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
if (empty($list)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
?>
|
||||
<div class="mod-articles-news newsflash<?php echo $suffix ? ' ' . $suffix : ''; ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-articles-news__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($list as $item) : ?>
|
||||
<div class="mod-articles-news__item" itemscope itemtype="https://schema.org/Article">
|
||||
<?php if ($params->get('item_title')) : ?>
|
||||
<h4 class="mod-articles-news__item-title" itemprop="name">
|
||||
<?php if ($item->link !== '' && $params->get('link_titles')) : ?>
|
||||
<a href="<?php echo $item->link; ?>" itemprop="url"><?php echo $item->title; ?></a>
|
||||
<?php else : ?>
|
||||
<?php echo $item->title; ?>
|
||||
<?php endif; ?>
|
||||
</h4>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($item->afterDisplayTitle)) : ?>
|
||||
<?php echo $item->afterDisplayTitle; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('show_introtext', 1)) : ?>
|
||||
<div class="mod-articles-news__intro" itemprop="description">
|
||||
<?php echo $item->introtext; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (isset($item->readmore) && $item->readmore) : ?>
|
||||
<a class="mod-articles-news__readmore" href="<?php echo $item->link; ?>" itemprop="url">
|
||||
<?php echo $item->linkText; ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
76
src/html/mod_articles_news/index.html
Normal file
76
src/html/mod_articles_news/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
38
src/html/mod_articles_popular/default.php
Normal file
38
src/html/mod_articles_popular/default.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_articles_popular.
|
||||
* Adds showtitle support and respects module settings.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
if (empty($list)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
?>
|
||||
<div class="mod-articles-popular<?php echo $suffix ? ' ' . $suffix : ''; ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-articles-popular__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<ul class="mod-articles-popular__list">
|
||||
<?php foreach ($list as $item) : ?>
|
||||
<li class="mod-articles-popular__item" itemscope itemtype="https://schema.org/Article">
|
||||
<a href="<?php echo $item->link; ?>" itemprop="url">
|
||||
<span itemprop="name"><?php echo $item->title; ?></span>
|
||||
</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
76
src/html/mod_articles_popular/index.html
Normal file
76
src/html/mod_articles_popular/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
52
src/html/mod_banners/default.php
Normal file
52
src/html/mod_banners/default.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_banners.
|
||||
* Adds showtitle support.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
if (empty($list)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
?>
|
||||
<div class="mod-banners<?php echo $suffix ? ' ' . $suffix : ''; ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-banners__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<?php foreach ($list as $item) : ?>
|
||||
<div class="mod-banners__item">
|
||||
<?php $link = $item->params->get('url') ?: ''; ?>
|
||||
<?php if ($item->type == 1) : ?>
|
||||
<?php // Image banner ?>
|
||||
<?php $imageUrl = $item->params->get('imageurl', ''); ?>
|
||||
<?php $alt = htmlspecialchars($item->name, ENT_COMPAT, 'UTF-8'); ?>
|
||||
<?php if ($link) : ?>
|
||||
<a href="<?php echo htmlspecialchars($link, ENT_COMPAT, 'UTF-8'); ?>" target="_blank" rel="noopener noreferrer">
|
||||
<img src="<?php echo htmlspecialchars($imageUrl, ENT_COMPAT, 'UTF-8'); ?>" alt="<?php echo $alt; ?>" class="mod-banners__image" loading="lazy" />
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<img src="<?php echo htmlspecialchars($imageUrl, ENT_COMPAT, 'UTF-8'); ?>" alt="<?php echo $alt; ?>" class="mod-banners__image" loading="lazy" />
|
||||
<?php endif; ?>
|
||||
<?php else : ?>
|
||||
<?php // Custom HTML banner ?>
|
||||
<?php echo $item->custombannercode; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
76
src/html/mod_banners/index.html
Normal file
76
src/html/mod_banners/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
48
src/html/mod_breadcrumbs/default.php
Normal file
48
src/html/mod_breadcrumbs/default.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_breadcrumbs.
|
||||
* Bootstrap 5 breadcrumb with schema.org BreadcrumbList markup.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
Factory::getApplication()->getLanguage()->load('mod_breadcrumbs', JPATH_SITE);
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
?>
|
||||
<nav class="mod-breadcrumbs<?php echo $suffix ? ' ' . $suffix : ''; ?>" aria-label="<?php echo Text::_('MOD_BREADCRUMBS_HERE'); ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-breadcrumbs__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<ol class="breadcrumb" itemscope itemtype="https://schema.org/BreadcrumbList">
|
||||
<?php foreach ($list as $key => $item) : ?>
|
||||
<?php
|
||||
$isLast = ($key === array_key_last($list));
|
||||
?>
|
||||
<li class="breadcrumb-item<?php echo $isLast ? ' active' : ''; ?>" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem"
|
||||
<?php echo $isLast ? ' aria-current="page"' : ''; ?>>
|
||||
<?php if (!$isLast && $item->link) : ?>
|
||||
<a href="<?php echo $item->link; ?>" itemprop="item">
|
||||
<span itemprop="name"><?php echo $item->name; ?></span>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<span itemprop="name"><?php echo $item->name; ?></span>
|
||||
<?php endif; ?>
|
||||
<meta itemprop="position" content="<?php echo $key + 1; ?>" />
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ol>
|
||||
</nav>
|
||||
76
src/html/mod_breadcrumbs/index.html
Normal file
76
src/html/mod_breadcrumbs/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><html><head><title></title></head><body></body></html>
|
||||
@@ -1,164 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Community Builder
|
||||
* @subpackage mod_cblogin
|
||||
*
|
||||
* @copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*
|
||||
* Mobile responsive override for mod_cblogin module
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
// Ensure module language file is loaded
|
||||
$lang = Factory::getLanguage();
|
||||
$lang->load('mod_cblogin', JPATH_SITE);
|
||||
|
||||
$moduleclass_sfx = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
|
||||
// Add responsive wrapper class
|
||||
$wrapperClass = 'mod-cblogin mod-cblogin-responsive ' . $moduleclass_sfx;
|
||||
?>
|
||||
|
||||
<div class="<?php echo $wrapperClass; ?>">
|
||||
<?php if ($type === 'logout') : ?>
|
||||
<div class="mod-cblogin__logout">
|
||||
<?php if ($params->get('greeting', 1)) : ?>
|
||||
<div class="mod-cblogin__greeting">
|
||||
<?php if ($cbUser) : ?>
|
||||
<div class="mod-cblogin__avatar">
|
||||
<?php echo $cbUser->getField('avatar', null, 'html', 'none', 'list'); ?>
|
||||
</div>
|
||||
<div class="mod-cblogin__user-info">
|
||||
<span class="mod-cblogin__username">
|
||||
<?php echo htmlspecialchars($cbUser->getField('formatname', null, 'html', 'none', 'list'), ENT_COMPAT, 'UTF-8'); ?>
|
||||
</span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="<?php echo $action; ?>" method="post" class="mod-cblogin__form">
|
||||
<div class="mod-cblogin__actions">
|
||||
<?php if ($params->get('profileLink', 1) && $cbUser) : ?>
|
||||
<a href="<?php echo $cbUser->getField('canvas', null, 'html', 'none', 'profile'); ?>" class="mod-cblogin__btn mod-cblogin__btn--profile btn btn-secondary">
|
||||
<span class="icon-user" aria-hidden="true"></span>
|
||||
<?php echo Text::_('MOD_CBLOGIN_PROFILE'); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<button type="submit" name="Submit" class="mod-cblogin__btn mod-cblogin__btn--logout btn btn-primary">
|
||||
<span class="icon-sign-out" aria-hidden="true"></span>
|
||||
<?php echo Text::_('MOD_CBLOGIN_LOGOUT'); ?>
|
||||
</button>
|
||||
</div>
|
||||
<input type="hidden" name="op2" value="logout" />
|
||||
<input type="hidden" name="lang" value="<?php echo $lang; ?>" />
|
||||
<input type="hidden" name="return" value="<?php echo $return; ?>" />
|
||||
<?php echo $securityToken; ?>
|
||||
</form>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<form action="<?php echo $action; ?>" method="post" name="login<?php echo $moduleId; ?>" id="login<?php echo $moduleId; ?>" class="mod-cblogin__form mod-cblogin__form--login">
|
||||
<?php if ($params->get('pretext')) : ?>
|
||||
<div class="mod-cblogin__pretext">
|
||||
<?php echo $params->get('pretext'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="mod-cblogin__fields">
|
||||
<div class="mod-cblogin__field">
|
||||
<label for="modloginusername<?php echo $moduleId; ?>" class="mod-cblogin__label">
|
||||
<?php echo Text::_('MOD_CBLOGIN_USERNAME'); ?>
|
||||
</label>
|
||||
<input
|
||||
id="modloginusername<?php echo $moduleId; ?>"
|
||||
type="text"
|
||||
name="username"
|
||||
class="mod-cblogin__input form-control"
|
||||
placeholder="<?php echo Text::_('MOD_CBLOGIN_USERNAME'); ?>"
|
||||
autocomplete="username"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mod-cblogin__field">
|
||||
<label for="modloginpass<?php echo $moduleId; ?>" class="mod-cblogin__label">
|
||||
<?php echo Text::_('MOD_CBLOGIN_PASSWORD'); ?>
|
||||
</label>
|
||||
<input
|
||||
id="modloginpass<?php echo $moduleId; ?>"
|
||||
type="password"
|
||||
name="passwd"
|
||||
class="mod-cblogin__input form-control"
|
||||
placeholder="<?php echo Text::_('MOD_CBLOGIN_PASSWORD'); ?>"
|
||||
autocomplete="current-password"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<?php if ($params->get('remember_me', 1)) : ?>
|
||||
<div class="mod-cblogin__remember">
|
||||
<input
|
||||
id="modloginrememberme<?php echo $moduleId; ?>"
|
||||
type="checkbox"
|
||||
name="remember"
|
||||
class="mod-cblogin__checkbox"
|
||||
value="yes"
|
||||
/>
|
||||
<label for="modloginrememberme<?php echo $moduleId; ?>" class="mod-cblogin__remember-label">
|
||||
<?php echo Text::_('MOD_CBLOGIN_REMEMBER_ME'); ?>
|
||||
</label>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="mod-cblogin__actions">
|
||||
<button type="submit" name="Submit" class="mod-cblogin__btn mod-cblogin__btn--submit btn btn-primary">
|
||||
<span class="icon-sign-in" aria-hidden="true"></span>
|
||||
<?php echo Text::_('MOD_CBLOGIN_LOGIN'); ?>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mod-cblogin__links">
|
||||
<?php if ($params->get('lostpassword_link', 1)) : ?>
|
||||
<a href="<?php echo $lostPasswordLink; ?>" class="mod-cblogin__link">
|
||||
<?php echo Text::_('MOD_CBLOGIN_FORGOT_PASSWORD'); ?>
|
||||
<span class="icon-chevron-right" aria-hidden="true"></span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('lostusername_link', 1)) : ?>
|
||||
<a href="<?php echo $lostUsernameLink; ?>" class="mod-cblogin__link">
|
||||
<?php echo Text::_('MOD_CBLOGIN_FORGOT_USERNAME'); ?>
|
||||
<span class="icon-chevron-right" aria-hidden="true"></span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('registration_link', 1)) : ?>
|
||||
<a href="<?php echo $registrationLink; ?>" class="mod-cblogin__link">
|
||||
<?php echo Text::_('MOD_CBLOGIN_REGISTER'); ?>
|
||||
<span class="icon-chevron-right" aria-hidden="true"></span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($params->get('posttext')) : ?>
|
||||
<div class="mod-cblogin__posttext">
|
||||
<?php echo $params->get('posttext'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<input type="hidden" name="op2" value="login" />
|
||||
<input type="hidden" name="lang" value="<?php echo $lang; ?>" />
|
||||
<input type="hidden" name="return" value="<?php echo $return; ?>" />
|
||||
<input type="hidden" name="message" value="0" />
|
||||
<input type="hidden" name="loginfrom" value="loginmodule" />
|
||||
<?php echo $securityToken; ?>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><html><head><title></title></head><body></body></html>
|
||||
@@ -1,99 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Community Builder
|
||||
* @subpackage mod_comprofilerOnline
|
||||
*
|
||||
* @copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*
|
||||
* Mobile responsive override for mod_comprofilerOnline module
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
// Ensure module language file is loaded
|
||||
$lang = Factory::getLanguage();
|
||||
$lang->load('mod_comprofilerOnline', JPATH_SITE);
|
||||
|
||||
$moduleclass_sfx = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
|
||||
// Add responsive wrapper class
|
||||
$wrapperClass = 'mod-cb-online mod-cb-online-responsive ' . $moduleclass_sfx;
|
||||
?>
|
||||
|
||||
<div class="<?php echo $wrapperClass; ?>">
|
||||
<?php if (!empty($onlineUsers)) : ?>
|
||||
<div class="mod-cb-online__stats">
|
||||
<div class="mod-cb-online__count">
|
||||
<span class="mod-cb-online__count-number"><?php echo $totalOnline; ?></span>
|
||||
<span class="mod-cb-online__count-label">
|
||||
<?php echo $totalOnline == 1 ? Text::_('MOD_CB_ONLINE_USER') : Text::_('MOD_CB_ONLINE_USERS'); ?>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<?php if ($params->get('show_guest_count', 1)) : ?>
|
||||
<div class="mod-cb-online__breakdown">
|
||||
<span class="mod-cb-online__breakdown-item">
|
||||
<span class="icon-users" aria-hidden="true"></span>
|
||||
<?php echo $membersOnline; ?> <?php echo Text::_('MOD_CB_ONLINE_MEMBERS'); ?>
|
||||
</span>
|
||||
<span class="mod-cb-online__breakdown-item">
|
||||
<span class="icon-eye" aria-hidden="true"></span>
|
||||
<?php echo $guestsOnline; ?> <?php echo Text::_('MOD_CB_ONLINE_GUESTS'); ?>
|
||||
</span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($params->get('show_user_list', 1) && !empty($onlineUsers)) : ?>
|
||||
<div class="mod-cb-online__users">
|
||||
<h<?php echo $params->get('header_level', 3); ?> class="mod-cb-online__heading">
|
||||
<?php echo Text::_('MOD_CB_ONLINE_WHO_IS_ONLINE'); ?>
|
||||
</h<?php echo $params->get('header_level', 3); ?>>
|
||||
|
||||
<ul class="mod-cb-online__list">
|
||||
<?php foreach ($onlineUsers as $user) : ?>
|
||||
<li class="mod-cb-online__user">
|
||||
<?php if ($params->get('show_avatar', 1) && !empty($user->avatar)) : ?>
|
||||
<div class="mod-cb-online__avatar">
|
||||
<?php echo $user->avatar; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="mod-cb-online__info">
|
||||
<?php if ($params->get('link_names', 1) && !empty($user->link)) : ?>
|
||||
<a href="<?php echo $user->link; ?>" class="mod-cb-online__name">
|
||||
<?php echo htmlspecialchars($user->name, ENT_COMPAT, 'UTF-8'); ?>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<span class="mod-cb-online__name">
|
||||
<?php echo htmlspecialchars($user->name, ENT_COMPAT, 'UTF-8'); ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('show_status', 1) && !empty($user->status)) : ?>
|
||||
<span class="mod-cb-online__status">
|
||||
<?php echo htmlspecialchars($user->status, ENT_COMPAT, 'UTF-8'); ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($params->get('show_online_icon', 1)) : ?>
|
||||
<span class="mod-cb-online__indicator" title="<?php echo Text::_('MOD_CB_ONLINE_NOW'); ?>">
|
||||
<span class="icon-checkmark" aria-hidden="true"></span>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php else : ?>
|
||||
<div class="mod-cb-online__empty">
|
||||
<p><?php echo Text::_('MOD_CB_ONLINE_NO_USERS'); ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
39
src/html/mod_custom/default.php
Normal file
39
src/html/mod_custom/default.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_custom.
|
||||
* Adds showtitle support and respects all module settings.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
|
||||
$modId = 'mod-custom' . $module->id;
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
|
||||
if ($params->get('backgroundimage')) {
|
||||
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
|
||||
$wa = $app->getDocument()->getWebAssetManager();
|
||||
$wa->addInlineStyle(
|
||||
'#' . $modId . '{background-image: url("' . Uri::root(true) . '/' . HTMLHelper::_('cleanImageURL', $params->get('backgroundimage'))->url . '");}',
|
||||
['name' => $modId]
|
||||
);
|
||||
}
|
||||
?>
|
||||
<div class="mod-custom custom<?php echo $suffix ? ' ' . $suffix : ''; ?>" id="<?php echo $modId; ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-custom__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<?php echo $module->content; ?>
|
||||
</div>
|
||||
@@ -1,12 +1,14 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package Joomla.Site
|
||||
* @subpackage mod_custom
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* @copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Template override for mod_custom adding banner-overlay wrapper pattern.
|
||||
* Based on Cassiopeia's banner layout approach.
|
||||
*/
|
||||
@@ -17,7 +19,9 @@ use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
|
||||
$modId = 'mod-custom' . $module->id;
|
||||
$moduleclass = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
|
||||
if ($params->get('backgroundimage')) {
|
||||
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
|
||||
@@ -28,9 +32,11 @@ if ($params->get('backgroundimage')) {
|
||||
);
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="mod-custom custom banner-overlay custom-hero<?php echo $moduleclass ? ' ' . $moduleclass : ''; ?>" id="<?php echo $modId; ?>">
|
||||
<div class="mod-custom custom banner-overlay custom-hero<?php echo $suffix ? ' ' . $suffix : ''; ?>" id="<?php echo $modId; ?>">
|
||||
<div class="overlay">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-custom__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<?php echo $module->content; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
88
src/html/mod_feed/default.php
Normal file
88
src/html/mod_feed/default.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_feed.
|
||||
* Adds showtitle support.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
|
||||
if (!$feed) {
|
||||
return;
|
||||
}
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
$rssurl = $params->get('rssurl', '');
|
||||
$rsstitle = $params->get('rsstitle', 1);
|
||||
$rssdesc = $params->get('rssrtl', 0) ? ' feed-rtl' : '';
|
||||
$rssimage = $params->get('rssimage', 1);
|
||||
$rssitems = $params->get('rssitems', 5);
|
||||
$rssitemdesc = $params->get('rssitemdesc', 1);
|
||||
$word_count = $params->get('word_count', 0);
|
||||
?>
|
||||
<div class="mod-feed<?php echo $suffix ? ' ' . $suffix : ''; ?><?php echo $rssdesc; ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-feed__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($feed->title && $rsstitle) : ?>
|
||||
<h4 class="mod-feed__feed-title">
|
||||
<?php if (!empty($rssurl)) : ?>
|
||||
<a href="<?php echo htmlspecialchars($rssurl, ENT_COMPAT, 'UTF-8'); ?>" target="_blank" rel="noopener noreferrer">
|
||||
<?php echo $feed->title; ?>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<?php echo $feed->title; ?>
|
||||
<?php endif; ?>
|
||||
</h4>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($feed->description && $rssdesc) : ?>
|
||||
<p class="mod-feed__description"><?php echo $feed->description; ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($rssimage && $feed->image) : ?>
|
||||
<img src="<?php echo $feed->image->uri; ?>" alt="<?php echo $feed->image->title ?? ''; ?>" class="mod-feed__image" />
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($feed->items)) : ?>
|
||||
<ul class="mod-feed__list">
|
||||
<?php for ($i = 0, $max = min(count($feed->items), $rssitems); $i < $max; $i++) :
|
||||
$item = $feed->items[$i];
|
||||
?>
|
||||
<li class="mod-feed__item">
|
||||
<?php if (!empty($item->uri)) : ?>
|
||||
<a href="<?php echo htmlspecialchars($item->uri, ENT_COMPAT, 'UTF-8'); ?>" target="_blank" rel="noopener noreferrer">
|
||||
<?php echo $item->title; ?>
|
||||
</a>
|
||||
<?php else : ?>
|
||||
<?php echo $item->title; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($rssitemdesc && !empty($item->content)) :
|
||||
$desc = $item->content;
|
||||
if ($word_count) {
|
||||
$words = explode(' ', strip_tags($desc));
|
||||
if (count($words) > $word_count) {
|
||||
$desc = implode(' ', array_slice($words, 0, $word_count)) . '…';
|
||||
}
|
||||
}
|
||||
?>
|
||||
<p class="mod-feed__item-description"><?php echo $desc; ?></p>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php endfor; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
76
src/html/mod_feed/index.html
Normal file
76
src/html/mod_feed/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
85
src/html/mod_finder/default.php
Normal file
85
src/html/mod_finder/default.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_finder (Smart Search).
|
||||
* Bootstrap 5 search form with showtitle support.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
// Load component language for search labels
|
||||
$lang = $app->getLanguage();
|
||||
$lang->load('com_finder', JPATH_SITE);
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
|
||||
$showLabel = $params->get('show_label', 1);
|
||||
$labelClass = (!$showLabel ? 'visually-hidden ' : '') . 'finder';
|
||||
|
||||
Text::script('MOD_FINDER_SEARCH_VALUE');
|
||||
|
||||
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
|
||||
$wa = $app->getDocument()->getWebAssetManager();
|
||||
$wa->getRegistry()->addExtensionRegistryFile('com_finder');
|
||||
|
||||
if ($params->get('show_autosuggest', 1)) {
|
||||
$wa->usePreset('awesomplete');
|
||||
$app->getDocument()->addScriptOptions('finder-search', ['url' => Route::_('index.php?option=com_finder&task=suggestions.suggest&format=json&tmpl=component', false)]);
|
||||
Text::script('COM_FINDER_SEARCH_FORM_LIST_LABEL');
|
||||
Text::script('JLIB_JS_AJAX_ERROR_OTHER');
|
||||
Text::script('JLIB_JS_AJAX_ERROR_PARSE');
|
||||
}
|
||||
|
||||
$wa->useScript('com_finder.finder');
|
||||
?>
|
||||
<div class="mod-finder<?php echo $suffix ? ' ' . $suffix : ''; ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-finder__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<form class="mod-finder__form js-finder-searchform form-search" action="<?php echo Route::_($route); ?>" method="get" role="search">
|
||||
<label for="mod-finder-searchword<?php echo $module->id; ?>" class="<?php echo $labelClass; ?>">
|
||||
<?php echo $params->get('alt_label', Text::_('JSEARCH_FILTER_SUBMIT')); ?>
|
||||
</label>
|
||||
<div class="input-group">
|
||||
<input type="text" name="q" id="mod-finder-searchword<?php echo $module->id; ?>"
|
||||
class="js-finder-search-query form-control"
|
||||
value="<?php echo htmlspecialchars($app->getInput()->get('q', '', 'string'), ENT_COMPAT, 'UTF-8'); ?>"
|
||||
placeholder="<?php echo Text::_('MOD_FINDER_SEARCH_VALUE'); ?>">
|
||||
<?php if ($params->get('show_button', 0)) : ?>
|
||||
<button class="btn btn-primary" type="submit">
|
||||
<span class="fa-solid fa-magnifying-glass" aria-hidden="true"></span>
|
||||
<span class="visually-hidden"><?php echo Text::_('JSEARCH_FILTER_SUBMIT'); ?></span>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php $show_advanced = $params->get('show_advanced', 0); ?>
|
||||
<?php if ($show_advanced == 2) : ?>
|
||||
<a href="<?php echo Route::_($route); ?>" class="mod-finder__advanced-link mt-2 d-inline-block">
|
||||
<?php echo Text::_('COM_FINDER_ADVANCED_SEARCH'); ?>
|
||||
</a>
|
||||
<?php elseif ($show_advanced == 1) : ?>
|
||||
<div class="mod-finder__advanced js-finder-advanced mt-2">
|
||||
<?php echo HTMLHelper::_('filter.select', $query, $params); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
$finderHelper = $app->bootModule('mod_finder', 'site')->getHelper('FinderHelper');
|
||||
echo $finderHelper->getHiddenFields($route);
|
||||
?>
|
||||
</form>
|
||||
</div>
|
||||
76
src/html/mod_finder/index.html
Normal file
76
src/html/mod_finder/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
29
src/html/mod_footer/default.php
Normal file
29
src/html/mod_footer/default.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_footer.
|
||||
* Adds showtitle support and respects module settings.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
?>
|
||||
<div class="mod-footer<?php echo $suffix ? ' ' . $suffix : ''; ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-footer__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<div class="mod-footer__line1"><?php echo $lineone; ?></div>
|
||||
<div class="mod-footer__line2"><?php echo Text::_('MOD_FOOTER_LINE2'); ?></div>
|
||||
</div>
|
||||
76
src/html/mod_footer/index.html
Normal file
76
src/html/mod_footer/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><html><head><title></title></head><body></body></html>
|
||||
@@ -1,110 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package HikaShop
|
||||
* @subpackage mod_hikashop_cart
|
||||
*
|
||||
* @copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*
|
||||
* Mobile responsive override for mod_hikashop_cart module
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
$moduleclass_sfx = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
|
||||
// Add responsive wrapper class
|
||||
$wrapperClass = 'mod-hikashop-cart mod-hikashop-cart-responsive ' . $moduleclass_sfx;
|
||||
?>
|
||||
|
||||
<div class="<?php echo $wrapperClass; ?>" id="hikashop_cart_module<?php echo $params->get('id'); ?>">
|
||||
<?php if (!empty($cart->products)) : ?>
|
||||
<div class="mod-hikashop-cart__header">
|
||||
<span class="mod-hikashop-cart__icon" aria-hidden="true">
|
||||
<span class="icon-basket"></span>
|
||||
</span>
|
||||
<div class="mod-hikashop-cart__summary">
|
||||
<div class="mod-hikashop-cart__count">
|
||||
<?php echo count($cart->products); ?>
|
||||
<?php echo count($cart->products) == 1 ? Text::_('ITEM') : Text::_('ITEMS'); ?>
|
||||
</div>
|
||||
<?php if (!empty($cart->total)) : ?>
|
||||
<div class="mod-hikashop-cart__total">
|
||||
<?php echo $cart->total->price_value_with_tax_formated; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($params->get('show_products', 1)) : ?>
|
||||
<div class="mod-hikashop-cart__products">
|
||||
<?php foreach ($cart->products as $product) : ?>
|
||||
<div class="mod-hikashop-cart__product">
|
||||
<?php if (!empty($product->images[0]) && $params->get('show_image', 1)) : ?>
|
||||
<div class="mod-hikashop-cart__product-image">
|
||||
<img src="<?php echo $product->images[0]->file_path; ?>"
|
||||
alt="<?php echo htmlspecialchars($product->product_name, ENT_COMPAT, 'UTF-8'); ?>" />
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="mod-hikashop-cart__product-details">
|
||||
<div class="mod-hikashop-cart__product-name">
|
||||
<?php echo htmlspecialchars($product->product_name, ENT_COMPAT, 'UTF-8'); ?>
|
||||
</div>
|
||||
|
||||
<div class="mod-hikashop-cart__product-quantity">
|
||||
<?php echo Text::_('QUANTITY'); ?>:
|
||||
<span class="mod-hikashop-cart__quantity-value"><?php echo $product->cart_product_quantity; ?></span>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($product->prices[0])) : ?>
|
||||
<div class="mod-hikashop-cart__product-price">
|
||||
<?php echo $product->prices[0]->price_value_with_tax_formated; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($params->get('show_delete', 1)) : ?>
|
||||
<div class="mod-hikashop-cart__product-remove">
|
||||
<a href="#"
|
||||
class="mod-hikashop-cart__remove-btn hikashop_cart_product_delete"
|
||||
data-product-id="<?php echo $product->product_id; ?>"
|
||||
title="<?php echo Text::_('HIKA_DELETE'); ?>"
|
||||
aria-label="<?php echo Text::_('HIKA_DELETE') . ' ' . htmlspecialchars($product->product_name, ENT_COMPAT, 'UTF-8'); ?>">
|
||||
<span class="icon-remove" aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="mod-hikashop-cart__actions">
|
||||
<?php if ($params->get('show_cart_button', 1)) : ?>
|
||||
<a href="<?php echo hikashop_completeLink('cart'); ?>"
|
||||
class="mod-hikashop-cart__btn mod-hikashop-cart__btn--view btn btn-secondary">
|
||||
<?php echo Text::_('HIKASHOP_CART_VIEW'); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('show_checkout_button', 1)) : ?>
|
||||
<a href="<?php echo hikashop_completeLink('checkout'); ?>"
|
||||
class="mod-hikashop-cart__btn mod-hikashop-cart__btn--checkout btn btn-primary">
|
||||
<?php echo Text::_('HIKASHOP_CHECKOUT'); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<div class="mod-hikashop-cart__empty">
|
||||
<span class="mod-hikashop-cart__empty-icon" aria-hidden="true">
|
||||
<span class="icon-basket"></span>
|
||||
</span>
|
||||
<p class="mod-hikashop-cart__empty-text">
|
||||
<?php echo Text::_('HIKASHOP_CART_EMPTY'); ?>
|
||||
</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><html><head><title></title></head><body></body></html>
|
||||
@@ -1,112 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package K2
|
||||
* @subpackage mod_k2_content
|
||||
*
|
||||
* @copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*
|
||||
* Mobile responsive override for mod_k2_content module
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
$moduleclass_sfx = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
|
||||
// Add responsive wrapper class
|
||||
$wrapperClass = 'mod-k2-content mod-k2-content-responsive ' . $moduleclass_sfx;
|
||||
?>
|
||||
|
||||
<div class="<?php echo $wrapperClass; ?>">
|
||||
<?php if (count($items)) : ?>
|
||||
<ul class="mod-k2-content__list">
|
||||
<?php foreach ($items as $key => $item) : ?>
|
||||
<li class="mod-k2-content__item">
|
||||
<?php if ($params->get('itemImage') && !empty($item->imageXSmall)) : ?>
|
||||
<div class="mod-k2-content__image">
|
||||
<a href="<?php echo $item->link; ?>" title="<?php echo htmlspecialchars($item->title, ENT_COMPAT, 'UTF-8'); ?>">
|
||||
<img src="<?php echo $item->imageXSmall; ?>" alt="<?php echo htmlspecialchars($item->title, ENT_COMPAT, 'UTF-8'); ?>" />
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="mod-k2-content__content">
|
||||
<?php if ($params->get('itemTitle')) : ?>
|
||||
<h<?php echo $params->get('item_heading', 4); ?> class="mod-k2-content__title">
|
||||
<a href="<?php echo $item->link; ?>">
|
||||
<?php echo htmlspecialchars($item->title, ENT_COMPAT, 'UTF-8'); ?>
|
||||
</a>
|
||||
</h<?php echo $params->get('item_heading', 4); ?>>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('itemAuthor') || $params->get('itemDateCreated') || $params->get('itemCategory') || $params->get('itemHits')) : ?>
|
||||
<div class="mod-k2-content__meta">
|
||||
<?php if ($params->get('itemAuthor')) : ?>
|
||||
<span class="mod-k2-content__author">
|
||||
<span class="icon-user" aria-hidden="true"></span>
|
||||
<?php echo $item->author; ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('itemDateCreated')) : ?>
|
||||
<span class="mod-k2-content__date">
|
||||
<span class="icon-calendar" aria-hidden="true"></span>
|
||||
<time datetime="<?php echo HTMLHelper::_('date', $item->created, 'c'); ?>">
|
||||
<?php echo HTMLHelper::_('date', $item->created, Text::_('DATE_FORMAT_LC3')); ?>
|
||||
</time>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('itemCategory')) : ?>
|
||||
<span class="mod-k2-content__category">
|
||||
<span class="icon-folder" aria-hidden="true"></span>
|
||||
<a href="<?php echo $item->categoryLink; ?>">
|
||||
<?php echo $item->categoryname; ?>
|
||||
</a>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('itemHits')) : ?>
|
||||
<span class="mod-k2-content__hits">
|
||||
<span class="icon-eye" aria-hidden="true"></span>
|
||||
<?php echo $item->hits; ?> <?php echo Text::_('MOD_K2_CONTENT_HITS'); ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('itemIntroText') && !empty($item->introtext)) : ?>
|
||||
<div class="mod-k2-content__intro">
|
||||
<?php echo $item->introtext; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('itemReadMore')) : ?>
|
||||
<div class="mod-k2-content__readmore">
|
||||
<a href="<?php echo $item->link; ?>" class="mod-k2-content__readmore-link btn btn-secondary">
|
||||
<?php echo Text::_('MOD_K2_CONTENT_READ_MORE'); ?>
|
||||
<span class="icon-chevron-right" aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
|
||||
<?php if ($params->get('itemCustomLink')) : ?>
|
||||
<div class="mod-k2-content__custom-link">
|
||||
<a href="<?php echo $params->get('itemCustomLinkURL'); ?>" class="btn btn-primary">
|
||||
<?php echo $params->get('itemCustomLinkTitle'); ?>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php else : ?>
|
||||
<div class="mod-k2-content__empty">
|
||||
<p><?php echo Text::_('MOD_K2_CONTENT_NO_ITEMS'); ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><html><head><title></title></head><body></body></html>
|
||||
@@ -1,110 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Kunena
|
||||
* @subpackage mod_kunenalatest
|
||||
*
|
||||
* @copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*
|
||||
* Mobile responsive override for mod_kunenalatest module
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
$moduleclass_sfx = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
|
||||
// Add responsive wrapper class
|
||||
$wrapperClass = 'mod-kunena-latest mod-kunena-latest-responsive ' . $moduleclass_sfx;
|
||||
?>
|
||||
|
||||
<div class="<?php echo $wrapperClass; ?>">
|
||||
<?php if (!empty($posts)) : ?>
|
||||
<ul class="mod-kunena-latest__list">
|
||||
<?php foreach ($posts as $post) : ?>
|
||||
<li class="mod-kunena-latest__item">
|
||||
<?php if ($params->get('sh_userpic', 1) && !empty($post->getAuthor()->getAvatarImage())) : ?>
|
||||
<div class="mod-kunena-latest__avatar">
|
||||
<?php echo $post->getAuthor()->getAvatarImage('', 40, 40); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="mod-kunena-latest__content">
|
||||
<?php if ($params->get('sh_topic', 1)) : ?>
|
||||
<h<?php echo $params->get('header_level', 4); ?> class="mod-kunena-latest__title">
|
||||
<a href="<?php echo $post->getUrl(); ?>">
|
||||
<?php echo htmlspecialchars($post->subject, ENT_COMPAT, 'UTF-8'); ?>
|
||||
</a>
|
||||
</h<?php echo $params->get('header_level', 4); ?>>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="mod-kunena-latest__meta">
|
||||
<?php if ($params->get('sh_username', 1)) : ?>
|
||||
<span class="mod-kunena-latest__author">
|
||||
<span class="icon-user" aria-hidden="true"></span>
|
||||
<a href="<?php echo $post->getAuthor()->getLink(); ?>">
|
||||
<?php echo $post->getAuthor()->getName(); ?>
|
||||
</a>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('sh_time', 1)) : ?>
|
||||
<span class="mod-kunena-latest__date">
|
||||
<span class="icon-clock" aria-hidden="true"></span>
|
||||
<time datetime="<?php echo HTMLHelper::_('date', $post->time, 'c'); ?>">
|
||||
<?php echo $post->getTime(); ?>
|
||||
</time>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('sh_category', 1)) : ?>
|
||||
<span class="mod-kunena-latest__category">
|
||||
<span class="icon-folder" aria-hidden="true"></span>
|
||||
<a href="<?php echo $post->getCategory()->getUrl(); ?>">
|
||||
<?php echo $post->getCategory()->name; ?>
|
||||
</a>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('sh_hits', 0)) : ?>
|
||||
<span class="mod-kunena-latest__hits">
|
||||
<span class="icon-eye" aria-hidden="true"></span>
|
||||
<?php echo $post->getTopic()->hits; ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('sh_replies', 0)) : ?>
|
||||
<span class="mod-kunena-latest__replies">
|
||||
<span class="icon-comments" aria-hidden="true"></span>
|
||||
<?php echo $post->getTopic()->getReplies(); ?>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($params->get('sh_text', 0) && !empty($post->message)) : ?>
|
||||
<div class="mod-kunena-latest__excerpt">
|
||||
<?php echo KunenaHtmlParser::parseBBCode($post->message, $params->get('txt_len', 50)); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
|
||||
<?php if ($params->get('more_link', 1)) : ?>
|
||||
<div class="mod-kunena-latest__more">
|
||||
<a href="<?php echo KunenaRoute::_('index.php?option=com_kunena'); ?>"
|
||||
class="mod-kunena-latest__more-link btn btn-secondary">
|
||||
<?php echo Text::_('MOD_KUNENALATEST_MORE'); ?>
|
||||
<span class="icon-chevron-right" aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php else : ?>
|
||||
<div class="mod-kunena-latest__empty">
|
||||
<p><?php echo Text::_('MOD_KUNENALATEST_NO_POSTS'); ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><html><head><title></title></head><body></body></html>
|
||||
@@ -1,187 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Kunena
|
||||
* @subpackage mod_kunenalogin
|
||||
*
|
||||
* @copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*
|
||||
* Mobile responsive override for mod_kunenalogin module
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
$moduleclass_sfx = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
|
||||
// Add responsive wrapper class
|
||||
$wrapperClass = 'mod-kunena-login mod-kunena-login-responsive ' . $moduleclass_sfx;
|
||||
?>
|
||||
|
||||
<div class="<?php echo $wrapperClass; ?>">
|
||||
<?php if ($kunena_my->exists()) : ?>
|
||||
<!-- Logged in state -->
|
||||
<div class="mod-kunena-login__profile">
|
||||
<?php if ($params->get('showAvatar', 1)) : ?>
|
||||
<div class="mod-kunena-login__avatar">
|
||||
<?php echo $kunena_my->getAvatarImage('', 60, 60); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="mod-kunena-login__user-info">
|
||||
<div class="mod-kunena-login__username">
|
||||
<a href="<?php echo $kunena_my->getURL(); ?>">
|
||||
<?php echo $kunena_my->getName(); ?>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<?php if ($params->get('showRank', 1) && !empty($kunena_my->getRank())) : ?>
|
||||
<div class="mod-kunena-login__rank">
|
||||
<?php echo $kunena_my->getRank(); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($params->get('showStats', 1)) : ?>
|
||||
<div class="mod-kunena-login__stats">
|
||||
<div class="mod-kunena-login__stat">
|
||||
<span class="mod-kunena-login__stat-label"><?php echo Text::_('MOD_KUNENALOGIN_POSTS'); ?>:</span>
|
||||
<span class="mod-kunena-login__stat-value"><?php echo $kunena_my->posts; ?></span>
|
||||
</div>
|
||||
|
||||
<?php if ($params->get('showKarma', 0) && isset($kunena_my->karma)) : ?>
|
||||
<div class="mod-kunena-login__stat">
|
||||
<span class="mod-kunena-login__stat-label"><?php echo Text::_('MOD_KUNENALOGIN_KARMA'); ?>:</span>
|
||||
<span class="mod-kunena-login__stat-value"><?php echo $kunena_my->karma; ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="mod-kunena-login__actions">
|
||||
<?php if ($params->get('showProfile', 1)) : ?>
|
||||
<a href="<?php echo $kunena_my->getURL(); ?>" class="mod-kunena-login__btn btn btn-secondary">
|
||||
<span class="icon-user" aria-hidden="true"></span>
|
||||
<?php echo Text::_('MOD_KUNENALOGIN_PROFILE'); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('showMessages', 1)) : ?>
|
||||
<a href="<?php echo KunenaRoute::_('index.php?option=com_kunena&view=user&layout=messages'); ?>"
|
||||
class="mod-kunena-login__btn btn btn-secondary">
|
||||
<span class="icon-envelope" aria-hidden="true"></span>
|
||||
<?php echo Text::_('MOD_KUNENALOGIN_PRIVATE_MESSAGES'); ?>
|
||||
<?php if (!empty($private_messages)) : ?>
|
||||
<span class="mod-kunena-login__badge"><?php echo $private_messages; ?></span>
|
||||
<?php endif; ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<form action="<?php echo Route::_('index.php', true); ?>" method="post" class="mod-kunena-login__logout-form">
|
||||
<button type="submit" class="mod-kunena-login__btn mod-kunena-login__btn--logout btn btn-primary">
|
||||
<span class="icon-sign-out" aria-hidden="true"></span>
|
||||
<?php echo Text::_('MOD_KUNENALOGIN_LOGOUT'); ?>
|
||||
</button>
|
||||
<input type="hidden" name="option" value="com_users" />
|
||||
<input type="hidden" name="task" value="user.logout" />
|
||||
<input type="hidden" name="return" value="<?php echo $return; ?>" />
|
||||
<?php echo JHtml::_('form.token'); ?>
|
||||
</form>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<!-- Login form -->
|
||||
<form action="<?php echo Route::_('index.php', true); ?>" method="post" class="mod-kunena-login__form">
|
||||
<?php if ($params->get('pretext')) : ?>
|
||||
<div class="mod-kunena-login__pretext">
|
||||
<?php echo $params->get('pretext'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="mod-kunena-login__fields">
|
||||
<div class="mod-kunena-login__field">
|
||||
<label for="kunena-login-username-<?php echo $module->id; ?>" class="mod-kunena-login__label">
|
||||
<?php echo Text::_('MOD_KUNENALOGIN_USERNAME'); ?>
|
||||
</label>
|
||||
<input
|
||||
id="kunena-login-username-<?php echo $module->id; ?>"
|
||||
type="text"
|
||||
name="username"
|
||||
class="mod-kunena-login__input form-control"
|
||||
placeholder="<?php echo Text::_('MOD_KUNENALOGIN_USERNAME'); ?>"
|
||||
autocomplete="username"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="mod-kunena-login__field">
|
||||
<label for="kunena-login-password-<?php echo $module->id; ?>" class="mod-kunena-login__label">
|
||||
<?php echo Text::_('MOD_KUNENALOGIN_PASSWORD'); ?>
|
||||
</label>
|
||||
<input
|
||||
id="kunena-login-password-<?php echo $module->id; ?>"
|
||||
type="password"
|
||||
name="password"
|
||||
class="mod-kunena-login__input form-control"
|
||||
placeholder="<?php echo Text::_('MOD_KUNENALOGIN_PASSWORD'); ?>"
|
||||
autocomplete="current-password"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<?php if ($params->get('showRememberMe', 1)) : ?>
|
||||
<div class="mod-kunena-login__remember">
|
||||
<input
|
||||
id="kunena-login-remember-<?php echo $module->id; ?>"
|
||||
type="checkbox"
|
||||
name="remember"
|
||||
class="mod-kunena-login__checkbox"
|
||||
value="yes"
|
||||
/>
|
||||
<label for="kunena-login-remember-<?php echo $module->id; ?>" class="mod-kunena-login__remember-label">
|
||||
<?php echo Text::_('MOD_KUNENALOGIN_REMEMBER_ME'); ?>
|
||||
</label>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="mod-kunena-login__actions">
|
||||
<button type="submit" class="mod-kunena-login__btn mod-kunena-login__btn--submit btn btn-primary">
|
||||
<span class="icon-sign-in" aria-hidden="true"></span>
|
||||
<?php echo Text::_('MOD_KUNENALOGIN_LOGIN'); ?>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="mod-kunena-login__links">
|
||||
<?php if ($params->get('showRegister', 1) && $usersConfig->get('allowUserRegistration')) : ?>
|
||||
<a href="<?php echo Route::_('index.php?option=com_users&view=registration'); ?>"
|
||||
class="mod-kunena-login__link">
|
||||
<?php echo Text::_('MOD_KUNENALOGIN_REGISTER'); ?>
|
||||
<span class="icon-chevron-right" aria-hidden="true"></span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('showForgot', 1)) : ?>
|
||||
<a href="<?php echo Route::_('index.php?option=com_users&view=reset'); ?>"
|
||||
class="mod-kunena-login__link">
|
||||
<?php echo Text::_('MOD_KUNENALOGIN_FORGOT_PASSWORD'); ?>
|
||||
<span class="icon-chevron-right" aria-hidden="true"></span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($params->get('posttext')) : ?>
|
||||
<div class="mod-kunena-login__posttext">
|
||||
<?php echo $params->get('posttext'); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<input type="hidden" name="option" value="com_users" />
|
||||
<input type="hidden" name="task" value="user.login" />
|
||||
<input type="hidden" name="return" value="<?php echo $return; ?>" />
|
||||
<?php echo JHtml::_('form.token'); ?>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><html><head><title></title></head><body></body></html>
|
||||
@@ -1,74 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Kunena
|
||||
* @subpackage mod_kunenasearch
|
||||
*
|
||||
* @copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*
|
||||
* Mobile responsive override for mod_kunenasearch module
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
$moduleclass_sfx = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
|
||||
// Add responsive wrapper class
|
||||
$wrapperClass = 'mod-kunena-search mod-kunena-search-responsive ' . $moduleclass_sfx;
|
||||
|
||||
$button_pos = $params->get('button_pos', 'right');
|
||||
$button_text = $params->get('button_text', '');
|
||||
?>
|
||||
|
||||
<div class="<?php echo $wrapperClass; ?>">
|
||||
<form action="<?php echo KunenaRoute::_('index.php?option=com_kunena&view=search'); ?>"
|
||||
method="post"
|
||||
class="mod-kunena-search__form mod-kunena-search__form--button-<?php echo $button_pos; ?>">
|
||||
|
||||
<?php if ($button_pos === 'top' || $button_pos === 'left') : ?>
|
||||
<div class="mod-kunena-search__button-wrapper mod-kunena-search__button-wrapper--<?php echo $button_pos; ?>">
|
||||
<button type="submit" class="mod-kunena-search__button btn btn-primary">
|
||||
<?php if ($button_text) : ?>
|
||||
<?php echo htmlspecialchars($button_text, ENT_COMPAT, 'UTF-8'); ?>
|
||||
<?php else : ?>
|
||||
<span class="icon-search" aria-hidden="true"></span>
|
||||
<span class="visually-hidden"><?php echo Text::_('MOD_KUNENASEARCH_SEARCH'); ?></span>
|
||||
<?php endif; ?>
|
||||
</button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="mod-kunena-search__input-wrapper">
|
||||
<label for="mod-kunena-search-<?php echo $module->id; ?>" class="visually-hidden">
|
||||
<?php echo Text::_('MOD_KUNENASEARCH_SEARCH_FORUM'); ?>
|
||||
</label>
|
||||
<input
|
||||
type="search"
|
||||
name="q"
|
||||
id="mod-kunena-search-<?php echo $module->id; ?>"
|
||||
class="mod-kunena-search__input form-control"
|
||||
placeholder="<?php echo Text::_('MOD_KUNENASEARCH_SEARCH_FORUM'); ?>"
|
||||
aria-label="<?php echo Text::_('MOD_KUNENASEARCH_SEARCH_FORUM'); ?>"
|
||||
value="<?php echo htmlspecialchars($params->get('default_value', ''), ENT_COMPAT, 'UTF-8'); ?>"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<?php if ($button_pos === 'bottom' || $button_pos === 'right') : ?>
|
||||
<div class="mod-kunena-search__button-wrapper mod-kunena-search__button-wrapper--<?php echo $button_pos; ?>">
|
||||
<button type="submit" class="mod-kunena-search__button btn btn-primary">
|
||||
<?php if ($button_text) : ?>
|
||||
<?php echo htmlspecialchars($button_text, ENT_COMPAT, 'UTF-8'); ?>
|
||||
<?php else : ?>
|
||||
<span class="icon-search" aria-hidden="true"></span>
|
||||
<span class="visually-hidden"><?php echo Text::_('MOD_KUNENASEARCH_SEARCH'); ?></span>
|
||||
<?php endif; ?>
|
||||
</button>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<input type="hidden" name="task" value="results" />
|
||||
<input type="hidden" name="option" value="com_kunena" />
|
||||
</form>
|
||||
</div>
|
||||
@@ -1 +0,0 @@
|
||||
<!DOCTYPE html><html><head><title></title></head><body></body></html>
|
||||
@@ -1,100 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Kunena
|
||||
* @subpackage mod_kunenastats
|
||||
*
|
||||
* @copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
*
|
||||
* Mobile responsive override for mod_kunenastats module
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
|
||||
$moduleclass_sfx = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
|
||||
// Add responsive wrapper class
|
||||
$wrapperClass = 'mod-kunena-stats mod-kunena-stats-responsive ' . $moduleclass_sfx;
|
||||
?>
|
||||
|
||||
<div class="<?php echo $wrapperClass; ?>">
|
||||
<div class="mod-kunena-stats__container">
|
||||
<?php if ($params->get('sh_latestMemberCount', 1)) : ?>
|
||||
<div class="mod-kunena-stats__stat">
|
||||
<div class="mod-kunena-stats__icon">
|
||||
<span class="icon-users" aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class="mod-kunena-stats__content">
|
||||
<div class="mod-kunena-stats__value"><?php echo $kunena_stats->memberCount; ?></div>
|
||||
<div class="mod-kunena-stats__label"><?php echo Text::_('MOD_KUNENASTATS_MEMBERS'); ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('sh_latestMember', 1) && !empty($kunena_stats->latestMember)) : ?>
|
||||
<div class="mod-kunena-stats__stat mod-kunena-stats__stat--latest-member">
|
||||
<div class="mod-kunena-stats__icon">
|
||||
<span class="icon-user-plus" aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class="mod-kunena-stats__content">
|
||||
<div class="mod-kunena-stats__label"><?php echo Text::_('MOD_KUNENASTATS_LATEST_MEMBER'); ?></div>
|
||||
<div class="mod-kunena-stats__value mod-kunena-stats__value--link">
|
||||
<a href="<?php echo $kunena_stats->latestMember->getURL(); ?>">
|
||||
<?php echo $kunena_stats->latestMember->getName(); ?>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('sh_messageCount', 1)) : ?>
|
||||
<div class="mod-kunena-stats__stat">
|
||||
<div class="mod-kunena-stats__icon">
|
||||
<span class="icon-comments" aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class="mod-kunena-stats__content">
|
||||
<div class="mod-kunena-stats__value"><?php echo $kunena_stats->messageCount; ?></div>
|
||||
<div class="mod-kunena-stats__label"><?php echo Text::_('MOD_KUNENASTATS_MESSAGES'); ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('sh_topicCount', 1)) : ?>
|
||||
<div class="mod-kunena-stats__stat">
|
||||
<div class="mod-kunena-stats__icon">
|
||||
<span class="icon-folder-open" aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class="mod-kunena-stats__content">
|
||||
<div class="mod-kunena-stats__value"><?php echo $kunena_stats->topicCount; ?></div>
|
||||
<div class="mod-kunena-stats__label"><?php echo Text::_('MOD_KUNENASTATS_TOPICS'); ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('sh_todayTopicCount', 0)) : ?>
|
||||
<div class="mod-kunena-stats__stat">
|
||||
<div class="mod-kunena-stats__icon">
|
||||
<span class="icon-calendar-check" aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class="mod-kunena-stats__content">
|
||||
<div class="mod-kunena-stats__value"><?php echo $kunena_stats->todayTopicCount; ?></div>
|
||||
<div class="mod-kunena-stats__label"><?php echo Text::_('MOD_KUNENASTATS_TODAY_TOPICS'); ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('sh_yesterdayTopicCount', 0)) : ?>
|
||||
<div class="mod-kunena-stats__stat">
|
||||
<div class="mod-kunena-stats__icon">
|
||||
<span class="icon-calendar" aria-hidden="true"></span>
|
||||
</div>
|
||||
<div class="mod-kunena-stats__content">
|
||||
<div class="mod-kunena-stats__value"><?php echo $kunena_stats->yesterdayTopicCount; ?></div>
|
||||
<div class="mod-kunena-stats__label"><?php echo Text::_('MOD_KUNENASTATS_YESTERDAY_TOPICS'); ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
63
src/html/mod_languages/default.php
Normal file
63
src/html/mod_languages/default.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_languages.
|
||||
* Adds showtitle support.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Uri\Uri;
|
||||
|
||||
if (empty($list)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
?>
|
||||
<div class="mod-languages<?php echo $suffix ? ' ' . $suffix : ''; ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-languages__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<ul class="mod-languages__list">
|
||||
<?php foreach ($list as $language) : ?>
|
||||
<?php $isActive = $language->active ? ' active' : ''; ?>
|
||||
<li class="mod-languages__item<?php echo $isActive; ?>" dir="<?php echo $language->rtl ? 'rtl' : 'ltr'; ?>">
|
||||
<?php if ($language->active) : ?>
|
||||
<span class="mod-languages__link mod-languages__link--active" lang="<?php echo $language->sef; ?>">
|
||||
<?php else : ?>
|
||||
<a class="mod-languages__link" href="<?php echo htmlspecialchars($language->link, ENT_COMPAT, 'UTF-8'); ?>" lang="<?php echo $language->sef; ?>">
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('image', 1)) : ?>
|
||||
<?php if ($language->image) : ?>
|
||||
<?php echo HTMLHelper::_('image', 'mod_languages/' . $language->image . '.gif', '', null, true); ?>
|
||||
<?php else : ?>
|
||||
<span class="mod-languages__badge badge bg-secondary"><?php echo strtoupper($language->sef); ?></span>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('show_name', 1)) : ?>
|
||||
<?php echo $language->title_native; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($language->active) : ?>
|
||||
</span>
|
||||
<?php else : ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
76
src/html/mod_languages/index.html
Normal file
76
src/html/mod_languages/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
126
src/html/mod_login/default.php
Normal file
126
src/html/mod_login/default.php
Normal file
@@ -0,0 +1,126 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_login.
|
||||
* Bootstrap 5 login form with showtitle support.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\HTML\HTMLHelper;
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
Factory::getApplication()->getLanguage()->load('mod_login', JPATH_SITE);
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
?>
|
||||
<div class="mod-login<?php echo $suffix ? ' ' . $suffix : ''; ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-login__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($type === 'logout') : ?>
|
||||
<form action="<?php echo Route::_('index.php', true); ?>" method="post" class="mod-login__form mod-login__form--logout">
|
||||
<?php if ($params->get('greeting', 1)) : ?>
|
||||
<div class="mod-login__greeting">
|
||||
<?php if (!empty($user->name)) : ?>
|
||||
<span class="mod-login__name"><?php echo Text::sprintf('MOD_LOGIN_HINAME', htmlspecialchars($user->name, ENT_COMPAT, 'UTF-8')); ?></span>
|
||||
<?php else : ?>
|
||||
<span class="mod-login__name"><?php echo Text::sprintf('MOD_LOGIN_HINAME', htmlspecialchars($user->username, ENT_COMPAT, 'UTF-8')); ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="mod-login__submit">
|
||||
<button type="submit" name="Submit" class="btn btn-primary w-100"><?php echo Text::_('JLOGOUT'); ?></button>
|
||||
</div>
|
||||
<input type="hidden" name="option" value="com_users">
|
||||
<input type="hidden" name="task" value="user.logout">
|
||||
<input type="hidden" name="return" value="<?php echo $return; ?>">
|
||||
<?php echo HTMLHelper::_('form.token'); ?>
|
||||
</form>
|
||||
<?php else : ?>
|
||||
<form action="<?php echo Route::_('index.php', true); ?>" method="post" class="mod-login__form mod-login__form--login">
|
||||
<?php if ($params->get('pretext')) : ?>
|
||||
<div class="mod-login__pretext"><?php echo $params->get('pretext'); ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="mod-login__field mb-3">
|
||||
<label for="modlgn-username-<?php echo $module->id; ?>" class="form-label visually-hidden"><?php echo Text::_('JGLOBAL_USERNAME'); ?></label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="fa-solid fa-user" aria-hidden="true"></i></span>
|
||||
<input id="modlgn-username-<?php echo $module->id; ?>" type="text" name="username" class="form-control" autocomplete="username" placeholder="<?php echo Text::_('JGLOBAL_USERNAME'); ?>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mod-login__field mb-3">
|
||||
<label for="modlgn-passwd-<?php echo $module->id; ?>" class="form-label visually-hidden"><?php echo Text::_('JGLOBAL_PASSWORD'); ?></label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="fa-solid fa-lock" aria-hidden="true"></i></span>
|
||||
<input id="modlgn-passwd-<?php echo $module->id; ?>" type="password" name="password" class="form-control" autocomplete="current-password" placeholder="<?php echo Text::_('JGLOBAL_PASSWORD'); ?>">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($twofactormethods) && count($twofactormethods) > 1) : ?>
|
||||
<div class="mod-login__field mb-3">
|
||||
<label for="modlgn-secretkey-<?php echo $module->id; ?>" class="form-label visually-hidden"><?php echo Text::_('JGLOBAL_SECRETKEY'); ?></label>
|
||||
<div class="input-group">
|
||||
<span class="input-group-text"><i class="fa-solid fa-shield-halved" aria-hidden="true"></i></span>
|
||||
<input id="modlgn-secretkey-<?php echo $module->id; ?>" type="text" name="secretkey" class="form-control" autocomplete="one-time-code" placeholder="<?php echo Text::_('JGLOBAL_SECRETKEY'); ?>">
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($params->get('remember', 1)) : ?>
|
||||
<div class="mod-login__remember form-check mb-3">
|
||||
<input id="modlgn-remember-<?php echo $module->id; ?>" type="checkbox" name="remember" class="form-check-input" value="yes">
|
||||
<label for="modlgn-remember-<?php echo $module->id; ?>" class="form-check-label"><?php echo Text::_('JGLOBAL_REMEMBER_ME'); ?></label>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="mod-login__submit mb-3">
|
||||
<button type="submit" name="Submit" class="btn btn-primary w-100"><?php echo Text::_('JLOGIN'); ?></button>
|
||||
</div>
|
||||
|
||||
<?php $usersConfig = \Joomla\CMS\Component\ComponentHelper::getParams('com_users'); ?>
|
||||
<ul class="mod-login__options list-unstyled small">
|
||||
<?php if ($usersConfig->get('allowUserRegistration')) : ?>
|
||||
<li>
|
||||
<a href="<?php echo Route::_('index.php?option=com_users&view=registration'); ?>">
|
||||
<?php echo Text::_('MOD_LOGIN_REGISTER'); ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif; ?>
|
||||
<li>
|
||||
<a href="<?php echo Route::_('index.php?option=com_users&view=remind'); ?>">
|
||||
<?php echo Text::_('MOD_LOGIN_FORGOT_YOUR_USERNAME'); ?>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="<?php echo Route::_('index.php?option=com_users&view=reset'); ?>">
|
||||
<?php echo Text::_('MOD_LOGIN_FORGOT_YOUR_PASSWORD'); ?>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<input type="hidden" name="option" value="com_users">
|
||||
<input type="hidden" name="task" value="user.login">
|
||||
<input type="hidden" name="return" value="<?php echo $return; ?>">
|
||||
<?php echo HTMLHelper::_('form.token'); ?>
|
||||
|
||||
<?php if ($params->get('posttext')) : ?>
|
||||
<div class="mod-login__posttext"><?php echo $params->get('posttext'); ?></div>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
76
src/html/mod_login/index.html
Normal file
76
src/html/mod_login/index.html
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
95
src/html/mod_menu/default.php
Normal file
95
src/html/mod_menu/default.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Default layout override for mod_menu.
|
||||
* Simple list menu with showtitle support, suitable for sidebars and footers.
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Helper\ModuleHelper;
|
||||
|
||||
$id = '';
|
||||
|
||||
if ($tagId = $params->get('tag_id', '')) {
|
||||
$id = ' id="' . $tagId . '"';
|
||||
}
|
||||
|
||||
$suffix = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COMPAT, 'UTF-8');
|
||||
$headerTag = htmlspecialchars($params->get('header_tag', 'h3'), ENT_COMPAT, 'UTF-8');
|
||||
$headerClass = htmlspecialchars($params->get('header_class', ''), ENT_COMPAT, 'UTF-8');
|
||||
?>
|
||||
<nav class="mod-menu<?php echo $suffix ? ' ' . $suffix : ''; ?>"<?php echo $id; ?> aria-label="<?php echo htmlspecialchars($module->title, ENT_COMPAT, 'UTF-8'); ?>">
|
||||
<?php if ($module->showtitle) : ?>
|
||||
<<?php echo $headerTag; ?> class="mod-menu__title<?php echo $headerClass ? ' ' . $headerClass : ''; ?>"><?php echo $module->title; ?></<?php echo $headerTag; ?>>
|
||||
<?php endif; ?>
|
||||
<ul class="mod-menu__list nav flex-column">
|
||||
<?php foreach ($list as $i => &$item) :
|
||||
$itemParams = $item->getParams();
|
||||
$class = 'nav-item mod-menu__item item-' . $item->id;
|
||||
|
||||
if ($item->id == $default_id) {
|
||||
$class .= ' default';
|
||||
}
|
||||
|
||||
if ($item->id == $active_id || ($item->type === 'alias' && $itemParams->get('aliasoptions') == $active_id)) {
|
||||
$class .= ' current';
|
||||
}
|
||||
|
||||
if (in_array($item->id, $path)) {
|
||||
$class .= ' active';
|
||||
} elseif ($item->type === 'alias') {
|
||||
$aliasToId = $itemParams->get('aliasoptions');
|
||||
|
||||
if (count($path) > 0 && $aliasToId == $path[count($path) - 1]) {
|
||||
$class .= ' active';
|
||||
} elseif (in_array($aliasToId, $path)) {
|
||||
$class .= ' alias-parent-active';
|
||||
}
|
||||
}
|
||||
|
||||
if ($item->type === 'separator') {
|
||||
$class .= ' divider';
|
||||
}
|
||||
|
||||
if ($item->deeper) {
|
||||
$class .= ' deeper';
|
||||
}
|
||||
|
||||
if ($item->parent) {
|
||||
$class .= ' parent';
|
||||
}
|
||||
|
||||
echo '<li class="' . $class . '">';
|
||||
|
||||
switch ($item->type) :
|
||||
case 'separator':
|
||||
case 'component':
|
||||
case 'heading':
|
||||
case 'url':
|
||||
require ModuleHelper::getLayoutPath('mod_menu', 'default_' . $item->type);
|
||||
break;
|
||||
|
||||
default:
|
||||
require ModuleHelper::getLayoutPath('mod_menu', 'default_url');
|
||||
break;
|
||||
endswitch;
|
||||
|
||||
if ($item->deeper) {
|
||||
echo '<ul class="mod-menu__sub nav flex-column ms-3">';
|
||||
} elseif ($item->shallower) {
|
||||
echo '</li>';
|
||||
echo str_repeat('</ul></li>', $item->level_diff);
|
||||
} else {
|
||||
echo '</li>';
|
||||
}
|
||||
endforeach;
|
||||
?></ul>
|
||||
</nav>
|
||||
@@ -1,9 +1,76 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
|
||||
This file is part of a Moko Consulting project.
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title></title>
|
||||
<meta charset="utf-8" />
|
||||
<title>Redirecting…</title>
|
||||
|
||||
<!-- Search engines: do not index this placeholder redirect page -->
|
||||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||||
|
||||
<!-- Instant redirect fallback even if JavaScript is disabled -->
|
||||
<meta http-equiv="refresh" content="0; url=/" />
|
||||
|
||||
<!-- Canonical root reference -->
|
||||
<link rel="canonical" href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<script>
|
||||
|
||||
(function redirectToRoot() {
|
||||
// Configuration object with safe defaults.
|
||||
var opts = {
|
||||
fallbackPath: "/", // string: fallback destination if origin is unavailable
|
||||
delayMs: 0, // number: delay before redirect in ms (0 = immediate)
|
||||
behavior: "replace" // enum: "replace" | "assign"
|
||||
};
|
||||
|
||||
// Determine absolute origin in all mainstream browsers.
|
||||
var origin = (typeof location.origin === "string" && location.origin)
|
||||
|| (location.protocol + "//" + location.host);
|
||||
|
||||
// Final destination: absolute root of the current site, or fallback path.
|
||||
var destination = origin ? origin + "/" : opts.fallbackPath;
|
||||
|
||||
function go() {
|
||||
if (opts.behavior === "assign") {
|
||||
location.assign(destination);
|
||||
} else {
|
||||
location.replace(destination);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute redirect, optionally after a short delay.
|
||||
if (opts.delayMs > 0) {
|
||||
setTimeout(go, opts.delayMs);
|
||||
} else {
|
||||
go();
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!--
|
||||
Secondary meta-refresh for no-JS environments is already set above.
|
||||
Some very old crawlers may ignore JS; the meta refresh ensures coverage.
|
||||
-->
|
||||
|
||||
<noscript>
|
||||
<!-- Extra defense-in-depth: if JS is disabled, meta refresh (above) handles redirect. -->
|
||||
<style>
|
||||
html, body { height:100%; }
|
||||
body { display:flex; align-items:center; justify-content:center; margin:0; font: 16px/1.4 system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; }
|
||||
.msg { opacity: .75; text-align: center; }
|
||||
</style>
|
||||
</noscript>
|
||||
</head>
|
||||
<body>
|
||||
<div class="msg">Redirecting to the site root… If you are not redirected, <a href="/">click here</a>.</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Joomla.Site
|
||||
* @subpackage mod_menu
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* @copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Main Menu - Mobile responsive collapsible dropdown menu override
|
||||
* Bootstrap 5 responsive navbar with hamburger menu
|
||||
*/
|
||||
@@ -29,7 +31,7 @@ $moduleclass_sfx = htmlspecialchars($params->get('moduleclass_sfx', ''), ENT_COM
|
||||
<div class="container-fluid">
|
||||
<!-- Hamburger toggle button for mobile -->
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#mainMenuCollapse" aria-controls="mainMenuCollapse" aria-expanded="false" aria-label="Toggle Main Menu">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
<span class="fa-solid fa-bars" aria-hidden="true"></span>
|
||||
</button>
|
||||
|
||||
<!-- Collapsible menu content -->
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Joomla.Site
|
||||
* @subpackage mod_menu
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* @copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Main Menu - Component item layout
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Joomla.Site
|
||||
* @subpackage mod_menu
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* @copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Main Menu - Heading item layout
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Joomla.Site
|
||||
* @subpackage mod_menu
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* @copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Main Menu - Separator item layout
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* @package Joomla.Site
|
||||
* @subpackage mod_menu
|
||||
* Copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
*
|
||||
* @copyright (C) 2025 Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @license GNU General Public License version 2 or later; see LICENSE.txt
|
||||
* This file is part of a Moko Consulting project.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
/**
|
||||
* Main Menu - URL item layout
|
||||
*/
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user