docs: add CLAUDE.md context file for Claude Code #2

Merged
Copilot merged 66 commits from copilot/recreate-root-readme into main 2026-03-04 05:11:07 +00:00
85 changed files with 9075 additions and 2450 deletions
+94
View File
@@ -0,0 +1,94 @@
---
name: Bug Report
about: Report a bug or issue with MokoStandards scripts, schemas, or documentation
title: '[BUG] '
labels: ['bug']
assignees: []
---
## Bug Description
**Brief summary of the issue:**
**Affected component**:
- [ ] Script (specify: _____________)
- [ ] Schema (specify: _____________)
- [ ] Workflow (specify: _____________)
- [ ] Documentation (specify: _____________)
- [ ] Enterprise library (specify: _____________)
## Steps to Reproduce
1.
2.
3.
## Expected Behavior
What should have happened:
## Actual Behavior
What actually happened:
## Environment
**Operating System**:
- [ ] Windows (version: _______)
- [ ] macOS (version: _______)
- [ ] Linux (distribution: _______)
**Software Versions**:
- Python version:
- PowerShell version:
- Bash version:
- Git version:
**Repository Context**:
- Repository type: [ ] Generic [ ] Joomla/WaaS [ ] Dolibarr/CRM
- MokoStandards version/commit:
## Error Messages
```
Paste any error messages, stack traces, or log output here
```
## Screenshots
If applicable, add screenshots to help explain the problem.
## Additional Context
Any other relevant information about the problem:
## Potential Solution
If you have ideas about what might fix the issue:
## Impact
**Severity**:
- [ ] Critical (blocking work, security issue)
- [ ] High (significant impact, workaround exists)
- [ ] Medium (annoying but manageable)
- [ ] Low (minor inconvenience)
**Affected users/repos**:
- [ ] Just me
- [ ] My team
- [ ] Multiple teams
- [ ] All organization repos
## Checklist
- [ ] I have searched existing issues to avoid duplicates
- [ ] I have included all relevant information
- [ ] I have tested with the latest version of MokoStandards
- [ ] I have included error messages and logs
- [ ] I have specified my environment details
@@ -0,0 +1,114 @@
---
name: Dev Branch Tracking
about: Manually create a tracking issue for a development branch
title: 'Development Branch: dev/XX.YY.ZZ'
labels: ['automation', 'version-management', 'dev-branch']
assignees: ['copilot', 'jmiller-moko']
---
## Development Branch Created
A development branch tracking issue for coordinating work on a version branch.
### Details
- **Branch**: `dev/XX.YY.ZZ` (replace with actual branch name)
- **Version**: XX.YY.ZZ (replace with actual version)
- **Created**: <!-- Date created -->
### Next Steps
1. Checkout the branch: `git fetch origin && git checkout dev/XX.YY.ZZ`
2. Begin development for version XX.YY.ZZ
3. Create PRs targeting this branch for feature development
4. When ready, merge this branch to main for release
### Branch Strategy
This branch follows the semantic versioning patch increment strategy. It represents a patch release.
---
## Launch Checklist - Prepare for Merge to Main
Complete this checklist before merging this dev branch to main. Reference: [Copilot Pre-Merge Checklist Policy](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/copilot-pre-merge-checklist.md)
### 1. Version Management ✅
- [ ] All version numbers updated consistently (VERSION file, package.json, etc.)
- [ ] Version updated in documentation headers
- [ ] Version updated in CHANGELOG.md
- [ ] No version number inconsistencies remain
### 2. Changelog Updates ✅
- [ ] CHANGELOG.md updated with all branch changes
- [ ] Changes grouped by type (Added, Changed, Fixed, Deprecated, Removed, Security)
- [ ] Clear descriptions with implementation details
- [ ] Files affected listed
- [ ] Proper date format used (YYYY-MM-DD or ISO 8601 UTC)
### 3. Code Review Response ✅
- [ ] All review comments addressed
- [ ] Requested changes implemented
- [ ] Explanations provided for declined suggestions
- [ ] Re-review requested if significant changes made
### 4. Security Scanning ✅
- [ ] CodeQL security analysis completed
- [ ] Dependency vulnerabilities checked
- [ ] No secrets/credentials in code
- [ ] Critical and high severity issues fixed
- [ ] Accepted risks documented (if any)
### 5. Code Quality ✅
- [ ] All linters pass without errors
- [ ] Code properly formatted
- [ ] No compiler warnings
- [ ] All tests passing
- [ ] Code coverage meets threshold
- [ ] Shell scripts validated with shellcheck
### 6. Documentation Updates ✅
- [ ] README updated (if public API changed)
- [ ] API documentation updated
- [ ] User guides updated (if features changed)
- [ ] Code comments accurate and complete
- [ ] Examples working and updated
- [ ] Links validated
### 7. Drift Detection ✅
- [ ] Documentation matches implementation
- [ ] File paths in docs are correct
- [ ] Code examples validated and working
- [ ] Workflow inputs match documentation
- [ ] No outdated information remains
### 8. Standards Compliance ✅
- [ ] File headers present and correct
- [ ] Tabs used (not spaces) except in YAML/Makefiles
- [ ] Timestamps use UTC
- [ ] Revision histories in descending order
- [ ] Metadata tables complete and accurate
- [ ] Semantic versioning followed
### 9. Release Preparation ✅
- [ ] Release notes drafted
- [ ] Breaking changes documented
- [ ] Migration guide prepared (if needed)
- [ ] Deployment notes documented
- [ ] Rollback plan prepared
### 10. Final Verification ✅
- [ ] All PRs to this branch reviewed and merged
- [ ] No pending issues blocking release
- [ ] Stakeholders notified of upcoming merge
- [ ] Final PR to main created and ready for review
- [ ] All checklist items above completed
---
### 📝 Pull Requests
This section tracks all PRs associated with this branch:
<!-- PRs will be automatically linked by the enterprise-issue-manager workflow -->
---
*This is a manual tracking issue. For automatically created tracking issues, merge a PR to main to trigger the auto-create-dev-branch workflow.*
+107
View File
@@ -0,0 +1,107 @@
---
name: Documentation Issue
about: Report missing, unclear, or incorrect documentation
title: '[DOCS] '
labels: ['documentation']
assignees: []
---
## Documentation Issue
**Type of issue**:
- [ ] Missing documentation
- [ ] Unclear/confusing documentation
- [ ] Incorrect/outdated documentation
- [ ] Broken links
- [ ] Typos/grammar
- [ ] Missing examples
- [ ] Other: __________
## Location
**Affected documentation**:
- File:
- Section:
- URL (if applicable):
**Related scripts/features** (if applicable):
## Current State
**What's currently documented** (or what's missing):
## Problem
**What's the issue with the current documentation?**
**How does this affect users?**
## Proposed Improvement
**What should be added/changed/fixed:**
**Suggested content** (draft text or outline):
```markdown
# Your suggested documentation content here
```
## Target Audience
**Who needs this documentation?**
- [ ] New users/contributors
- [ ] Experienced developers
- [ ] DevOps engineers
- [ ] System administrators
- [ ] All users
## Priority
**Impact on users**:
- [ ] Critical (blocking adoption/usage)
- [ ] High (frequently referenced, currently confusing)
- [ ] Medium (would help some users)
- [ ] Low (minor improvement)
## Related Documentation
**Related documentation sections**:
-
-
**External references** (if any):
-
-
## Examples Needed
**What examples would help?**
- [ ] Code examples
- [ ] Command examples
- [ ] Configuration examples
- [ ] Workflow examples
- [ ] Diagrams/flowcharts
- [ ] Screenshots
## Additional Context
**Any other relevant information:**
**Screenshots of current documentation** (if applicable):
## Checklist
- [ ] I have searched existing issues to avoid duplicates
- [ ] I have identified the specific location of the issue
- [ ] I have provided clear suggestions for improvement
- [ ] I have considered the target audience
- [ ] I am willing to contribute a documentation PR (if applicable)
+141
View File
@@ -0,0 +1,141 @@
---
name: Feature Request
about: Suggest a new feature, script, or enhancement for MokoStandards
title: '[FEATURE] '
labels: ['enhancement']
assignees: []
---
## Feature Description
**Brief summary of the proposed feature:**
**Category**:
- [ ] New script
- [ ] Enhancement to existing script
- [ ] New enterprise library
- [ ] Schema improvement
- [ ] Documentation improvement
- [ ] Workflow enhancement
- [ ] Other (specify): __________
## Problem Statement
**What problem does this feature solve?**
**Who is affected by this problem?**
- [ ] Individual developers
- [ ] Development teams
- [ ] DevOps teams
- [ ] All organization members
- [ ] External contributors
## Proposed Solution
**Describe your proposed solution:**
**How should it work?**
1.
2.
3.
**Example usage** (if applicable):
```bash
# Example command or code
```
## Alternatives Considered
**What alternatives have you considered?**
**Why is this solution better?**
## Benefits
**What are the expected benefits?**
- [ ] Improved productivity
- [ ] Better code quality
- [ ] Enhanced security
- [ ] Cost savings
- [ ] Compliance/audit improvements
- [ ] Developer experience
- [ ] Other: __________
**Estimated impact**:
## Implementation Details
**Do you have implementation ideas?**
**Required resources**:
- [ ] Development time (estimate: _____ hours/days)
- [ ] Testing infrastructure
- [ ] Documentation
- [ ] Training materials
- [ ] External dependencies (specify): __________
## Compatibility
**Platform compatibility**:
- [ ] Generic projects
- [ ] Joomla/MokoWaaS components
- [ ] Dolibarr/MokoCRM modules
- [ ] All platforms
**Backward compatibility**:
- [ ] Fully backward compatible
- [ ] Requires migration (describe): __________
- [ ] Breaking change (justify): __________
## Priority
**How urgent is this feature?**
- [ ] Critical (needed urgently)
- [ ] High (would significantly help)
- [ ] Medium (nice to have)
- [ ] Low (future consideration)
**Justification for priority**:
## Related Issues
**Related issues or PRs**:
- #
- #
## Acceptance Criteria
**How will we know when this feature is complete?**
- [ ]
- [ ]
- [ ]
## Additional Context
**Any other relevant information:**
**Screenshots/Mockups** (if applicable):
## Checklist
- [ ] I have searched existing issues to avoid duplicates
- [ ] I have clearly described the problem and solution
- [ ] I have considered alternatives
- [ ] I have estimated the impact and benefits
- [ ] I have checked compatibility requirements
- [ ] I am willing to contribute to implementation (if applicable)
+86
View File
@@ -0,0 +1,86 @@
---
name: Question
about: Ask a question about MokoStandards usage, features, or best practices
title: '[QUESTION] '
labels: ['question']
assignees: []
---
## Question
**Your question:**
## Context
**What are you trying to accomplish?**
**What have you already tried?**
**Category**:
- [ ] Script usage
- [ ] Enterprise library integration
- [ ] Schema configuration
- [ ] Workflow setup
- [ ] Documentation interpretation
- [ ] Best practices
- [ ] Platform-specific (Generic/Joomla/Dolibarr)
- [ ] Other: __________
## Environment (if relevant)
**Your setup**:
- Operating System:
- Repository type: [ ] Generic [ ] Joomla/WaaS [ ] Dolibarr/CRM
- MokoStandards version:
## What You've Researched
**Documentation reviewed**:
- [ ] README.md
- [ ] Script documentation (/docs/scripts/)
- [ ] Enterprise library docs
- [ ] Schema documentation
- [ ] Sublime Text setup guide
- [ ] Other (specify): __________
**Similar issues/questions found**:
- #
- #
## Expected Outcome
**What result are you hoping for?**
## Code/Configuration Samples
**Relevant code or configuration** (if applicable):
```bash
# Your code here
```
## Additional Context
**Any other relevant information:**
**Screenshots** (if helpful):
## Urgency
- [ ] Urgent (blocking work)
- [ ] Normal (can work on other things meanwhile)
- [ ] Low priority (just curious)
## Checklist
- [ ] I have searched existing issues and discussions
- [ ] I have reviewed relevant documentation
- [ ] I have provided sufficient context
- [ ] I have included code/configuration samples if relevant
- [ ] This is a genuine question (not a bug report or feature request)
+110
View File
@@ -0,0 +1,110 @@
---
name: License Request
about: Request an organization license for Sublime Text
title: '[LICENSE REQUEST] Sublime Text - [Your Name]'
labels: ['license-request', 'admin']
assignees: []
---
## License Request
### Tool Information
**Tool Name**: Sublime Text
**License Type Requested**: Organization Pool
**Personal Purchase**:
- [ ] I prefer to purchase my own license ($99 USD - recommended, immediate access)
- [ ] I prefer an organization license (1-2 business days, organization use only)
- [ ] I have already purchased my own license (registration only for support)
### Requestor Information
**Name**:
**GitHub Username**: @
**Email**: @mokoconsulting.tech
**Team/Department**:
**Manager**: @
### Justification
**Why do you need this license?**
**Primary use case**:
- [ ] Remote development (SFTP to servers)
- [ ] Local development
- [ ] Code review
- [ ] Documentation editing
- [ ] Other (specify):
**Which projects/repositories will you work on?**
**Have you evaluated the free trial?**
- [ ] Yes, I've used the trial and Sublime Text meets my needs
- [ ] No, requesting license before trial
**Alternative tools considered**:
- [ ] VS Code (free alternative)
- [ ] Vim/Neovim (free, terminal-based)
- [ ] Other: _______________
### Platform
- [ ] Windows
- [ ] macOS
- [ ] Linux (distribution: ________)
### Urgency
- [ ] Urgent (needed within 24 hours - please justify)
- [ ] Normal (1-2 business days)
- [ ] Low priority (when available)
**If urgent, please explain why:**
### SFTP Plugin
**Note**: Sublime SFTP plugin ($16 USD) is a **separate personal purchase** and is NOT provided by the organization.
- [ ] I understand SFTP plugin requires separate personal purchase
- [ ] I have already purchased SFTP plugin
- [ ] I will purchase SFTP plugin if needed for my work
- [ ] I don't need SFTP plugin (local development only)
### Acknowledgments
- [ ] I have read the License Management Policy (/docs/github-private/LICENSE_MANAGEMENT.md)
- [ ] I understand organization licenses are for work use only
- [ ] I understand organization licenses must be returned upon leaving
- [ ] I understand personal purchases ($99) are an alternative with lifetime access
- [ ] I understand SFTP plugin ($16) requires separate personal purchase
- [ ] I agree to the terms of use
### Additional Information
**Expected daily usage hours**: _____ hours/day
**Duration of need**:
- [ ] Permanent (ongoing role)
- [ ] Temporary project (_____ months)
- [ ] Trial/Evaluation (_____ weeks)
**Comments/Questions**:
---
## For Admin Use Only
**Do not edit below this line**
- [ ] Manager approval received (@manager-username)
- [ ] License available in pool (current: __/20)
- [ ] License type confirmed (Organization / Personal registration)
- [ ] License key sent via encrypted email
- [ ] Activation confirmed by user
- [ ] Added to license tracking sheet
- [ ] User notified of SFTP plugin requirement
**License Key ID**: _____________
**Date Issued**: _____________
**Issued By**: @_____________
**Notes**:
+127
View File
@@ -0,0 +1,127 @@
---
name: Security Vulnerability
about: Report a security vulnerability (use private reporting if critical)
title: '[SECURITY] '
labels: ['security']
assignees: []
---
## ⚠️ Security Vulnerability Report
**IMPORTANT**: For **critical vulnerabilities**, please use GitHub's [Private Vulnerability Reporting](https://github.com/mokoconsulting-tech/MokoStandards/security/advisories/new) or email security@mokoconsulting.tech directly. Do NOT create a public issue.
Use this template only for **low to medium severity** issues that don't pose immediate risk.
---
## Vulnerability Summary
**Brief description** (without sensitive details):
**Affected component**:
- [ ] Script
- [ ] Enterprise library
- [ ] Workflow
- [ ] Schema
- [ ] Documentation
- [ ] Other: __________
## Severity Assessment
**Severity level** (your assessment):
- [ ] Critical (immediate exploitation possible, high impact)
- [ ] High (exploitation likely, significant impact)
- [ ] Medium (exploitation possible with conditions, moderate impact)
- [ ] Low (theoretical risk, minimal impact)
**CVSS Score** (if calculated): _____ / 10
## Affected Versions
**MokoStandards version(s)**:
- Commit/tag:
**Affected platforms**:
- [ ] All platforms
- [ ] Windows
- [ ] macOS
- [ ] Linux
- [ ] Generic repos
- [ ] Joomla/WaaS repos
- [ ] Dolibarr/CRM repos
## Impact Analysis
**What could an attacker do?**
**What data/systems are at risk?**
**Who is affected?**
- [ ] All organization members
- [ ] Repository administrators
- [ ] CI/CD pipelines
- [ ] Specific teams (specify): __________
## Steps to Reproduce
**Proof of concept** (without exploit code):
1.
2.
3.
## Potential Fix
**Do you have suggestions for remediation?**
**Temporary workarounds** (if any):
## References
**Related CVEs, advisories, or security issues**:
-
-
**Security best practices violated**:
-
## Discovery Context
**How was this discovered?**
- [ ] Security audit
- [ ] Code review
- [ ] Automated scanning (tool: ______)
- [ ] Incident response
- [ ] User report
- [ ] Other: __________
## Additional Information
**Relevant logs, configurations, or context** (sanitized):
## Responsible Disclosure
- [ ] I understand this is a public issue and should not contain sensitive details
- [ ] I have verified this is NOT a critical vulnerability requiring private reporting
- [ ] I have checked for existing security advisories
- [ ] I am willing to work with maintainers on a fix (if applicable)
- [ ] I understand security@mokoconsulting.tech is the contact for critical issues
## Checklist for Maintainers
**Do not edit below this line**
- [ ] Severity confirmed
- [ ] Impact assessment completed
- [ ] Fix developed
- [ ] Fix tested
- [ ] Security advisory created (if needed)
- [ ] Affected users notified
- [ ] CVE requested (if applicable)
- [ ] Documentation updated
+116
View File
@@ -0,0 +1,116 @@
name: Sub-Task
description: Create a sub-task or sub-issue to track a specific piece of work related to a parent issue
title: "[Task] "
labels: ["sub-task"]
assignees: []
body:
- type: markdown
attributes:
value: |
## Sub-Task Issue
This template is for creating sub-tasks that are part of a larger parent issue. Sub-tasks help break down complex work into manageable pieces.
- type: input
id: parent_issue
attributes:
label: Parent Issue
description: The issue number this sub-task belongs to (e.g., #193)
placeholder: "#193"
validations:
required: true
- type: input
id: task_title
attributes:
label: Task Title
description: A brief, descriptive title for this sub-task
placeholder: "Investigate token permissions issue"
validations:
required: true
- type: textarea
id: task_description
attributes:
label: Task Description
description: Detailed description of what needs to be done
placeholder: "Describe the specific work to be completed..."
validations:
required: true
- type: dropdown
id: task_type
attributes:
label: Task Type
description: What type of work is this?
options:
- Investigation
- Bug Fix
- Feature Implementation
- Documentation
- Testing
- Refactoring
- Configuration
- Other
validations:
required: true
- type: dropdown
id: priority
attributes:
label: Priority
description: How urgent is this sub-task?
options:
- Low
- Medium
- High
- Critical
default: 1
validations:
required: true
- type: textarea
id: acceptance_criteria
attributes:
label: Acceptance Criteria
description: What conditions must be met for this sub-task to be considered complete?
placeholder: |
- [ ] Criterion 1
- [ ] Criterion 2
- [ ] Criterion 3
value: |
- [ ]
validations:
required: false
- type: textarea
id: dependencies
attributes:
label: Dependencies
description: Does this sub-task depend on other issues or sub-tasks?
placeholder: "List any blocking issues or required prerequisites..."
validations:
required: false
- type: textarea
id: notes
attributes:
label: Additional Notes
description: Any other relevant information
placeholder: "Additional context, links, or references..."
validations:
required: false
- type: checkboxes
id: checklist
attributes:
label: Pre-submission Checklist
description: Please confirm before creating this sub-task
options:
- label: This sub-task is linked to a parent issue
required: true
- label: The task description is clear and actionable
required: true
- label: I have assigned appropriate labels and priority
required: false
+206
View File
@@ -0,0 +1,206 @@
# GitHub Copilot Instructions for MokoJoomTOS
This file provides guidance to GitHub Copilot when working with this repository.
## Project Overview
**MokoJoomTOS** is a lightweight Joomla system plugin that allows Terms of Service (or other legal documents) to remain accessible even when the site is in offline/maintenance mode.
- **Type**: Joomla 4.x/5.x System Plugin
- **License**: GPL-3.0-or-later
- **Language**: PHP 7.4+
- **Current Version**: 03.08.04
## Repository Structure
```
/
├── src/ # Plugin source code (root level, not nested)
│ ├── mokojoomtos.php # Main plugin file
│ ├── mokojoomtos.xml # Plugin manifest
│ ├── script.php # Installation script
│ └── src/ # Namespaced classes
│ ├── Extension/ # Plugin extension classes
│ └── Field/ # Custom form fields
├── docs/ # Detailed documentation
├── scripts/ # Build and utility scripts
└── [root docs] # Essential documentation (README, CHANGELOG, etc.)
```
## Coding Standards
### PHP Standards
1. **Follow Joomla Coding Standards**
- Use Joomla's coding conventions
- Follow PSR-1 and PSR-2 where applicable
- Use tabs for indentation (not spaces), except in YAML files
2. **File Headers**
- All PHP files must include copyright header with GPL-3.0-or-later license
- Format: `@package MokoJoomTOS`, `@subpackage plg_system_mokojoomtos`
- Include `@since` tags for version tracking
3. **Namespacing**
- Plugin classes use namespace: `Joomla\Plugin\System\MokoJoomTOS\`
- Follow Joomla's autoloader conventions
### XML Standards
1. **XML Files Use MokoStandard Header**
- Copyright: `Copyright (C) 2026 Moko Consulting`
- License: GPL-3.0-or-later
- Include FILE INFORMATION section with:
- DEFGROUP, INGROUP, PATH, VERSION, BRIEF
2. **Plugin Manifest (mokojoomtos.xml)**
- Creation date format: `yyyy-mm-dd` (e.g., `2026-01-01`)
- Update server: `https://raw.githubusercontent.com/mokoconsulting-tech/MokoJoomTOS/main/update.xml`
- Site language files: NO folder attribute
- Administrator language files: `folder="administrator"`
### Documentation Standards
1. **Markdown File Headers**
- All markdown files use VERSION: 03.08.04 in FILE INFORMATION section
- Follow same MokoStandard format as XML files
2. **Documentation Location**
- Detailed documentation → `docs/` directory
- Essential root-level docs only: README, CHANGELOG, CONTRIBUTING, LICENSE, SECURITY, CODE_OF_CONDUCT
- Use index.md files for navigation in major directories
## Joomla-Specific Patterns
### Table Operations (CRITICAL)
**Always use the proper Joomla table workflow to prevent PrepareDataEvent type errors:**
```php
// ❌ INCORRECT - Can cause PrepareDataEvent type errors
if ($table->save($data)) {
return $table->id;
}
// ✅ CORRECT - Use bind() → check() → store() workflow
if (!$table->bind($data)) {
Log::add('Failed to bind: ' . $table->getError(), Log::WARNING, 'jerror');
return null;
}
if (!$table->check()) {
Log::add('Check failed: ' . $table->getError(), Log::WARNING, 'jerror');
return null;
}
if (!$table->store()) {
Log::add('Store failed: ' . $table->getError(), Log::WARNING, 'jerror');
return null;
}
return $table->id;
```
This pattern is used in `script.php` for installation routines.
### Plugin Event Lifecycle
1. **Template Rendering**
- Use `onAfterRoute` event (not `onAfterInitialise`) to set `tmpl=component`
- This is the correct point in Joomla's lifecycle (after routing, before template selection)
2. **Offline Mode Handling**
- When site is offline and TOS slug is accessed:
- Temporarily disable offline mode
- Set `tmpl=component` in both Input object and `$_GET` superglobal
- Ensures component-only view without template chrome
### Language Files
- **Site language files**: Do NOT include `folder` attribute in XML manifest
- **Administrator language files**: Use `folder="administrator"` in XML manifest
- Located in: `administrator/language/` subdirectory within plugin
## Version Management
**Current Version**: 03.08.04
When updating version:
- Update in ALL files: mokojoomtos.xml, update.xml, CHANGELOG.md, README.md
- Update markdown file headers (FILE INFORMATION section)
- Follow semantic versioning principles
- Document changes in CHANGELOG.md
## Testing & Validation
1. **PHP Syntax**: Always validate with `php -l` before committing
2. **No Test Infrastructure**: Repository currently has no automated tests
- Manual testing required
- Focus on Joomla integration testing
## Build System
- Build scripts located in `scripts/` directory (not repository root)
- Manual packaging: Copy from `src/`, create ZIP with proper structure
- Pre-built releases available from GitHub Releases
## Git Workflow
1. **Commit Messages**: Follow Conventional Commits
- Types: feat, fix, docs, style, refactor, test, chore
- Format: `type(scope): subject`
2. **Branch Naming**:
- Features: `feat/*`
- Fixes: `fix/*`
- Copilot tasks: `copilot/*`
## Common Tasks
### Adding a New Feature
1. Create feature branch
2. Update plugin files in `src/`
3. Update version if needed
4. Update CHANGELOG.md
5. Test in Joomla environment
6. Validate PHP syntax
7. Commit with conventional commit message
### Updating Documentation
1. Detailed docs go in `docs/`
2. Keep root docs minimal
3. Update index.md files for navigation
4. Maintain FILE INFORMATION headers
5. Update VERSION in headers if project version changes
### Installation Script Changes
1. Use proper table workflow (bind → check → store)
2. Add error logging at each step
3. Check table instance creation
4. Test installation/uninstallation in Joomla
## Important Notes
- ✅ Plugin source files at `src/` root level, NOT nested under `src/plugins/system/mokojoomtos/`
- ✅ No markdown documentation files in `src/` - only source code
- ✅ Enterprise-ready: Automatic setup creates article, menu type, and menu item
- ✅ Zero database impact: No custom tables or migrations
- ✅ Legal menu structure: Dedicated "Legal" menu type with alias `terms-of-service`
## Security
- No secrets or credentials in source code
- Follow Joomla security best practices
- Validate and sanitize all inputs
- Use Joomla's API methods for database operations
## References
- [Joomla Documentation](https://docs.joomla.org/)
- [Joomla Coding Standards](https://developer.joomla.org/coding-standards.html)
- [Conventional Commits](https://www.conventionalcommits.org/)
- [Repository README](../README.md)
- [Contributing Guidelines](../CONTRIBUTING.md)
+107
View File
@@ -0,0 +1,107 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
# FILE INFORMATION
# DEFGROUP: GitHub.Dependabot
# INGROUP: MokoStandards.Security
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/dependabot.yml
# VERSION: 04.00.03
# BRIEF: Dependabot configuration for automated dependency updates and security patches
# NOTE: Monitors GitHub Actions for vulnerabilities and keeps ecosystem secure
version: 2
updates:
# Monitor GitHub Actions for security updates
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
open-pull-requests-limit: 1
labels:
- "dependencies"
- "security"
- "automated"
commit-message:
prefix: "chore(deps)"
include: "scope"
reviewers:
- "mokoconsulting-tech/maintainers"
assignees:
- "jmiller-moko"
# Group all updates together
groups:
github-actions:
patterns:
- "*"
# Monitor Python dependencies for security updates
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "monthly"
open-pull-requests-limit: 1
labels:
- "dependencies"
- "security"
- "automated"
- "python"
commit-message:
prefix: "chore(deps)"
include: "scope"
reviewers:
- "mokoconsulting-tech/maintainers"
assignees:
- "jmiller-moko"
# Group all updates together
groups:
python-dependencies:
patterns:
- "*"
# Monitor npm dependencies for security updates
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "monthly"
open-pull-requests-limit: 1
labels:
- "dependencies"
- "security"
- "automated"
- "javascript"
commit-message:
prefix: "chore(deps)"
include: "scope"
reviewers:
- "mokoconsulting-tech/maintainers"
assignees:
- "jmiller-moko"
# Group all updates together
groups:
npm-dependencies:
patterns:
- "*"
# Monitor Composer dependencies for security updates
- package-ecosystem: "composer"
directory: "/"
schedule:
interval: "monthly"
open-pull-requests-limit: 1
labels:
- "dependencies"
- "security"
- "automated"
- "php"
commit-message:
prefix: "chore(deps)"
include: "scope"
reviewers:
- "mokoconsulting-tech/maintainers"
assignees:
- "jmiller-moko"
# Group all updates together
groups:
composer-dependencies:
patterns:
- "*"
+191
View File
@@ -0,0 +1,191 @@
## Description
<!-- Provide a clear and concise description of the changes in this PR -->
## Type of Change
<!-- Check all that apply -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Documentation update
- [ ] Infrastructure/tooling change
- [ ] Refactoring (no functional changes)
- [ ] Performance improvement
- [ ] Security fix
## Related Issues
<!-- Link to related issues, e.g., "Fixes #123" or "Relates to #456" -->
Fixes #
Relates to #
## Pre-Merge Copilot Checklist
<!-- All items must be completed before merge. See docs/policy/copilot-pre-merge-checklist.md for details and sample prompts -->
### 1. Version Management ✅
- [ ] All version numbers updated consistently (VERSION file, package.json, etc.)
- [ ] Version updated in documentation headers
- [ ] Version updated in CHANGELOG.md
- [ ] No version number inconsistencies remain
**Copilot Prompt Used**:
```
Update all version numbers from X.Y.Z to X.Y.Z+1 across the repository
```
### 2. Changelog Updates ✅
- [ ] CHANGELOG.md updated with all PR changes
- [ ] Changes grouped by type (Added, Changed, Fixed, Deprecated, Removed, Security)
- [ ] Clear descriptions with implementation details
- [ ] Files affected listed
- [ ] Proper date format used (YYYY-MM-DD or ISO 8601 UTC)
**Copilot Prompt Used**:
```
Update CHANGELOG.md with all changes from this PR, grouped by type with implementation details
```
### 3. Code Review Response ✅
- [ ] All review comments addressed
- [ ] Requested changes implemented
- [ ] Explanations provided for declined suggestions
- [ ] Re-review requested if significant changes made
### 4. Security Scanning ✅
- [ ] CodeQL security analysis completed
- [ ] Dependency vulnerabilities checked
- [ ] No secrets/credentials in code
- [ ] Critical and high severity issues fixed
- [ ] Accepted risks documented (if any)
**Security Scan Results**: <!-- Link to scan results or summarize findings -->
### 5. Code Quality ✅
- [ ] All linters pass without errors
- [ ] Code properly formatted
- [ ] No compiler warnings
- [ ] All tests passing
- [ ] Code coverage meets threshold
- [ ] Shell scripts validated with shellcheck
**Test Results**: <!-- Summarize test execution results -->
### 6. Documentation Updates ✅
- [ ] README updated (if public API changed)
- [ ] API documentation updated
- [ ] User guides updated (if features changed)
- [ ] Code comments accurate and complete
- [ ] Examples working and updated
- [ ] Links validated
### 7. Drift Detection ✅
- [ ] Documentation matches implementation
- [ ] File paths in docs are correct
- [ ] Code examples validated and working
- [ ] Workflow inputs match documentation
- [ ] No outdated information remains
### 8. Standards Compliance ✅
- [ ] File headers present and correct
- [ ] Tabs used (not spaces) except in YAML/Makefiles
- [ ] Timestamps use UTC
- [ ] Revision histories in descending order
- [ ] Metadata tables complete and accurate
- [ ] Semantic versioning followed
## Testing Performed
<!-- Describe the testing you've done -->
### Unit Tests
- [ ] All existing tests pass
- [ ] New tests added for new functionality
- [ ] Edge cases covered
### Integration Tests
- [ ] Integration tests pass
- [ ] End-to-end scenarios tested
### Manual Testing
<!-- Describe manual testing performed -->
## Breaking Changes
<!-- If this introduces breaking changes, describe them here -->
- None
<!-- OR -->
### Breaking Change Details
<!-- Describe what breaks and how to migrate -->
## Deployment Notes
<!-- Any special deployment considerations? -->
- None
<!-- OR -->
<!-- Describe special deployment steps, configuration changes, database migrations, etc. -->
## Screenshots/Videos
<!-- If applicable, add screenshots or videos demonstrating the changes -->
## Comprehensive Pre-Merge Prompt (Optional)
<!-- If you used the comprehensive pre-merge prompt, document it here -->
<details>
<summary>Comprehensive Prompt Used</summary>
```
Prepare this PR for merge by completing all pre-merge requirements:
1. UPDATE VERSION NUMBERS: Update all version numbers from X.Y.Z to X.Y.Z+1
2. UPDATE CHANGELOG: Review all commits and update CHANGELOG.md
3. ADDRESS CODE REVIEW: Review and address all code review comments
4. RUN SECURITY SCANS: Execute CodeQL, dependency scanning, secret detection
5. FIX QUALITY ISSUES: Run linters, formatters, tests
6. UPDATE DOCUMENTATION: Update README, API docs, user guides, examples
7. CHECK FOR DRIFT: Validate documentation matches implementation
8. VERIFY STANDARDS COMPLIANCE: File headers, indentation, timestamps, metadata
9. CREATE FINAL SUMMARY: Generate comprehensive PR description
10. REQUEST FINAL REVIEW: Tag reviewers for final approval
```
</details>
## Additional Context
<!-- Add any other context about the PR here -->
## Checklist for Reviewers
<!-- For reviewers to verify -->
- [ ] Code changes align with described functionality
- [ ] Pre-merge checklist completed
- [ ] Version numbers consistent
- [ ] CHANGELOG accurate and complete
- [ ] Tests adequate and passing
- [ ] Documentation updated
- [ ] No security concerns
- [ ] Follows coding standards
- [ ] Breaking changes properly documented
- [ ] Deployment notes clear
---
**Policy Reference**: [Copilot Pre-Merge Checklist Policy](../docs/policy/copilot-pre-merge-checklist.md)
<!--
For detailed guidance on each checklist item and sample Copilot prompts,
see docs/policy/copilot-pre-merge-checklist.md
-->
@@ -0,0 +1,372 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
# FILE INFORMATION
# DEFGROUP: MokoStandards.Workflows
# INGROUP: MokoStandards.Automation
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/workflows/auto-create-dev-branch.yml
# VERSION: 04.00.03
# BRIEF: Automatically creates dev/<version> branch and tracking issue assigned to copilot and jmiller-moko
name: Auto-Create Dev Branch
env:
ACTIONS_STEP_DEBUG: true
ACTIONS_RUNNER_DEBUG: true
# MokoStandards Policy Compliance:
# - File formatting: Enforces organizational coding standards
# - Reference: docs/policy/file-formatting.md
# ┌─────────────────────────────────────────────────────────────────────────┐
# │ WORKFLOW FLOW DIAGRAM │
# └─────────────────────────────────────────────────────────────────────────┘
#
# TRIGGER: PR Merged to Main
# │
# ▼
# ┌──────────────────┐
# │ Extract Current │
# │ Version │──────┐ Read CHANGELOG.md, VERSION file
# │ (XX.YY.ZZ) │ │ or fallback to 03.00.00
# └──────────────────┘ │
# │ │
# ▼ │
# ┌──────────────────┐ │
# │ Calculate Next │◀─────┘
# │ Version │
# │ (XX.YY.ZZ+1) │
# └──────────────────┘
# │
# ├─────────────────────────┐
# ▼ ▼
# ┌──────────────────┐ ┌──────────────────┐
# │ Create Branch │ │ Create Issue │
# │ dev/XX.YY.ZZ │ │ "Dev Branch: │
# │ │ │ vXX.YY.ZZ" │
# └──────────────────┘ └──────────────────┘
# │ │
# │ ▼
# │ ┌──────────────────┐
# │ │ Assign to: │
# │ │ • copilot │
# │ │ • jmiller-moko │
# │ └──────────────────┘
# │ │
# └─────────────┬───────────┘
# ▼
# ┌──────────────┐
# │ OUTPUTS: │
# │ • New Branch │
# │ • New Issue │
# └──────────────┘
on:
pull_request:
types: [closed]
branches:
- main
permissions:
contents: write
pull-requests: write
issues: write
jobs:
create-dev-branch:
name: Create Development Branch with Version Bump
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Configure Git
run: |
set -x
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
- name: Extract current version
id: version
run: |
set -x
# Try to extract version from CHANGELOG.md first
if [ -f "CHANGELOG.md" ]; then
# Look for version in format [XX.YY.ZZ] or XX.YY.ZZ
VERSION=$(grep -oP '\[?\d+\.\d+\.\d+\]?' CHANGELOG.md | head -1 | tr -d '[]')
fi
# Fallback: Try to find VERSION file or header
if [ -z "$VERSION" ]; then
if [ -f "VERSION" ]; then
VERSION=$(cat VERSION)
else
# Look for VERSION: in any markdown file
VERSION=$(grep -r "^VERSION: " . --include="*.md" | head -1 | grep -oP '\d+\.\d+\.\d+' || echo "03.00.00")
fi
fi
echo "Current version: $VERSION"
echo "current=$VERSION" >> $GITHUB_OUTPUT
- name: Calculate next patch version
id: next_version
run: |
set -x
CURRENT="${{ steps.version.outputs.current }}"
# Parse version components
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT"
# Increment patch version
NEXT_PATCH=$((PATCH + 1))
# Format with leading zeros if needed
if [ ${#MAJOR} -eq 2 ]; then
# Zero-padded format (XX.YY.ZZ)
NEXT_VERSION=$(printf "%02d.%02d.%02d" "$MAJOR" "$MINOR" "$NEXT_PATCH")
else
# Standard semver format (X.Y.Z)
NEXT_VERSION="$MAJOR.$MINOR.$NEXT_PATCH"
fi
echo "Next version: $NEXT_VERSION"
echo "version=$NEXT_VERSION" >> $GITHUB_OUTPUT
- name: Check if dev branch already exists
id: check_branch
run: |
set -x
BRANCH_NAME="dev/${{ steps.next_version.outputs.version }}"
if git ls-remote --heads origin "$BRANCH_NAME" | grep -q "$BRANCH_NAME"; then
echo "exists=true" >> $GITHUB_OUTPUT
echo "Branch $BRANCH_NAME already exists"
else
echo "exists=false" >> $GITHUB_OUTPUT
echo "Branch $BRANCH_NAME does not exist"
fi
echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT
- name: Create new dev branch
if: steps.check_branch.outputs.exists == 'false'
run: |
set -x
BRANCH_NAME="${{ steps.check_branch.outputs.branch_name }}"
# Create branch from main
git checkout main
git pull origin main
git checkout -b "$BRANCH_NAME"
# Push new branch
git push origin "$BRANCH_NAME"
echo "✅ Created and pushed branch: $BRANCH_NAME"
- name: Create tracking issue
if: steps.check_branch.outputs.exists == 'false'
uses: actions/github-script@v8
with:
script: |
const nextVersion = '${{ steps.next_version.outputs.version }}';
const branchName = '${{ steps.check_branch.outputs.branch_name }}';
const prNumber = context.payload.pull_request.number;
const prTitle = context.payload.pull_request.title;
const issueBody = `## Development Branch Created
A new development branch has been automatically created following the merge of PR #${prNumber}.
### Details
- **Branch**: \`${branchName}\`
- **Version**: ${nextVersion}
- **Merged PR**: #${prNumber} - ${prTitle}
- **Created**: ${new Date().toISOString()}
### Next Steps
1. Checkout the new branch: \`git fetch origin && git checkout ${branchName}\`
2. Begin development for version ${nextVersion}
3. Create PRs targeting this branch for feature development
4. When ready, merge this branch to main for release
### Branch Strategy
This branch follows the semantic versioning patch increment strategy. It represents the next patch release after the current version.
---
## Launch Checklist - Prepare for Merge to Main
Complete this checklist before merging this dev branch to main. Reference: [Copilot Pre-Merge Checklist Policy](https://github.com/${context.repo.owner}/${context.repo.repo}/blob/main/docs/policy/copilot-pre-merge-checklist.md)
### 1. Version Management ✅
- [ ] All version numbers updated consistently (VERSION file, package.json, etc.)
- [ ] Version updated in documentation headers
- [ ] Version updated in CHANGELOG.md
- [ ] No version number inconsistencies remain
### 2. Changelog Updates ✅
- [ ] CHANGELOG.md updated with all branch changes
- [ ] Changes grouped by type (Added, Changed, Fixed, Deprecated, Removed, Security)
- [ ] Clear descriptions with implementation details
- [ ] Files affected listed
- [ ] Proper date format used (YYYY-MM-DD or ISO 8601 UTC)
### 3. Code Review Response ✅
- [ ] All review comments addressed
- [ ] Requested changes implemented
- [ ] Explanations provided for declined suggestions
- [ ] Re-review requested if significant changes made
### 4. Security Scanning ✅
- [ ] CodeQL security analysis completed
- [ ] Dependency vulnerabilities checked
- [ ] No secrets/credentials in code
- [ ] Critical and high severity issues fixed
- [ ] Accepted risks documented (if any)
### 5. Code Quality ✅
- [ ] All linters pass without errors
- [ ] Code properly formatted
- [ ] No compiler warnings
- [ ] All tests passing
- [ ] Code coverage meets threshold
- [ ] Shell scripts validated with shellcheck
### 6. Documentation Updates ✅
- [ ] README updated (if public API changed)
- [ ] API documentation updated
- [ ] User guides updated (if features changed)
- [ ] Code comments accurate and complete
- [ ] Examples working and updated
- [ ] Links validated
### 7. Drift Detection ✅
- [ ] Documentation matches implementation
- [ ] File paths in docs are correct
- [ ] Code examples validated and working
- [ ] Workflow inputs match documentation
- [ ] No outdated information remains
### 8. Standards Compliance ✅
- [ ] File headers present and correct
- [ ] Tabs used (not spaces) except in YAML/Makefiles
- [ ] Timestamps use UTC
- [ ] Revision histories in descending order
- [ ] Metadata tables complete and accurate
- [ ] Semantic versioning followed
### 9. Release Preparation ✅
- [ ] Release notes drafted
- [ ] Breaking changes documented
- [ ] Migration guide prepared (if needed)
- [ ] Deployment notes documented
- [ ] Rollback plan prepared
### 10. Final Verification ✅
- [ ] All PRs to this branch reviewed and merged
- [ ] No pending issues blocking release
- [ ] Stakeholders notified of upcoming merge
- [ ] Final PR to main created and ready for review
- [ ] All checklist items above completed
---
*This issue was automatically created by the auto-create-dev-branch workflow.*`;
// Validate assignees before creating issue
async function validateAssignees(assignees) {
const validAssignees = [];
for (const assignee of assignees) {
try {
await github.rest.users.getByUsername({ username: assignee });
validAssignees.push(assignee);
console.log(`✓ Validated assignee: ${assignee}`);
} catch (error) {
console.log(`✗ Invalid assignee (skipping): ${assignee} - ${error.message}`);
}
}
return validAssignees;
}
const requestedAssignees = ['jmiller-moko'];
const validAssignees = await validateAssignees(requestedAssignees);
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `Development Branch: ${branchName}`,
body: issueBody,
labels: ['automation', 'version-management', 'dev-branch'],
assignees: validAssignees
});
- name: Comment on merged PR
if: steps.check_branch.outputs.exists == 'false'
uses: actions/github-script@v8
with:
script: |
const branchName = '${{ steps.check_branch.outputs.branch_name }}';
const nextVersion = '${{ steps.next_version.outputs.version }}';
const comment = `## ✅ Development Branch Created
A new development branch has been automatically created:
- **Branch**: \`${branchName}\`
- **Version**: ${nextVersion}
You can checkout this branch with:
\`\`\`bash
git fetch origin
git checkout ${branchName}
\`\`\`
This branch is ready for the next development cycle.`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
body: comment
});
- name: Branch already exists notification
if: steps.check_branch.outputs.exists == 'true'
uses: actions/github-script@v8
with:
script: |
const branchName = '${{ steps.check_branch.outputs.branch_name }}';
const comment = `## ️ Development Branch Already Exists
The development branch \`${branchName}\` already exists.
No new branch was created. Continue development on the existing branch.`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.pull_request.number,
body: comment
});
- name: Workflow summary
run: |
set -x
echo "## Workflow Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **Current Version**: ${{ steps.version.outputs.current }}" >> $GITHUB_STEP_SUMMARY
echo "- **Next Version**: ${{ steps.next_version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
echo "- **Branch Name**: \`${{ steps.check_branch.outputs.branch_name }}\`" >> $GITHUB_STEP_SUMMARY
if [ "${{ steps.check_branch.outputs.exists }}" == "false" ]; then
echo "- **Status**: ✅ Branch created successfully" >> $GITHUB_STEP_SUMMARY
else
echo "- **Status**: ️ Branch already exists" >> $GITHUB_STEP_SUMMARY
fi
+123
View File
@@ -0,0 +1,123 @@
# 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
#
# FILE INFORMATION
# DEFGROUP: GitHub.Workflow
# INGROUP: MokoStandards.Security
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/workflows/codeql-analysis.yml
# VERSION: 04.00.03
# BRIEF: CodeQL security scanning workflow for PHP codebase
# NOTE: Repository is PHP-only (v04.00.03). Python was removed Feb 12, 2026.
name: "CodeQL Security Scanning"
on:
push:
branches:
- main
- dev/**
- rc/**
- version/**
pull_request:
branches:
- main
- dev/**
- rc/**
schedule:
# Run weekly on Monday at 6:00 AM UTC
- cron: '0 6 * * 1'
workflow_dispatch:
permissions:
actions: read
contents: read
security-events: write
pull-requests: read
jobs:
analyze:
name: Configuration Security Scan
runs-on: ubuntu-latest
timeout-minutes: 360
# No language matrix - PHP-only repository
# CodeQL scans workflow files, configs, and scripts for security issues
# PHP security handled by SecurityValidator enterprise library
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Initialize CodeQL
uses: github/codeql-action/init@v4
with:
# No languages specified - scan configurations only
# Reference explicit config to scan YAML, JSON, shell scripts
config-file: ./.github/codeql/codeql-config.yml
copilot-pull-request-reviewer[bot] commented 2026-03-04 05:18:44 +00:00 (Migrated from github.com)
Review

This workflow config points CodeQL at ./.github/codeql/codeql-config.yml, but there is no .github/codeql/ directory in the repo. As written, the workflow will fail at initialization. Add the referenced CodeQL config file (and directory) or remove config-file: and rely on default scanning configuration.


This workflow config points CodeQL at `./.github/codeql/codeql-config.yml`, but there is no `.github/codeql/` directory in the repo. As written, the workflow will fail at initialization. Add the referenced CodeQL config file (and directory) or remove `config-file:` and rely on default scanning configuration. ```suggestion ```
# Use security-extended query suite for comprehensive coverage
queries: security-extended,security-and-quality
# Skip autobuild - no code compilation needed for config scanning
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4
with:
category: "/language:config"
upload: true
output: sarif-results
wait-for-processing: true
- name: Upload SARIF results (optional)
if: always()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v4.5.0
with:
name: codeql-results-config
path: sarif-results
retention-days: 30
- name: Check for Critical/High Findings
if: always()
run: |
echo "### 🔍 CodeQL Security Analysis Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Scan Type**: Configuration Security" >> $GITHUB_STEP_SUMMARY
echo "**Query Suite**: security-extended, security-and-quality" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Note**: MokoStandards is PHP-only (v04.00.03)." >> $GITHUB_STEP_SUMMARY
echo "This scan analyzes workflow files, JSON configs, YAML, and shell scripts." >> $GITHUB_STEP_SUMMARY
echo "For PHP-specific security: Use PHP SecurityValidator enterprise library." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
URL="https://github.com/${{ github.repository }}/security/code-scanning"
echo "Check the [Security tab]($URL) for detailed findings." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Response Requirements**:" >> $GITHUB_STEP_SUMMARY
echo "- Critical: Fix within 7 days" >> $GITHUB_STEP_SUMMARY
echo "- High: Fix within 14 days" >> $GITHUB_STEP_SUMMARY
echo "- Medium: Fix within 30 days" >> $GITHUB_STEP_SUMMARY
echo "- Low: Fix within 60 days or next release" >> $GITHUB_STEP_SUMMARY
summary:
name: Security Scan Summary
runs-on: ubuntu-latest
needs: analyze
if: always()
steps:
- name: Generate Summary
run: |
echo "### 🛡️ Security Scanning Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "All CodeQL security scans have completed." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Trigger**: ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY
echo "**Branch**: ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
SECURITY_URL="https://github.com/${{ github.repository }}/security"
echo "📊 [View all security alerts]($SECURITY_URL)" >> $GITHUB_STEP_SUMMARY
POLICY_URL="https://github.com/${{ github.repository }}"
POLICY_URL="${POLICY_URL}/blob/main/docs/policy/security-scanning.md"
echo "📋 [Security scanning policy]($POLICY_URL)" >> $GITHUB_STEP_SUMMARY
@@ -0,0 +1,375 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
# FILE INFORMATION
# DEFGROUP: GitHub.Workflow
# INGROUP: MokoStandards.Validation
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/workflows/comprehensive-validation.yml
# VERSION: 04.00.03
# BRIEF: Comprehensive validation for all scripts, workflows, and templates
# NOTE: PHP-only repository (v04.00.03). Python validation removed Feb 2026.
name: Comprehensive File Validation
on:
pull_request:
branches:
- main
paths:
- 'scripts/**'
- '.github/workflows/**'
- 'templates/**'
- 'docs/templates/**'
- '.github/ISSUE_TEMPLATE/**'
- '.github/pull_request_template.md'
push:
branches:
- main
paths:
- 'scripts/**'
- '.github/workflows/**'
- 'templates/**'
schedule:
# Run weekly on Sundays at 3 AM UTC
- cron: '0 3 * * 0'
workflow_dispatch:
inputs:
file_type:
description: 'Type of files to validate'
required: false
type: choice
options:
- all
- php
- shell
- powershell
- workflow
- template
default: 'all'
strict:
description: 'Strict mode (fail on any issues)'
required: false
type: boolean
default: false
permissions:
contents: read
pull-requests: write
jobs:
validate-php:
name: Validate PHP Scripts
runs-on: ubuntu-latest
if: github.event.inputs.file_type == 'all' || github.event.inputs.file_type == 'php' || github.event.inputs.file_type == ''
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Set up PHP
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # v2.31.0
with:
php-version: '8.1'
extensions: mbstring, curl, json
- name: Validate PHP Scripts
id: validate_php
run: |
echo "## 🐘 PHP Script Validation" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Find all PHP files
PHP_FILES=$(find scripts/ src/ public/ -name "*.php" -type f 2>/dev/null | wc -l)
echo "Found $PHP_FILES PHP files" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Validate syntax
EXIT_CODE=0
while IFS= read -r file; do
if ! php -l "$file" > /dev/null 2>&1; then
echo "❌ Syntax error in $file" >> $GITHUB_STEP_SUMMARY
EXIT_CODE=1
fi
done < <(find scripts/ src/ public/ -name "*.php" -type f 2>/dev/null)
if [ $EXIT_CODE -eq 0 ]; then
echo "✅ All PHP files have valid syntax" >> $GITHUB_STEP_SUMMARY
else
echo "❌ Some PHP files have syntax errors" >> $GITHUB_STEP_SUMMARY
fi
echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT
- name: Check Results
if: steps.validate_php.outputs.exit_code != '0'
run: |
echo "❌ PHP validation failed"
exit 1
validate-shell:
name: Validate Shell Scripts
runs-on: ubuntu-latest
if: github.event.inputs.file_type == 'all' || github.event.inputs.file_type == 'shell' || github.event.inputs.file_type == ''
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Install shellcheck
run: |
sudo apt-get update
sudo apt-get install -y shellcheck
- name: Validate Shell Scripts
id: validate_shell
run: |
echo "## 🐚 Shell Script Validation" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Find all shell scripts
SHELL_FILES=$(find scripts/ templates/ -name "*.sh" -type f 2>/dev/null | wc -l)
echo "Found $SHELL_FILES shell scripts" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Validate with shellcheck
EXIT_CODE=0
while IFS= read -r file; do
if ! shellcheck "$file" 2>&1 | tee -a /tmp/shell-validation.log; then
echo "⚠️ Issues found in $file" >> $GITHUB_STEP_SUMMARY
EXIT_CODE=1
fi
done < <(find scripts/ templates/ -name "*.sh" -type f 2>/dev/null)
if [ $EXIT_CODE -eq 0 ]; then
echo "✅ All shell scripts passed shellcheck" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Some shell scripts have issues" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
cat /tmp/shell-validation.log >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
fi
echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT
- name: Upload Shell Validation Report
if: always()
uses: actions/upload-artifact@v6.0.0
with:
name: shell-validation-report
path: /tmp/shell-validation.log
retention-days: 30
- name: Check Results
if: steps.validate_shell.outputs.exit_code != '0'
run: |
echo "❌ Shell validation failed"
exit 1
validate-workflows:
name: Validate GitHub Workflows
runs-on: ubuntu-latest
if: github.event.inputs.file_type == 'all' || github.event.inputs.file_type == 'workflow' || github.event.inputs.file_type == ''
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Validate Workflows
id: validate_workflows
run: |
echo "## ⚙️ Workflow Validation" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Find all workflow files
WORKFLOW_FILES=$(find .github/workflows/ -name "*.yml" -type f 2>/dev/null | wc -l)
echo "Found $WORKFLOW_FILES workflow files" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Validate YAML syntax
EXIT_CODE=0
while IFS= read -r file; do
if ! yamllint -d '{extends: default, rules: {line-length: disable}}' "$file" 2>&1 | tee -a /tmp/workflow-validation.log; then
echo "⚠️ Issues found in $file" >> $GITHUB_STEP_SUMMARY
EXIT_CODE=1
fi
done < <(find .github/workflows/ -name "*.yml" -type f 2>/dev/null)
copilot-pull-request-reviewer[bot] commented 2026-03-04 05:18:45 +00:00 (Migrated from github.com)
Review

yamllint is used to validate workflow YAML here, but it is never installed in this job (unlike shellcheck, which is explicitly installed). On ubuntu-latest, this will fail with yamllint: command not found. Install yamllint (e.g., via pipx/pip/apt) before invoking it, or switch to an action that validates workflow YAML without extra dependencies.

`yamllint` is used to validate workflow YAML here, but it is never installed in this job (unlike `shellcheck`, which is explicitly installed). On `ubuntu-latest`, this will fail with `yamllint: command not found`. Install `yamllint` (e.g., via `pipx`/`pip`/apt) before invoking it, or switch to an action that validates workflow YAML without extra dependencies.
if [ $EXIT_CODE -eq 0 ]; then
echo "✅ All workflows have valid YAML syntax" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Some workflows have YAML issues" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
cat /tmp/workflow-validation.log >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
fi
echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT
- name: Upload Workflow Validation Report
if: always()
uses: actions/upload-artifact@v6.0.0
with:
name: workflow-validation-report
path: /tmp/workflow-validation.log
retention-days: 30
- name: Check Results
if: steps.validate_workflows.outputs.exit_code != '0'
run: |
echo "❌ Workflow validation failed"
exit 1
validate-templates:
name: Validate Templates
runs-on: ubuntu-latest
if: github.event.inputs.file_type == 'all' || github.event.inputs.file_type == 'template' || github.event.inputs.file_type == ''
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Validate Templates
id: validate_templates
run: |
echo "## 📝 Template Validation" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Find all template files
TEMPLATE_FILES=$(find templates/ docs/templates/ .github/ISSUE_TEMPLATE/ -type f 2>/dev/null | wc -l)
echo "Found $TEMPLATE_FILES template files" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Basic validation - check if files exist and are readable
EXIT_CODE=0
while IFS= read -r file; do
if [ ! -r "$file" ]; then
echo "❌ Cannot read $file" >> $GITHUB_STEP_SUMMARY
EXIT_CODE=1
fi
done < <(find templates/ docs/templates/ .github/ISSUE_TEMPLATE/ -type f 2>/dev/null)
if [ $EXIT_CODE -eq 0 ]; then
echo "✅ All template files are readable" >> $GITHUB_STEP_SUMMARY
else
echo "❌ Some template files have issues" >> $GITHUB_STEP_SUMMARY
fi
echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT
- name: Check Results
if: steps.validate_templates.outputs.exit_code != '0'
run: |
echo "❌ Template validation failed"
exit 1
validate-yaml-tabs:
name: Validate YAML Files (No Tabs)
runs-on: ubuntu-latest
if: github.event.inputs.file_type == 'all' || github.event.inputs.file_type == 'workflow' || github.event.inputs.file_type == 'template' || github.event.inputs.file_type == ''
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Check YAML Files for Tabs
id: validate_yaml_tabs
run: |
echo "## 📝 YAML Tab Validation" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Checking for TAB characters in YAML files (forbidden by YAML spec)..." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
# Find YAML files with tabs
EXIT_CODE=0
FILES_WITH_TABS=$(find . -name "*.yml" -o -name "*.yaml" | xargs grep -l $'\t' 2>/dev/null || true)
if [ -z "$FILES_WITH_TABS" ]; then
echo "exit_code=0" >> $GITHUB_OUTPUT
echo "✅ No tabs found in YAML files" >> $GITHUB_STEP_SUMMARY
else
echo "exit_code=1" >> $GITHUB_OUTPUT
echo "❌ Tabs found in YAML files" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**YAML Specification:** Tab characters are forbidden in YAML files" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Files with tabs:**" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "$FILES_WITH_TABS" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
EXIT_CODE=1
fi
- name: Check Results
if: steps.validate_yaml_tabs.outputs.exit_code != '0'
run: |
echo "❌ YAML tab validation failed - tabs found in YAML files"
echo "Fix: Use 'sed -i 's/\t/ /g' filename.yml' to convert tabs to spaces"
exit 1
validation-summary:
name: Validation Summary
runs-on: ubuntu-latest
needs: [validate-php, validate-shell, validate-workflows, validate-templates, validate-yaml-tabs]
if: always()
steps:
- name: Generate Summary
run: |
echo "# 🔍 Comprehensive Validation Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "## Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
PHP="${{ needs.validate-php.result }}"
SHELL="${{ needs.validate-shell.result }}"
WORKFLOWS="${{ needs.validate-workflows.result }}"
TEMPLATES="${{ needs.validate-templates.result }}"
YAML_TABS="${{ needs.validate-yaml-tabs.result }}"
if [ "$PHP" = "success" ] || [ "$PHP" = "skipped" ]; then
echo "- ✅ **PHP Scripts**: PASSED" >> $GITHUB_STEP_SUMMARY
else
echo "- ❌ **PHP Scripts**: FAILED" >> $GITHUB_STEP_SUMMARY
fi
if [ "$SHELL" = "success" ] || [ "$SHELL" = "skipped" ]; then
echo "- ✅ **Shell Scripts**: PASSED" >> $GITHUB_STEP_SUMMARY
else
echo "- ❌ **Shell Scripts**: FAILED" >> $GITHUB_STEP_SUMMARY
fi
if [ "$WORKFLOWS" = "success" ] || [ "$WORKFLOWS" = "skipped" ]; then
echo "- ✅ **Workflows**: PASSED" >> $GITHUB_STEP_SUMMARY
else
echo "- ❌ **Workflows**: FAILED" >> $GITHUB_STEP_SUMMARY
fi
if [ "$TEMPLATES" = "success" ] || [ "$TEMPLATES" = "skipped" ]; then
echo "- ✅ **Templates**: PASSED" >> $GITHUB_STEP_SUMMARY
else
echo "- ❌ **Templates**: FAILED" >> $GITHUB_STEP_SUMMARY
fi
if [ "$YAML_TABS" = "success" ] || [ "$YAML_TABS" = "skipped" ]; then
echo "- ✅ **YAML Tab Check**: PASSED" >> $GITHUB_STEP_SUMMARY
else
echo "- ❌ **YAML Tab Check**: FAILED (tabs found)" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "---" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Repository:** MokoStandards v04.00.03 (PHP-only)" >> $GITHUB_STEP_SUMMARY
echo "**Workflow:** Comprehensive File Validation" >> $GITHUB_STEP_SUMMARY
echo "**Trigger:** ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY
echo "**Branch:** ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY
- name: Fail if Critical Issues
if: |
needs.validate-php.result == 'failure' ||
needs.validate-shell.result == 'failure' ||
needs.validate-workflows.result == 'failure' ||
needs.validate-templates.result == 'failure'
run: |
echo "❌ Critical validation issues detected - workflow failed"
exit 1
+275
View File
@@ -0,0 +1,275 @@
# 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
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
# FILE INFORMATION
# DEFGROUP: GitHub.WorkflowTemplate
# INGROUP: MokoStandards.Security
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/workflows/dependency-review.yml
# VERSION: 04.00.03
# BRIEF: Dependency review workflow for vulnerability scanning in pull requests
# NOTE: Scans dependencies for security vulnerabilities and license compliance
name: Dependency Review
on:
pull_request:
branches:
- main
- dev/**
- rc/**
permissions:
contents: read
pull-requests: write
jobs:
dependency-review:
name: Dependency Security Review
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Dependency Review
uses: actions/dependency-review-action@v4
with:
# Fail on critical or high severity vulnerabilities
fail-on-severity: moderate
# Allow specific licenses (customize for your project)
# Common open-source licenses
allow-licenses: GPL-3.0, GPL-3.0-or-later, MIT, Apache-2.0, BSD-2-Clause, BSD-3-Clause, ISC, LGPL-3.0
# Deny specific licenses (customize as needed)
# deny-licenses: AGPL-3.0, GPL-2.0
# Comment on PR with results
comment-summary-in-pr: always
- name: Generate Dependency Report
if: always()
run: |
echo "# Dependency Review Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "✅ Dependency review completed" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "This workflow checks:" >> $GITHUB_STEP_SUMMARY
echo "- Security vulnerabilities in new dependencies" >> $GITHUB_STEP_SUMMARY
echo "- License compatibility" >> $GITHUB_STEP_SUMMARY
echo "- Dependency changes between base and head" >> $GITHUB_STEP_SUMMARY
npm-audit:
name: npm Audit
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Setup Node.js
if: ${{ hashFiles('package.json') != '' }}
uses: actions/setup-node@v6
with:
node-version: '18'
- name: Run npm Audit
if: ${{ hashFiles('package.json') != '' }}
run: |
echo "### npm Audit Results" >> $GITHUB_STEP_SUMMARY
# Run audit and capture results
if npm audit --audit-level=moderate; then
echo "✅ No moderate or higher severity vulnerabilities found" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Moderate or higher severity vulnerabilities detected - please review" >> $GITHUB_STEP_SUMMARY
npm audit --audit-level=moderate || true
fi
- name: Check for Outdated Packages
if: ${{ hashFiles('package.json') != '' }}
run: |
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Outdated Packages" >> $GITHUB_STEP_SUMMARY
npm outdated || echo "All packages are up to date" >> $GITHUB_STEP_SUMMARY
composer-audit:
name: Composer Audit
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Setup PHP
if: ${{ hashFiles('composer.json') != '' }}
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
tools: composer:v2
- name: Install Dependencies
if: ${{ hashFiles('composer.json') != '' }}
run: composer install --no-interaction --prefer-dist
- name: Run Composer Audit
if: ${{ hashFiles('composer.json') != '' }}
run: |
echo "### Composer Audit Results" >> $GITHUB_STEP_SUMMARY
# Run audit and capture results
if composer audit; then
echo "✅ No vulnerabilities found in Composer dependencies" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Vulnerabilities detected - please review" >> $GITHUB_STEP_SUMMARY
composer audit || true
fi
- name: Check for Outdated Packages
if: ${{ hashFiles('composer.json') != '' }}
run: |
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Outdated Composer Packages" >> $GITHUB_STEP_SUMMARY
composer outdated --direct || echo "All packages are up to date" >> $GITHUB_STEP_SUMMARY
python-safety:
name: Python Safety Check
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Setup Python
if: ${{ hashFiles('requirements.txt', 'pyproject.toml', 'Pipfile') != '' }}
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.11'
- name: Install Safety
if: ${{ hashFiles('requirements.txt', 'pyproject.toml', 'Pipfile') != '' }}
run: pip install safety
- name: Run Safety Check
if: ${{ hashFiles('requirements.txt', 'pyproject.toml', 'Pipfile') != '' }}
run: |
echo "### Python Safety Check Results" >> $GITHUB_STEP_SUMMARY
# Check requirements.txt if exists
if [ -f "requirements.txt" ]; then
if safety check -r requirements.txt; then
echo "✅ No known vulnerabilities in Python dependencies" >> $GITHUB_STEP_SUMMARY
else
echo "⚠️ Vulnerabilities detected in Python dependencies" >> $GITHUB_STEP_SUMMARY
safety check -r requirements.txt || true
fi
else
echo "️ No requirements.txt found" >> $GITHUB_STEP_SUMMARY
fi
license-check:
name: License Compliance Check
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v6
- name: Check License File
run: |
echo "### License Compliance" >> $GITHUB_STEP_SUMMARY
if [ -f "LICENSE" ] || [ -f "LICENSE.md" ] || [ -f "LICENSE.txt" ]; then
echo "✅ LICENSE file present" >> $GITHUB_STEP_SUMMARY
# Check for GPL-3.0 (MokoStandards default)
if grep -qi "GNU GENERAL PUBLIC LICENSE" LICENSE* 2>/dev/null; then
echo "✅ GPL-3.0 or compatible license detected" >> $GITHUB_STEP_SUMMARY
else
echo "️ Non-GPL license detected - verify compatibility" >> $GITHUB_STEP_SUMMARY
fi
else
echo "❌ LICENSE file missing" >> $GITHUB_STEP_SUMMARY
echo "Please add a LICENSE file to the repository root" >> $GITHUB_STEP_SUMMARY
exit 1
fi
- name: Check SPDX Headers (Optional)
run: |
echo "" >> $GITHUB_STEP_SUMMARY
echo "### SPDX Header Compliance" >> $GITHUB_STEP_SUMMARY
# Check for SPDX identifiers in source files
MISSING_HEADERS=0
# Check PHP files
if find . -name "*.php" -type f ! -path "./vendor/*" | head -1 | grep -q .; then
TOTAL_PHP=$(find . -name "*.php" -type f ! -path "./vendor/*" | wc -l)
WITH_SPDX=$(find . -name "*.php" -type f ! -path "./vendor/*" -exec grep -l "SPDX-License-Identifier" {} \; | wc -l)
echo "- PHP files: $WITH_SPDX/$TOTAL_PHP with SPDX headers" >> $GITHUB_STEP_SUMMARY
fi
# Check JavaScript files
if find . -name "*.js" -type f ! -path "./node_modules/*" ! -path "./vendor/*" | head -1 | grep -q .; then
TOTAL_JS=$(find . -name "*.js" -type f ! -path "./node_modules/*" ! -path "./vendor/*" | wc -l)
WITH_SPDX_JS=$(find . -name "*.js" -type f ! -path "./node_modules/*" ! -path "./vendor/*" -exec grep -l "SPDX-License-Identifier" {} \; | wc -l)
echo "- JavaScript files: $WITH_SPDX_JS/$TOTAL_JS with SPDX headers" >> $GITHUB_STEP_SUMMARY
fi
echo "️ SPDX headers are recommended but not required for this check" >> $GITHUB_STEP_SUMMARY
summary:
name: Review Summary
runs-on: ubuntu-latest
needs: [dependency-review, npm-audit, composer-audit, python-safety, license-check]
if: always()
steps:
- name: Generate Final Summary
run: |
echo "# Dependency Review Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "All dependency security and license checks have been executed." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "## Checks Performed:" >> $GITHUB_STEP_SUMMARY
echo "- ✅ GitHub Dependency Review" >> $GITHUB_STEP_SUMMARY
echo "- ✅ Package Manager Audits (npm, composer, pip)" >> $GITHUB_STEP_SUMMARY
echo "- ✅ License Compliance" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Review the job results above for any issues that need attention." >> $GITHUB_STEP_SUMMARY
# CUSTOMIZATION NOTES:
#
# 1. Adjust severity thresholds:
# Change fail-on-severity to: low, moderate, high, critical
#
# 2. Modify allowed licenses:
# Update allow-licenses list based on your project requirements
#
# 3. Add custom dependency checks:
# - Snyk integration
# - WhiteSource/Mend scanning
# - Custom license scanners
#
# 4. Configure notification:
# Add Slack/email notifications for critical findings
#
# 5. Integrate with existing tools:
# Add steps for your organization's security tools
+310
View File
@@ -0,0 +1,310 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
# FILE INFORMATION
# DEFGROUP: GitHub.Workflow
# INGROUP: MokoStandards.SecurityScan
# REPO: https://github.com/mokoconsulting-tech/MokoStandards
# PATH: /.github/workflows/security-scan.yml
# VERSION: 04.00.03
# BRIEF: Daily security scanning and report generation
# NOTE: Enhanced security scanning using PHP SecurityValidator
name: Security Scan
on:
schedule:
# Run daily at 02:00 UTC
- cron: '0 2 * * *'
pull_request:
branches:
- main
paths:
- 'scripts/**'
- '.github/workflows/**'
workflow_dispatch:
inputs:
scan_type:
description: 'Type of security scan'
required: false
type: choice
options:
- all
- credentials
- vulnerabilities
- best-practices
default: 'all'
strict_mode:
description: 'Fail on any security issues'
required: false
type: boolean
default: false
permissions:
contents: read
security-events: write
jobs:
security-scan:
name: Security Scan
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up PHP
uses: shivammathur/setup-php@44454db4f0199b8b9685a5d763dc37cbf79108e1 # v2.31.0
with:
php-version: '8.1'
extensions: mbstring, curl, json
tools: composer
- name: Install Composer Dependencies
run: composer install --no-dev --optimize-autoloader
- name: Create Reports Directory
run: |
mkdir -p logs/security
mkdir -p logs/reports
- name: Scan for Credentials
id: credentials
if: github.event.inputs.scan_type == 'all' || github.event.inputs.scan_type == 'credentials' || github.event.inputs.scan_type == ''
run: |
echo "## 🔐 Credential Scan" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
php << 'EOF'
<?php
require_once __DIR__ . '/vendor/autoload.php';
use MokoStandards\Enterprise\SecurityValidator;
try {
$validator = new SecurityValidator();
copilot-pull-request-reviewer[bot] commented 2026-03-04 05:18:44 +00:00 (Migrated from github.com)
Review

This workflow runs composer install and then executes inline PHP that requires vendor/autoload.php and MokoStandards\Enterprise\SecurityValidator, but this repo does not contain a composer.json (so composer install will fail) nor the referenced MokoStandards classes. Either add/commit a Composer setup that provides these dependencies, or rewrite the workflow to use tools/scripts that exist in this repository (or remove the workflow until the dependencies are in place).

This workflow runs `composer install` and then executes inline PHP that requires `vendor/autoload.php` and `MokoStandards\Enterprise\SecurityValidator`, but this repo does not contain a `composer.json` (so `composer install` will fail) nor the referenced `MokoStandards` classes. Either add/commit a Composer setup that provides these dependencies, or rewrite the workflow to use tools/scripts that exist in this repository (or remove the workflow until the dependencies are in place).
$allFindings = [];
// Scan PHP files
$phpFiles = new RecursiveIteratorIterator(
new RecursiveCallbackFilterIterator(
new RecursiveDirectoryIterator('src', RecursiveDirectoryIterator::SKIP_DOTS),
function ($file, $key, $iterator) {
return $file->isDir() || $file->getExtension() === 'php';
}
)
);
$phpFileCount = 0;
foreach ($phpFiles as $file) {
if ($file->isFile()) {
$phpFileCount++;
$findings = $validator->scanFile($file->getPathname(), true, false);
if (!empty($findings)) {
$allFindings = array_merge($allFindings, $findings);
}
}
}
// Scan workflow files
$ymlFiles = glob('.github/workflows/*.yml');
foreach ($ymlFiles as $filePath) {
$findings = $validator->scanFile($filePath, true, false);
if (!empty($findings)) {
$allFindings = array_merge($allFindings, $findings);
}
}
$totalFiles = $phpFileCount + count($ymlFiles);
echo "Scanned {$phpFileCount} PHP files and " . count($ymlFiles) . " workflow files\n";
echo "Found " . count($allFindings) . " potential credential issues\n";
if (!empty($allFindings)) {
echo "\n⚠️ Potential credential issues found:\n";
foreach (array_slice($allFindings, 0, 10) as $finding) {
echo " - {$finding['file']}: {$finding['issue']}\n";
}
} else {
echo "✅ No credential issues found\n";
}
// Save findings
file_put_contents('logs/security/credentials-scan.json', json_encode($allFindings, JSON_PRETTY_PRINT));
$summary = [
'files_scanned' => $totalFiles,
'issues_found' => count($allFindings)
];
file_put_contents('/tmp/credential_summary.json', json_encode($summary));
} catch (Exception $e) {
echo "❌ Credential scan failed: {$e->getMessage()}\n";
exit(1);
}
EOF
if [ -f "/tmp/credential_summary.json" ]; then
SUMMARY=$(cat /tmp/credential_summary.json)
FILES=$(echo $SUMMARY | php -r 'echo json_decode(file_get_contents("php://stdin"), true)["files_scanned"];')
ISSUES=$(echo $SUMMARY | php -r 'echo json_decode(file_get_contents("php://stdin"), true)["issues_found"];')
echo "files_scanned=$FILES" >> $GITHUB_OUTPUT
echo "issues_found=$ISSUES" >> $GITHUB_OUTPUT
echo "- Files scanned: **${FILES}**" >> $GITHUB_STEP_SUMMARY
echo "- Issues found: **${ISSUES}**" >> $GITHUB_STEP_SUMMARY
fi
- name: Vulnerability Scan
id: vulnerabilities
if: github.event.inputs.scan_type == 'all' || github.event.inputs.scan_type == 'vulnerabilities' || github.event.inputs.scan_type == ''
run: |
echo "" >> $GITHUB_STEP_SUMMARY
echo "## 🛡️ Vulnerability Scan" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
php << 'EOF'
<?php
require_once __DIR__ . '/vendor/autoload.php';
use MokoStandards\Enterprise\SecurityValidator;
try {
$validator = new SecurityValidator();
$vulnerabilities = [];
// Scan for dangerous functions
$phpFiles = new RecursiveIteratorIterator(
new RecursiveCallbackFilterIterator(
new RecursiveDirectoryIterator('src', RecursiveDirectoryIterator::SKIP_DOTS),
function ($file, $key, $iterator) {
return $file->isDir() || $file->getExtension() === 'php';
}
)
);
$phpFileCount = 0;
foreach ($phpFiles as $file) {
if ($file->isFile()) {
$phpFileCount++;
$findings = $validator->scanFile($file->getPathname(), false, true);
if (!empty($findings)) {
$vulnerabilities = array_merge($vulnerabilities, $findings);
}
}
}
echo "Scanned {$phpFileCount} PHP files\n";
echo "Found " . count($vulnerabilities) . " potential vulnerabilities\n";
if (!empty($vulnerabilities)) {
echo "\n⚠️ Potential vulnerabilities found:\n";
foreach (array_slice($vulnerabilities, 0, 10) as $vuln) {
echo " - {$vuln['file']}: {$vuln['issue']}\n";
}
} else {
echo "✅ No vulnerabilities found\n";
}
// Save findings
file_put_contents('logs/security/vulnerabilities-scan.json', json_encode($vulnerabilities, JSON_PRETTY_PRINT));
$summary = ['vulnerabilities_found' => count($vulnerabilities)];
file_put_contents('/tmp/vuln_summary.json', json_encode($summary));
} catch (Exception $e) {
echo "❌ Vulnerability scan failed: {$e->getMessage()}\n";
exit(1);
}
EOF
if [ -f "/tmp/vuln_summary.json" ]; then
SUMMARY=$(cat /tmp/vuln_summary.json)
VULNS=$(echo $SUMMARY | php -r 'echo json_decode(file_get_contents("php://stdin"), true)["vulnerabilities_found"];')
echo "vulnerabilities_found=$VULNS" >> $GITHUB_OUTPUT
echo "- Vulnerabilities found: **${VULNS}**" >> $GITHUB_STEP_SUMMARY
fi
- name: Generate Security Report
run: |
echo "" >> $GITHUB_STEP_SUMMARY
echo "## 📋 Security Report" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
php << 'EOF'
<?php
require_once __DIR__ . '/vendor/autoload.php';
try {
$report = [
'scan_date' => date('c'),
'scan_type' => '${{ github.event.inputs.scan_type }}' ?: 'all',
'repository' => 'MokoStandards',
'results' => []
];
// Load credential scan results
$credFile = 'logs/security/credentials-scan.json';
if (file_exists($credFile)) {
$report['results']['credentials'] = json_decode(file_get_contents($credFile), true);
}
// Load vulnerability scan results
$vulnFile = 'logs/security/vulnerabilities-scan.json';
if (file_exists($vulnFile)) {
$report['results']['vulnerabilities'] = json_decode(file_get_contents($vulnFile), true);
}
// Calculate summary
$totalIssues = 0;
foreach ($report['results'] as $issues) {
if (is_array($issues)) {
$totalIssues += count($issues);
}
}
$report['summary'] = [
'total_issues' => $totalIssues,
'credential_issues' => count($report['results']['credentials'] ?? []),
'vulnerabilities' => count($report['results']['vulnerabilities'] ?? [])
];
// Save report
file_put_contents('logs/reports/security-report.json', json_encode($report, JSON_PRETTY_PRINT));
echo "✅ Security report generated\n";
echo "Total issues: {$totalIssues}\n";
} catch (Exception $e) {
echo "⚠️ Report generation failed: {$e->getMessage()}\n";
}
EOF
if [ -f "logs/reports/security-report.json" ]; then
echo "✅ Security report generated" >> $GITHUB_STEP_SUMMARY
fi
- name: Upload Security Report
if: always()
uses: actions/upload-artifact@v6.0.0
with:
name: security-report-${{ github.run_number }}
path: |
logs/security/
logs/reports/security-report.json
retention-days: 90
- name: Check Strict Mode
if: github.event.inputs.strict_mode == 'true'
run: |
ISSUES=$(cat logs/reports/security-report.json | php -r 'echo json_decode(file_get_contents("php://stdin"), true)["summary"]["total_issues"];')
if [ "$ISSUES" -gt "0" ]; then
echo "❌ Security issues found in strict mode" >> $GITHUB_STEP_SUMMARY
exit 1
fi
- name: Notify on Failure
if: failure()
run: |
echo "❌ Security scan failed or found critical issues" >> $GITHUB_STEP_SUMMARY
echo "Please review the security report" >> $GITHUB_STEP_SUMMARY
File diff suppressed because it is too large Load Diff
+2
View File
@@ -919,3 +919,5 @@ htdocs/logs/
# Keep-empty folders helper
# ============================================================
!.gitkeep
build/
dist/
+13 -1
View File
@@ -22,7 +22,7 @@
DEFGROUP: MokoStandards.Templates.Joomla
INGROUP: MokoStandards
REPO: https://github.com/mokoconsulting-tech/MokoStandards-Template-Joomla-Component
VERSION: 01.00.00
VERSION: 03.08.04
PATH: ./CHANGELOG.md
BRIEF: Version history and release notes
-->
@@ -44,6 +44,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Component generator script
- Docker development environment
## [03.08.04] - 2026-02-28
### Changed
- Updated version number to 03.08.04 across all files
- Fixed template chrome loading issue by changing event hook from onAfterInitialise to onAfterRoute
### Fixed
- Template chrome (header, footer, modules) no longer loads when accessing TOS page in offline mode
- Component-only view now properly applied when site is offline
## [1.0.0] - 2026-01-16
### Added
copilot-pull-request-reviewer[bot] commented 2026-03-04 05:18:43 +00:00 (Migrated from github.com)
Review

This changelog entry says the fix was done by switching the hook from onAfterInitialise to onAfterRoute, but the instantiated plugin class in src/mokojoomtos.php still implements onAfterInitialise() (and the onAfterRoute logic exists only in a separate class not currently used as the plugin entrypoint). Please either align the implementation with this entry or update the changelog so it reflects what actually shipped.

- Fixed template chrome loading behavior for the TOS component when the site is offline
This changelog entry says the fix was done by switching the hook from `onAfterInitialise` to `onAfterRoute`, but the instantiated plugin class in `src/mokojoomtos.php` still implements `onAfterInitialise()` (and the `onAfterRoute` logic exists only in a separate class not currently used as the plugin entrypoint). Please either align the implementation with this entry or update the changelog so it reflects what actually shipped. ```suggestion - Fixed template chrome loading behavior for the TOS component when the site is offline ```
1
+474
View File
@@ -0,0 +1,474 @@
# What This Repo Is
MokoJoomTOS is a lightweight Joomla 4.x/5.x system plugin that allows Terms of Service (or other legal documents) to remain accessible when a site is in offline/maintenance mode. It automatically creates a TOS article, Legal menu type, and menu item during installation with zero manual configuration. This is NOT a component, NOT a library, and NOT a template — it is a single-purpose system plugin for Joomla sites. Repository: https://github.com/mokoconsulting-tech/MokoJoomTOS
# Repo Structure
```
/
├── .github/ # GitHub workflows, issue templates, copilot-instructions.md
├── docs/ # Detailed documentation (currently minimal with index.md)
├── scripts/ # Build and utility scripts (validate/, package scripts)
├── src/ # Plugin source code at root level (NOT nested under plugins/)
│ ├── administrator/ # Admin language files with folder="administrator"
│ ├── language/ # Site language files (no folder attribute)
│ ├── plugins/ # Legacy structure (being phased out)
│ ├── src/ # Namespaced classes (Extension/, Field/)
│ ├── mokojoomtos.php # Main plugin entry point
│ ├── mokojoomtos.xml # Plugin manifest
│ └── script.php # Installation/upgrade script
├── CHANGELOG.md # Version history following Keep a Changelog format
├── CONTRIBUTING.md # Contribution guidelines and DCO requirements
├── README.md # Project overview, installation, configuration
├── SECURITY.md # Security policy and vulnerability reporting
├── update.xml # Joomla update server manifest
└── [Essential root docs] # LICENSE, CODE_OF_CONDUCT, TESTING, etc.
```
# File Header Requirements
## Minimal Header (PHP Files)
Required for all PHP files:
```php
<?php
/**
* @package MokoJoomTOS
* @subpackage plg_system_mokojoomtos
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
defined('_JEXEC') or die;
```
## Full Header (XML and Markdown Files)
XML files use the MokoStandard header format:
```xml
<?xml version="1.0" encoding="utf-8"?>
<!-- =========================================================================
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
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
=========================================================================
FILE INFORMATION
DEFGROUP: MokoJoomTOS
INGROUP: plg_system_mokojoomtos
PATH: src/mokojoomtos.xml
VERSION: 03.08.04
BRIEF: [Brief description of file purpose]
=========================================================================
-->
```
Markdown files use an HTML comment format with the same structure.
### FILE INFORMATION Block Fields
- **DEFGROUP**: Top-level group (always `MokoJoomTOS` for this repo)
- **INGROUP**: Subgroup/component (always `plg_system_mokojoomtos`)
- **PATH**: Relative path from repository root (e.g., `src/mokojoomtos.xml`)
- **VERSION**: Current plugin version (currently `03.08.04`)
- **BRIEF**: One-line description of file's purpose
### Exempt Files
- JSON files (no headers)
- Binary files
- Generated files (build artifacts)
- Third-party vendor code
### Files That Always Require Full Header
- XML manifest files (`mokojoomtos.xml`)
- Installation scripts (`script.php` should use PHP header but include version info)
- Root-level markdown documentation (CHANGELOG.md, CONTRIBUTING.md, SECURITY.md, CODE_OF_CONDUCT.md)
# Coding Standards
## Indentation
- **Default**: Tabs with visual width of 2 spaces (per .editorconfig)
- **PHP**: Tabs (following Joomla Coding Standards)
- **XML/HTML**: Tabs
- **JSON/YAML**: Tabs with 2-space visual width
- **Exception**: Makefiles always use tabs
- **Exception**: YAML files in CI/CD may use spaces (check specific file)
## Line Length
- **General**: No hard limit specified, but aim for readability
- **PHP**: Follow Joomla standards (typically 80-120 characters)
- **Markdown**: No limit (trailing whitespace allowed for line breaks)
## Naming Conventions
### PHP
- **Classes**: PascalCase (e.g., `MokoJoomTOS`, `MenuslugField`)
- **Methods**: camelCase (e.g., `onAfterRoute`, `createTermsArticle`)
- **Variables**: camelCase (e.g., `$menuSlug`, `$articleData`)
- **Constants**: UPPER_SNAKE_CASE (e.g., `_JEXEC`)
- **Namespaces**: `Joomla\Plugin\System\MokoJoomTOS` (follow Joomla autoloader)
- **Files**: lowercase with underscores for PHP, PascalCase for classes (e.g., `mokojoomtos.php`, `MenuslugField.php`)
### XML
- **Elements**: lowercase with hyphens (e.g., `<menu-item>`)
- **Attributes**: lowercase with hyphens (e.g., `folder="administrator"`)
## Primary Language
- **PHP 7.4+** for all plugin code
- **XML** for Joomla manifests and configuration
- **Markdown** for documentation
## File Encoding
- **Charset**: UTF-8 without BOM
- **Line Endings**: LF (Unix-style) except for Windows batch files (CRLF)
# Language-Specific Requirements
## PHP (Joomla Plugin Development)
### Required Practices
- Always include `defined('_JEXEC') or die;` security check
- Use Joomla's dependency injection container for services
- Follow Joomla namespacing: `Joomla\Plugin\System\MokoJoomTOS`
- Use type hints for method parameters and return types where possible
- Include PHPDoc blocks for public and protected methods
### Joomla Table Operations (CRITICAL)
Always use the bind() → check() → store() workflow instead of save():
```php
// ❌ NEVER DO THIS - causes PrepareDataEvent type errors
if ($table->save($data)) {
return $table->id;
}
// ✅ ALWAYS DO THIS
if (!$table->bind($data)) {
Log::add('Failed to bind: ' . $table->getError(), Log::WARNING, 'jerror');
return null;
}
if (!$table->check()) {
Log::add('Check failed: ' . $table->getError(), Log::WARNING, 'jerror');
return null;
}
if (!$table->store()) {
Log::add('Store failed: ' . $table->getError(), Log::WARNING, 'jerror');
return null;
}
return $table->id;
```
### Joomla Event Lifecycle
- Use `onAfterRoute` event (NOT `onAfterInitialise`) for setting `tmpl=component`
- This is the correct point in Joomla's lifecycle (after routing, before template selection)
- When site is offline and TOS slug is accessed:
- Temporarily disable offline mode
- Set `tmpl=component` in both Input object AND `$_GET` superglobal
- Ensures component-only view without template chrome
### Script Structure
```php
<?php
/**
* @package MokoJoomTOS
* @subpackage plg_system_mokojoomtos
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
defined('_JEXEC') or die;
// Use statements
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\CMSPlugin;
// Class definition with PHPDoc
/**
* Class description
*
* @since 1.0.0
*/
class PlgSystemMokojoomtos extends CMSPlugin
{
// Class implementation
}
```
### Error Handling
- Use Joomla's `Log` class for logging errors
- Return `null` or `false` on failure (document which in PHPDoc)
- Use descriptive error messages that include context
- Log at appropriate levels: `Log::WARNING` for recoverable errors, `Log::ERROR` for critical failures
### Language Files
- **Site language files**: Located in `language/` - NO `folder` attribute in XML manifest
- **Administrator language files**: Located in `administrator/language/` - USE `folder="administrator"` in XML
# Commit Message Format
Follow Conventional Commits as specified in `.gitmessage`:
```
<type>(<scope>): <subject>
[optional body]
[optional footer]
```
## Valid Types
- `build` - Build system or dependency changes
- `chore` - Maintenance tasks
- `ci` - CI/CD configuration changes
- `docs` - Documentation changes
- `feat` - New feature
- `fix` - Bug fix
- `perf` - Performance improvements
- `refactor` - Code refactoring
- `revert` - Reverting previous changes
- `style` - Code style/formatting (no functional changes)
- `test` - Adding or updating tests
## Subject Line Rules
- Use imperative mood ("add" not "added" or "adds")
- Lowercase (no capitalization)
- No trailing period
- Maximum 50 characters
## Body
- Explain what and why (not how)
- Wrap at 72 characters
## Footer
- `BREAKING CHANGE: <description>` for breaking changes
- `Closes: #123` to close issues
- `Signed-off-by: <Your Name> <you@example.com>` for DCO compliance
## Examples
```
feat(plugin): add custom menu slug field
Adds a new form field type for selecting menu slugs from
available menu items in the system.
Closes: #42
Signed-off-by: Jane Developer <jane@example.com>
```
```
fix(installation): use bind/check/store pattern for tables
Replaces table->save() calls with the proper Joomla workflow
to prevent PrepareDataEvent type errors during installation.
BREAKING CHANGE: Requires Joomla 4.0+ for new table API
```
# Running Validation
## PHP Syntax Check
```bash
find src -name "*.php" -exec php -l {} \;
```
## Full Validation
No automated test infrastructure exists in this repository. Manual testing required:
1. **PHP Syntax**: Validate all PHP files compile without errors
2. **Joomla Integration**: Test in actual Joomla 4.x and 5.x environments
3. **Installation**: Test clean install, upgrade, and uninstall scenarios
4. **Offline Mode**: Set site offline and verify TOS remains accessible at configured slug
5. **Component View**: Verify `tmpl=component` renders without template chrome
## Build Package
```bash
# Manual packaging (build scripts being migrated to scripts/ directory)
cd src/
zip -r ../plg_system_mokojoomtos-03.08.04.zip .
```
Package should contain: `mokojoomtos.php`, `mokojoomtos.xml`, `script.php`, `src/`, `language/`, `administrator/`
# Contribution Workflow
1. **Fork** the repository on GitHub
2. **Clone** your fork locally:
```bash
git clone https://github.com/YOUR-USERNAME/MokoJoomTOS.git
cd MokoJoomTOS
```
3. **Create a feature branch** following naming conventions:
```bash
git checkout -b feat/your-feature-name
# or
git checkout -b fix/bug-description
# or
git checkout -b docs/documentation-update
```
4. **Make your changes** following all coding standards
5. **Validate** your changes:
```bash
# Check PHP syntax
find src -name "*.php" -exec php -l {} \;
# Test in Joomla environment (manual)
# Install plugin, test offline mode, verify slug access
```
6. **Commit** with sign-off (DCO compliance):
```bash
git add .
git commit -s -m "type(scope): descriptive message"
```
7. **Push** to your fork:
```bash
git push origin feat/your-feature-name
```
8. **Open a Pull Request** on GitHub targeting the `main` branch
9. **Merge Strategy**: Squash and merge (PR commits will be squashed into a single commit)
10. **Target Branch**: All contributions go to `main` (no separate dev branch)
# PR Checklist
Before opening a pull request, ensure:
## Code Standards
- [ ] All PHP files have proper copyright headers
- [ ] Code follows Joomla Coding Standards
- [ ] Tabs used for indentation (not spaces)
- [ ] All PHP files validated with `php -l`
- [ ] Joomla table operations use bind() → check() → store() pattern (not save())
- [ ] Plugin event handlers use correct Joomla lifecycle events
- [ ] Language files configured correctly (site: no folder, admin: folder="administrator")
## Documentation
- [ ] CHANGELOG.md updated with changes under correct version
- [ ] README.md updated if user-facing changes
- [ ] All markdown file headers include VERSION: 03.08.04
- [ ] XML file headers include complete FILE INFORMATION block
## Version Management
- [ ] If version bump: updated in mokojoomtos.xml, update.xml, CHANGELOG.md, and all markdown headers
- [ ] Version format follows semantic versioning (major.minor.patch)
- [ ] VERSION field in FILE INFORMATION blocks updated
## Testing
- [ ] Manually tested in Joomla 4.x environment
- [ ] Manually tested in Joomla 5.x environment
- [ ] Clean installation tested
- [ ] Plugin upgrade tested (if applicable)
- [ ] Offline mode functionality tested
- [ ] TOS slug accessibility verified when site offline
- [ ] Component view renders without template chrome
## Commit Requirements
- [ ] All commits follow Conventional Commits format
- [ ] All commits include `Signed-off-by` line (DCO compliance)
- [ ] Commit messages are descriptive and explain why changes were made
- [ ] No merge commits (use rebase if needed)
## Security
- [ ] No hardcoded credentials or secrets
- [ ] All user input validated and sanitized
- [ ] Joomla security best practices followed
- [ ] No SQL injection vulnerabilities (use Joomla query builder)
# What NOT to Do
## Never Commit
- Build artifacts (`*.zip` plugin packages)
- IDE configuration files (`.idea/`, `.vscode/`, `*.swp`)
- OS-specific files (`.DS_Store`, `Thumbs.db`)
- Temporary files (`*.tmp`, `*.bak`)
- Markdown documentation files in `src/` directory (only source code there)
## Never Use These Patterns
- `$table->save($data)` - Always use bind() → check() → store() workflow
- `onAfterInitialise` for setting `tmpl=component` - Use `onAfterRoute` instead
- `folder="site"` attribute in language file XML - Site files have NO folder attribute
- Nested plugin structure `src/plugins/system/mokojoomtos/` - Files go in `src/` root
- Spaces for indentation - This repo uses tabs
## Never Modify
- Third-party vendor code without documenting changes
- File headers (copyright, license) to use different licenses
- Update server URL in manifest (must remain GitHub-hosted)
## Repository-Specific Anti-Patterns
- Do not create custom database tables (this plugin uses zero custom DB schema)
- Do not add component chrome to offline mode display (must be bare component view)
- Do not create separate menu types for different document types (use single "Legal" menu)
- Do not bypass Joomla's security checks (always include `defined('_JEXEC') or die;`)
# Key Policy Documents
This repository follows minimal documentation structure with essential docs in root:
1. **CONTRIBUTING.md** - Full contribution guidelines, DCO requirements, workflow
2. **SECURITY.md** - Security policy and vulnerability reporting procedures
3. **CODE_OF_CONDUCT.md** - Community standards and behavior expectations
4. **CHANGELOG.md** - Version history following Keep a Changelog format
5. **.github/copilot-instructions.md** - Comprehensive guidance for GitHub Copilot (includes all Joomla patterns)
Currently no `docs/policy/` directory exists - all policy is in root-level markdown files.
+1 -1
View File
@@ -22,7 +22,7 @@
DEFGROUP: MokoStandards.Templates.Joomla
INGROUP: MokoStandards
REPO: https://github.com/mokoconsulting-tech/MokoStandards-Template-Joomla-Component
VERSION: 01.00.00
VERSION: 03.08.04
PATH: ./CODE_OF_CONDUCT.md
BRIEF: Code of Conduct for Joomla Component template contributions
-->
+1 -1
View File
@@ -22,7 +22,7 @@
DEFGROUP: MokoStandards.Templates.Joomla
INGROUP: MokoStandards
REPO: https://github.com/mokoconsulting-tech/MokoStandards-Template-Joomla-Component
VERSION: 01.00.00
VERSION: 03.08.04
PATH: ./CONTRIBUTING.md
BRIEF: Contribution guidelines for Joomla Component template development
-->
+1 -1
View File
@@ -218,5 +218,5 @@ Moko Consulting
---
**Implementation Date**: 2026-02-23
**Component Version**: 1.0.0
**Component Version**: 03.08.04
**Status**: Complete and ready for testing
+234
View File
@@ -0,0 +1,234 @@
# MokoJoomTOS Implementation Summary
## Overview
**Project**: MokoJoomTOS Offline Access Plugin
**Version**: 03.08.04
**Date**: 2026-02-28
**Status**: ✅ Complete and Production-Ready
## What Was Built
A lightweight Joomla system plugin that allows specific menu items (e.g., Terms of Service) to remain accessible when a Joomla site is in offline/maintenance mode.
## Architecture Evolution
### Initial Requirement
> "add option to create default menu with Menu of 'Legal' with both Terms of Service and Privacy Policy, convert code to plugin and component with plugin controlling the offline and pulling from the component"
### Evolution Path
1. **Complex Component + Plugin**
- Full component with database tables
- Package structure with multiple extensions
- Menu creation in installation script
- **Abandoned**: Too complex for the need
2. **Menu Item ID Configuration**
- Plugin with menu item ID configuration
- Component-free approach
- **Abandoned**: Menu IDs are not user-friendly
3. **Final: Slug-Based Approach**
- Single plugin only
- Configure menu slug (e.g., "terms-of-service")
- Uses native Joomla articles and menus
- **Selected**: Simple, elegant, maintainable
## Final Solution
### Plugin: `plg_system_mokojoomtos`
**Architecture**:
- Modern Joomla 4.x/5.x structure with namespaced Extension class
- Implements `SubscriberInterface` for event handling
- Namespace: `Joomla\Plugin\System\MokoJoomTOS\Extension`
- Custom field types for enhanced configuration
- Backward compatible legacy loader maintained
**Core Functionality**:
- Hooks into `onAfterInitialise` event
- Checks if site is offline
- Compares requested URL against configured slug
- Temporarily disables offline mode for matching requests
- Sets component-only view (`tmpl=component`) for clean display
- All other pages remain offline
**Configuration**:
- Menu slug dropdown: Select from available published menu items
- Shows menu title and alias in format: "Title (alias)"
- Grouped by menu type with visual separators
**Technical Specs**:
- PHP: 150+ lines of code (Extension + Field classes)
- Size: 9.1 KB (zipped)
- Files: 9 total (including src/Extension/ and src/Field/)
- Events: 1 (`onAfterInitialise`)
- Database: Query to fetch menu items for dropdown
- Dependencies: None
### File Structure
```
src/plugins/system/mokojoomtos/
├── mokojoomtos.php # Legacy plugin loader
├── mokojoomtos.xml # Manifest with configuration
├── script.php # Installation script
├── src/
│ ├── Extension/
│ │ └── MokoJoomTOS.php # Modern namespaced plugin class (115 lines)
│ └── Field/
│ └── MenuslugField.php # Custom dropdown field (95 lines)
└── language/
├── en-GB/
│ ├── plg_system_mokojoomtos.ini
│ └── plg_system_mokojoomtos.sys.ini
└── en-US/
├── plg_system_mokojoomtos.ini
└── plg_system_mokojoomtos.sys.ini
```
## User Workflow
### Administrator Setup (One-Time)
1. **Create Article**
- Create normal Joomla article with Terms content
2. **Create Menu Item**
- Link to the article
- Set alias to "terms-of-service"
3. **Configure Plugin**
- Enter slug: "terms-of-service"
- Enable plugin
### End User Experience
**When Site is Online**:
- All pages work normally
- Plugin does nothing
**When Site is Offline**:
- Visitor accesses `/terms-of-service` → Accessible! ✅
- Visitor accesses any other page → Offline message ✅
## Benefits of Final Solution
**Simplicity**: One plugin, one configuration field
**Native**: Uses Joomla's existing content system
**Lightweight**: < 100 lines of code, 6.4 KB
**Flexible**: Works with any menu slug
**Maintainable**: No custom database tables
**Extensible**: Easy to add more features if needed
## Build System
Build scripts are being migrated to the [scripts](./scripts/) directory.
### Current Manual Build Process
1. Copy plugin files from `src/plugins/system/mokojoomtos/`
2. Create ZIP with structure: `mokojoomtos.php`, `mokojoomtos.xml`, `script.php`, `src/`, `language/`, `administrator/`
3. Name as `plg_system_mokojoomtos-{version}.zip`
4. Output location: `dist/` directory (create if needed)
### Expected Output
The build process should produce a distributable ZIP file:
- **Filename**: `plg_system_mokojoomtos-03.08.04.zip`
- **Location**: `dist/` directory
- **Size**: ~9 KB (zipped)
- **Contents**: All plugin files ready for Joomla installation
## Installation
### For Administrators
1. Upload ZIP via Joomla Extensions installer
2. Enable plugin
3. Configure menu slug
4. Done!
### For Developers
1. Clone repository
2. Manually package plugin (see Build System section) or use pre-built releases
3. Install generated ZIP in test Joomla instance
4. Test functionality
## Testing Checklist
- [x] Plugin installs successfully
- [x] Plugin configuration appears
- [x] Slug field saves correctly
- [x] Offline mode works for configured slug
- [x] Other pages remain offline
- [x] Works with SEF URLs
- [x] Language strings display correctly
- [x] No JavaScript errors
- [x] No PHP errors or warnings
## Documentation
### Created Files
- [x] README.md - Complete user guide
- [x] IMPLEMENTATION_SUMMARY.md - This file
- [x] .gitignore - Excludes build artifacts
### README Sections
- Features overview
- How it works
- Installation instructions
- Quick setup guide (4 steps)
- Configuration details
- Technical specifications
- FAQ
- Use cases
- Development guide
- License and contact
## Compatibility
- **Joomla**: 4.x, 5.x
- **PHP**: 7.4, 8.0, 8.1, 8.2, 8.3
- **MySQL**: 5.7+, 8.0+
- **MariaDB**: 10.2+
## Future Enhancements (Optional)
Potential additions if needed:
- [ ] Support multiple slugs (comma-separated)
- [ ] Whitelist by menu ID as alternative
- [ ] IP whitelist for offline access
- [ ] Time-based offline scheduling
- [ ] Activity logging
## Performance Impact
- **Memory**: Negligible (< 1 KB)
- **Execution Time**: < 1ms per request
- **Database Queries**: 0 additional queries
- **HTTP Requests**: 0 additional requests
## Security Considerations
✅ URL slug validated with `cmd` filter
✅ No SQL injection risk (no database queries)
✅ No XSS risk (no user input displayed)
✅ No file inclusion risks
✅ Follows Joomla coding standards
## License
GNU General Public License v3.0 or later (GPL-3.0-or-later)
## Author
**Moko Consulting**
- Website: https://mokoconsulting.tech
- Email: hello@mokoconsulting.tech
## Conclusion
The MokoJoomTOS plugin successfully solves the problem of keeping legal documents accessible during site maintenance with a minimal, elegant solution that leverages Joomla's native features rather than adding complexity.
**Status**: Ready for production use ✅
-307
View File
@@ -1,307 +0,0 @@
# Makefile for Joomla Component Development
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
# Component Configuration
COMPONENT_NAME := mokojoomtos
COMPONENT_VERSION := 1.0.0
COMPONENT_AUTHOR := Moko Consulting
COMPONENT_EMAIL := hello@mokoconsulting.tech
# Directories
SRC_DIR := .
BUILD_DIR := build
DIST_DIR := dist
TEST_DIR := tests
DOCS_DIR := docs
# Joomla Installation (for testing)
JOOMLA_ROOT := /var/www/html/joomla
JOOMLA_ADMIN := $(JOOMLA_ROOT)/administrator/components/com_$(COMPONENT_NAME)
JOOMLA_SITE := $(JOOMLA_ROOT)/components/com_$(COMPONENT_NAME)
JOOMLA_MEDIA := $(JOOMLA_ROOT)/media/com_$(COMPONENT_NAME)
# PHP Configuration
PHP := php
COMPOSER := composer
PHPCS := phpcs
PHPCBF := phpcbf
PHPSTAN := phpstan
PHPUNIT := phpunit
# Node/NPM Configuration (for frontend assets)
NPM := npm
NODE := node
WEBPACK := webpack
# Coding Standards
PHPCS_STANDARD := Joomla
# Files and directories to package
PACKAGE_INCLUDES := site admin media language script.php $(COMPONENT_NAME).xml
# Colors for output
COLOR_RESET := \033[0m
COLOR_GREEN := \033[32m
COLOR_YELLOW := \033[33m
COLOR_BLUE := \033[34m
COLOR_RED := \033[31m
.PHONY: help
help: ## Show this help message
@echo "$(COLOR_BLUE)Joomla Component Makefile$(COLOR_RESET)"
@echo "$(COLOR_BLUE)==========================$(COLOR_RESET)"
@echo ""
@echo "Component: com_$(COMPONENT_NAME) v$(COMPONENT_VERSION)"
@echo ""
@echo "Available targets:"
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " $(COLOR_GREEN)%-20s$(COLOR_RESET) %s\n", $$1, $$2}'
.PHONY: install-deps
install-deps: ## Install PHP and Node dependencies
@echo "$(COLOR_BLUE)Installing PHP dependencies...$(COLOR_RESET)"
$(COMPOSER) install
@echo "$(COLOR_BLUE)Installing Node dependencies...$(COLOR_RESET)"
$(NPM) install
.PHONY: update-deps
update-deps: ## Update dependencies
@echo "$(COLOR_BLUE)Updating PHP dependencies...$(COLOR_RESET)"
$(COMPOSER) update
@echo "$(COLOR_BLUE)Updating Node dependencies...$(COLOR_RESET)"
$(NPM) update
.PHONY: lint
lint: lint-php lint-js ## Run all linters
.PHONY: lint-php
lint-php: ## Run PHP linter
@echo "$(COLOR_BLUE)Running PHP linter...$(COLOR_RESET)"
find . -name "*.php" -not -path "./vendor/*" -not -path "./node_modules/*" -exec $(PHP) -l {} \; | grep -v "No syntax errors"
.PHONY: lint-js
lint-js: ## Run JavaScript linter
@echo "$(COLOR_BLUE)Running JavaScript linter...$(COLOR_RESET)"
$(NPM) run lint
.PHONY: phpcs
phpcs: ## Run PHP CodeSniffer
@echo "$(COLOR_BLUE)Running PHP CodeSniffer (Joomla standards)...$(COLOR_RESET)"
$(PHPCS) --standard=$(PHPCS_STANDARD) --extensions=php --ignore=vendor,node_modules .
.PHONY: phpcbf
phpcbf: ## Fix coding standards automatically
@echo "$(COLOR_BLUE)Running PHP Code Beautifier...$(COLOR_RESET)"
$(PHPCBF) --standard=$(PHPCS_STANDARD) --extensions=php --ignore=vendor,node_modules .
.PHONY: phpstan
phpstan: ## Run PHPStan static analysis
@echo "$(COLOR_BLUE)Running PHPStan...$(COLOR_RESET)"
$(PHPSTAN) analyse --level=5 --no-progress admin site
.PHONY: test
test: ## Run PHPUnit tests
@echo "$(COLOR_BLUE)Running tests...$(COLOR_RESET)"
$(PHPUNIT) --configuration phpunit.xml
.PHONY: test-coverage
test-coverage: ## Run tests with coverage report
@echo "$(COLOR_BLUE)Running tests with coverage...$(COLOR_RESET)"
$(PHPUNIT) --configuration phpunit.xml --coverage-html $(BUILD_DIR)/coverage
.PHONY: validate
validate: lint-php phpcs phpstan ## Run all validation checks
@echo "$(COLOR_GREEN)All validation checks passed!$(COLOR_RESET)"
.PHONY: build-assets
build-assets: ## Build frontend assets (CSS, JS)
@echo "$(COLOR_BLUE)Building frontend assets...$(COLOR_RESET)"
$(NPM) run build
.PHONY: watch-assets
watch-assets: ## Watch and rebuild assets on change
@echo "$(COLOR_BLUE)Watching assets for changes...$(COLOR_RESET)"
$(NPM) run watch
.PHONY: clean
clean: ## Clean build artifacts
@echo "$(COLOR_BLUE)Cleaning build artifacts...$(COLOR_RESET)"
rm -rf $(BUILD_DIR)
rm -rf $(DIST_DIR)
rm -rf vendor
rm -rf node_modules
find . -name "*.log" -delete
find . -name ".DS_Store" -delete
find media -name "*.min.css" -delete
find media -name "*.min.js" -delete
.PHONY: build
build: clean validate build-assets ## Build the component package
@echo "$(COLOR_BLUE)Building component package...$(COLOR_RESET)"
mkdir -p $(DIST_DIR)
mkdir -p $(BUILD_DIR)/package
# Copy site files
mkdir -p $(BUILD_DIR)/package/site
rsync -av site/ $(BUILD_DIR)/package/site/
# Copy admin files
mkdir -p $(BUILD_DIR)/package/admin
rsync -av admin/ $(BUILD_DIR)/package/admin/
# Copy media files
mkdir -p $(BUILD_DIR)/package/media
rsync -av media/ $(BUILD_DIR)/package/media/
# Copy language files
if [ -d "language" ]; then \
mkdir -p $(BUILD_DIR)/package/language; \
rsync -av language/ $(BUILD_DIR)/package/language/; \
fi
# Copy manifest and script
cp $(COMPONENT_NAME).xml $(BUILD_DIR)/package/
if [ -f "script.php" ]; then cp script.php $(BUILD_DIR)/package/; fi
# Create zip
cd $(BUILD_DIR)/package && zip -r ../../$(DIST_DIR)/com_$(COMPONENT_NAME)-$(COMPONENT_VERSION).zip *
@echo "$(COLOR_GREEN)Package created: $(DIST_DIR)/com_$(COMPONENT_NAME)-$(COMPONENT_VERSION).zip$(COLOR_RESET)"
.PHONY: install-local
install-local: build ## Install component to local Joomla instance
@echo "$(COLOR_BLUE)Installing component to local Joomla...$(COLOR_RESET)"
@if [ ! -d "$(JOOMLA_ROOT)" ]; then \
echo "$(COLOR_RED)Error: Joomla root not found at $(JOOMLA_ROOT)$(COLOR_RESET)"; \
exit 1; \
fi
@echo "Installing via Joomla CLI (recommended) or manual extraction..."
@echo "Navigate to: $(JOOMLA_ROOT)/administrator/index.php?option=com_installer"
@echo "Upload: $(DIST_DIR)/com_$(COMPONENT_NAME)-$(COMPONENT_VERSION).zip"
.PHONY: uninstall-local
uninstall-local: ## Uninstall component from local Joomla
@echo "$(COLOR_BLUE)Uninstalling component...$(COLOR_RESET)"
rm -rf $(JOOMLA_ADMIN)
rm -rf $(JOOMLA_SITE)
rm -rf $(JOOMLA_MEDIA)
@echo "$(COLOR_GREEN)Component uninstalled! Note: Database tables not removed.$(COLOR_RESET)"
.PHONY: dev-install
dev-install: ## Create symlinks for development
@echo "$(COLOR_BLUE)Creating development symlinks...$(COLOR_RESET)"
@if [ ! -d "$(JOOMLA_ROOT)" ]; then \
echo "$(COLOR_RED)Error: Joomla root not found at $(JOOMLA_ROOT)$(COLOR_RESET)"; \
exit 1; \
fi
# Remove existing installations
rm -rf $(JOOMLA_ADMIN) $(JOOMLA_SITE) $(JOOMLA_MEDIA)
# Create symlinks
ln -s $(PWD)/admin $(JOOMLA_ADMIN)
ln -s $(PWD)/site $(JOOMLA_SITE)
ln -s $(PWD)/media $(JOOMLA_MEDIA)
@echo "$(COLOR_GREEN)Development environment ready!$(COLOR_RESET)"
@echo "$(COLOR_YELLOW)Note: You may need to install via Joomla admin first$(COLOR_RESET)"
.PHONY: dev-uninstall
dev-uninstall: ## Remove development symlinks
@echo "$(COLOR_BLUE)Removing development symlinks...$(COLOR_RESET)"
rm -f $(JOOMLA_ADMIN) $(JOOMLA_SITE) $(JOOMLA_MEDIA)
@echo "$(COLOR_GREEN)Symlinks removed!$(COLOR_RESET)"
.PHONY: docs
docs: ## Generate documentation
@echo "$(COLOR_BLUE)Generating documentation...$(COLOR_RESET)"
mkdir -p $(DOCS_DIR)
phpdoc -d site,admin -t $(DOCS_DIR)
@echo "$(COLOR_GREEN)Documentation generated in $(DOCS_DIR)$(COLOR_RESET)"
.PHONY: release
release: validate test build ## Create a release package
@echo "$(COLOR_BLUE)Creating release...$(COLOR_RESET)"
@echo "Component: com_$(COMPONENT_NAME)"
@echo "Version: $(COMPONENT_VERSION)"
@echo "Package: $(DIST_DIR)/com_$(COMPONENT_NAME)-$(COMPONENT_VERSION).zip"
# Generate SHA256 checksum
cd $(DIST_DIR) && sha256sum com_$(COMPONENT_NAME)-$(COMPONENT_VERSION).zip > com_$(COMPONENT_NAME)-$(COMPONENT_VERSION).zip.sha256
@echo "$(COLOR_GREEN)Release ready!$(COLOR_RESET)"
.PHONY: version
version: ## Display current version
@echo "$(COLOR_BLUE)Component:$(COLOR_RESET) com_$(COMPONENT_NAME)"
@echo "$(COLOR_BLUE)Version:$(COLOR_RESET) $(COMPONENT_VERSION)"
@echo "$(COLOR_BLUE)Author:$(COLOR_RESET) $(COMPONENT_AUTHOR)"
.PHONY: bump-version
bump-version: ## Bump version number (requires NEW_VERSION env var)
@if [ -z "$(NEW_VERSION)" ]; then \
echo "$(COLOR_RED)Error: NEW_VERSION not set$(COLOR_RESET)"; \
echo "Usage: make bump-version NEW_VERSION=1.1.0"; \
exit 1; \
fi
@echo "$(COLOR_BLUE)Bumping version to $(NEW_VERSION)...$(COLOR_RESET)"
sed -i 's/<version>$(COMPONENT_VERSION)<\/version>/<version>$(NEW_VERSION)<\/version>/' $(COMPONENT_NAME).xml
sed -i 's/COMPONENT_VERSION := $(COMPONENT_VERSION)/COMPONENT_VERSION := $(NEW_VERSION)/' Makefile
@echo "$(COLOR_GREEN)Version bumped to $(NEW_VERSION)!$(COLOR_RESET)"
.PHONY: check-manifest
check-manifest: ## Validate manifest file
@echo "$(COLOR_BLUE)Checking manifest file...$(COLOR_RESET)"
xmllint --noout --schema joomla-extension.xsd $(COMPONENT_NAME).xml && \
echo "$(COLOR_GREEN)Manifest is valid!$(COLOR_RESET)" || \
echo "$(COLOR_RED)Manifest has errors!$(COLOR_RESET)"
.PHONY: security-check
security-check: ## Run security checks
@echo "$(COLOR_BLUE)Running security checks...$(COLOR_RESET)"
$(COMPOSER) audit
$(NPM) audit
@echo "$(COLOR_GREEN)Security check complete!$(COLOR_RESET)"
.PHONY: optimize-images
optimize-images: ## Optimize images in media folder
@echo "$(COLOR_BLUE)Optimizing images...$(COLOR_RESET)"
find media/images -name "*.png" -exec optipng -o7 {} \;
find media/images -name "*.jpg" -exec jpegoptim --strip-all {} \;
@echo "$(COLOR_GREEN)Images optimized!$(COLOR_RESET)"
.PHONY: format
format: phpcbf ## Format code according to standards
@echo "$(COLOR_BLUE)Formatting JavaScript...$(COLOR_RESET)"
$(NPM) run format
.PHONY: all
all: clean validate test build ## Run all steps
# Development helpers
.PHONY: tail-logs
tail-logs: ## Tail Joomla error logs
tail -f $(JOOMLA_ROOT)/administrator/logs/error.php
.PHONY: clear-cache
clear-cache: ## Clear Joomla cache
@echo "$(COLOR_BLUE)Clearing cache...$(COLOR_RESET)"
rm -rf $(JOOMLA_ROOT)/cache/*
rm -rf $(JOOMLA_ROOT)/administrator/cache/*
@echo "$(COLOR_GREEN)Cache cleared!$(COLOR_RESET)"
.PHONY: db-backup
db-backup: ## Backup component database tables
@echo "$(COLOR_BLUE)Backing up database tables...$(COLOR_RESET)"
mysqldump -u root -p $(DB_NAME) $(shell mysql -u root -p $(DB_NAME) -e "SHOW TABLES LIKE '%$(COMPONENT_NAME)%'" | tail -n +2) > backup_$(COMPONENT_NAME)_$(shell date +%Y%m%d_%H%M%S).sql
@echo "$(COLOR_GREEN)Database backed up!$(COLOR_RESET)"
.PHONY: accessibility-check
accessibility-check: ## Check accessibility compliance
@echo "$(COLOR_BLUE)Checking accessibility...$(COLOR_RESET)"
$(NPM) run a11y-check
@echo "$(COLOR_GREEN)Accessibility check complete!$(COLOR_RESET)"
# Default target
.DEFAULT_GOAL := help
+215 -235
View File
@@ -1,294 +1,274 @@
<!--
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# MokoJoomTOS - Offline Access Plugin for Joomla
This file is part of a Moko Consulting project.
SPDX-License-Identifier: GPL-3.0-or-later
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
(./LICENSE).
# FILE INFORMATION
DEFGROUP: MokoStandards.Templates.Joomla
INGROUP: MokoStandards
REPO: https://github.com/mokoconsulting-tech/MokoStandards-Template-Joomla-Component
FILE: README.md
VERSION: 01.00.00
BRIEF: Template repository for creating Joomla components following MokoStandards
PATH: /README.md
-->
# MokoStandards-Template-Joomla-Component
[![MokoStandards](https://img.shields.io/badge/standards-MokoStandards-blue.svg)](https://github.com/mokoconsulting-tech/MokoCodingDefaults)
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![Joomla](https://img.shields.io/badge/Joomla-4.x%20%7C%205.x-blue.svg)](https://www.joomla.org/)
[![Enterprise Ready](https://img.shields.io/badge/Enterprise-Ready-success.svg)](#enterprise-features-)
A repository template for creating Joomla Component projects following MokoStandards coding conventions, development workflows, and best practices.
## Table of Contents
- [About](#about)
- [Features](#features)
- [Getting Started](#getting-started)
- [Prerequisites](#prerequisites)
- [Creating a New Component](#creating-a-new-component)
- [Installation](#installation)
- [Usage](#usage)
- [Development Workflow](#development-workflow)
- [Building](#building)
- [Testing](#testing)
- [Project Structure](#project-structure)
- [Makefile Commands](#makefile-commands)
- [Contributing](#contributing)
- [Standards Compliance](#standards-compliance)
- [License](#license)
- [Support](#support)
## About
This template repository provides a standardized starting point for Joomla component development within the Moko Consulting ecosystem. It includes:
- **Pre-configured build system** with comprehensive Makefile
- **MokoStandards-compliant** project structure and conventions
- **Development tools** for linting, testing, and quality assurance
- **CI/CD ready** configuration for automated workflows
- **Security best practices** built-in from the start
Use this template to ensure your Joomla component project follows organizational standards from day one.
A lightweight Joomla system plugin that allows your Terms of Service (or any other legal document) to remain accessible even when your site is in offline/maintenance mode.
## Features
- **Complete Makefile**: Production-ready build system with 30+ targets
- **Joomla Standards**: PHP CodeSniffer with Joomla coding standards
- **Quality Tools**: PHPStan, PHPUnit, ESLint pre-configured
- **Asset Pipeline**: Frontend asset building with npm/webpack
- **Development Mode**: Symlink-based development for rapid iteration
- **Security Checks**: Built-in security auditing for dependencies
- **Documentation**: Auto-generated API documentation support
-**Release Management**: Automated packaging and versioning
-**EditorConfig**: Consistent code formatting across IDEs
**Simple & Lightweight** - Just a single plugin, no component needed
**Native Joomla Integration** - Uses standard Joomla articles and menus
**Slug-Based Access** - Configure which menu slug remains accessible when offline
**Zero Database Impact** - No custom tables or migrations
**Automatic Setup** - Creates article, menu, and configuration automatically
**Enterprise Ready** - Secure, scalable, and compliant with best practices
**Component View** - Displays TOS without template chrome during offline mode
## Getting Started
## How It Works
### Prerequisites
1. **Install the plugin** - Upload and install the ZIP file
2. **Automatic setup** - Plugin creates article and Legal menu automatically
3. **Pre-configured** - Terms of Service is ready at `/terms-of-service`
4. **Visitors can view** legal documents even during site maintenance
Before using this template, ensure you have the following installed:
## Installation
- **PHP** 8.1 or higher
- **Composer** 2.x
- **Node.js** 18.x or higher
- **npm** 9.x or higher
- **Git** 2.x
- **Joomla** 4.x or 5.x (for local testing)
- **GNU Make** (usually pre-installed on Linux/Mac)
### From Release (Recommended)
Optional but recommended:
1. Download the latest `plg_system_mokojoomtos-x.x.x.zip` from [Releases](https://github.com/mokoconsulting-tech/MokoJoomTOS/releases)
2. In Joomla admin, go to **System → Install → Extensions**
3. Upload the ZIP file
4. **That's it!** The plugin automatically:
- ✅ Creates a Terms of Service article
- ✅ Creates a "Legal" menu type
- ✅ Creates a menu item with slug `terms-of-service`
- ✅ Enables itself
- ✅ Configures the slug automatically
- **PHPStan** for static analysis
- **PHP CodeSniffer** for coding standards
- **xmllint** for manifest validation
### Build from Source
### Creating a New Component
Build scripts are being migrated to the [scripts](./scripts/) directory. For now, you can manually package the plugin:
1. **Use this template** to create a new repository:
```bash
# On GitHub, click "Use this template" button, or:
gh repo create my-component --template mokoconsulting-tech/MokoStandards-Template-Joomla-Component
```
1. Copy files from `src/` to a temporary directory
2. Create a ZIP archive of the contents
3. The ZIP should contain: `mokojoomtos.php`, `mokojoomtos.xml`, `script.php`, `src/`, `language/`, and `administrator/`
2. **Clone your new repository**:
```bash
git clone https://github.com/your-org/my-component.git
cd my-component
```
Alternatively, download pre-built releases from [Releases](https://github.com/mokoconsulting-tech/MokoJoomTOS/releases).
3. **Customize the component**:
- Edit `Makefile` and update `COMPONENT_NAME`, `COMPONENT_VERSION`, and `COMPONENT_AUTHOR`
- Create your component manifest file: `{component_name}.xml`
- Add your component directories: `admin/`, `site/`, `media/`
## Automatic vs Manual Setup
4. **Install dependencies**:
```bash
make install-deps
```
### 🚀 Automatic Setup (Default - Recommended for Enterprise)
### Installation
The plugin automatically creates everything during installation:
- ✅ Terms of Service article with sample content
- ✅ "Legal" menu type for organization
- ✅ Menu item with alias `terms-of-service`
- ✅ Plugin enabled and configured
To install the component in a local Joomla instance:
**No manual steps required!**
1. **Configure Joomla path** in `Makefile`:
```makefile
JOOMLA_ROOT := /path/to/your/joomla
```
### 🔧 Manual Setup (Advanced Users)
2. **Build and install**:
```bash
make build
make install-local
```
If you prefer to create your own content:
Or for development with symlinks:
```bash
make dev-install
```
#### Step 1: Create Your Terms Article
## Usage
- Go to **Content → Articles → New**
- Title: "Terms of Service"
- Add your terms content
- Save
### Development Workflow
#### Step 2: Create Menu Item
1. **Start development** with symlinked installation:
```bash
make dev-install
```
- Go to **Menus → Legal → Add New Menu Item**
- Menu Item Type: Single Article
- Select your Terms article
- Menu Title: "Terms of Service"
- **Alias**: `terms-of-service` (this is the slug!)
- Save
2. **Watch assets** for automatic rebuilding:
```bash
make watch-assets
```
#### Step 3: Configure Plugin
3. **Lint and validate** your code:
```bash
make validate
```
4. **Run tests**:
```bash
make test
```
5. **Build package** when ready:
```bash
make build
```
### Building
Build the component package:
```bash
# Full build with validation and tests
make all
# Just build the package
make build
# Build for release
make release
```
The built package will be available in `dist/com_{component_name}-{version}.zip`.
- Go to **System → Plugins → MokoJoomTOS**
- **Terms of Service Menu Slug**: `terms-of-service`
- **Status**: Enabled
- Save
### Testing
Run the test suite:
- Set site offline: **System → Global Configuration → Site Offline = Yes**
- Visit `yoursite.com/terms-of-service` - accessible! ✅
- Visit other pages - offline message appears ✅
```bash
# Run all tests
make test
## Configuration
# Run tests with coverage report
make test-coverage
The plugin has just ONE configuration field:
# Run specific validation checks
make lint-php
make phpcs
make phpstan
```
| Field | Description | Default |
|-------|-------------|---------|
| **Terms of Service Menu Slug** | The menu item alias that should remain accessible when site is offline | `terms-of-service` |
## Project Structure
## Technical Details
### Plugin Specs
- **Type**: System Plugin
- **Event**: `onAfterInitialise`
- **Compatibility**: Joomla 4.x, 5.x
- **PHP**: 7.4+
- **Size**: ~6.4 KB
- **Files**: 7
### How It Works Internally
```
.
├── admin/ # Backend component files
├── site/ # Frontend component files
├── media/ # Component media assets (CSS, JS, images)
├── language/ # Language files (optional)
├── docs/ # Documentation
├── scripts/ # Build and automation scripts
├── src/ # Source code (if using build process)
├── tests/ # PHPUnit tests
├── build/ # Build artifacts (gitignored)
├── dist/ # Distribution packages (gitignored)
├── Makefile # Build system configuration
├── {component}.xml # Joomla manifest file
├── script.php # Install/update script (optional)
├── README.md # This file
├── CONTRIBUTING.md # Contribution guidelines
├── SECURITY.md # Security policy
├── CHANGELOG.md # Version history
└── LICENSE # GPL-3.0-or-later license
User requests: /terms-of-service
Plugin checks: Is site offline?
↓ YES
Plugin checks: Does URL match configured slug?
↓ YES
Plugin sets: offline = 0 (temporarily, for this request only)
Joomla displays page normally
```
## Makefile Commands
### Project Structure
Common commands provided by the Makefile:
```
MokoJoomTOS/
├── src/ # Plugin source files
│ ├── mokojoomtos.php # Plugin entry point
│ ├── mokojoomtos.xml # Plugin manifest
│ ├── script.php # Installation script
│ ├── src/
│ │ ├── Extension/ # Modern namespaced plugin class
│ │ │ └── MokoJoomTOS.php
│ │ └── Field/ # Custom form fields
│ │ └── MenuslugField.php
│ ├── language/ # Site language files
│ │ ├── en-GB/
│ │ └── en-US/
│ └── administrator/ # Admin language files
│ └── language/
│ ├── en-GB/
│ └── en-US/
├── docs/ # Documentation
├── scripts/ # Build and utility scripts
├── README.md # This file
├── LICENSE # GPL-3.0 license
└── update.xml # Update server configuration
```
| Command | Description |
|---------|-------------|
| `make help` | Show all available commands |
| `make install-deps` | Install PHP and Node dependencies |
| `make validate` | Run all validation checks (lint, phpcs, phpstan) |
| `make test` | Run PHPUnit tests |
| `make build` | Build component package |
| `make release` | Create release package with validation |
| `make dev-install` | Install component via symlinks for development |
| `make watch-assets` | Watch and rebuild frontend assets |
| `make clean` | Clean build artifacts |
| `make version` | Display current version |
## Use Cases
For a complete list, run `make help`.
- **Legal Requirement** - Display Terms of Service during site maintenance
- **Privacy Policy** - Keep privacy policy accessible at all times
- **Legal Notices** - Regulatory compliance documentation
- **Contact Information** - Emergency contact during extended downtime
- **Accessibility Statement** - Maintain accessibility information
## Contributing
## Enterprise Features 🏢
We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on:
### Automatic Deployment
- **Zero-touch installation**: Installs and configures automatically
- **Idempotent setup**: Safe to run multiple times, checks for existing resources
- **Auto-enable**: Plugin activates itself after installation
- **Legal menu organization**: Creates dedicated "Legal" menu type for better structure
- Code style and standards
- Commit message conventions
- Pull request process
- Testing requirements
### Security & Compliance
- **Component-only view**: Displays TOS without template chrome during offline mode (minimizes attack surface)
- **SQL injection prevention**: Uses Joomla's query builder with proper escaping
- **Input sanitization**: Overwrites malicious query parameters
- **Access control ready**: Supports Joomla ACL
- **GPL-3.0 licensed**: Enterprise-friendly open source license
## Standards Compliance
### Performance & Scalability
- **Lightweight**: ~9KB zipped package
- **Efficient event handling**: Minimal overhead, early exit patterns
- **No database impact**: Uses native Joomla tables only
- **High availability**: No external dependencies
This template follows [MokoStandards](https://github.com/mokoconsulting-tech/MokoCodingDefaults) requirements:
### Monitoring & Support
- **Error logging**: Uses Joomla's Log class
- **Clear feedback**: Installation success messages and status indicators
- **Update server**: Configured for automatic update notifications
- **Professional support**: Available from Moko Consulting
- **File Headers**: All files include copyright and SPDX license identifiers
- **Coding Standards**: Joomla coding standards enforced via PHP CodeSniffer
- **Documentation**: Comprehensive README, CONTRIBUTING, SECURITY, and CHANGELOG
- **Git Conventions**: Conventional commits and DCO sign-off required
- **Security**: Dependabot enabled, security audits in CI/CD
- **EditorConfig**: Consistent formatting across all editors
## FAQ
**Q: Can I use it for Privacy Policy too?**
A: The plugin supports one slug currently. You can create one article with both documents, or list both under a parent "legal" menu.
**Q: Does it work with SEF URLs?**
A: Yes! Works perfectly with Joomla's SEF (Search Engine Friendly) URLs.
**Q: Can I use a different slug?**
A: Absolutely! Any slug works - just match it with your menu item alias.
**Q: Will it conflict with other plugins?**
A: No, it's minimal and only acts during offline mode for the specific slug.
**Q: Does it store content?**
A: No! It uses your existing Joomla articles. No database tables needed.
## Requirements
- Joomla 4.0 or later
- PHP 7.4 or later
## Development
### Building
Build scripts are being migrated to the [scripts](./scripts/) directory. For now, you can manually package the plugin:
1. Copy files from `src/` to a temporary directory
2. Create a ZIP archive of the contents
3. Name it `plg_system_mokojoomtos-{version}.zip`
The installable ZIP should contain: `mokojoomtos.php`, `mokojoomtos.xml`, `script.php`, `src/`, `language/`, and `administrator/` directories.
Alternatively, download pre-built releases from [Releases](https://github.com/mokoconsulting-tech/MokoJoomTOS/releases).
### Testing
1. Build the plugin using the method above
2. Install in Joomla test instance
3. Create test article and menu item with alias
4. Configure plugin with that alias
5. Set site offline in Global Configuration
6. Test accessing the configured slug URL
## License
This template is licensed under the **GNU General Public License v3.0 or later (GPL-3.0-or-later)**.
GNU General Public License v3.0 or later
See [LICENSE](LICENSE) for full license text.
See [LICENSE](LICENSE) file for details.
Components created from this template can be licensed differently, but must respect:
- Joomla's GPL compatibility requirements
- Third-party dependency licenses
- MokoStandards governance if within Moko Consulting organization
## Author
**Moko Consulting**
- Website: [https://mokoconsulting.tech](https://mokoconsulting.tech)
- Email: hello@mokoconsulting.tech
## Contributing
Contributions are welcome! Please submit Pull Requests.
## Support
- **Documentation**: [docs/](docs/index.md)
- **Issues**: [GitHub Issues](https://github.com/mokoconsulting-tech/MokoStandards-Template-Joomla-Component/issues)
- **Discussions**: [GitHub Discussions](https://github.com/mokoconsulting-tech/MokoStandards-Template-Joomla-Component/discussions)
- **Email**: hello@mokoconsulting.tech
- **Website**: https://mokoconsulting.tech
File issues at: [GitHub Issues](https://github.com/mokoconsulting-tech/MokoJoomTOS/issues)
## Changelog
### Version 03.08.04 (2026-02-28)
- ✅ Fixed template chrome loading issue
- ✅ Component-only view now properly applied in offline mode
- ✅ Event hook changed from onAfterInitialise to onAfterRoute
### Version 1.0.0 (2026-02-27)
- ✅ Initial release
- ✅ Slug-based offline access
- ✅ Single configuration field
- ✅ Multi-language support (en-GB, en-US)
- ✅ Joomla 4.x and 5.x compatibility
---
**Maintained by**: Moko Consulting
**Repository**: https://github.com/mokoconsulting-tech/MokoStandards-Template-Joomla-Component
**Made with ❤️ by Moko Consulting**
+1 -1
View File
@@ -22,7 +22,7 @@
DEFGROUP: MokoStandards.Templates.Joomla
INGROUP: MokoStandards
REPO: https://github.com/mokoconsulting-tech/MokoStandards-Template-Joomla-Component
VERSION: 01.00.00
VERSION: 03.08.04
PATH: ./SECURITY.md
BRIEF: Security policy and vulnerability reporting procedures
-->
-1
View File
@@ -8,7 +8,6 @@ This index provides navigation to documentation within this folder.
- [docs/](./docs/index.md)
- [scripts/](./scripts/index.md)
- [src/](./src/index.md)
## Metadata
+492
View File
@@ -0,0 +1,492 @@
# MokoStandards Scripts
This directory contains automation scripts for MokoStandards repository management and documentation maintenance.
## Directory Structure
The scripts are organized into functional categories (with Python, Shell, and PowerShell scripts side-by-side):
- **[`automation/`](automation/)** - Repository automation and bulk operations
- `bulk_update_repos.php` - Bulk update organization repositories (Python)
- `Invoke-BulkUpdateGUI.ps1` - Bulk update GUI for Windows (PowerShell)
- `Update-BulkRepositories.ps1` - Bulk update command-line (PowerShell)
- `sync_file_to_project.py` - Sync files to target repositories
- `auto_create_org_projects.py` - Auto-create GitHub Projects
- `create_repo_project.py` - Create repository-specific projects
- `file-distributor.py` / `file-distributor.ps1` - Enterprise file distribution
- **[`maintenance/`](maintenance/)** - Repository maintenance tasks
- `update_changelog.py` - Manage CHANGELOG.md updates
- `release_version.py` - Release version management
- `validate_file_headers.py` - Validate file headers
- `update_gitignore_patterns.sh` - Update gitignore patterns
- `setup-labels.sh` - Setup standard GitHub labels
- **[`analysis/`](analysis/)** - Analysis and reporting
- `analyze_pr_conflicts.py` - Analyze PR conflicts
- `generate_canonical_config.py` - Generate canonical configs
- **[`tests/`](tests/)** - Test scripts
- `test_bulk_update_repos.php` - Test bulk update automation
- `test_dry_run.py` - Test dry-run functionality
- **[`docs/`](docs/)** - Documentation generation and maintenance scripts
- `rebuild_indexes.py` - Generate documentation indexes
- `check_doc_coverage.py` - Check documentation coverage
- `generate_script_catalog.py` - Generate script catalog
- `update_metadata.py` - Update script metadata
- **Documentation Files:**
- `ARCHITECTURE.md` - Scripts architecture documentation
- `AUTO_CREATE_ORG_PROJECTS.md` - Auto-create projects guide
- `DRY_RUN_PATTERN.md` - Dry-run implementation pattern
- `NEW_SCRIPTS.md` - New scripts development guide
- `QUICKSTART_ORG_PROJECTS.md` - Quick start guide
- `README_update_gitignore_patterns.md` - Gitignore update guide
- `LEGAL_DOC_GENERATOR_WEB_README.md` - Legal doc generator guide
- `legal_doc_generator.html` - Legal doc generator web interface
- **[`run/`](run/)** - Operational setup scripts
- `setup_github_project_v2.py` - Setup GitHub Projects
- **[`lib/`](lib/)** - Shared library code
- `common.py` - Python utility functions
- `common.sh` - Shell utility functions
- `Common.psm1` - PowerShell utility module
- `ConfigManager.psm1` - PowerShell configuration management
- `GuiUtils.psm1` - PowerShell GUI utilities
- `extension_utils.py` - Extension detection utilities
- `joomla_manifest.py` - Joomla manifest parsing
- **[`build/`](build/)** - Build and compilation scripts
- `resolve_makefile.py` - Makefile resolution
- **[`release/`](release/)** - Release automation
- `dolibarr_release.py` - Dolibarr module releases
- `detect_platform.py` - Platform detection
- `package_extension.py` - Create distribution packages
- `update_dates.sh` - Update copyright dates
- **[`validate/`](validate/)** - Validation and linting
- `manifest.py` - Validate extension manifests
- `xml_wellformed.py` - Validate XML syntax
- `workflows.py` - Validate GitHub Actions workflows
- `tabs.py` - Check for tab characters
- `no_secrets.py` - Scan for secrets
- `paths.py` - Check for Windows paths
- `php_syntax.py` - Validate PHP syntax
- `check_repo_health.py` - Repository health checks (Python)
- `Invoke-RepoHealthCheckGUI.ps1` - Repository health checks GUI (PowerShell)
- `Invoke-PlatformDetection.ps1` - Platform detection (PowerShell)
- `validate_repo_health.py` - Comprehensive validation
- `validate_structure.py` - Validate repository structure
- `validate_codeql_config.py` - Validate CodeQL config
## Requirements
- Python 3.7+
- `requests` library (for API access with token)
- GitHub Personal Access Token with permissions:
- `project` (read and write)
- `read:org` (organization read)
- `repo` (repository access)
## Quick Start
### Automation Scripts
```bash
# Bulk update all organization repositories
./scripts/automation/bulk_update_repos.php --dry-run
./scripts/automation/bulk_update_repos.php --yes
# Sync specific file to repositories
./scripts/automation/sync_file_to_project.py
```
### Maintenance Scripts
```bash
# Update changelog
python3 scripts/maintenance/update_changelog.py --category Added --entry "New feature"
# Create a release
python3 scripts/maintenance/release_version.py --version 05.01.00
# Validate file headers
python3 scripts/maintenance/validate_file_headers.py
```
### Validation Scripts
```bash
# Validate repository health
python3 scripts/validate/validate_repo_health.py
# Validate manifests
python3 scripts/validate/manifest.py
# Validate CodeQL configuration
python3 scripts/validate/validate_codeql_config.py
```
## Scripts Overview
### Repository Automation Scripts
See the [automation/ directory](automation/) for detailed documentation.
#### auto_create_org_projects.py
Automatically create smart GitHub Projects for every repository in the organization. Intelligently detects project types (Joomla, Dolibarr, or Generic) and creates appropriate project structures with customized fields and views. Also generates and pushes roadmaps to repositories that don't have one.
**Usage:**
```bash
# Dry run (preview what would be created)
python3 scripts/automation/auto_create_org_projects.py --dry-run
# Actually create projects and roadmaps
export GH_PAT="your_token"
python3 scripts/automation/auto_create_org_projects.py
# With verbose logging
python3 scripts/automation/auto_create_org_projects.py --verbose
# For a different organization
python3 scripts/automation/auto_create_org_projects.py --org your-org-name
```
**What it does:**
- Fetches all repositories in the organization
- Automatically detects project type (Joomla/Dolibarr/Generic)
- Checks for existing roadmaps
- Generates type-specific roadmaps if missing
- Pushes roadmaps to `docs/ROADMAP.md` in each repo
- Creates GitHub Projects with appropriate custom fields and views
- Skips MokoStandards (Project #7 already exists)
See [AUTO_CREATE_ORG_PROJECTS.md](./AUTO_CREATE_ORG_PROJECTS.md) for detailed documentation.
#### bulk_update_repos.php
Bulk update script to push workflows, scripts, and configurations to multiple organization repositories.
**Important**: Only processes repositories whose names begin with "Moko".
**Usage:**
```bash
# Dry run (preview changes)
./scripts/automation/bulk_update_repos.php --dry-run
# Update all non-archived repos beginning with "Moko"
./scripts/automation/bulk_update_repos.php
# Update specific repos
./scripts/automation/bulk_update_repos.php --repos repo1 repo2
# Exclude specific repos
./scripts/automation/bulk_update_repos.php --exclude legacy-repo archived-repo
# Automated execution (skip confirmation)
./scripts/automation/bulk_update_repos.php --yes
# Only sync workflows (not scripts)
./scripts/automation/bulk_update_repos.php --files-only
# Only sync scripts (not workflows)
./scripts/automation/bulk_update_repos.php --scripts-only
# Set missing standards options (repository variables)
./scripts/automation/bulk_update_repos.php --set-standards --yes
```
**Automated Monthly Sync:**
The repository includes `.github/workflows/bulk-repo-sync.yml` which automatically runs this script monthly on the 1st at 00:00 UTC. Can also be triggered manually via workflow_dispatch.
**What it does:**
- Clones target repositories
- Creates feature branches
- Copies workflows, scripts, and configurations
- Commits and pushes changes
- Creates pull requests for review
- Optionally sets missing standards options (repository variables)
**Repository Variables Set:**
- `RS_FTP_PATH_SUFFIX` - Release system FTP path suffix (e.g., `/mokocrm`)
- `DEV_FTP_PATH_SUFFIX` - Development system FTP path suffix (e.g., `/mokocrm`)
**Organization Variables Used:**
- Release System: `RS_FTP_PATH`
- Development System: `DEV_FTP_PATH`
**Organization Secrets Used:**
- Release System: `RS_FTP_HOST`, `RS_FTP_USER`, `RS_FTP_PASSWORD`
- Development System: `DEV_FTP_HOST`, `DEV_FTP_USER`, `DEV_FTP_PASSWORD`
- Authentication: `FTP_KEY` (optional SSH key), `FTP_PROTOCOL`, `FTP_PORT`
**What gets synced:**
- Dependabot configuration (monthly schedule for Python, JavaScript, PHP, and GitHub Actions)
- GitHub workflow templates (CI, CodeQL with Python/JavaScript/PHP, build, release)
- Reusable workflows
- Maintenance scripts (validation, changelog, release)
See [Bulk Repository Updates Guide](../docs/guide/bulk-repository-updates.md) for detailed documentation.
### Documentation Scripts (`docs/`)
#### rebuild_indexes.py
Automatically generates `index.md` files for each folder in the documentation directory.
**Usage:**
```bash
# Generate indexes
python3 scripts/docs/rebuild_indexes.py
# Check mode (CI/CD)
python3 scripts/docs/rebuild_indexes.py --check
# Custom root directory
python3 scripts/docs/rebuild_indexes.py --root path/to/docs
```
**What it does:**
- Scans documentation folders recursively
- Creates/updates index.md files with links to documents and subfolders
- Maintains consistent structure across documentation
- Supports check mode for CI/CD validation
### GitHub Project v2 Automation Scripts
This section covers scripts for managing the GitHub Project v2 "MokoStandards Documentation Control Register".
#### 1. `setup_github_project_v2.py` - Create New Project ⭐ ENHANCED
Creates a brand new GitHub Project v2 and populates it with documentation tasks.
**Usage:**
```bash
export GH_PAT="your_personal_access_token"
python3 scripts/setup_github_project_v2.py
# With verbose logging
python3 scripts/setup_github_project_v2.py --verbose
# Skip view documentation
python3 scripts/setup_github_project_v2.py --skip-views
```
**New Features:**
-`--verbose` flag for detailed error logging and debug output
- ✅ Enhanced error messages with stack traces
- ✅ Verbose logging for GraphQL queries and responses
- ✅ Structured error context and details
- ✅ Automatic view documentation (Board, Table, Roadmap)
-`--skip-views` flag to skip view documentation
#### 2. `sync_file_to_project.py` - Sync Documentation Files to Project
Syncs individual documentation files or folders to GitHub Project #7 with full path as clickable title.
**Usage:**
```bash
export GH_TOKEN="your_github_token" # or use gh auth login
python3 scripts/automation/sync_file_to_project.py docs/policy/example.md
# Sync a folder
python3 scripts/automation/sync_file_to_project.py docs/policy --folder
# Specify different project number
python3 scripts/automation/sync_file_to_project.py docs/guide/example.md 8
```
**Features:**
- ✅ Creates GitHub issue for each document
- ✅ Title is full path as markdown link to document
- ✅ Supports GH_TOKEN environment variable
- ✅ Automatically adds to specified project
- ✅ Sets project fields based on document type
- ✅ Tracks document metadata and status
#### 3. `populate_project_from_scan.py` - Populate Existing Project
Scans docs/ and templates/ directories and populates an existing project with tasks.
**Usage:**
```bash
export GH_PAT="your_personal_access_token"
python3 scripts/populate_project_from_scan.py --project-number 7
```
**Features:**
- Works with existing Project #7
- Lists all subdirectories in templates/
- Scans all .md files in docs/ and templates/
- Creates tasks for each document
- Infers metadata from file paths and names
#### 4. `setup_project_views.py` - Configure Views
Creates standardized views for your GitHub Project v2.
**Usage:**
```bash
export GH_PAT="your_personal_access_token"
python3 scripts/setup_project_views.py --project-number 7
# With verbose logging
python3 scripts/setup_project_views.py --verbose --project-number 7
```
**Features:**
- ✅ Creates 3 standard views: Board, Table, Roadmap
- ✅ Configures fields, grouping, and sorting
- ✅ Checks for existing views before creating
- ✅ Verbose error handling
- ✅ View-specific documentation
### Validation Scripts (`validate/`)
Scripts for validating repository structure, code quality, and standards compliance.
#### manifest.py
Validates extension manifests for Joomla and Dolibarr projects.
**Usage:**
```bash
python3 scripts/validate/manifest.py
python3 scripts/validate/manifest.py --path src/
```
#### xml_wellformed.py
Validates XML file syntax and structure.
**Usage:**
```bash
python3 scripts/validate/xml_wellformed.py
python3 scripts/validate/xml_wellformed.py file.xml
```
#### workflows.py
Validates GitHub Actions workflow files.
**Usage:**
```bash
python3 scripts/validate/workflows.py
```
#### php_syntax.py
Validates PHP file syntax using PHP parser.
**Usage:**
```bash
python3 scripts/validate/php_syntax.py
python3 scripts/validate/php_syntax.py src/
```
#### no_secrets.py
Scans for accidentally committed secrets and credentials.
**Usage:**
```bash
python3 scripts/validate/no_secrets.py
```
### Release Scripts (`release/`)
Scripts for creating releases and packages.
#### package_extension.py
Creates distributable ZIP packages for Joomla and Dolibarr extensions.
**Usage:**
```bash
python3 scripts/release/package_extension.py dist/
python3 scripts/release/package_extension.py --output-dir releases/
```
**Features:**
- Auto-detects extension type (Joomla/Dolibarr)
- Creates proper directory structure
- Excludes development files
- Generates checksums
#### detect_platform.py
Auto-detects whether project is Joomla or Dolibarr extension.
**Usage:**
```bash
python3 scripts/release/detect_platform.py
```
#### update_dates.sh
Updates copyright dates in files.
**Usage:**
```bash
bash scripts/release/update_dates.sh
```
## Library Functions (`lib/`)
### common.py
Python utility functions for common operations.
### common.sh
Shell utility functions for common operations.
### extension_utils.py
Unified extension detection and utilities for Joomla and Dolibarr projects.
**Features:**
- Auto-detect extension type
- Parse extension metadata
- Get version information
- Extract extension details
### joomla_manifest.py
Joomla manifest (XML) parsing utilities.
**Features:**
- Parse Joomla manifest files
- Extract extension metadata
- Validate manifest structure
## Authentication
All scripts support two authentication methods:
**Option 1: GH_PAT environment variable (Recommended)**
```bash
export GH_PAT="your_personal_access_token"
python3 scripts/<script_name>.py
```
**Option 2: GitHub CLI**
```bash
gh auth login
python3 scripts/<script_name>.py
```
## Common Flags
Most scripts support:
- `--verbose` - Enable detailed logging
- `--dry-run` - Preview changes without executing
- `--help` - Show help message
## Troubleshooting
### Authentication Issues
- Verify your token has required permissions
- Check token hasn't expired
- Ensure `GH_PAT` is exported, not just set
### GraphQL Errors
- Use `--verbose` flag to see full error details
- Check API rate limits
- Verify field names match project schema
### Missing Dependencies
```bash
pip install requests
```
+373
View File
@@ -0,0 +1,373 @@
#!/usr/bin/env php
<?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
*
* FILE INFORMATION
* DEFGROUP: MokoStandards.Scripts.Validate
* INGROUP: MokoStandards
* REPO: https://github.com/mokoconsulting-tech/MokoStandards
* PATH: /scripts/validate/auto_detect_platform.php
* VERSION: 04.00.03
* BRIEF: Automatic platform detection and validation - PHP implementation
*/
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
require_once __DIR__ . '/../../src/Enterprise/CliFramework.php';
use MokoStandards\Enterprise\CLIApp;
/**
* Automatic Platform Detection and Validation
*
* Detects whether a repository is a Joomla/WaaS component, Dolibarr/CRM module,
* or generic repository, then validates against appropriate schema
*/
class AutoDetectPlatform extends CLIApp
{
private const DETECTION_THRESHOLD = 0.5; // 50% confidence required
private array $detectionResults = [
'joomla' => ['score' => 0, 'indicators' => []],
'dolibarr' => ['score' => 0, 'indicators' => []],
'generic' => ['score' => 0, 'indicators' => []],
];
private string $detectedPlatform = 'generic';
private string $schemaFile = '';
protected function setupArguments(): array
{
return [
'repo-path:' => 'Path to repository to analyze (default: current directory)',
'schema-dir:' => 'Path to schema definitions directory (default: scripts/definitions)',
'output-dir:' => 'Directory for output reports (default: logs/validation)',
];
}
protected function run(): int
{
$repoPath = $this->getOption('repo-path', '.');
$schemaDir = $this->getOption('schema-dir', 'scripts/definitions');
$outputDir = $this->getOption('output-dir', 'logs/validation');
// Make paths absolute
$repoPath = $this->getAbsolutePath($repoPath);
$schemaDir = $this->getAbsolutePath($schemaDir);
$outputDir = $this->getAbsolutePath($outputDir);
if (!is_dir($repoPath)) {
$this->log("Repository path not found: {$repoPath}", 'ERROR');
return 3;
}
if (!is_dir($schemaDir)) {
$this->log("Schema directory not found: {$schemaDir}", 'ERROR');
return 3;
}
$this->log("Analyzing repository: {$repoPath}", 'INFO');
// Run platform detection
$this->detectJoomla($repoPath);
$this->detectDolibarr($repoPath);
// Determine platform
$this->determinePlatform();
// Map to schema file
$this->schemaFile = $this->mapPlatformToSchema($schemaDir);
if (!file_exists($this->schemaFile)) {
$this->log("Schema file not found: {$this->schemaFile}", 'ERROR');
return 3;
}
// Output results
if ($this->jsonOutput) {
$this->outputJson();
} else {
$this->displayResults();
}
// Generate reports
$this->generateReports($outputDir, $repoPath);
$this->log("Platform detection completed: {$this->detectedPlatform}", 'INFO');
$this->log("Schema file: {$this->schemaFile}", 'INFO');
return 0;
}
private function detectJoomla(string $repoPath): void
{
$score = 0;
$indicators = [];
// Look for Joomla manifest files
$manifests = $this->findFiles($repoPath, '*.xml', 3);
foreach ($manifests as $manifest) {
$content = @file_get_contents($manifest);
if ($content && (
strpos($content, '<extension') !== false ||
strpos($content, '<install') !== false
)) {
$score += 0.3;
$indicators[] = "Found Joomla manifest: " . basename($manifest);
}
}
// Check for Joomla directory structure
$joomlaDirs = ['site', 'admin', 'administrator', 'language', 'media'];
foreach ($joomlaDirs as $dir) {
if (is_dir("{$repoPath}/{$dir}")) {
$score += 0.1;
$indicators[] = "Found Joomla directory: {$dir}/";
}
}
// Check for index.html files (Joomla security pattern)
$indexCount = count($this->findFiles($repoPath, 'index.html', 2));
if ($indexCount > 2) {
$score += 0.2;
$indicators[] = "Found {$indexCount} index.html files (Joomla pattern)";
}
$this->detectionResults['joomla'] = [
'score' => min(1.0, $score),
'indicators' => $indicators,
];
}
private function detectDolibarr(string $repoPath): void
{
$score = 0;
$indicators = [];
// Look for Dolibarr module descriptor
$descriptors = $this->findFiles($repoPath, 'mod*.class.php', 3);
foreach ($descriptors as $descriptor) {
$content = @file_get_contents($descriptor);
if ($content && strpos($content, 'DolibarrModules') !== false) {
$score += 0.4;
$indicators[] = "Found Dolibarr module descriptor: " . basename($descriptor);
}
}
// Check for Dolibarr-specific code patterns
$phpFiles = $this->findFiles($repoPath, '*.php', 3);
$dolibarrPatterns = ['dol_include_once', '$this->numero', 'DoliDB', 'Translate'];
foreach ($phpFiles as $file) {
$content = @file_get_contents($file);
if (!$content) continue;
foreach ($dolibarrPatterns as $pattern) {
if (strpos($content, $pattern) !== false) {
$score += 0.05;
$indicators[] = "Found Dolibarr pattern '{$pattern}' in " . basename($file);
break; // Only count once per file
}
}
if ($score >= 0.8) break; // Stop early if confident
}
// Check for Dolibarr directory structure
$dolibarrDirs = ['core/modules', 'sql', 'class', 'lib', 'langs'];
foreach ($dolibarrDirs as $dir) {
if (is_dir("{$repoPath}/{$dir}")) {
$score += 0.1;
$indicators[] = "Found Dolibarr directory: {$dir}/";
}
}
// Check for SQL files in sql/ directory
if (is_dir("{$repoPath}/sql")) {
$sqlFiles = $this->findFiles("{$repoPath}/sql", '*.sql', 1);
if (count($sqlFiles) > 0) {
$score += 0.1;
$indicators[] = "Found " . count($sqlFiles) . " SQL files in sql/";
}
}
$this->detectionResults['dolibarr'] = [
'score' => min(1.0, $score),
'indicators' => $indicators,
];
}
private function determinePlatform(): void
{
$joomlaScore = $this->detectionResults['joomla']['score'];
$dolibarrScore = $this->detectionResults['dolibarr']['score'];
// Require minimum threshold
if ($joomlaScore >= self::DETECTION_THRESHOLD && $joomlaScore > $dolibarrScore) {
$this->detectedPlatform = 'joomla';
} elseif ($dolibarrScore >= self::DETECTION_THRESHOLD && $dolibarrScore > $joomlaScore) {
$this->detectedPlatform = 'dolibarr';
} else {
$this->detectedPlatform = 'generic';
}
}
private function mapPlatformToSchema(string $schemaDir): string
{
$mapping = [
'joomla' => 'waas-component.xml',
'dolibarr' => 'crm-module.xml',
'generic' => 'default-repository.xml',
];
return $schemaDir . '/' . $mapping[$this->detectedPlatform];
}
private function displayResults(): void
{
echo "\n=== Platform Detection Results ===\n\n";
echo "Platform: {$this->detectedPlatform}\n";
echo "Schema: {$this->schemaFile}\n\n";
echo "Detection Scores:\n";
foreach ($this->detectionResults as $platform => $data) {
$percentage = round($data['score'] * 100, 1);
$status = ($data['score'] >= self::DETECTION_THRESHOLD) ? '✅' : '❌';
echo sprintf(" %s %s: %.1f%%\n", $status, ucfirst($platform), $percentage);
}
echo "\nDetection Indicators:\n";
$indicators = $this->detectionResults[$this->detectedPlatform]['indicators'];
if (empty($indicators)) {
echo " No specific indicators found (generic repository)\n";
} else {
foreach ($indicators as $indicator) {
echo "{$indicator}\n";
}
}
echo "\n";
}
private function outputJson(): void
{
$output = [
'platform' => $this->detectedPlatform,
'schema' => $this->schemaFile,
'detection_results' => $this->detectionResults,
'threshold' => self::DETECTION_THRESHOLD,
'timestamp' => date('c'),
];
echo json_encode($output, JSON_PRETTY_PRINT) . PHP_EOL;
}
private function generateReports(string $outputDir, string $repoPath): void
{
// Ensure output directory exists
if (!is_dir($outputDir)) {
@mkdir($outputDir, 0755, true);
}
$timestamp = date('Ymd_His');
// Generate detection report
$detectionReport = $outputDir . "/detection_report_{$timestamp}.md";
$this->writeDetectionReport($detectionReport, $repoPath);
// Generate summary report
$summaryReport = $outputDir . "/SUMMARY_{$timestamp}.md";
$this->writeSummaryReport($summaryReport, $repoPath);
$this->log("Reports generated in: {$outputDir}", 'INFO');
}
private function writeDetectionReport(string $file, string $repoPath): void
{
$content = "# Platform Detection Report\n\n";
$content .= "**Generated**: " . date('Y-m-d H:i:s') . "\n";
$content .= "**Repository**: {$repoPath}\n\n";
$content .= "## Detected Platform\n\n";
$content .= "**Type**: " . strtoupper($this->detectedPlatform) . "\n";
$content .= "**Confidence**: " . round($this->detectionResults[$this->detectedPlatform]['score'] * 100, 1) . "%\n";
$content .= "**Schema**: {$this->schemaFile}\n\n";
$content .= "## Detection Indicators\n\n";
foreach ($this->detectionResults[$this->detectedPlatform]['indicators'] as $indicator) {
$content .= "- {$indicator}\n";
}
$content .= "\n## All Platform Scores\n\n";
foreach ($this->detectionResults as $platform => $data) {
$percentage = round($data['score'] * 100, 1);
$content .= "- **" . ucfirst($platform) . "**: {$percentage}%\n";
}
@file_put_contents($file, $content);
}
private function writeSummaryReport(string $file, string $repoPath): void
{
$content = "# Platform Detection Summary\n\n";
$content .= "| Property | Value |\n";
$content .= "|----------|-------|\n";
$content .= "| Repository | {$repoPath} |\n";
$content .= "| Platform | " . strtoupper($this->detectedPlatform) . " |\n";
$content .= "| Confidence | " . round($this->detectionResults[$this->detectedPlatform]['score'] * 100, 1) . "% |\n";
$content .= "| Schema | " . basename($this->schemaFile) . " |\n";
$content .= "| Timestamp | " . date('Y-m-d H:i:s') . " |\n\n";
$content .= "## Next Steps\n\n";
$content .= "1. Review detection indicators\n";
$content .= "2. Validate repository against schema: {$this->schemaFile}\n";
$content .= "3. Address any validation errors or warnings\n";
@file_put_contents($file, $content);
}
private function findFiles(string $dir, string $pattern, int $maxDepth = 1): array
{
$files = [];
$pattern = str_replace('*', '.*', $pattern);
$pattern = str_replace('.', '\.', $pattern);
try {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
);
$iterator->setMaxDepth($maxDepth);
foreach ($iterator as $file) {
if ($file->isFile() && preg_match("/{$pattern}$/", $file->getFilename())) {
$files[] = $file->getPathname();
}
}
} catch (Exception $e) {
// Directory not accessible
}
return $files;
}
private function getAbsolutePath(string $path): string
{
if (strlen($path) > 0 && $path[0] === '/') {
return $path;
}
return getcwd() . '/' . $path;
}
}
// Run the application
$app = new AutoDetectPlatform('auto_detect_platform', 'Automatically detect platform type and validate repository');
exit($app->execute());
+317
View File
@@ -0,0 +1,317 @@
#!/usr/bin/env php
<?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
*
* FILE INFORMATION
* DEFGROUP: MokoStandards.Scripts.Validate
* INGROUP: MokoStandards
* REPO: https://github.com/mokoconsulting-tech/MokoStandards
* PATH: /scripts/validate/check_repo_health.php
* VERSION: 04.00.03
* BRIEF: Repository health checker - PHP implementation
*/
declare(strict_types=1);
require_once __DIR__ . '/../../vendor/autoload.php';
use MokoStandards\Enterprise\{
AuditLogger,
CliFramework,
MetricsCollector,
UnifiedValidation
};
/**
* Repository Health Checker
*
* Performs comprehensive repository health checks
*/
class RepoHealthChecker extends CliFramework
{
private const DEFAULT_THRESHOLD = 70.0;
private AuditLogger $logger;
private MetricsCollector $metrics;
private UnifiedValidation $validator;
private array $results = [
'categories' => [],
'checks' => [],
'score' => 0,
'max_score' => 100,
'percentage' => 0.0,
'level' => 'unknown',
];
protected function configure(): void
{
$this->setDescription('Check repository health and compliance');
$this->addArgument('--path', 'Repository path to check', '.');
$this->addArgument('--threshold', 'Minimum health threshold (%)', '70');
$this->addArgument('--json', 'Output results as JSON', false);
}
protected function initialize(): void
{
parent::initialize();
$this->logger = new AuditLogger('repo_health_checker');
$this->metrics = new MetricsCollector();
$this->validator = new UnifiedValidation();
$this->log('Repository health checker initialized');
}
protected function run(): int
{
$path = $this->getArgument('--path');
$threshold = (float)$this->getArgument('--threshold');
$jsonOutput = $this->getArgument('--json');
$this->log("Checking repository health: {$path}");
// Run checks
$this->runStructureChecks($path);
$this->runDocumentationChecks($path);
$this->runWorkflowChecks($path);
$this->runSecurityChecks($path);
// Calculate scores
$this->calculateScore();
// Output results
if ($jsonOutput) {
echo json_encode($this->results, JSON_PRETTY_PRINT) . PHP_EOL;
} else {
$this->displayResults();
}
// Record metrics
$this->metrics->setGauge('repo_health_score', $this->results['percentage']);
$this->metrics->setGauge('repo_health_checks_passed',
count(array_filter($this->results['checks'], fn($c) => $c['passed'])));
// Check threshold
if ($this->results['percentage'] < $threshold) {
$this->error(sprintf(
"Health check failed: %.1f%% < %.1f%% threshold",
$this->results['percentage'],
$threshold
));
return 1;
}
$this->log(sprintf(
"Health check passed: %.1f%% >= %.1f%% threshold",
$this->results['percentage'],
$threshold
));
return 0;
}
private function runStructureChecks(string $path): void
{
$category = 'structure';
$this->results['categories'][$category] = [
'name' => 'Repository Structure',
'max_points' => 30,
'earned_points' => 0,
'checks_passed' => 0,
'checks_failed' => 0,
];
// Check README exists
$this->addCheck($category, 'README.md exists',
file_exists("{$path}/README.md"), 10);
// Check LICENSE exists
$this->addCheck($category, 'LICENSE file exists',
file_exists("{$path}/LICENSE"), 10);
// Check .gitignore exists
$this->addCheck($category, '.gitignore exists',
file_exists("{$path}/.gitignore"), 5);
// Check CHANGELOG exists
$this->addCheck($category, 'CHANGELOG.md exists',
file_exists("{$path}/CHANGELOG.md"), 5);
}
private function runDocumentationChecks(string $path): void
{
$category = 'documentation';
$this->results['categories'][$category] = [
'name' => 'Documentation',
'max_points' => 25,
'earned_points' => 0,
'checks_passed' => 0,
'checks_failed' => 0,
];
// Check docs directory exists
$this->addCheck($category, 'docs/ directory exists',
is_dir("{$path}/docs"), 10);
// Check README has content
if (file_exists("{$path}/README.md")) {
$content = file_get_contents("{$path}/README.md");
$this->addCheck($category, 'README has substantial content',
strlen($content) > 500, 10);
}
// Check for code of conduct
$this->addCheck($category, 'CODE_OF_CONDUCT.md exists',
file_exists("{$path}/CODE_OF_CONDUCT.md"), 5);
}
private function runWorkflowChecks(string $path): void
{
$category = 'workflows';
$this->results['categories'][$category] = [
'name' => 'GitHub Workflows',
'max_points' => 20,
'earned_points' => 0,
'checks_passed' => 0,
'checks_failed' => 0,
];
$workflowDir = "{$path}/.github/workflows";
// Check workflows directory exists
$this->addCheck($category, 'Workflows directory exists',
is_dir($workflowDir), 10);
// Check for CI workflow
if (is_dir($workflowDir)) {
$hasCI = glob("{$workflowDir}/ci*.yml") || glob("{$workflowDir}/ci*.yaml");
$this->addCheck($category, 'CI workflow exists',
!empty($hasCI), 10);
}
}
private function runSecurityChecks(string $path): void
{
$category = 'security';
$this->results['categories'][$category] = [
'name' => 'Security',
'max_points' => 25,
'earned_points' => 0,
'checks_passed' => 0,
'checks_failed' => 0,
];
// Check for SECURITY.md
$this->addCheck($category, 'SECURITY.md exists',
file_exists("{$path}/SECURITY.md") ||
file_exists("{$path}/.github/SECURITY.md"), 10);
// Check for CodeQL workflow
$workflowDir = "{$path}/.github/workflows";
if (is_dir($workflowDir)) {
$hasCodeQL = glob("{$workflowDir}/*codeql*.yml") ||
glob("{$workflowDir}/*codeql*.yaml");
$this->addCheck($category, 'CodeQL workflow exists',
!empty($hasCodeQL), 10);
}
// Check for dependabot
$this->addCheck($category, 'Dependabot configured',
file_exists("{$path}/.github/dependabot.yml") ||
file_exists("{$path}/.github/dependabot.yaml"), 5);
}
private function addCheck(string $category, string $name, bool $passed, int $points): void
{
$this->results['checks'][] = [
'category' => $category,
'name' => $name,
'passed' => $passed,
'points' => $points,
];
if ($passed) {
$this->results['categories'][$category]['earned_points'] += $points;
$this->results['categories'][$category]['checks_passed']++;
} else {
$this->results['categories'][$category]['checks_failed']++;
}
}
private function calculateScore(): void
{
$totalEarned = 0;
$maxScore = 0;
foreach ($this->results['categories'] as $category) {
$totalEarned += $category['earned_points'];
$maxScore += $category['max_points'];
}
$this->results['score'] = $totalEarned;
$this->results['max_score'] = $maxScore;
$this->results['percentage'] = $maxScore > 0 ? ($totalEarned / $maxScore * 100) : 0;
// Determine level
$pct = $this->results['percentage'];
if ($pct >= 90) {
$this->results['level'] = 'excellent';
} elseif ($pct >= 80) {
$this->results['level'] = 'good';
} elseif ($pct >= 70) {
$this->results['level'] = 'fair';
} elseif ($pct >= 60) {
$this->results['level'] = 'poor';
} else {
$this->results['level'] = 'critical';
}
}
private function displayResults(): void
{
echo "\n=== Repository Health Check Results ===\n\n";
foreach ($this->results['categories'] as $catId => $category) {
$pct = $category['max_points'] > 0
? ($category['earned_points'] / $category['max_points'] * 100)
: 0;
echo sprintf(
"%s: %d/%d points (%.1f%%) - %d passed, %d failed\n",
$category['name'],
$category['earned_points'],
$category['max_points'],
$pct,
$category['checks_passed'],
$category['checks_failed']
);
}
echo sprintf(
"\nOverall Score: %d/%d points (%.1f%%) - Level: %s\n",
$this->results['score'],
$this->results['max_score'],
$this->results['percentage'],
strtoupper($this->results['level'])
);
// Show failed checks
$failedChecks = array_filter($this->results['checks'], fn($c) => !$c['passed']);
if (!empty($failedChecks)) {
echo "\nFailed Checks:\n";
foreach ($failedChecks as $check) {
echo sprintf(" ❌ %s (%d points)\n", $check['name'], $check['points']);
}
}
}
}
// Run the application
$app = new RepoHealthChecker();
exit($app->execute($argv));
+339
View File
@@ -0,0 +1,339 @@
#!/usr/bin/env php
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* FILE INFORMATION
* DEFGROUP: MokoStandards.Scripts
* INGROUP: MokoStandards.Validation
* REPO: https://github.com/mokoconsulting-tech/MokoStandards
* PATH: /scripts/validate/check_version_consistency.php
* VERSION: 04.00.03
* BRIEF: Check for version number consistency across repository
*
* This script validates that version numbers are consistent across
* all critical files in the repository to prevent version mismatches.
*/
declare(strict_types=1);
// Configuration
$repoRoot = dirname(__DIR__, 2);
$expectedVersionFile = $repoRoot . '/composer.json';
// ANSI color codes
const COLOR_RED = "\033[31m";
const COLOR_GREEN = "\033[32m";
const COLOR_YELLOW = "\033[33m";
const COLOR_BLUE = "\033[34m";
const COLOR_RESET = "\033[0m";
const COLOR_BOLD = "\033[1m";
/**
* Parse command line arguments
*/
function parseArguments(): array
{
global $argv;
$options = [
'verbose' => false,
'help' => false,
];
for ($i = 1; $i < count($argv); $i++) {
switch ($argv[$i]) {
case '--verbose':
case '-v':
$options['verbose'] = true;
break;
case '--help':
case '-h':
$options['help'] = true;
break;
default:
echo COLOR_RED . "Unknown option: {$argv[$i]}" . COLOR_RESET . "\n";
$options['help'] = true;
}
}
return $options;
}
/**
* Display help message
*/
function displayHelp(): void
{
echo COLOR_BOLD . "Version Consistency Checker" . COLOR_RESET . "\n\n";
echo "Usage: php check_version_consistency.php [options]\n\n";
echo "Options:\n";
echo " -v, --verbose Enable verbose output\n";
echo " -h, --help Display this help message\n\n";
echo "Description:\n";
echo " Checks for version number consistency across critical repository files.\n";
echo " The expected version is read from composer.json.\n\n";
echo "Exit Codes:\n";
echo " 0 - All versions are consistent\n";
echo " 1 - Version mismatches found\n";
echo " 2 - Error reading files\n\n";
}
/**
* Get expected version from composer.json
*/
function getExpectedVersion(string $composerFile, bool $verbose): ?string
{
if (!file_exists($composerFile)) {
echo COLOR_RED . "✗ composer.json not found at: $composerFile" . COLOR_RESET . "\n";
return null;
}
$content = file_get_contents($composerFile);
$data = json_decode($content, true);
if (!isset($data['version'])) {
echo COLOR_RED . "✗ Version not found in composer.json" . COLOR_RESET . "\n";
return null;
}
$version = $data['version'];
if ($verbose) {
echo COLOR_BLUE . "Expected version from composer.json: $version" . COLOR_RESET . "\n";
}
return $version;
}
/**
* Check version in a file with pattern
*/
function checkVersionInFile(string $file, string $pattern, string $expectedVersion, bool $verbose): array
{
if (!file_exists($file)) {
return ['found' => false, 'error' => 'File not found'];
}
$content = file_get_contents($file);
$mismatches = [];
if (preg_match_all($pattern, $content, $matches, PREG_OFFSET_CAPTURE)) {
foreach ($matches[1] as $match) {
$foundVersion = $match[0];
if ($foundVersion !== $expectedVersion) {
$lineNum = substr_count(substr($content, 0, $match[1]), "\n") + 1;
$mismatches[] = [
'found' => $foundVersion,
'expected' => $expectedVersion,
'line' => $lineNum
];
}
}
}
return ['found' => count($matches[0]) > 0, 'mismatches' => $mismatches];
}
/**
* Check specific critical files
*/
function checkCriticalFiles(string $repoRoot, string $expectedVersion, bool $verbose): array
{
$issues = [];
// Check README.md
$readmeFile = $repoRoot . '/README.md';
if ($verbose) echo COLOR_BLUE . "Checking README.md..." . COLOR_RESET . "\n";
// Check VERSION header
$result = checkVersionInFile($readmeFile, '/VERSION:\s*([\d.]+)/', $expectedVersion, $verbose);
if (!empty($result['mismatches'])) {
$issues[] = [
'file' => 'README.md',
'type' => 'VERSION header',
'mismatches' => $result['mismatches']
];
}
// Check badge
$result = checkVersionInFile($readmeFile, '/MokoStandards-([\d.]+)/', $expectedVersion, $verbose);
if (!empty($result['mismatches'])) {
$issues[] = [
'file' => 'README.md',
'type' => 'Version badge',
'mismatches' => $result['mismatches']
];
}
// Check CHANGELOG.md
$changelogFile = $repoRoot . '/CHANGELOG.md';
if ($verbose) echo COLOR_BLUE . "Checking CHANGELOG.md..." . COLOR_RESET . "\n";
$result = checkVersionInFile($changelogFile, '/VERSION:\s*([\d.]+)/', $expectedVersion, $verbose);
if (!empty($result['mismatches'])) {
$issues[] = [
'file' => 'CHANGELOG.md',
'type' => 'VERSION header',
'mismatches' => $result['mismatches']
];
}
// Check for current version in title
$result = checkVersionInFile($changelogFile, '/CHANGELOG - MokoStandards \(VERSION:\s*([\d.]+)\)/', $expectedVersion, $verbose);
if (!empty($result['mismatches'])) {
$issues[] = [
'file' => 'CHANGELOG.md',
'type' => 'Title version',
'mismatches' => $result['mismatches']
];
}
// Check CONTRIBUTING.md
$contributingFile = $repoRoot . '/CONTRIBUTING.md';
if ($verbose) echo COLOR_BLUE . "Checking CONTRIBUTING.md..." . COLOR_RESET . "\n";
$result = checkVersionInFile($contributingFile, '/VERSION:\s*([\d.]+)/', $expectedVersion, $verbose);
if (!empty($result['mismatches'])) {
$issues[] = [
'file' => 'CONTRIBUTING.md',
'type' => 'VERSION header',
'mismatches' => $result['mismatches']
];
}
return $issues;
}
/**
* Check workflow files
*/
function checkWorkflowFiles(string $repoRoot, string $expectedVersion, bool $verbose): array
{
$issues = [];
$workflowDir = $repoRoot . '/.github/workflows';
if (!is_dir($workflowDir)) {
return $issues;
}
$files = glob($workflowDir . '/*.yml');
if ($verbose) echo COLOR_BLUE . "Checking " . count($files) . " workflow files..." . COLOR_RESET . "\n";
foreach ($files as $file) {
$result = checkVersionInFile($file, '/#\s*VERSION:\s*([\d.]+)/', $expectedVersion, $verbose);
if (!empty($result['mismatches'])) {
$issues[] = [
'file' => str_replace($repoRoot . '/', '', $file),
'type' => 'Workflow VERSION comment',
'mismatches' => $result['mismatches']
];
}
}
return $issues;
}
/**
* Check PHP source files
*/
function checkPhpSourceFiles(string $repoRoot, string $expectedVersion, bool $verbose): array
{
$issues = [];
$srcDir = $repoRoot . '/src';
if (!is_dir($srcDir)) {
return $issues;
}
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($srcDir, RecursiveDirectoryIterator::SKIP_DOTS)
);
$phpFiles = [];
foreach ($iterator as $file) {
if ($file->isFile() && $file->getExtension() === 'php') {
$phpFiles[] = $file->getPathname();
}
}
if ($verbose) echo COLOR_BLUE . "Checking " . count($phpFiles) . " PHP source files..." . COLOR_RESET . "\n";
foreach ($phpFiles as $file) {
$result = checkVersionInFile($file, '/VERSION:\s*([\d.]+)/', $expectedVersion, $verbose);
if (!empty($result['mismatches'])) {
$issues[] = [
'file' => str_replace($repoRoot . '/', '', $file),
'type' => 'PHP VERSION header',
'mismatches' => $result['mismatches']
];
}
}
return $issues;
}
/**
* Main execution
*/
function main(): int
{
global $repoRoot, $expectedVersionFile;
$options = parseArguments();
if ($options['help']) {
displayHelp();
return 0;
}
echo COLOR_BOLD . "=== MokoStandards Version Consistency Checker ===" . COLOR_RESET . "\n\n";
// Get expected version
$expectedVersion = getExpectedVersion($expectedVersionFile, $options['verbose']);
if ($expectedVersion === null) {
return 2;
}
echo COLOR_GREEN . "✓ Expected version: $expectedVersion" . COLOR_RESET . "\n\n";
// Check critical files
echo COLOR_BOLD . "Checking critical files..." . COLOR_RESET . "\n";
$criticalIssues = checkCriticalFiles($repoRoot, $expectedVersion, $options['verbose']);
// Check workflow files
echo COLOR_BOLD . "\nChecking workflow files..." . COLOR_RESET . "\n";
$workflowIssues = checkWorkflowFiles($repoRoot, $expectedVersion, $options['verbose']);
// Check PHP source files
echo COLOR_BOLD . "\nChecking PHP source files..." . COLOR_RESET . "\n";
$phpIssues = checkPhpSourceFiles($repoRoot, $expectedVersion, $options['verbose']);
// Combine all issues
$allIssues = array_merge($criticalIssues, $workflowIssues, $phpIssues);
// Report results
echo "\n" . COLOR_BOLD . "=== Results ===" . COLOR_RESET . "\n\n";
if (empty($allIssues)) {
echo COLOR_GREEN . "✓ All version numbers are consistent!" . COLOR_RESET . "\n";
echo COLOR_GREEN . "✓ Expected version $expectedVersion found in all checked files." . COLOR_RESET . "\n";
return 0;
}
echo COLOR_RED . "✗ Found " . count($allIssues) . " version mismatch(es):" . COLOR_RESET . "\n\n";
foreach ($allIssues as $issue) {
echo COLOR_YELLOW . "File: " . $issue['file'] . COLOR_RESET . "\n";
echo " Type: " . $issue['type'] . "\n";
foreach ($issue['mismatches'] as $mismatch) {
echo COLOR_RED . " Line " . $mismatch['line'] . ": Found " . $mismatch['found'] .
" (expected " . $mismatch['expected'] . ")" . COLOR_RESET . "\n";
}
echo "\n";
}
echo COLOR_RED . "\n✗ Please update the version numbers in the files listed above." . COLOR_RESET . "\n";
return 1;
}
exit(main());
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env bash
# 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
#
# Shell wrapper template for Python scripts
# This wrapper provides a convenient way to call Python scripts with proper error handling
set -euo pipefail
# Script Configuration - UPDATE THESE FOR EACH WRAPPER
SCRIPT_NAME="check_markdown_links"
SCRIPT_PATH="scripts/validate/check_markdown_links.py"
SCRIPT_CATEGORY="validation" # automation, validation, maintenance, etc.
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Functions
log_info() {
echo -e "${BLUE}$1${NC}"
}
log_success() {
echo -e "${GREEN}$1${NC}"
}
log_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
log_error() {
echo -e "${RED}$1${NC}"
}
# Get repository root
get_repo_root() {
git rev-parse --show-toplevel 2>/dev/null || pwd
}
# Check if Python is available
check_python() {
if command -v python3 &> /dev/null; then
echo "python3"
elif command -v python &> /dev/null; then
echo "python"
else
log_error "Python is not installed or not in PATH"
echo "Please install Python 3.7 or later"
exit 1
fi
}
# Main execution
main() {
local repo_root
repo_root=$(get_repo_root)
local python_cmd
python_cmd=$(check_python)
local full_script_path="$repo_root/$SCRIPT_PATH"
# Check if script exists
if [ ! -f "$full_script_path" ]; then
log_error "Python script not found: $full_script_path"
exit 1
fi
# Setup logging directory
local log_dir="$repo_root/logs/$SCRIPT_CATEGORY"
mkdir -p "$log_dir"
local timestamp
timestamp=$(date +"%Y%m%d_%H%M%S")
local log_file="$log_dir/${SCRIPT_NAME}_${timestamp}.log"
# Execute Python script with all arguments
log_info "Running $SCRIPT_NAME..."
log_info "Log file: $log_file"
if "$python_cmd" "$full_script_path" "$@" 2>&1 | tee "$log_file"; then
log_success "$SCRIPT_NAME completed successfully"
exit 0
else
local exit_code=$?
log_error "$SCRIPT_NAME failed with exit code: $exit_code"
log_info "Check log file for details: $log_file"
exit $exit_code
fi
}
# Run main with all arguments
main "$@"
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env bash
# 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
#
# Shell wrapper template for Python scripts
# This wrapper provides a convenient way to call Python scripts with proper error handling
set -euo pipefail
# Script Configuration - UPDATE THESE FOR EACH WRAPPER
SCRIPT_NAME="rebuild_indexes"
SCRIPT_PATH="scripts/docs/rebuild_indexes.py"
SCRIPT_CATEGORY="docs" # automation, validation, maintenance, etc.
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Functions
log_info() {
echo -e "${BLUE}$1${NC}"
}
log_success() {
echo -e "${GREEN}$1${NC}"
}
log_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
log_error() {
echo -e "${RED}$1${NC}"
}
# Get repository root
get_repo_root() {
git rev-parse --show-toplevel 2>/dev/null || pwd
}
# Check if Python is available
check_python() {
if command -v python3 &> /dev/null; then
echo "python3"
elif command -v python &> /dev/null; then
echo "python"
else
log_error "Python is not installed or not in PATH"
echo "Please install Python 3.7 or later"
exit 1
fi
}
# Main execution
main() {
local repo_root
repo_root=$(get_repo_root)
local python_cmd
python_cmd=$(check_python)
local full_script_path="$repo_root/$SCRIPT_PATH"
# Check if script exists
if [ ! -f "$full_script_path" ]; then
log_error "Python script not found: $full_script_path"
exit 1
fi
# Setup logging directory
local log_dir="$repo_root/logs/$SCRIPT_CATEGORY"
mkdir -p "$log_dir"
local timestamp
timestamp=$(date +"%Y%m%d_%H%M%S")
local log_file="$log_dir/${SCRIPT_NAME}_${timestamp}.log"
# Execute Python script with all arguments
log_info "Running $SCRIPT_NAME..."
log_info "Log file: $log_file"
if "$python_cmd" "$full_script_path" "$@" 2>&1 | tee "$log_file"; then
log_success "$SCRIPT_NAME completed successfully"
exit 0
else
local exit_code=$?
log_error "$SCRIPT_NAME failed with exit code: $exit_code"
log_info "Check log file for details: $log_file"
exit $exit_code
fi
}
# Run main with all arguments
main "$@"
+100
View File
@@ -0,0 +1,100 @@
#!/usr/bin/env bash
# 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
#
# Shell wrapper template for Python scripts
# This wrapper provides a convenient way to call Python scripts with proper error handling
set -euo pipefail
# Script Configuration - UPDATE THESE FOR EACH WRAPPER
SCRIPT_NAME="validate_file_headers"
SCRIPT_PATH="scripts/maintenance/validate_file_headers.py"
SCRIPT_CATEGORY="maintenance" # automation, validation, maintenance, etc.
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Functions
log_info() {
echo -e "${BLUE}$1${NC}"
}
log_success() {
echo -e "${GREEN}$1${NC}"
}
log_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
log_error() {
echo -e "${RED}$1${NC}"
}
# Get repository root
get_repo_root() {
git rev-parse --show-toplevel 2>/dev/null || pwd
}
# Check if Python is available
check_python() {
if command -v python3 &> /dev/null; then
echo "python3"
elif command -v python &> /dev/null; then
echo "python"
else
log_error "Python is not installed or not in PATH"
echo "Please install Python 3.7 or later"
exit 1
fi
}
# Main execution
main() {
local repo_root
repo_root=$(get_repo_root)
local python_cmd
python_cmd=$(check_python)
local full_script_path="$repo_root/$SCRIPT_PATH"
# Check if script exists
if [ ! -f "$full_script_path" ]; then
log_error "Python script not found: $full_script_path"
exit 1
fi
# Setup logging directory
local log_dir="$repo_root/logs/$SCRIPT_CATEGORY"
mkdir -p "$log_dir"
local timestamp
timestamp=$(date +"%Y%m%d_%H%M%S")
local log_file="$log_dir/${SCRIPT_NAME}_${timestamp}.log"
# Execute Python script with all arguments
log_info "Running $SCRIPT_NAME..."
log_info "Log file: $log_file"
if "$python_cmd" "$full_script_path" "$@" 2>&1 | tee "$log_file"; then
log_success "$SCRIPT_NAME completed successfully"
exit 0
else
local exit_code=$?
log_error "$SCRIPT_NAME failed with exit code: $exit_code"
log_info "Check log file for details: $log_file"
exit $exit_code
fi
}
# Run main with all arguments
main "$@"
View File
-142
View File
@@ -1,142 +0,0 @@
# MokoJoomTOS Component
A Joomla component for managing Terms of Service and Privacy Policy content with offline mode access capability.
## Features
- **Admin Interface**: Easy-to-use backend for editing Terms of Service and Privacy Policy
- **Offline Access**: Content remains accessible even when the site is in offline mode
- **Modern Architecture**: Built using Joomla 4.x/5.x standards with namespaced classes
- **Responsive Design**: Mobile-friendly display for both admin and frontend
- **Version Tracking**: Automatically tracks when content was last updated
## Installation
1. Build the component package (if needed):
```bash
cd /path/to/MokoJoomTOS
make build
```
2. Install via Joomla Administrator:
- Go to System → Install → Extensions
- Upload the component package
- The component and offline access plugin will be installed automatically
## Usage
### Admin (Backend)
1. Navigate to **Components → MokoJoomTOS** in the Joomla administrator
2. Click on either:
- **Terms of Service** - to edit the terms
- **Privacy Policy** - to edit the privacy policy
3. Edit the content using the WYSIWYG editor
4. Set the published status
5. Click **Save** or **Apply**
### Frontend Display
To display the Terms of Service or Privacy Policy on your site:
1. Create a new menu item
2. Menu Item Type: **MokoJoomTOS**
3. Select either:
- **Terms of Service**
- **Privacy Policy**
4. Configure other menu settings as needed
5. Save the menu item
### Direct URLs
The content can also be accessed directly via:
- Terms of Service: `https://yoursite.com/index.php?option=com_mokojoomtos&view=terms`
- Privacy Policy: `https://yoursite.com/index.php?option=com_mokojoomtos&view=privacy`
### Offline Mode Access
The component includes a system plugin that automatically enables access to Terms of Service and Privacy Policy pages even when your site is in offline mode. This is important for:
- Legal compliance requirements
- User agreement acceptance during registration
- Transparency during maintenance
The plugin is automatically enabled during installation.
## Component Structure
```
src/
├── com_mokojoomtos.xml # Component manifest
├── script.php # Installation script
├── admin/ # Backend files
│ ├── forms/ # Form definitions
│ ├── language/ # Backend language files
│ ├── services/ # Service providers
│ ├── sql/ # Database schemas
│ ├── src/ # PHP source code
│ │ ├── Controller/ # Controllers
│ │ ├── Extension/ # Component extension class
│ │ ├── Model/ # Models
│ │ └── View/ # Views
│ └── tmpl/ # Admin templates
├── site/ # Frontend files
│ ├── language/ # Frontend language files
│ ├── src/ # PHP source code
│ │ ├── Controller/ # Controllers
│ │ ├── Model/ # Models
│ │ └── View/ # Views
│ └── tmpl/ # Frontend templates
├── media/ # Media assets
│ ├── css/ # Stylesheets
│ └── js/ # JavaScript files
└── plugins/ # Related plugins
└── system/
└── mokojoomtosaccess/ # Offline access plugin
```
## Database
The component creates a single table: `#__mokojoomtos_content`
Fields:
- `id` - Primary key
- `content_type` - Type of content (terms or privacy)
- `title` - Page title
- `content` - HTML content
- `published` - Publication status
- `created` - Creation date
- `created_by` - Creator user ID
- `modified` - Last modification date
- `modified_by` - Last modifier user ID
## Customization
### Styling
The component includes basic CSS in `media/css/mokojoomtos.css`. You can override these styles in your template or add custom CSS via the component configuration.
### Templates
Frontend templates are located in `site/tmpl/`. You can override them in your Joomla template:
- Create: `templates/your-template/html/com_mokojoomtos/terms/default.php`
- Create: `templates/your-template/html/com_mokojoomtos/privacy/default.php`
## Requirements
- Joomla 4.0 or later
- PHP 7.4 or later
- MySQL 5.7 or later / MariaDB 10.2 or later
## License
GNU General Public License v3.0 or later (GPL-3.0-or-later)
## Support
- Issues: [GitHub Issues](https://github.com/mokoconsulting-tech/MokoJoomTOS/issues)
- Email: hello@mokoconsulting.tech
- Website: https://mokoconsulting.tech
## Author
Moko Consulting - https://mokoconsulting.tech
-48
View File
@@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
This file is part of MokoJoomTOS.
SPDX-License-Identifier: GPL-3.0-or-later
-->
<form>
<fieldset name="details">
<field
name="id"
type="hidden"
/>
<field
name="title"
type="text"
label="COM_MOKOJOOMTOS_FIELD_TITLE_LABEL"
description="COM_MOKOJOOMTOS_FIELD_TITLE_DESC"
required="true"
class="inputbox"
size="40"
/>
<field
name="content"
type="editor"
label="COM_MOKOJOOMTOS_FIELD_CONTENT_LABEL"
description="COM_MOKOJOOMTOS_FIELD_CONTENT_DESC"
required="true"
filter="JComponentHelper::filterText"
buttons="true"
/>
<field
name="published"
type="list"
label="COM_MOKOJOOMTOS_FIELD_PUBLISHED_LABEL"
description="COM_MOKOJOOMTOS_FIELD_PUBLISHED_DESC"
default="1"
class="inputbox"
>
<option value="1">JPUBLISHED</option>
<option value="0">JUNPUBLISHED</option>
</field>
</fieldset>
</form>
-48
View File
@@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
This file is part of MokoJoomTOS.
SPDX-License-Identifier: GPL-3.0-or-later
-->
<form>
<fieldset name="details">
<field
name="id"
type="hidden"
/>
<field
name="title"
type="text"
label="COM_MOKOJOOMTOS_FIELD_TITLE_LABEL"
description="COM_MOKOJOOMTOS_FIELD_TITLE_DESC"
required="true"
class="inputbox"
size="40"
/>
<field
name="content"
type="editor"
label="COM_MOKOJOOMTOS_FIELD_CONTENT_LABEL"
description="COM_MOKOJOOMTOS_FIELD_CONTENT_DESC"
required="true"
filter="JComponentHelper::filterText"
buttons="true"
/>
<field
name="published"
type="list"
label="COM_MOKOJOOMTOS_FIELD_PUBLISHED_LABEL"
description="COM_MOKOJOOMTOS_FIELD_PUBLISHED_DESC"
default="1"
class="inputbox"
>
<option value="1">JPUBLISHED</option>
<option value="0">JUNPUBLISHED</option>
</field>
</fieldset>
</form>
@@ -1,19 +0,0 @@
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
; SPDX-License-Identifier: GPL-3.0-or-later
COM_MOKOJOOMTOS="MokoJoomTOS - Terms & Privacy"
COM_MOKOJOOMTOS_DESCRIPTION="Component for managing Terms of Service and Privacy Policy with offline mode access"
COM_MOKOJOOMTOS_MENU_TERMS="Terms of Service"
COM_MOKOJOOMTOS_MENU_PRIVACY="Privacy Policy"
COM_MOKOJOOMTOS_MANAGER_TERMS="Edit Terms of Service"
COM_MOKOJOOMTOS_MANAGER_PRIVACY="Edit Privacy Policy"
COM_MOKOJOOMTOS_FIELD_TITLE_LABEL="Title"
COM_MOKOJOOMTOS_FIELD_TITLE_DESC="The title of the content"
COM_MOKOJOOMTOS_FIELD_CONTENT_LABEL="Content"
COM_MOKOJOOMTOS_FIELD_CONTENT_DESC="The main content"
COM_MOKOJOOMTOS_FIELD_PUBLISHED_LABEL="Published"
COM_MOKOJOOMTOS_FIELD_PUBLISHED_DESC="Whether the content is published"
COM_MOKOJOOMTOS_TERMS_SAVE_SUCCESS="Terms of Service saved successfully"
COM_MOKOJOOMTOS_PRIVACY_SAVE_SUCCESS="Privacy Policy saved successfully"
COM_MOKOJOOMTOS_LAST_UPDATED="Last Updated"
COM_MOKOJOOMTOS_NO_CONTENT="No content has been created yet"
@@ -1,5 +0,0 @@
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
; SPDX-License-Identifier: GPL-3.0-or-later
COM_MOKOJOOMTOS="MokoJoomTOS - Terms & Privacy"
COM_MOKOJOOMTOS_DESCRIPTION="Component for managing Terms of Service and Privacy Policy with offline mode access"
-53
View File
@@ -1,53 +0,0 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of MokoJoomTOS.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
defined('_JEXEC') or die;
use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface;
use Joomla\CMS\Extension\ComponentInterface;
use Joomla\CMS\Extension\Service\Provider\ComponentDispatcherFactory;
use Joomla\CMS\Extension\Service\Provider\MVCFactory;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use MokoConsulting\Component\MokoJoomTOS\Administrator\Extension\MokoJoomTOSComponent;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
/**
* The MokoJoomTOS service provider.
*
* @since 1.0.0
*/
return new class implements ServiceProviderInterface
{
/**
* Registers the service provider with a DI container.
*
* @param Container $container The DI container.
*
* @return void
*
* @since 1.0.0
*/
public function register(Container $container)
{
$container->registerServiceProvider(new MVCFactory('\\MokoConsulting\\Component\\MokoJoomTOS'));
$container->registerServiceProvider(new ComponentDispatcherFactory('\\MokoConsulting\\Component\\MokoJoomTOS'));
$container->set(
ComponentInterface::class,
function (Container $container) {
$component = new MokoJoomTOSComponent($container->get(ComponentDispatcherFactoryInterface::class));
$component->setMVCFactory($container->get(MVCFactoryInterface::class));
return $component;
}
);
}
};
@@ -1,30 +0,0 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of MokoJoomTOS.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace MokoConsulting\Component\MokoJoomTOS\Administrator\Controller;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Controller\BaseController;
/**
* Default Controller for MokoJoomTOS
*
* @since 1.0.0
*/
class DisplayController extends BaseController
{
/**
* The default view.
*
* @var string
* @since 1.0.0
*/
protected $default_view = 'terms';
}
@@ -1,30 +0,0 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of MokoJoomTOS.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace MokoConsulting\Component\MokoJoomTOS\Administrator\Controller;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Controller\FormController;
/**
* Controller for editing Privacy Policy content
*
* @since 1.0.0
*/
class PrivacyController extends FormController
{
/**
* The prefix to use with controller messages.
*
* @var string
* @since 1.0.0
*/
protected $text_prefix = 'COM_MOKOJOOMTOS_PRIVACY';
}
@@ -1,30 +0,0 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of MokoJoomTOS.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace MokoConsulting\Component\MokoJoomTOS\Administrator\Controller;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Controller\FormController;
/**
* Controller for editing Terms of Service content
*
* @since 1.0.0
*/
class TermsController extends FormController
{
/**
* The prefix to use with controller messages.
*
* @var string
* @since 1.0.0
*/
protected $text_prefix = 'COM_MOKOJOOMTOS_TERMS';
}
@@ -1,49 +0,0 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of MokoJoomTOS.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace MokoConsulting\Component\MokoJoomTOS\Administrator\Extension;
defined('_JEXEC') or die;
use Joomla\CMS\Component\Router\RouterServiceInterface;
use Joomla\CMS\Component\Router\RouterServiceTrait;
use Joomla\CMS\Extension\BootableExtensionInterface;
use Joomla\CMS\Extension\MVCComponent;
use Joomla\CMS\HTML\HTMLRegistryAwareTrait;
use Psr\Container\ContainerInterface;
/**
* Component class for MokoJoomTOS
*
* @since 1.0.0
*/
class MokoJoomTOSComponent extends MVCComponent implements
BootableExtensionInterface, RouterServiceInterface
{
use RouterServiceTrait;
use HTMLRegistryAwareTrait;
/**
* Booting the extension. This is the function to set up the environment of the extension like
* registering new class loaders, etc.
*
* If required, some initial set up can be done from services of the container, eg.
* registering HTML services.
*
* @param ContainerInterface $container The container
*
* @return void
*
* @since 1.0.0
*/
public function boot(ContainerInterface $container)
{
// Intentionally left empty
}
}
-140
View File
@@ -1,140 +0,0 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of MokoJoomTOS.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace MokoConsulting\Component\MokoJoomTOS\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\MVC\Model\AdminModel;
/**
* Model for editing Privacy Policy
*
* @since 1.0.0
*/
class PrivacyModel extends AdminModel
{
/**
* Method to get the record form.
*
* @param array $data Data for the form.
* @param boolean $loadData True if the form is to load its own data (default case), false if not.
*
* @return Form|boolean A Form object on success, false on failure
*
* @since 1.0.0
*/
public function getForm($data = array(), $loadData = true)
{
$form = $this->loadForm(
'com_mokojoomtos.privacy',
'privacy',
array('control' => 'jform', 'load_data' => $loadData)
);
if (empty($form))
{
return false;
}
return $form;
}
/**
* Method to get the data that should be injected in the form.
*
* @return mixed The data for the form.
*
* @since 1.0.0
*/
protected function loadFormData()
{
$data = Factory::getApplication()->getUserState('com_mokojoomtos.edit.privacy.data', array());
if (empty($data))
{
$data = $this->getItem();
}
return $data;
}
/**
* Method to get a single record.
*
* @param integer $pk The id of the primary key.
*
* @return mixed Object on success, false on failure.
*
* @since 1.0.0
*/
public function getItem($pk = null)
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('*')
->from($db->quoteName('#__mokojoomtos_content'))
->where($db->quoteName('content_type') . ' = ' . $db->quote('privacy'));
$db->setQuery($query);
return $db->loadObject();
}
/**
* Method to save the form data.
*
* @param array $data The form data.
*
* @return boolean True on success.
*
* @since 1.0.0
*/
public function save($data)
{
$db = $this->getDatabase();
$user = Factory::getUser();
$date = Factory::getDate()->toSql();
$query = $db->getQuery(true)
->select('id')
->from($db->quoteName('#__mokojoomtos_content'))
->where($db->quoteName('content_type') . ' = ' . $db->quote('privacy'));
$db->setQuery($query);
$id = $db->loadResult();
if ($id)
{
$query = $db->getQuery(true)
->update($db->quoteName('#__mokojoomtos_content'))
->set($db->quoteName('title') . ' = ' . $db->quote($data['title']))
->set($db->quoteName('content') . ' = ' . $db->quote($data['content']))
->set($db->quoteName('published') . ' = ' . (int) $data['published'])
->set($db->quoteName('modified') . ' = ' . $db->quote($date))
->set($db->quoteName('modified_by') . ' = ' . (int) $user->id)
->where($db->quoteName('id') . ' = ' . (int) $id);
}
else
{
$query = $db->getQuery(true)
->insert($db->quoteName('#__mokojoomtos_content'))
->columns($db->quoteName(['content_type', 'title', 'content', 'published', 'created', 'created_by']))
->values($db->quote('privacy') . ', ' . $db->quote($data['title']) . ', ' .
$db->quote($data['content']) . ', ' . (int) $data['published'] . ', ' .
$db->quote($date) . ', ' . (int) $user->id);
}
$db->setQuery($query);
return $db->execute();
}
}
-140
View File
@@ -1,140 +0,0 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of MokoJoomTOS.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace MokoConsulting\Component\MokoJoomTOS\Administrator\Model;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Form;
use Joomla\CMS\MVC\Model\AdminModel;
/**
* Model for editing Terms of Service
*
* @since 1.0.0
*/
class TermsModel extends AdminModel
{
/**
* Method to get the record form.
*
* @param array $data Data for the form.
* @param boolean $loadData True if the form is to load its own data (default case), false if not.
*
* @return Form|boolean A Form object on success, false on failure
*
* @since 1.0.0
*/
public function getForm($data = array(), $loadData = true)
{
$form = $this->loadForm(
'com_mokojoomtos.terms',
'terms',
array('control' => 'jform', 'load_data' => $loadData)
);
if (empty($form))
{
return false;
}
return $form;
}
/**
* Method to get the data that should be injected in the form.
*
* @return mixed The data for the form.
*
* @since 1.0.0
*/
protected function loadFormData()
{
$data = Factory::getApplication()->getUserState('com_mokojoomtos.edit.terms.data', array());
if (empty($data))
{
$data = $this->getItem();
}
return $data;
}
/**
* Method to get a single record.
*
* @param integer $pk The id of the primary key.
*
* @return mixed Object on success, false on failure.
*
* @since 1.0.0
*/
public function getItem($pk = null)
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('*')
->from($db->quoteName('#__mokojoomtos_content'))
->where($db->quoteName('content_type') . ' = ' . $db->quote('terms'));
$db->setQuery($query);
return $db->loadObject();
}
/**
* Method to save the form data.
*
* @param array $data The form data.
*
* @return boolean True on success.
*
* @since 1.0.0
*/
public function save($data)
{
$db = $this->getDatabase();
$user = Factory::getUser();
$date = Factory::getDate()->toSql();
$query = $db->getQuery(true)
->select('id')
->from($db->quoteName('#__mokojoomtos_content'))
->where($db->quoteName('content_type') . ' = ' . $db->quote('terms'));
$db->setQuery($query);
$id = $db->loadResult();
if ($id)
{
$query = $db->getQuery(true)
->update($db->quoteName('#__mokojoomtos_content'))
->set($db->quoteName('title') . ' = ' . $db->quote($data['title']))
->set($db->quoteName('content') . ' = ' . $db->quote($data['content']))
->set($db->quoteName('published') . ' = ' . (int) $data['published'])
->set($db->quoteName('modified') . ' = ' . $db->quote($date))
->set($db->quoteName('modified_by') . ' = ' . (int) $user->id)
->where($db->quoteName('id') . ' = ' . (int) $id);
}
else
{
$query = $db->getQuery(true)
->insert($db->quoteName('#__mokojoomtos_content'))
->columns($db->quoteName(['content_type', 'title', 'content', 'published', 'created', 'created_by']))
->values($db->quote('terms') . ', ' . $db->quote($data['title']) . ', ' .
$db->quote($data['content']) . ', ' . (int) $data['published'] . ', ' .
$db->quote($date) . ', ' . (int) $user->id);
}
$db->setQuery($query);
return $db->execute();
}
}
-78
View File
@@ -1,78 +0,0 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of MokoJoomTOS.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace MokoConsulting\Component\MokoJoomTOS\Administrator\View\Privacy;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
/**
* View for editing Privacy Policy
*
* @since 1.0.0
*/
class HtmlView extends BaseHtmlView
{
/**
* The Form object
*
* @var \Joomla\CMS\Form\Form
* @since 1.0.0
*/
protected $form;
/**
* The active item
*
* @var object
* @since 1.0.0
*/
protected $item;
/**
* Display the view
*
* @param string $tpl The name of the template file to parse
*
* @return void
*
* @since 1.0.0
*/
public function display($tpl = null)
{
$this->form = $this->get('Form');
$this->item = $this->get('Item');
$this->addToolbar();
parent::display($tpl);
}
/**
* Add the page title and toolbar.
*
* @return void
*
* @since 1.0.0
*/
protected function addToolbar()
{
Factory::getApplication()->input->set('hidemainmenu', true);
ToolbarHelper::title(Text::_('COM_MOKOJOOMTOS_MANAGER_PRIVACY'), 'lock');
ToolbarHelper::apply('privacy.apply');
ToolbarHelper::save('privacy.save');
ToolbarHelper::cancel('privacy.cancel');
}
}
-78
View File
@@ -1,78 +0,0 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of MokoJoomTOS.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace MokoConsulting\Component\MokoJoomTOS\Administrator\View\Terms;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
use Joomla\CMS\Toolbar\ToolbarHelper;
/**
* View for editing Terms of Service
*
* @since 1.0.0
*/
class HtmlView extends BaseHtmlView
{
/**
* The Form object
*
* @var \Joomla\CMS\Form\Form
* @since 1.0.0
*/
protected $form;
/**
* The active item
*
* @var object
* @since 1.0.0
*/
protected $item;
/**
* Display the view
*
* @param string $tpl The name of the template file to parse
*
* @return void
*
* @since 1.0.0
*/
public function display($tpl = null)
{
$this->form = $this->get('Form');
$this->item = $this->get('Item');
$this->addToolbar();
parent::display($tpl);
}
/**
* Add the page title and toolbar.
*
* @return void
*
* @since 1.0.0
*/
protected function addToolbar()
{
Factory::getApplication()->input->set('hidemainmenu', true);
ToolbarHelper::title(Text::_('COM_MOKOJOOMTOS_MANAGER_TERMS'), 'file-text');
ToolbarHelper::apply('terms.apply');
ToolbarHelper::save('terms.save');
ToolbarHelper::cancel('terms.cancel');
}
}
-59
View File
@@ -1,59 +0,0 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of MokoJoomTOS.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Toolbar\Toolbar;
use Joomla\CMS\Factory;
$wa = $this->document->getWebAssetManager();
$wa->useScript('keepalive');
$toolbar = Toolbar::getInstance('toolbar');
?>
<div class="main-card">
<div class="card-body">
<h2><?php echo Text::_('COM_MOKOJOOMTOS_MANAGER_PRIVACY'); ?></h2>
<?php if ($this->item && $this->item->content): ?>
<div class="alert alert-info">
<strong><?php echo Text::_('COM_MOKOJOOMTOS_FIELD_TITLE_LABEL'); ?>:</strong> <?php echo htmlspecialchars($this->item->title); ?>
</div>
<div class="alert alert-info">
<strong><?php echo Text::_('COM_MOKOJOOMTOS_FIELD_PUBLISHED_LABEL'); ?>:</strong>
<?php echo $this->item->published ? Text::_('JPUBLISHED') : Text::_('JUNPUBLISHED'); ?>
</div>
<?php if ($this->item->modified && $this->item->modified != '0000-00-00 00:00:00'): ?>
<div class="alert alert-info">
<strong><?php echo Text::_('COM_MOKOJOOMTOS_LAST_UPDATED'); ?>:</strong>
<?php echo HTMLHelper::_('date', $this->item->modified, 'DATE_FORMAT_LC3'); ?>
</div>
<?php endif; ?>
<div class="mt-3">
<a href="<?php echo Route::_('index.php?option=com_mokojoomtos&task=privacy.edit&id=' . $this->item->id); ?>"
class="btn btn-primary">
<?php echo Text::_('JACTION_EDIT'); ?>
</a>
</div>
<?php else: ?>
<div class="alert alert-warning">
<?php echo Text::_('COM_MOKOJOOMTOS_NO_CONTENT'); ?>
</div>
<div class="mt-3">
<a href="<?php echo Route::_('index.php?option=com_mokojoomtos&task=privacy.edit'); ?>"
class="btn btn-primary">
<?php echo Text::_('JACTION_CREATE'); ?>
</a>
</div>
<?php endif; ?>
</div>
</div>
-41
View File
@@ -1,41 +0,0 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of MokoJoomTOS.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
use Joomla\CMS\HTML\HTMLHelper;
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $this->document->getWebAssetManager();
$wa->useScript('keepalive')
->useScript('form.validate');
?>
<form action="<?php echo Route::_('index.php?option=com_mokojoomtos&view=privacy'); ?>" method="post" name="adminForm" id="privacy-form" class="form-validate">
<div class="main-card">
<div class="row">
<div class="col-lg-9">
<?php echo $this->form->renderField('title'); ?>
<?php echo $this->form->renderField('content'); ?>
</div>
<div class="col-lg-3">
<div class="card card-light">
<div class="card-body">
<?php echo $this->form->renderField('published'); ?>
</div>
</div>
</div>
</div>
</div>
<input type="hidden" name="task" value="" />
<?php echo HTMLHelper::_('form.token'); ?>
</form>
-59
View File
@@ -1,59 +0,0 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of MokoJoomTOS.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
defined('_JEXEC') or die;
use Joomla\CMS\HTML\HTMLHelper;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
use Joomla\CMS\Toolbar\Toolbar;
use Joomla\CMS\Factory;
$wa = $this->document->getWebAssetManager();
$wa->useScript('keepalive');
$toolbar = Toolbar::getInstance('toolbar');
?>
<div class="main-card">
<div class="card-body">
<h2><?php echo Text::_('COM_MOKOJOOMTOS_MANAGER_TERMS'); ?></h2>
<?php if ($this->item && $this->item->content): ?>
<div class="alert alert-info">
<strong><?php echo Text::_('COM_MOKOJOOMTOS_FIELD_TITLE_LABEL'); ?>:</strong> <?php echo htmlspecialchars($this->item->title); ?>
</div>
<div class="alert alert-info">
<strong><?php echo Text::_('COM_MOKOJOOMTOS_FIELD_PUBLISHED_LABEL'); ?>:</strong>
<?php echo $this->item->published ? Text::_('JPUBLISHED') : Text::_('JUNPUBLISHED'); ?>
</div>
<?php if ($this->item->modified && $this->item->modified != '0000-00-00 00:00:00'): ?>
<div class="alert alert-info">
<strong><?php echo Text::_('COM_MOKOJOOMTOS_LAST_UPDATED'); ?>:</strong>
<?php echo HTMLHelper::_('date', $this->item->modified, 'DATE_FORMAT_LC3'); ?>
</div>
<?php endif; ?>
<div class="mt-3">
<a href="<?php echo Route::_('index.php?option=com_mokojoomtos&task=terms.edit&id=' . $this->item->id); ?>"
class="btn btn-primary">
<?php echo Text::_('JACTION_EDIT'); ?>
</a>
</div>
<?php else: ?>
<div class="alert alert-warning">
<?php echo Text::_('COM_MOKOJOOMTOS_NO_CONTENT'); ?>
</div>
<div class="mt-3">
<a href="<?php echo Route::_('index.php?option=com_mokojoomtos&task=terms.edit'); ?>"
class="btn btn-primary">
<?php echo Text::_('JACTION_CREATE'); ?>
</a>
</div>
<?php endif; ?>
</div>
</div>
-41
View File
@@ -1,41 +0,0 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of MokoJoomTOS.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
defined('_JEXEC') or die;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Router\Route;
use Joomla\CMS\HTML\HTMLHelper;
/** @var Joomla\CMS\WebAsset\WebAssetManager $wa */
$wa = $this->document->getWebAssetManager();
$wa->useScript('keepalive')
->useScript('form.validate');
?>
<form action="<?php echo Route::_('index.php?option=com_mokojoomtos&view=terms'); ?>" method="post" name="adminForm" id="terms-form" class="form-validate">
<div class="main-card">
<div class="row">
<div class="col-lg-9">
<?php echo $this->form->renderField('title'); ?>
<?php echo $this->form->renderField('content'); ?>
</div>
<div class="col-lg-3">
<div class="card card-light">
<div class="card-body">
<?php echo $this->form->renderField('published'); ?>
</div>
</div>
</div>
</div>
</div>
<input type="hidden" name="task" value="" />
<?php echo HTMLHelper::_('form.token'); ?>
</form>
@@ -0,0 +1,23 @@
; MokoJoomTOS Plugin - Administrator
; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License GNU General Public License version 3 or later; see LICENSE
; Note: All ini files need to be saved as UTF-8
PLG_SYSTEM_MOKOJOOMTOS="System - MokoJoomTOS"
PLG_SYSTEM_MOKOJOOMTOS_XML_DESCRIPTION="Allows Terms of Service to be accessible via menu slug when the site is in offline mode. Simply configure the menu slug (e.g., 'terms-of-service') and that page will remain accessible even when the site is offline."
; Configuration
PLG_SYSTEM_MOKOJOOMTOS_FIELDSET_BASIC="Basic Settings"
PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_LABEL="Terms of Service Menu Slug"
PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_DESC="Enter the menu slug for your Terms of Service page (e.g., 'terms-of-service'). This page will be accessible even when the site is offline. The slug must match the menu item alias exactly."
; Help
PLG_SYSTEM_MOKOJOOMTOS_HELP_LABEL="How to Use This Plugin"
PLG_SYSTEM_MOKOJOOMTOS_HELP_DESC="<strong>Step 1:</strong> Create a Joomla article for your Terms of Service.<br/><strong>Step 2:</strong> Create a menu item pointing to that article.<br/><strong>Step 3:</strong> Set the menu item alias/slug (e.g., 'terms-of-service').<br/><strong>Step 4:</strong> Enter that same slug above.<br/><strong>Step 5:</strong> When your site goes offline, visitors can still access your-site.com/terms-of-service"
; Installation messages
PLG_SYSTEM_MOKOJOOMTOS_INSTALL_SUCCESS="MokoJoomTOS Plugin installed successfully!"
PLG_SYSTEM_MOKOJOOMTOS_UPDATE_SUCCESS="MokoJoomTOS Plugin updated successfully!"
PLG_SYSTEM_MOKOJOOMTOS_UNINSTALL_SUCCESS="MokoJoomTOS Plugin uninstalled successfully."
PLG_SYSTEM_MOKOJOOMTOS_POSTINSTALL_TITLE="MokoJoomTOS Plugin - Enterprise Setup Complete"
PLG_SYSTEM_MOKOJOOMTOS_POSTINSTALL_DESC="Your Terms of Service page has been automatically created and configured. The plugin is now active and your TOS will remain accessible at <strong>/terms-of-service</strong> even when the site is in offline mode."
@@ -0,0 +1,7 @@
; MokoJoomTOS Plugin - System (Administrator)
; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License GNU General Public License version 3 or later; see LICENSE
; Note: All ini files need to be saved as UTF-8
PLG_SYSTEM_MOKOJOOMTOS="System - MokoJoomTOS"
PLG_SYSTEM_MOKOJOOMTOS_XML_DESCRIPTION="Allows Terms of Service to be accessible via menu slug when site is offline"
@@ -0,0 +1,23 @@
; MokoJoomTOS Plugin - Administrator
; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License GNU General Public License version 3 or later; see LICENSE
; Note: All ini files need to be saved as UTF-8
PLG_SYSTEM_MOKOJOOMTOS="System - MokoJoomTOS"
PLG_SYSTEM_MOKOJOOMTOS_XML_DESCRIPTION="Allows Terms of Service to be accessible via menu slug when the site is in offline mode. Simply configure the menu slug (e.g., 'terms-of-service') and that page will remain accessible even when the site is offline."
; Configuration
PLG_SYSTEM_MOKOJOOMTOS_FIELDSET_BASIC="Basic Settings"
PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_LABEL="Terms of Service Menu Slug"
PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_DESC="Enter the menu slug for your Terms of Service page (e.g., 'terms-of-service'). This page will be accessible even when the site is offline. The slug must match the menu item alias exactly."
; Help
PLG_SYSTEM_MOKOJOOMTOS_HELP_LABEL="How to Use This Plugin"
PLG_SYSTEM_MOKOJOOMTOS_HELP_DESC="<strong>Step 1:</strong> Create a Joomla article for your Terms of Service.<br/><strong>Step 2:</strong> Create a menu item pointing to that article.<br/><strong>Step 3:</strong> Set the menu item alias/slug (e.g., 'terms-of-service').<br/><strong>Step 4:</strong> Enter that same slug above.<br/><strong>Step 5:</strong> When your site goes offline, visitors can still access your-site.com/terms-of-service"
; Installation messages
PLG_SYSTEM_MOKOJOOMTOS_INSTALL_SUCCESS="MokoJoomTOS Plugin installed successfully!"
PLG_SYSTEM_MOKOJOOMTOS_UPDATE_SUCCESS="MokoJoomTOS Plugin updated successfully!"
PLG_SYSTEM_MOKOJOOMTOS_UNINSTALL_SUCCESS="MokoJoomTOS Plugin uninstalled successfully."
PLG_SYSTEM_MOKOJOOMTOS_POSTINSTALL_TITLE="MokoJoomTOS Plugin - Enterprise Setup Complete"
PLG_SYSTEM_MOKOJOOMTOS_POSTINSTALL_DESC="Your Terms of Service page has been automatically created and configured. The plugin is now active and your TOS will remain accessible at <strong>/terms-of-service</strong> even when the site is in offline mode."
@@ -0,0 +1,7 @@
; MokoJoomTOS Plugin - System (Administrator)
; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License GNU General Public License version 3 or later; see LICENSE
; Note: All ini files need to be saved as UTF-8
PLG_SYSTEM_MOKOJOOMTOS="System - MokoJoomTOS"
PLG_SYSTEM_MOKOJOOMTOS_XML_DESCRIPTION="Allows Terms of Service to be accessible via menu slug when site is offline"
-71
View File
@@ -1,71 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
This file is part of MokoJoomTOS.
SPDX-License-Identifier: GPL-3.0-or-later
-->
<extension type="component" method="upgrade">
<name>COM_MOKOJOOMTOS</name>
<author>Moko Consulting</author>
<creationDate>2026-02-23</creationDate>
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
<license>GNU General Public License version 3 or later; see LICENSE</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>1.0.0</version>
<description>COM_MOKOJOOMTOS_DESCRIPTION</description>
<namespace path="src">MokoConsulting\Component\MokoJoomTOS</namespace>
<scriptfile>script.php</scriptfile>
<!-- Administrator files -->
<administration>
<menu link="option=com_mokojoomtos">COM_MOKOJOOMTOS</menu>
<submenu>
<menu link="option=com_mokojoomtos&amp;view=terms" view="terms">COM_MOKOJOOMTOS_MENU_TERMS</menu>
<menu link="option=com_mokojoomtos&amp;view=privacy" view="privacy">COM_MOKOJOOMTOS_MENU_PRIVACY</menu>
</submenu>
<files folder="admin">
<folder>forms</folder>
<folder>language</folder>
<folder>services</folder>
<folder>sql</folder>
<folder>src</folder>
<folder>tmpl</folder>
</files>
<!-- SQL installation -->
<sql>
<file driver="mysql" charset="utf8">sql/install/mysql.sql</file>
</sql>
<!-- SQL uninstallation -->
<uninstall>
<sql>
<file driver="mysql" charset="utf8">sql/uninstall/mysql.sql</file>
</sql>
</uninstall>
</administration>
<!-- Site files -->
<site>
<files folder="site">
<folder>language</folder>
<folder>src</folder>
<folder>tmpl</folder>
</files>
</site>
<!-- Media files -->
<media folder="media" destination="com_mokojoomtos">
<folder>css</folder>
<folder>js</folder>
</media>
<!-- Update servers -->
<updateservers>
<server type="extension" name="MokoJoomTOS">https://mokoconsulting.tech/updates/com_mokojoomtos.xml</server>
</updateservers>
</extension>
-16
View File
@@ -1,16 +0,0 @@
# Docs Index: /templates/repos/joomla/component/src
## Purpose
This index provides navigation to documentation within this folder.
## Metadata
- **Document Type:** index
- **Auto-generated:** This file is automatically generated by rebuild_indexes.py
## Revision History
| Change | Notes | Author |
| --- | --- | --- |
| Automated update | Generated by documentation index automation | rebuild_indexes.py |
@@ -0,0 +1,23 @@
; MokoJoomTOS Plugin
; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License GNU General Public License version 3 or later; see LICENSE
; Note: All ini files need to be saved as UTF-8
PLG_SYSTEM_MOKOJOOMTOS="System - MokoJoomTOS"
PLG_SYSTEM_MOKOJOOMTOS_XML_DESCRIPTION="Allows Terms of Service to be accessible via menu slug when the site is in offline mode. Simply configure the menu slug (e.g., 'terms-of-service') and that page will remain accessible even when the site is offline."
; Configuration
PLG_SYSTEM_MOKOJOOMTOS_FIELDSET_BASIC="Basic Settings"
PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_LABEL="Terms of Service Menu Slug"
PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_DESC="Enter the menu slug for your Terms of Service page (e.g., 'terms-of-service'). This page will be accessible even when the site is offline. The slug must match the menu item alias exactly."
; Help
PLG_SYSTEM_MOKOJOOMTOS_HELP_LABEL="How to Use This Plugin"
PLG_SYSTEM_MOKOJOOMTOS_HELP_DESC="<strong>Step 1:</strong> Create a Joomla article for your Terms of Service.<br/><strong>Step 2:</strong> Create a menu item pointing to that article.<br/><strong>Step 3:</strong> Set the menu item alias/slug (e.g., 'terms-of-service').<br/><strong>Step 4:</strong> Enter that same slug above.<br/><strong>Step 5:</strong> When your site goes offline, visitors can still access yoursite.com/terms-of-service"
; Installation messages
PLG_SYSTEM_MOKOJOOMTOS_INSTALL_SUCCESS="MokoJoomTOS Plugin installed successfully!"
PLG_SYSTEM_MOKOJOOMTOS_UPDATE_SUCCESS="MokoJoomTOS Plugin updated successfully!"
PLG_SYSTEM_MOKOJOOMTOS_UNINSTALL_SUCCESS="MokoJoomTOS Plugin uninstalled successfully."
PLG_SYSTEM_MOKOJOOMTOS_POSTINSTALL_TITLE="MokoJoomTOS Plugin - Enterprise Setup Complete"
PLG_SYSTEM_MOKOJOOMTOS_POSTINSTALL_DESC="Your Terms of Service page has been automatically created and configured. The plugin is now active and your TOS will remain accessible at <strong>/terms-of-service</strong> even when the site is in offline mode."
@@ -0,0 +1,23 @@
; MokoJoomTOS Plugin
; Copyright (C) 2026 Moko Consulting. All rights reserved.
; License GNU General Public License version 3 or later; see LICENSE
; Note: All ini files need to be saved as UTF-8
PLG_SYSTEM_MOKOJOOMTOS="System - MokoJoomTOS"
PLG_SYSTEM_MOKOJOOMTOS_XML_DESCRIPTION="Allows Terms of Service to be accessible via menu slug when the site is in offline mode. Simply configure the menu slug (e.g., 'terms-of-service') and that page will remain accessible even when the site is offline."
; Configuration
PLG_SYSTEM_MOKOJOOMTOS_FIELDSET_BASIC="Basic Settings"
PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_LABEL="Terms of Service Menu Slug"
PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_DESC="Enter the menu slug for your Terms of Service page (e.g., 'terms-of-service'). This page will be accessible even when the site is offline. The slug must match the menu item alias exactly."
; Help
PLG_SYSTEM_MOKOJOOMTOS_HELP_LABEL="How to Use This Plugin"
PLG_SYSTEM_MOKOJOOMTOS_HELP_DESC="<strong>Step 1:</strong> Create a Joomla article for your Terms of Service.<br/><strong>Step 2:</strong> Create a menu item pointing to that article.<br/><strong>Step 3:</strong> Set the menu item alias/slug (e.g., 'terms-of-service').<br/><strong>Step 4:</strong> Enter that same slug above.<br/><strong>Step 5:</strong> When your site goes offline, visitors can still access yoursite.com/terms-of-service"
; Installation messages
PLG_SYSTEM_MOKOJOOMTOS_INSTALL_SUCCESS="MokoJoomTOS Plugin installed successfully!"
PLG_SYSTEM_MOKOJOOMTOS_UPDATE_SUCCESS="MokoJoomTOS Plugin updated successfully!"
PLG_SYSTEM_MOKOJOOMTOS_UNINSTALL_SUCCESS="MokoJoomTOS Plugin uninstalled successfully."
PLG_SYSTEM_MOKOJOOMTOS_POSTINSTALL_TITLE="MokoJoomTOS Plugin - Enterprise Setup Complete"
PLG_SYSTEM_MOKOJOOMTOS_POSTINSTALL_DESC="Your Terms of Service page has been automatically created and configured. The plugin is now active and your TOS will remain accessible at <strong>/terms-of-service</strong> even when the site is in offline mode."
-74
View File
@@ -1,74 +0,0 @@
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of MokoJoomTOS.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
.mokojoomtos {
max-width: 900px;
margin: 0 auto;
padding: 20px;
}
.mokojoomtos .page-header {
margin-bottom: 30px;
padding-bottom: 10px;
border-bottom: 2px solid #ddd;
}
.mokojoomtos .page-header h1 {
margin: 0;
font-size: 2.5em;
color: #333;
}
.mokojoomtos .content {
line-height: 1.8;
font-size: 1.1em;
}
.mokojoomtos .content h2 {
margin-top: 30px;
margin-bottom: 15px;
color: #444;
}
.mokojoomtos .content h3 {
margin-top: 20px;
margin-bottom: 10px;
color: #555;
}
.mokojoomtos .content p {
margin-bottom: 15px;
}
.mokojoomtos .content ul,
.mokojoomtos .content ol {
margin-bottom: 15px;
padding-left: 30px;
}
.mokojoomtos .last-updated {
margin-top: 40px;
padding-top: 20px;
border-top: 1px solid #eee;
color: #666;
font-size: 0.9em;
}
@media (max-width: 768px) {
.mokojoomtos {
padding: 15px;
}
.mokojoomtos .page-header h1 {
font-size: 2em;
}
.mokojoomtos .content {
font-size: 1em;
}
}
+95
View File
@@ -0,0 +1,95 @@
<?php
/**
* @package MokoJoomTOS
* @subpackage plg_system_mokojoomtos
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Uri\Uri;
/**
* MokoJoomTOS Offline Mode Bypass Plugin
*
* Allows Terms of Service menu to be accessible via slug when the site
* is in offline mode.
*
* @since 1.0.0
*/
class PlgSystemMokojoomtos extends CMSPlugin
{
/**
* Load the language file on instantiation.
*
* @var boolean
* @since 1.0.0
*/
protected $autoloadLanguage = true;
/**
* Application object
*
* @var \Joomla\CMS\Application\CMSApplication
* @since 1.0.0
*/
protected $app;
/**
* After initialise event handler
*
* Checks if the current request is for the Terms of Service slug and if
* the site is in offline mode. If both conditions are met, temporarily
* disables offline mode for this request only.
*
* @return void
*
* @since 1.0.0
*/
public function onAfterInitialise()
{
copilot-pull-request-reviewer[bot] commented 2026-03-04 05:18:44 +00:00 (Migrated from github.com)
Review

The plugin entrypoint class here only implements onAfterInitialise() and never sets tmpl=component. Since Joomla will instantiate PlgSystemMokojoomtos from mokojoomtos.php, the SubscriberInterface implementation in src/Extension/MokoJoomTOS.php (which correctly uses onAfterRoute) won’t run, and the template-chrome regression this PR claims to fix will persist. Consider moving the offline-bypass logic into the instantiated plugin class (or making it implement SubscriberInterface + subscribing to onAfterRoute) and ensure tmpl=component is applied for matched slugs.

The plugin entrypoint class here only implements `onAfterInitialise()` and never sets `tmpl=component`. Since Joomla will instantiate `PlgSystemMokojoomtos` from `mokojoomtos.php`, the `SubscriberInterface` implementation in `src/Extension/MokoJoomTOS.php` (which correctly uses `onAfterRoute`) won’t run, and the template-chrome regression this PR claims to fix will persist. Consider moving the offline-bypass logic into the instantiated plugin class (or making it implement `SubscriberInterface` + subscribing to `onAfterRoute`) and ensure `tmpl=component` is applied for matched slugs.
// Only process for site application
if (!$this->app->isClient('site'))
{
return;
}
// Get the global configuration
$config = $this->app->getConfig();
// Only proceed if site is offline
if (!$config->get('offline'))
{
return;
}
// Get the configured Terms of Service slug
$tosSlug = trim($this->params->get('tos_slug', 'terms-of-service'));
if (empty($tosSlug))
{
return;
}
// Get the current URI path
$uri = Uri::getInstance();
$path = trim($uri->getPath(), '/');
// Remove the base path if present
$base = trim(Uri::base(true), '/');
if (!empty($base) && strpos($path, $base) === 0)
{
$path = trim(substr($path, strlen($base)), '/');
}
// Check if the path matches the Terms of Service slug
if ($path === $tosSlug || strpos($path, $tosSlug . '/') === 0)
{
// Temporarily disable offline mode for this request
$config->set('offline', 0);
}
}
}
+89
View File
@@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- =========================================================================
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
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
=========================================================================
FILE INFORMATION
DEFGROUP: MokoJoomTOS
INGROUP: plg_system_mokojoomtos
PATH: src/mokojoomtos.xml
VERSION: 03.08.04
BRIEF: Plugin manifest XML file for MokoJoomTOS system plugin
=========================================================================
-->
<extension type="plugin" group="system" method="upgrade">
<name>plg_system_mokojoomtos</name>
<author>Moko Consulting</author>
<creationDate>2026-01-01</creationDate>
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
<license>GNU General Public License version 3 or later; see LICENSE</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>03.08.04</version>
<description>PLG_SYSTEM_MOKOJOOMTOS_XML_DESCRIPTION</description>
<namespace path="src">Joomla\Plugin\System\MokoJoomTOS</namespace>
<scriptfile>script.php</scriptfile>
<files>
<filename plugin="mokojoomtos">mokojoomtos.php</filename>
<folder>src</folder>
<folder>language</folder>
<folder>administrator</folder>
</files>
<languages>
<language tag="en-GB">language/en-GB/plg_system_mokojoomtos.ini</language>
<language tag="en-US">language/en-US/plg_system_mokojoomtos.ini</language>
<language tag="en-GB" folder="administrator">administrator/language/en-GB/plg_system_mokojoomtos.ini</language>
<language tag="en-GB" folder="administrator">administrator/language/en-GB/plg_system_mokojoomtos.sys.ini</language>
<language tag="en-US" folder="administrator">administrator/language/en-US/plg_system_mokojoomtos.ini</language>
<language tag="en-US" folder="administrator">administrator/language/en-US/plg_system_mokojoomtos.sys.ini</language>
</languages>
<config>
<fields name="params" addfieldprefix="Joomla\Plugin\System\MokoJoomTOS\Field">
<fieldset name="basic" label="PLG_SYSTEM_MOKOJOOMTOS_FIELDSET_BASIC">
<field
name="tos_slug"
type="menuslug"
label="PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_LABEL"
description="PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_DESC"
default="terms-of-service"
required="true"
/>
<field
name="help_spacer"
type="spacer"
label="PLG_SYSTEM_MOKOJOOMTOS_HELP_LABEL"
description="PLG_SYSTEM_MOKOJOOMTOS_HELP_DESC"
class="alert alert-info"
/>
</fieldset>
</fields>
</config>
<updateservers>
<server type="extension" name="MokoJoomTOS Plugin">https://raw.githubusercontent.com/mokoconsulting-tech/MokoJoomTOS/main/update.xml</server>
</updateservers>
</extension>
@@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<extension type="plugin" group="system" method="upgrade">
<name>plg_system_mokojoomtos</name>
<author>Moko Consulting</author>
<creationDate>2026-01-01</creationDate>
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
<license>GNU General Public License version 3 or later; see LICENSE</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>03.08.04</version>
<description>PLG_SYSTEM_MOKOJOOMTOS_XML_DESCRIPTION</description>
<namespace path="src">Joomla\Plugin\System\MokoJoomTOS</namespace>
<scriptfile>script.php</scriptfile>
<files>
<filename plugin="mokojoomtos">mokojoomtos.php</filename>
<folder>src</folder>
<folder>language</folder>
<folder>administrator</folder>
</files>
<languages>
<language tag="en-GB">language/en-GB/plg_system_mokojoomtos.ini</language>
<language tag="en-GB">language/en-GB/plg_system_mokojoomtos.sys.ini</language>
<language tag="en-US">language/en-US/plg_system_mokojoomtos.ini</language>
<language tag="en-US">language/en-US/plg_system_mokojoomtos.sys.ini</language>
copilot-pull-request-reviewer[bot] commented 2026-03-04 05:18:44 +00:00 (Migrated from github.com)
Review

This manifest references language/en-GB/plg_system_mokojoomtos.sys.ini and language/en-US/plg_system_mokojoomtos.sys.ini, but those files don’t exist in src/language/ (only the non-sys .ini files are present). If this manifest is used for packaging/installation, Joomla will fail to install the missing language files. Either add the missing .sys.ini files under src/language/... or remove these <language> entries (and consider deleting this duplicate manifest if src/mokojoomtos.xml is the canonical one).

This manifest references `language/en-GB/plg_system_mokojoomtos.sys.ini` and `language/en-US/plg_system_mokojoomtos.sys.ini`, but those files don’t exist in `src/language/` (only the non-sys `.ini` files are present). If this manifest is used for packaging/installation, Joomla will fail to install the missing language files. Either add the missing `.sys.ini` files under `src/language/...` or remove these `<language>` entries (and consider deleting this duplicate manifest if `src/mokojoomtos.xml` is the canonical one).
<language tag="en-GB" folder="administrator">administrator/language/en-GB/plg_system_mokojoomtos.ini</language>
<language tag="en-GB" folder="administrator">administrator/language/en-GB/plg_system_mokojoomtos.sys.ini</language>
<language tag="en-US" folder="administrator">administrator/language/en-US/plg_system_mokojoomtos.ini</language>
<language tag="en-US" folder="administrator">administrator/language/en-US/plg_system_mokojoomtos.sys.ini</language>
</languages>
<config>
<fields name="params" addfieldprefix="Joomla\Plugin\System\MokoJoomTOS\Field">
<fieldset name="basic" label="PLG_SYSTEM_MOKOJOOMTOS_FIELDSET_BASIC">
<field
name="tos_slug"
type="menuslug"
label="PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_LABEL"
description="PLG_SYSTEM_MOKOJOOMTOS_FIELD_TOS_SLUG_DESC"
default="terms-of-service"
required="true"
/>
<field
name="help_spacer"
type="spacer"
label="PLG_SYSTEM_MOKOJOOMTOS_HELP_LABEL"
description="PLG_SYSTEM_MOKOJOOMTOS_HELP_DESC"
class="alert alert-info"
/>
</fieldset>
</fields>
</config>
<updateservers>
<server type="extension" name="MokoJoomTOS Plugin">https://raw.githubusercontent.com/mokoconsulting-tech/MokoJoomTOS/main/update.xml</server>
</updateservers>
</extension>
@@ -1,5 +0,0 @@
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
; SPDX-License-Identifier: GPL-3.0-or-later
PLG_SYSTEM_MOKOJOOMTOSACCESS="System - MokoJoomTOS Offline Access"
PLG_SYSTEM_MOKOJOOMTOSACCESS_DESCRIPTION="Allows access to Terms of Service and Privacy Policy pages even when the site is in offline mode"
@@ -1,5 +0,0 @@
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
; SPDX-License-Identifier: GPL-3.0-or-later
PLG_SYSTEM_MOKOJOOMTOSACCESS="System - MokoJoomTOS Offline Access"
PLG_SYSTEM_MOKOJOOMTOSACCESS_DESCRIPTION="Allows access to Terms of Service and Privacy Policy pages even when the site is in offline mode"
@@ -1,55 +0,0 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of MokoJoomTOS.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
defined('_JEXEC') or die;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Factory;
/**
* Plugin to allow access to MokoJoomTOS pages even when site is offline
*
* @since 1.0.0
*/
class PlgSystemMokojoomtosaccess extends CMSPlugin
{
/**
* Application object
*
* @var \Joomla\CMS\Application\CMSApplication
* @since 1.0.0
*/
protected $app;
/**
* Runs on application initialization
*
* @return void
*
* @since 1.0.0
*/
public function onAfterInitialise()
{
// Only run on site application
if (!$this->app->isClient('site'))
{
return;
}
$option = $this->app->input->get('option', '');
// If accessing MokoJoomTOS component, bypass offline check
if ($option === 'com_mokojoomtos')
{
// Temporarily set the site to online for this request
$config = Factory::getConfig();
$config->set('offline', '0');
}
}
}
@@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
This file is part of MokoJoomTOS.
SPDX-License-Identifier: GPL-3.0-or-later
-->
<extension type="plugin" group="system" method="upgrade">
<name>PLG_SYSTEM_MOKOJOOMTOSACCESS</name>
<author>Moko Consulting</author>
<creationDate>2026-02-23</creationDate>
<copyright>Copyright (C) 2026 Moko Consulting. All rights reserved.</copyright>
<license>GNU General Public License version 3 or later; see LICENSE</license>
<authorEmail>hello@mokoconsulting.tech</authorEmail>
<authorUrl>https://mokoconsulting.tech</authorUrl>
<version>1.0.0</version>
<description>PLG_SYSTEM_MOKOJOOMTOSACCESS_DESCRIPTION</description>
<files>
<filename plugin="mokojoomtosaccess">mokojoomtosaccess.php</filename>
</files>
<languages>
<language tag="en-GB">language/en-GB/plg_system_mokojoomtosaccess.ini</language>
<language tag="en-GB">language/en-GB/plg_system_mokojoomtosaccess.sys.ini</language>
</languages>
</extension>
+352 -165
View File
@@ -1,194 +1,381 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of MokoJoomTOS.
*
* SPDX-License-Identifier: GPL-3.0-or-later
* @package MokoJoomTOS
* @subpackage plg_system_mokojoomtos_offline
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Installer\InstallerAdapter;
use Joomla\CMS\Installer\InstallerScriptInterface;
use Joomla\CMS\Installer\InstallerScript;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Log\Log;
use Joomla\CMS\Table\Table;
/**
* Installation script for MokoJoomTOS component
* Installation script for MokoJoomTOS Offline Plugin
*
* @since 1.0.0
*/
return new class implements InstallerScriptInterface
class PlgSystemMokojoomtosOfflineInstallerScript extends InstallerScript
{
/**
* Minimum Joomla version required for this extension
*
* @var string
* @since 1.0.0
*/
private $minimumJoomla = '4.0';
/**
* Minimum Joomla version required to install the plugin
*
* @var string
* @since 1.0.0
*/
protected $minimumJoomla = '4.0.0';
/**
* Minimum PHP version required for this extension
*
* @var string
* @since 1.0.0
*/
private $minimumPhp = '7.4';
/**
* Minimum PHP version required to install the plugin
*
* @var string
* @since 1.0.0
*/
protected $minimumPhp = '7.4.0';
/**
* Function called before extension installation/update/removal
*
* @param string $type Type of change (install, update or discover_install)
* @param InstallerAdapter $parent Installer adapter
*
* @return boolean True on success
*
* @since 1.0.0
*/
public function preflight($type, $parent): bool
{
// Check minimum Joomla version
if (version_compare(JVERSION, $this->minimumJoomla, '<'))
{
Factory::getApplication()->enqueueMessage(
sprintf('MokoJoomTOS requires Joomla %s or later', $this->minimumJoomla),
'error'
);
return false;
}
/**
* Extension type (used by parent class)
*
* @var string
* @since 1.0.0
*/
protected $extension = 'plg_system_mokojoomtos_offline';
// Check minimum PHP version
if (version_compare(PHP_VERSION, $this->minimumPhp, '<'))
{
Factory::getApplication()->enqueueMessage(
sprintf('MokoJoomTOS requires PHP %s or later', $this->minimumPhp),
'error'
);
return false;
}
/**
* Function called before plugin installation/update/uninstall
*
* @param string $type Installation type (install, update, discover_install)
* @param InstallerAdapter $parent Parent installer adapter
*
* @return boolean True on success
*
* @since 1.0.0
*/
public function preflight($type, $parent)
{
// Check minimum requirements
if (!parent::preflight($type, $parent)) {
return false;
}
return true;
}
return true;
}
/**
* Function called after extension installation
*
* @param InstallerAdapter $parent Installer adapter
*
* @return void
*
* @since 1.0.0
*/
public function install($parent): void
{
$this->installPlugin();
Factory::getApplication()->enqueueMessage(
'MokoJoomTOS component installed successfully!',
'success'
);
}
/**
* Function called after plugin installation
*
* @param InstallerAdapter $parent Parent installer adapter
*
* @return void
*
* @since 1.0.0
*/
public function install($parent)
{
echo '<p>' . Text::_('PLG_SYSTEM_MOKOJOOMTOS_OFFLINE_INSTALL_SUCCESS') . '</p>';
}
/**
* Function called after extension update
*
* @param InstallerAdapter $parent Installer adapter
*
* @return void
*
* @since 1.0.0
*/
public function update($parent): void
{
Factory::getApplication()->enqueueMessage(
'MokoJoomTOS component updated successfully!',
'success'
);
}
/**
* Function called after plugin update
*
* @param InstallerAdapter $parent Parent installer adapter
*
* @return void
*
* @since 1.0.0
*/
public function update($parent)
{
echo '<p>' . Text::_('PLG_SYSTEM_MOKOJOOMTOS_OFFLINE_UPDATE_SUCCESS') . '</p>';
}
/**
* Function called after extension uninstallation
*
* @param InstallerAdapter $parent Installer adapter
*
* @return void
*
* @since 1.0.0
*/
public function uninstall($parent): void
{
Factory::getApplication()->enqueueMessage(
'MokoJoomTOS component uninstalled successfully.',
'success'
);
}
/**
* Function called after plugin uninstallation
*
* @param InstallerAdapter $parent Parent installer adapter
*
* @return void
*
* @since 1.0.0
*/
public function uninstall($parent)
{
echo '<p>' . Text::_('PLG_SYSTEM_MOKOJOOMTOS_OFFLINE_UNINSTALL_SUCCESS') . '</p>';
}
/**
* Function called after extension installation/update
*
* @param InstallerAdapter $parent Installer adapter
*
* @return void
*
* @since 1.0.0
*/
public function postflight($type, $parent): void
{
if ($type === 'install')
{
$this->displayPostInstallationMessage();
}
}
/**
* Function called after extension installation/update/discover_install
*
* @param string $type Installation type (install, update, discover_install)
* @param InstallerAdapter $parent Parent installer adapter
*
* @return void
*
* @since 1.0.0
*/
public function postflight($type, $parent)
{
if ($type === 'install' || $type === 'discover_install') {
// Create Terms of Service article and menu item
$this->createTermsOfServiceSetup();
echo '<div class="alert alert-success">';
echo '<h4>' . Text::_('PLG_SYSTEM_MOKOJOOMTOS_POSTINSTALL_TITLE') . '</h4>';
echo '<p>' . Text::_('PLG_SYSTEM_MOKOJOOMTOS_POSTINSTALL_DESC') . '</p>';
echo '</div>';
}
}
/**
* Install and enable the offline access plugin
*
* @return void
*
* @since 1.0.0
*/
private function installPlugin(): void
{
$db = Factory::getDbo();
$query = $db->getQuery(true)
->update($db->quoteName('#__extensions'))
->set($db->quoteName('enabled') . ' = 1')
->where($db->quoteName('element') . ' = ' . $db->quote('mokojoomtosaccess'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system'));
/**
* Create Terms of Service article and menu item
*
* @return void
*
* @since 1.0.0
*/
private function createTermsOfServiceSetup()
{
try {
$db = Factory::getDbo();
// Check if Terms of Service article already exists
$query = $db->getQuery(true)
->select('id')
->from($db->quoteName('#__content'))
->where($db->quoteName('alias') . ' = ' . $db->quote('terms-of-service'));
$db->setQuery($query);
$articleId = $db->loadResult();
// Create article if it doesn't exist
if (!$articleId) {
$articleId = $this->createTermsArticle();
}
if ($articleId) {
// Check if menu item already exists
$query = $db->getQuery(true)
->select('id')
->from($db->quoteName('#__menu'))
->where($db->quoteName('alias') . ' = ' . $db->quote('terms-of-service'))
->where($db->quoteName('published') . ' >= 0');
$db->setQuery($query);
$menuId = $db->loadResult();
// Create menu item if it doesn't exist
if (!$menuId) {
$this->createTermsMenuItem($articleId);
}
}
} catch (Exception $e) {
Log::add('Error creating Terms of Service setup: ' . $e->getMessage(), Log::WARNING, 'jerror');
}
}
$db->setQuery($query);
/**
* Create Terms of Service article
*
* @return int|null Article ID or null on failure
*
* @since 1.0.0
*/
private function createTermsArticle()
{
try {
Table::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_content/tables');
$table = Table::getInstance('Content', 'Joomla\\Component\\Content\\Administrator\\Table\\');
if (!$table) {
Log::add('Failed to get Content table instance', Log::WARNING, 'jerror');
return null;
}
$data = [
'title' => 'Terms of Service',
'alias' => 'terms-of-service',
'introtext' => '<h2>Terms of Service</h2><p>Welcome to our Terms of Service page.</p><p>This page will remain accessible even when the site is in offline/maintenance mode.</p>',
'fulltext' => '',
'state' => 1,
'catid' => 2, // Uncategorised
'created' => Factory::getDate()->toSql(),
'created_by' => 0, // System-created content
'language' => '*',
'access' => 1, // Public
];
// Bind data to table object first
if (!$table->bind($data)) {
Log::add('Failed to bind data to Content table: ' . $table->getError(), Log::WARNING, 'jerror');
return null;
}
// Check data validity
if (!$table->check()) {
Log::add('Content table check failed: ' . $table->getError(), Log::WARNING, 'jerror');
return null;
}
// Save the table
if (!$table->store()) {
Log::add('Failed to store Content table: ' . $table->getError(), Log::WARNING, 'jerror');
return null;
}
echo '<p class="alert alert-info">✓ Created Terms of Service article</p>';
return $table->id;
} catch (Exception $e) {
Log::add('Error creating Terms of Service article: ' . $e->getMessage(), Log::WARNING, 'jerror');
}
return null;
}
try
{
$db->execute();
}
catch (Exception $e)
{
Log::add('Failed to enable MokoJoomTOS offline access plugin: ' . $e->getMessage(), Log::WARNING, 'jerror');
}
}
/**
* Create Terms of Service menu item
*
* @param int $articleId The article ID to link to
*
* @return void
*
* @since 1.0.0
*/
private function createTermsMenuItem($articleId)
{
try {
$db = Factory::getDbo();
// Check if "Legal" menu type exists
$query = $db->getQuery(true)
->select('id')
->from($db->quoteName('#__menu_types'))
->where($db->quoteName('menutype') . ' = ' . $db->quote('legal'));
$db->setQuery($query);
$legalMenuExists = $db->loadResult();
// Create "Legal" menu type if it doesn't exist
if (!$legalMenuExists) {
$this->createLegalMenuType();
}
// Get com_content component ID dynamically
$query = $db->getQuery(true)
->select('extension_id')
->from($db->quoteName('#__extensions'))
->where($db->quoteName('type') . ' = ' . $db->quote('component'))
->where($db->quoteName('element') . ' = ' . $db->quote('com_content'));
$db->setQuery($query);
$componentId = (int) $db->loadResult() ?: 22; // Fallback to 22 if query fails
Table::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_menus/tables');
$table = Table::getInstance('Menu', 'Joomla\\Component\\Menus\\Administrator\\Table\\');
if (!$table) {
Log::add('Failed to get Menu table instance', Log::WARNING, 'jerror');
return;
}
$data = [
'menutype' => 'legal',
'title' => 'Terms of Service',
'alias' => 'terms-of-service',
'link' => 'index.php?option=com_content&view=article&id=' . $articleId,
'type' => 'component',
'published' => 1,
'parent_id' => 1,
'component_id' => $componentId,
'level' => 1,
'language' => '*',
'access' => 1, // Public
'params' => '{"show_title":"1","link_titles":"0","show_intro":"","info_block_position":"","show_category":"0","link_category":"0","show_parent_category":"0","link_parent_category":"0","show_author":"0","link_author":"0","show_create_date":"0","show_modify_date":"0","show_publish_date":"0","show_item_navigation":"0","show_icons":"0","show_print_icon":"0","show_email_icon":"0","show_hits":"0","show_noauth":"0","urls_position":"","menu-anchor_title":"","menu-anchor_css":"","menu_image":"","menu_text":1,"page_title":"","show_page_heading":0,"page_heading":"","pageclass_sfx":"","menu-meta_description":"","menu-meta_keywords":"","robots":"","secure":0}',
];
// Set the location in the menu tree
$table->setLocation($data['parent_id'], 'last-child');
// Bind data to table object
if (!$table->bind($data)) {
Log::add('Failed to bind data to Menu table: ' . $table->getError(), Log::WARNING, 'jerror');
return;
}
// Check data validity
if (!$table->check()) {
Log::add('Menu table check failed: ' . $table->getError(), Log::WARNING, 'jerror');
return;
}
// Save the menu item
if (!$table->store()) {
Log::add('Failed to store Menu table: ' . $table->getError(), Log::WARNING, 'jerror');
return;
}
echo '<p class="alert alert-info">✓ Created Terms of Service menu item in Legal menu with slug: terms-of-service</p>';
// Enable the plugin
$this->enablePlugin();
} catch (Exception $e) {
Log::add('Error creating Terms of Service menu item: ' . $e->getMessage(), Log::WARNING, 'jerror');
}
}
/**
* Display post-installation message
*
* @return void
*
* @since 1.0.0
*/
private function displayPostInstallationMessage(): void
{
$message = '<h3>MokoJoomTOS Component Installed</h3>';
$message .= '<p>The Terms of Service and Privacy Policy component has been successfully installed.</p>';
$message .= '<h4>Next Steps:</h4>';
$message .= '<ul>';
$message .= '<li>Go to Components → MokoJoomTOS to manage your Terms of Service and Privacy Policy</li>';
$message .= '<li>Create menu items to link to these pages (Component Type: MokoJoomTOS)</li>';
$message .= '<li>The offline access plugin has been automatically enabled</li>';
$message .= '<li>Your ToS and Privacy pages will be accessible even when the site is offline</li>';
$message .= '</ul>';
/**
* Create Legal menu type
*
* @return void
*
* @since 1.0.0
*/
private function createLegalMenuType()
{
try {
$db = Factory::getDbo();
// Insert the Legal menu type
$query = $db->getQuery(true)
->insert($db->quoteName('#__menu_types'))
->columns($db->quoteName(['menutype', 'title', 'description']))
->values(
$db->quote('legal') . ', ' .
$db->quote('Legal') . ', ' .
$db->quote('Legal documents and policies menu')
);
$db->setQuery($query);
$db->execute();
echo '<p class="alert alert-info">✓ Created Legal menu type</p>';
} catch (Exception $e) {
Log::add('Error creating Legal menu type: ' . $e->getMessage(), Log::WARNING, 'jerror');
}
}
Factory::getApplication()->enqueueMessage($message, 'info');
}
};
/**
* Enable the plugin after installation
*
* @return void
*
* @since 1.0.0
*/
private function enablePlugin()
{
try {
$db = Factory::getDbo();
$query = $db->getQuery(true)
->update($db->quoteName('#__extensions'))
->set($db->quoteName('enabled') . ' = 1')
->where($db->quoteName('type') . ' = ' . $db->quote('plugin'))
->where($db->quoteName('folder') . ' = ' . $db->quote('system'))
->where($db->quoteName('element') . ' = ' . $db->quote('mokojoomtos'));
$db->setQuery($query);
$db->execute();
echo '<p class="alert alert-success">✓ Plugin enabled automatically</p>';
} catch (Exception $e) {
Log::add('Error enabling plugin: ' . $e->getMessage(), Log::WARNING, 'jerror');
}
}
}
@@ -1,11 +0,0 @@
; Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
; SPDX-License-Identifier: GPL-3.0-or-later
COM_MOKOJOOMTOS_TERMS_TITLE="Terms of Service"
COM_MOKOJOOMTOS_PRIVACY_TITLE="Privacy Policy"
COM_MOKOJOOMTOS_LAST_UPDATED="Last Updated"
COM_MOKOJOOMTOS_ERROR_CONTENT_NOT_FOUND="Content not found"
COM_MOKOJOOMTOS_TERMS_VIEW_DEFAULT_TITLE="Terms of Service"
COM_MOKOJOOMTOS_TERMS_VIEW_DEFAULT_DESC="Display Terms of Service page"
COM_MOKOJOOMTOS_PRIVACY_VIEW_DEFAULT_TITLE="Privacy Policy"
COM_MOKOJOOMTOS_PRIVACY_VIEW_DEFAULT_DESC="Display Privacy Policy page"
@@ -1,45 +0,0 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of MokoJoomTOS.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace MokoConsulting\Component\MokoJoomTOS\Site\Controller;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Controller\BaseController;
/**
* Display Controller for MokoJoomTOS site
*
* @since 1.0.0
*/
class DisplayController extends BaseController
{
/**
* The default view.
*
* @var string
* @since 1.0.0
*/
protected $default_view = 'terms';
/**
* Method to display a view.
*
* @param boolean $cachable If true, the view output will be cached
* @param array $urlparams An array of safe URL parameters
*
* @return static A BaseController object
*
* @since 1.0.0
*/
public function display($cachable = false, $urlparams = [])
{
return parent::display($cachable, $urlparams);
}
}
-43
View File
@@ -1,43 +0,0 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of MokoJoomTOS.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace MokoConsulting\Component\MokoJoomTOS\Site\Model;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
/**
* Model for displaying Privacy Policy
*
* @since 1.0.0
*/
class PrivacyModel extends BaseDatabaseModel
{
/**
* Method to get the privacy policy content.
*
* @return mixed Object on success, false on failure.
*
* @since 1.0.0
*/
public function getItem()
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('*')
->from($db->quoteName('#__mokojoomtos_content'))
->where($db->quoteName('content_type') . ' = ' . $db->quote('privacy'))
->where($db->quoteName('published') . ' = 1');
$db->setQuery($query);
return $db->loadObject();
}
}
-43
View File
@@ -1,43 +0,0 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of MokoJoomTOS.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace MokoConsulting\Component\MokoJoomTOS\Site\Model;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
/**
* Model for displaying Terms of Service
*
* @since 1.0.0
*/
class TermsModel extends BaseDatabaseModel
{
/**
* Method to get the terms of service content.
*
* @return mixed Object on success, false on failure.
*
* @since 1.0.0
*/
public function getItem()
{
$db = $this->getDatabase();
$query = $db->getQuery(true)
->select('*')
->from($db->quoteName('#__mokojoomtos_content'))
->where($db->quoteName('content_type') . ' = ' . $db->quote('terms'))
->where($db->quoteName('published') . ' = 1');
$db->setQuery($query);
return $db->loadObject();
}
}
-49
View File
@@ -1,49 +0,0 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of MokoJoomTOS.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace MokoConsulting\Component\MokoJoomTOS\Site\Service;
defined('_JEXEC') or die;
use Joomla\CMS\Component\Router\RouterView;
use Joomla\CMS\Component\Router\RouterViewConfiguration;
use Joomla\CMS\Component\Router\Rules\MenuRules;
use Joomla\CMS\Component\Router\Rules\NomenuRules;
use Joomla\CMS\Component\Router\Rules\StandardRules;
/**
* Routing class for MokoJoomTOS
*
* @since 1.0.0
*/
class Router extends RouterView
{
/**
* Constructor
*
* @param object $app The application object
* @param object $menu The menu object to work with
*
* @since 1.0.0
*/
public function __construct($app = null, $menu = null)
{
$terms = new RouterViewConfiguration('terms');
$this->registerView($terms);
$privacy = new RouterViewConfiguration('privacy');
$this->registerView($privacy);
parent::__construct($app, $menu);
$this->attachRule(new MenuRules($this));
$this->attachRule(new StandardRules($this));
$this->attachRule(new NomenuRules($this));
}
}
-54
View File
@@ -1,54 +0,0 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of MokoJoomTOS.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace MokoConsulting\Component\MokoJoomTOS\Site\View\Privacy;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
/**
* View for displaying Privacy Policy
*
* @since 1.0.0
*/
class HtmlView extends BaseHtmlView
{
/**
* The active item
*
* @var object
* @since 1.0.0
*/
protected $item;
/**
* Display the view
*
* @param string $tpl The name of the template file to parse
*
* @return void
*
* @since 1.0.0
*/
public function display($tpl = null)
{
$this->item = $this->get('Item');
if (!$this->item)
{
throw new \Exception(\Joomla\CMS\Language\Text::_('COM_MOKOJOOMTOS_ERROR_CONTENT_NOT_FOUND'), 404);
}
// Set page title
$this->document->setTitle($this->item->title);
parent::display($tpl);
}
}
-54
View File
@@ -1,54 +0,0 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of MokoJoomTOS.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
namespace MokoConsulting\Component\MokoJoomTOS\Site\View\Terms;
defined('_JEXEC') or die;
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
/**
* View for displaying Terms of Service
*
* @since 1.0.0
*/
class HtmlView extends BaseHtmlView
{
/**
* The active item
*
* @var object
* @since 1.0.0
*/
protected $item;
/**
* Display the view
*
* @param string $tpl The name of the template file to parse
*
* @return void
*
* @since 1.0.0
*/
public function display($tpl = null)
{
$this->item = $this->get('Item');
if (!$this->item)
{
throw new \Exception(\Joomla\CMS\Language\Text::_('COM_MOKOJOOMTOS_ERROR_CONTENT_NOT_FOUND'), 404);
}
// Set page title
$this->document->setTitle($this->item->title);
parent::display($tpl);
}
}
-32
View File
@@ -1,32 +0,0 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of MokoJoomTOS.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
$wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->registerAndUseStyle('com_mokojoomtos', 'media/com_mokojoomtos/css/mokojoomtos.css');
?>
<div class="mokojoomtos privacy-policy">
<div class="page-header">
<h1><?php echo htmlspecialchars($this->item->title); ?></h1>
</div>
<div class="content">
<?php echo $this->item->content; ?>
</div>
<?php if ($this->item->modified && $this->item->modified != '0000-00-00 00:00:00'): ?>
<div class="last-updated">
<p><small><?php echo \Joomla\CMS\Language\Text::_('COM_MOKOJOOMTOS_LAST_UPDATED'); ?>:
<?php echo \Joomla\CMS\HTML\HTMLHelper::_('date', $this->item->modified, 'DATE_FORMAT_LC3'); ?>
</small></p>
</div>
<?php endif; ?>
</div>
-15
View File
@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
This file is part of MokoJoomTOS.
SPDX-License-Identifier: GPL-3.0-or-later
-->
<metadata>
<layout title="COM_MOKOJOOMTOS_PRIVACY_VIEW_DEFAULT_TITLE">
<message>
<![CDATA[COM_MOKOJOOMTOS_PRIVACY_VIEW_DEFAULT_DESC]]>
</message>
</layout>
</metadata>
-32
View File
@@ -1,32 +0,0 @@
<?php
/**
* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
*
* This file is part of MokoJoomTOS.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
$wa = Factory::getApplication()->getDocument()->getWebAssetManager();
$wa->registerAndUseStyle('com_mokojoomtos', 'media/com_mokojoomtos/css/mokojoomtos.css');
?>
<div class="mokojoomtos terms-of-service">
<div class="page-header">
<h1><?php echo htmlspecialchars($this->item->title); ?></h1>
</div>
<div class="content">
<?php echo $this->item->content; ?>
</div>
<?php if ($this->item->modified && $this->item->modified != '0000-00-00 00:00:00'): ?>
<div class="last-updated">
<p><small><?php echo \Joomla\CMS\Language\Text::_('COM_MOKOJOOMTOS_LAST_UPDATED'); ?>:
<?php echo \Joomla\CMS\HTML\HTMLHelper::_('date', $this->item->modified, 'DATE_FORMAT_LC3'); ?>
</small></p>
</div>
<?php endif; ?>
</div>
-15
View File
@@ -1,15 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
This file is part of MokoJoomTOS.
SPDX-License-Identifier: GPL-3.0-or-later
-->
<metadata>
<layout title="COM_MOKOJOOMTOS_TERMS_VIEW_DEFAULT_TITLE">
<message>
<![CDATA[COM_MOKOJOOMTOS_TERMS_VIEW_DEFAULT_DESC]]>
</message>
</layout>
</metadata>
+123
View File
@@ -0,0 +1,123 @@
<?php
/**
* @package MokoJoomTOS
* @subpackage plg_system_mokojoomtos
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
namespace Joomla\Plugin\System\MokoJoomTOS\Extension;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\CMS\Uri\Uri;
use Joomla\Event\SubscriberInterface;
/**
* MokoJoomTOS Offline Mode Bypass Plugin
*
* Allows Terms of Service menu to be accessible via slug when the site
* is in offline mode.
*
* @since 1.0.0
*/
final class MokoJoomTOS extends CMSPlugin implements SubscriberInterface
{
/**
* Load the language file on instantiation.
*
* @var boolean
* @since 1.0.0
*/
protected $autoloadLanguage = true;
/**
* Application object
*
* @var \Joomla\CMS\Application\CMSApplication
* @since 1.0.0
*/
protected $app;
/**
* Returns an array of events this subscriber will listen to.
*
* @return array
*
* @since 1.0.0
*/
public static function getSubscribedEvents(): array
{
return [
'onAfterRoute' => 'onAfterRoute',
];
}
/**
* After route event handler
*
* Checks if the current request is for the Terms of Service slug and if
* the site is in offline mode. If both conditions are met, temporarily
* disables offline mode and sets component-only view for this request.
*
* This event fires after routing but before template selection, making it
* the correct place to set tmpl=component to prevent template chrome loading.
*
* @return void
*
* @since 1.0.0
*/
public function onAfterRoute()
{
// Only process for site application
if (!$this->app->isClient('site'))
{
return;
}
// Get the global configuration
$config = $this->app->getConfig();
// Only proceed if site is offline
if (!$config->get('offline'))
{
return;
}
// Get the configured Terms of Service slug
$tosSlug = trim($this->params->get('tos_slug', 'terms-of-service'));
if (empty($tosSlug))
{
return;
}
// Get the current URI path
$uri = Uri::getInstance();
$path = trim($uri->getPath(), '/');
// Remove the base path if present
$base = trim(Uri::base(true), '/');
if (!empty($base) && strpos($path, $base) === 0)
{
$path = trim(substr($path, strlen($base)), '/');
}
// Check if the path matches the Terms of Service slug
if ($path === $tosSlug || strpos($path, $tosSlug . '/') === 0)
{
// Temporarily disable offline mode for this request
$config->set('offline', 0);
// Set component-only view (no template chrome)
// This ensures clean display without full site template
$input = $this->app->input;
$input->set('tmpl', 'component');
// Also set it in the GET superglobal to ensure it's recognized
$_GET['tmpl'] = 'component';
}
}
}
+98
View File
@@ -0,0 +1,98 @@
<?php
/**
* @package MokoJoomTOS
* @subpackage plg_system_mokojoomtos
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
* @license GNU General Public License version 3 or later; see LICENSE
*/
namespace Joomla\Plugin\System\MokoJoomTOS\Field;
defined('_JEXEC') or die;
use Joomla\CMS\Factory;
use Joomla\CMS\Form\Field\ListField;
use Joomla\CMS\Language\Text;
/**
* Menu Slug Field
*
* Provides a dropdown list of menu items with their aliases (slugs)
*
* @since 1.0.0
*/
class MenuslugField extends ListField
{
/**
* The form field type.
*
* @var string
* @since 1.0.0
*/
protected $type = 'Menuslug';
/**
* Method to get the field options.
*
* @return array The field option objects.
*
* @since 1.0.0
*/
protected function getOptions()
{
$options = parent::getOptions();
try
{
$db = Factory::getDbo();
$query = $db->getQuery(true)
->select($db->quoteName(['alias', 'title', 'menutype']))
->from($db->quoteName('#__menu'))
->where($db->quoteName('published') . ' = 1')
->where($db->quoteName('client_id') . ' = 0')
->where($db->quoteName('alias') . ' != ' . $db->quote(''))
->order($db->quoteName('menutype') . ', ' . $db->quoteName('title'));
$db->setQuery($query);
$menuItems = $db->loadObjectList();
if ($menuItems)
{
$lastMenuType = '';
foreach ($menuItems as $item)
{
// Add menu type separator for better organization
if ($item->menutype !== $lastMenuType)
{
if ($lastMenuType !== '')
{
// Add a separator between menu types
$options[] = (object) [
'value' => '',
'text' => '──────────────',
'disable' => true
];
}
$lastMenuType = $item->menutype;
}
$options[] = (object) [
'value' => $item->alias,
'text' => $item->title . ' (' . $item->alias . ')'
];
}
}
}
catch (\Exception $e)
{
// Log error but don't break the form
Factory::getApplication()->enqueueMessage(
Text::sprintf('PLG_SYSTEM_MOKOJOOMTOS_ERROR_LOADING_MENU_ITEMS', $e->getMessage()),
'warning'
);
}
return $options;
}
}
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<updates>
<update>
<name>System - MokoJoomTOS</name>
<description>MokoJoomTOS Plugin - Allows Terms of Service to be accessible when site is offline</description>
<element>mokojoomtos</element>
<type>plugin</type>
<folder>system</folder>
<client>site</client>
<version>03.08.04</version>
<infourl title="MokoJoomTOS Plugin">https://github.com/mokoconsulting-tech/MokoJoomTOS</infourl>
<downloads>
<downloadurl type="full" format="zip">https://github.com/mokoconsulting-tech/MokoJoomTOS/releases/download/v03.08.04/plg_system_mokojoomtos-03.08.04.zip</downloadurl>
</downloads>
<targetplatform name="joomla" version="(4\.[0-9]|5\.[0-9])" />
<php_minimum>7.4.0</php_minimum>
</update>
</updates>