docs: add CLAUDE.md context file for Claude Code #2
@@ -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.*
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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**:
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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:
|
||||
- "*"
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
|
||||
# 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)
|
||||
|
`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
|
||||
@@ -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
|
||||
@@ -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();
|
||||
|
This workflow runs 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
|
||||
@@ -919,3 +919,5 @@ htdocs/logs/
|
||||
# Keep-empty folders helper
|
||||
# ============================================================
|
||||
!.gitkeep
|
||||
build/
|
||||
dist/
|
||||
|
||||
@@ -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
|
||||
|
This changelog entry says the fix was done by switching the hook from 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
```
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
-->
|
||||
|
||||
@@ -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
|
||||
-->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ✅
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
[](https://github.com/mokoconsulting-tech/MokoCodingDefaults)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0)
|
||||
[](https://www.joomla.org/)
|
||||
[](#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**
|
||||
|
||||
@@ -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
|
||||
-->
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
```
|
||||
@@ -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());
|
||||
@@ -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));
|
||||
@@ -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());
|
||||
@@ -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 "$@"
|
||||
@@ -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 "$@"
|
||||
@@ -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 "$@"
|
||||
@@ -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
|
||||
@@ -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,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"
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
@@ -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&view=terms" view="terms">COM_MOKOJOOMTOS_MENU_TERMS</menu>
|
||||
<menu link="option=com_mokojoomtos&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>
|
||||
@@ -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."
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
{
|
||||
|
The plugin entrypoint class here only implements 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
This manifest references 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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
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 removeconfig-file:and rely on default scanning configuration.