Compare commits
187 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 24a40a4117 | |||
| b6ec1cd5b3 | |||
| acef5eb3a3 | |||
| 5743915447 | |||
| 9905d1e634 | |||
| 47594c963d | |||
| 5621542141 | |||
| ee581032c3 | |||
| dd4de77202 | |||
| 3d567353c9 | |||
| 8e7381e3ea | |||
| 3eb56f738e | |||
| dba61e3e0c | |||
| 9c2dd1bdde | |||
| ee49fbbaa6 | |||
| 4785a1f5b4 | |||
| b6202a6a40 | |||
| 0c2074f801 | |||
| e27b958712 | |||
| a169ea4967 | |||
| d951d86b3a | |||
| b03c7c6ba7 | |||
| 1c15497c32 | |||
| 9e38609fe9 | |||
| b907b778c0 | |||
| 4d758890a8 | |||
| 824b4d9ecd | |||
| 307eb7741d | |||
| 4a13ea6ade | |||
| bcc17e4882 | |||
| 4ce96dc95b | |||
| 99e4a83ed5 | |||
| 63c4fbcd14 | |||
| 15a03b309b | |||
| a537132836 | |||
| 6f29c077e2 | |||
| 9fa2560ce4 | |||
| 45afb1f0b1 | |||
| 843c729828 | |||
| db061e2b75 | |||
| a6dc736787 | |||
| a247a5fd0e | |||
| e0c95b4291 | |||
| decb1ba8b7 | |||
| 290284a0c9 | |||
| c9eff72278 | |||
| a86686c30a | |||
| bf8cc9bd0a | |||
| bed05630ca | |||
| 831223f7bc | |||
| 0428904ae8 | |||
| 4cfde99e7f | |||
| 1e105d6c7b | |||
| 2140c9e07f | |||
| 5cb6dd8008 | |||
| 2264b00828 | |||
| f87086bd0f | |||
| fc23c771c0 | |||
| 96299a6b9a | |||
| 1961585e83 | |||
| 14f5407820 | |||
| 407b30a437 | |||
| 49041565eb | |||
| 1d1026f7e7 | |||
| 1f7329272d | |||
| 4c855ac7c8 | |||
| a350d02d08 | |||
| a860d414bd | |||
| e03c86f2c6 | |||
| 1b0025e55f | |||
| ffe599ee92 | |||
| ac56b3a776 | |||
| 95badba96e | |||
| de66983cda | |||
| 89a59f8a8e | |||
| 34367ae93c | |||
| b2d4071193 | |||
| 3bc5678768 | |||
| ca0cfd9a6d | |||
| 9cc4b90b78 | |||
| 4ebb9e30d6 | |||
| 0f164b607c | |||
| 6762764006 | |||
| efdcaa712f | |||
| ecb1ce592a | |||
| d3581564cf | |||
| 819d615ede | |||
| f2947a088e | |||
| 845e0bd5fb | |||
| a78b3c224b | |||
| 7136e45a90 | |||
| 882b45cbbd | |||
| 36ab4ff1ac | |||
| a29d8f4e12 | |||
| f87ae2f922 | |||
| 3a49732dfb | |||
| 109ca703ef | |||
| 794746e20d | |||
| 85848c2d6c | |||
| 86d4681fcd | |||
| 0a14a29ac6 | |||
| df07b4b672 | |||
| 7bd151ad62 | |||
| ddc867ad06 | |||
| a111f5b5e9 | |||
| 1897805483 | |||
| 8919db6fc3 | |||
| d69b26af51 | |||
| da9ac28f22 | |||
| a8dae85f42 | |||
| d3bc62f810 | |||
| 13683adfba | |||
| e183b62aba | |||
| 176047d161 | |||
| 2fa2f86bd6 | |||
| ce9d72b50d | |||
| ef066edffc | |||
| dec72b6af1 | |||
| 9ca3ab9fc2 | |||
| 92358a673b | |||
| afc2737663 | |||
| aacf5de7f1 | |||
| 2f10a5fb80 | |||
| 25fb7e9272 | |||
| b39ba30dc5 | |||
| 14c218092b | |||
| 833c8ca41a | |||
| c7551854ac | |||
| 27990652d0 | |||
| 351f1fc7f8 | |||
| 99308cd7a4 | |||
| 561ba24090 | |||
| 3e1cb9a500 | |||
| 5ae8e3e001 | |||
| faea3637e0 | |||
| 79eaa5217d | |||
| 0e0891f1a8 | |||
| 33aaf666ae | |||
| a634938799 | |||
| 14ff4ab2f1 | |||
| b3de21e7d1 | |||
| 72a373b17c | |||
| bc290f3bed | |||
| 1d5683ceda | |||
| f2cf0dfd24 | |||
| 3a1a201eb2 | |||
| 452d4795ed | |||
| a4704ad267 | |||
| d1762ad5df | |||
| df1467c518 | |||
| 7cdd97ca59 | |||
| 5b36d10b04 | |||
| 56699fdd4d | |||
| fcf1cc41c8 | |||
| b8640ccb1d | |||
| 4b51e2dd9a | |||
| e068e14004 | |||
| 941fd4c6cd | |||
| f2021d478e | |||
| 900ceb2bb5 | |||
| 9b498e6786 | |||
| ca06298e64 | |||
| dc8d7d59d4 | |||
| 55d2123c33 | |||
| 274c1f34af | |||
| 75c878507e | |||
| f1ea8ead74 | |||
| 23de84610e | |||
| 0cb24b4759 | |||
| 7fa97231a1 | |||
| 2291db32c5 | |||
| 491bd3b858 | |||
| 064d5e3ab1 | |||
| c512829cd4 | |||
| 69c728cd5a | |||
| fca6d4f25f | |||
| b668e1d4ed | |||
| 5b760a1b74 | |||
| 1f8dccf898 | |||
| b9d8eb3950 | |||
| 053fe2d52c | |||
| b3846fa633 | |||
| a8ef4cfb77 | |||
| 58317cd205 | |||
| 642e2bffe7 | |||
| d6d423b946 | |||
| f65d261598 |
@@ -0,0 +1,41 @@
|
||||
# EditorConfig helps maintain consistent coding styles across different editors and IDEs
|
||||
# https://editorconfig.org/
|
||||
|
||||
root = true
|
||||
|
||||
# Default settings — Tabs preferred, width = 2 spaces
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
indent_style = tab
|
||||
tab_width = 2
|
||||
|
||||
# PowerShell scripts — tabs, 2-space visual width
|
||||
[*.ps1]
|
||||
indent_style = tab
|
||||
tab_width = 2
|
||||
end_of_line = crlf
|
||||
|
||||
# Markdown files — keep trailing whitespace for line breaks
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
# JSON / YAML files — tabs, 2-space visual width
|
||||
[*.{json,yml,yaml}]
|
||||
indent_style = tab
|
||||
tab_width = 2
|
||||
|
||||
# Makefiles — always tabs, default width
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
tab_width = 2
|
||||
|
||||
# Windows batch scripts — keep CRLF endings
|
||||
[*.{bat,cmd}]
|
||||
end_of_line = crlf
|
||||
|
||||
# Shell scripts — ensure LF endings
|
||||
[*.sh]
|
||||
end_of_line = lf
|
||||
@@ -0,0 +1,110 @@
|
||||
---
|
||||
name: Architecture Decision Record (ADR)
|
||||
about: Propose or document an architectural decision
|
||||
title: '[ADR] '
|
||||
labels: 'architecture, decision'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
## ADR Number
|
||||
ADR-XXXX
|
||||
|
||||
## Status
|
||||
- [ ] Proposed
|
||||
- [ ] Accepted
|
||||
- [ ] Deprecated
|
||||
- [ ] Superseded by ADR-XXXX
|
||||
|
||||
## Context
|
||||
Describe the issue or problem that motivates this decision.
|
||||
|
||||
## Decision
|
||||
State the architecture decision and provide rationale.
|
||||
|
||||
## Consequences
|
||||
### Positive
|
||||
- List positive consequences
|
||||
|
||||
### Negative
|
||||
- List negative consequences or trade-offs
|
||||
|
||||
### Neutral
|
||||
- List neutral aspects
|
||||
|
||||
## Alternatives Considered
|
||||
### Alternative 1
|
||||
- Description
|
||||
- Pros
|
||||
- Cons
|
||||
- Why not chosen
|
||||
|
||||
### Alternative 2
|
||||
- Description
|
||||
- Pros
|
||||
- Cons
|
||||
- Why not chosen
|
||||
|
||||
## Implementation Plan
|
||||
1. Step 1
|
||||
2. Step 2
|
||||
3. Step 3
|
||||
|
||||
## Stakeholders
|
||||
- **Decision Makers**: @user1, @user2
|
||||
- **Consulted**: @user3, @user4
|
||||
- **Informed**: team-name
|
||||
|
||||
## Technical Details
|
||||
### Architecture Diagram
|
||||
```
|
||||
[Add diagram or link]
|
||||
```
|
||||
|
||||
### Dependencies
|
||||
- Dependency 1
|
||||
- Dependency 2
|
||||
|
||||
### Impact Analysis
|
||||
- **Performance**: [Impact description]
|
||||
- **Security**: [Impact description]
|
||||
- **Scalability**: [Impact description]
|
||||
- **Maintainability**: [Impact description]
|
||||
|
||||
## Testing Strategy
|
||||
- [ ] Unit tests
|
||||
- [ ] Integration tests
|
||||
- [ ] Performance tests
|
||||
- [ ] Security tests
|
||||
|
||||
## Documentation
|
||||
- [ ] Architecture documentation updated
|
||||
- [ ] API documentation updated
|
||||
- [ ] Developer guide updated
|
||||
- [ ] Runbook created
|
||||
|
||||
## Migration Path
|
||||
Describe how to migrate from current state to new architecture.
|
||||
|
||||
## Rollback Plan
|
||||
Describe how to rollback if issues occur.
|
||||
|
||||
## Timeline
|
||||
- **Proposal Date**:
|
||||
- **Decision Date**:
|
||||
- **Implementation Start**:
|
||||
- **Expected Completion**:
|
||||
|
||||
## References
|
||||
- Related ADRs:
|
||||
- External resources:
|
||||
- RFCs:
|
||||
|
||||
## Review Checklist
|
||||
- [ ] Aligns with enterprise architecture principles
|
||||
- [ ] Security implications reviewed
|
||||
- [ ] Performance implications reviewed
|
||||
- [ ] Cost implications reviewed
|
||||
- [ ] Compliance requirements met
|
||||
- [ ] Team consensus achieved
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
name: Bug Report
|
||||
about: Report a bug or issue with the project
|
||||
title: '[BUG] '
|
||||
labels: 'bug'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Bug Description
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
## Steps to Reproduce
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Scroll down to '...'
|
||||
4. See error
|
||||
|
||||
## Expected Behavior
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
## Actual Behavior
|
||||
A clear and concise description of what actually happened.
|
||||
|
||||
## Screenshots
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
## Environment
|
||||
- **Project**: [e.g., MokoDoliTools, moko-cassiopeia]
|
||||
- **Version**: [e.g., 1.2.3]
|
||||
- **Platform**: [e.g., Dolibarr 18.0, Joomla 5.0]
|
||||
- **PHP Version**: [e.g., 8.1]
|
||||
- **Database**: [e.g., MySQL 8.0, PostgreSQL 14]
|
||||
- **Browser** (if applicable): [e.g., Chrome 120, Firefox 121]
|
||||
- **OS**: [e.g., Ubuntu 22.04, Windows 11]
|
||||
|
||||
## Additional Context
|
||||
Add any other context about the problem here.
|
||||
|
||||
## Possible Solution
|
||||
If you have suggestions on how to fix the issue, please describe them here.
|
||||
|
||||
## Checklist
|
||||
- [ ] I have searched for similar issues before creating this one
|
||||
- [ ] I have provided all the requested information
|
||||
- [ ] I have tested this on the latest stable version
|
||||
- [ ] I have checked the documentation and couldn't find a solution
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: 💼 Enterprise Support
|
||||
url: https://mokoconsulting.tech/enterprise
|
||||
about: Enterprise-level support and consultation services
|
||||
- name: 💬 Ask a Question
|
||||
url: https://mokoconsulting.tech/
|
||||
about: Get help or ask questions through our website
|
||||
- name: 📚 MokoStandards Documentation
|
||||
url: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
about: View our coding standards and best practices
|
||||
- name: 🔒 Report a Security Vulnerability
|
||||
url: https://git.mokoconsulting.tech/mokoconsulting-tech/.github-private/security/advisories/new
|
||||
about: Report security vulnerabilities privately (for critical issues)
|
||||
- name: 💡 Community Discussions
|
||||
url: https://github.com/orgs/mokoconsulting-tech/discussions
|
||||
about: Join community discussions and Q&A
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
name: Documentation Issue
|
||||
about: Report an issue with documentation
|
||||
title: '[DOCS] '
|
||||
labels: 'documentation'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Documentation Issue
|
||||
|
||||
**Location**:
|
||||
<!-- Specify the file, page, or section with the issue -->
|
||||
|
||||
## Issue Type
|
||||
<!-- Mark the relevant option with an "x" -->
|
||||
- [ ] Typo or grammar error
|
||||
- [ ] Outdated information
|
||||
- [ ] Missing documentation
|
||||
- [ ] Unclear explanation
|
||||
- [ ] Broken links
|
||||
- [ ] Missing examples
|
||||
- [ ] Other (specify below)
|
||||
|
||||
## Description
|
||||
<!-- Clearly describe the documentation issue -->
|
||||
|
||||
## Current Content
|
||||
<!-- Quote or describe the current documentation (if applicable) -->
|
||||
```
|
||||
Current text here
|
||||
```
|
||||
|
||||
## Suggested Improvement
|
||||
<!-- Provide your suggestion for how to improve the documentation -->
|
||||
```
|
||||
Suggested text here
|
||||
```
|
||||
|
||||
## Additional Context
|
||||
<!-- Add any other context, screenshots, or references -->
|
||||
|
||||
## Standards Alignment
|
||||
- [ ] Follows MokoStandards documentation guidelines
|
||||
- [ ] Uses en_US/en_GB localization
|
||||
- [ ] Includes proper SPDX headers where applicable
|
||||
|
||||
## Checklist
|
||||
- [ ] I have searched for similar documentation issues
|
||||
- [ ] I have provided a clear description
|
||||
- [ ] I have suggested an improvement (if applicable)
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: Suggest a new feature or enhancement
|
||||
title: '[FEATURE] '
|
||||
labels: 'enhancement'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Feature Description
|
||||
A clear and concise description of the feature you'd like to see.
|
||||
|
||||
## Problem or Use Case
|
||||
Describe the problem this feature would solve or the use case it addresses.
|
||||
Ex. I'm always frustrated when [...]
|
||||
|
||||
## Proposed Solution
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
## Alternative Solutions
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
## Benefits
|
||||
Describe how this feature would benefit users:
|
||||
- Who would use this feature?
|
||||
- What problems does it solve?
|
||||
- What value does it add?
|
||||
|
||||
## Implementation Details (Optional)
|
||||
If you have ideas about how this could be implemented, share them here:
|
||||
- Technical approach
|
||||
- Files/components that might need changes
|
||||
- Any concerns or challenges you foresee
|
||||
|
||||
## Additional Context
|
||||
Add any other context, mockups, or screenshots about the feature request here.
|
||||
|
||||
## Relevant Standards
|
||||
Does this relate to any standards in [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards)?
|
||||
- [ ] Accessibility (WCAG 2.1 AA)
|
||||
- [ ] Localization (en_US/en_GB)
|
||||
- [ ] Security best practices
|
||||
- [ ] Code quality standards
|
||||
- [ ] Other: [specify]
|
||||
|
||||
## Checklist
|
||||
- [ ] I have searched for similar feature requests before creating this one
|
||||
- [ ] I have clearly described the use case and benefits
|
||||
- [ ] I have considered alternative solutions
|
||||
- [ ] This feature aligns with the project's goals and scope
|
||||
@@ -0,0 +1,87 @@
|
||||
---
|
||||
name: Joomla Extension Issue
|
||||
about: Report an issue with a Joomla extension
|
||||
title: '[JOOMLA] '
|
||||
labels: 'joomla'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Issue Type
|
||||
- [ ] Component Issue
|
||||
- [ ] Module Issue
|
||||
- [ ] Plugin Issue
|
||||
- [ ] Template Issue
|
||||
|
||||
## Extension Details
|
||||
- **Extension Name**: [e.g., moko-cassiopeia]
|
||||
- **Extension Version**: [e.g., 1.2.3]
|
||||
- **Extension Type**: [Component / Module / Plugin / Template]
|
||||
|
||||
## Joomla Environment
|
||||
- **Joomla Version**: [e.g., 4.4.0, 5.0.0]
|
||||
- **PHP Version**: [e.g., 8.1.0]
|
||||
- **Database**: [MySQL / PostgreSQL / MariaDB]
|
||||
- **Database Version**: [e.g., 8.0]
|
||||
- **Server**: [Apache / Nginx / IIS]
|
||||
- **Hosting**: [Shared / VPS / Dedicated / Cloud]
|
||||
|
||||
## Issue Description
|
||||
Provide a clear and detailed description of the issue.
|
||||
|
||||
## Steps to Reproduce
|
||||
1. Go to '...'
|
||||
2. Click on '...'
|
||||
3. Configure '...'
|
||||
4. See error
|
||||
|
||||
## Expected Behavior
|
||||
What you expected to happen.
|
||||
|
||||
## Actual Behavior
|
||||
What actually happened.
|
||||
|
||||
## Error Messages
|
||||
```
|
||||
# Paste any error messages from Joomla error logs
|
||||
# Location: administrator/logs/error.php
|
||||
```
|
||||
|
||||
## Browser Console Errors
|
||||
```javascript
|
||||
// Paste any JavaScript console errors (F12 in browser)
|
||||
```
|
||||
|
||||
## Screenshots
|
||||
Add screenshots to help explain the issue.
|
||||
|
||||
## Configuration
|
||||
```ini
|
||||
# Paste extension configuration (sanitize sensitive data)
|
||||
```
|
||||
|
||||
## Installed Extensions
|
||||
List other installed extensions that might conflict:
|
||||
- Extension 1 (version)
|
||||
- Extension 2 (version)
|
||||
|
||||
## Template Overrides
|
||||
- [ ] Using template overrides
|
||||
- [ ] Custom CSS
|
||||
- [ ] Custom JavaScript
|
||||
|
||||
## Additional Context
|
||||
- **Multilingual Site**: [Yes / No]
|
||||
- **Cache Enabled**: [Yes / No]
|
||||
- **Debug Mode**: [Yes / No]
|
||||
- **SEF URLs**: [Yes / No]
|
||||
|
||||
## Checklist
|
||||
- [ ] I have cleared Joomla cache
|
||||
- [ ] I have disabled other extensions to test for conflicts
|
||||
- [ ] I have checked Joomla error logs
|
||||
- [ ] I have tested with a default Joomla template
|
||||
- [ ] I have checked browser console for JavaScript errors
|
||||
- [ ] I have searched for similar issues
|
||||
- [ ] I am using a supported Joomla version
|
||||
@@ -0,0 +1,82 @@
|
||||
---
|
||||
name: Question
|
||||
about: Ask a question about usage, features, or best practices
|
||||
title: '[QUESTION] '
|
||||
labels: ['question']
|
||||
assignees: ['jmiller']
|
||||
---
|
||||
|
||||
|
||||
## Question
|
||||
|
||||
**Your question:**
|
||||
|
||||
|
||||
## Context
|
||||
|
||||
**What are you trying to accomplish?**
|
||||
|
||||
|
||||
**What have you already tried?**
|
||||
|
||||
|
||||
**Category**:
|
||||
- [ ] Script usage
|
||||
- [ ] Configuration
|
||||
- [ ] Workflow setup
|
||||
- [ ] Documentation interpretation
|
||||
- [ ] Best practices
|
||||
- [ ] Integration
|
||||
- [ ] Other: __________
|
||||
|
||||
## Environment (if relevant)
|
||||
|
||||
**Your setup**:
|
||||
- Operating System:
|
||||
- Version:
|
||||
|
||||
## What You've Researched
|
||||
|
||||
**Documentation reviewed**:
|
||||
- [ ] README.md
|
||||
- [ ] Project documentation
|
||||
- [ ] 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,126 @@
|
||||
---
|
||||
name: Request for Comments (RFC)
|
||||
about: Propose a significant change for community discussion
|
||||
title: '[RFC] '
|
||||
labels: 'rfc, discussion'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
## RFC Summary
|
||||
One-paragraph summary of the proposal.
|
||||
|
||||
## Motivation
|
||||
Why are we doing this? What use cases does it support? What is the expected outcome?
|
||||
|
||||
## Detailed Design
|
||||
### Overview
|
||||
Provide a detailed explanation of the proposed change.
|
||||
|
||||
### API Changes (if applicable)
|
||||
```php
|
||||
// Before
|
||||
function oldApi($param1) { }
|
||||
|
||||
// After
|
||||
function newApi($param1, $param2) { }
|
||||
```
|
||||
|
||||
### User Experience Changes
|
||||
Describe how users will interact with this change.
|
||||
|
||||
### Implementation Approach
|
||||
High-level implementation strategy.
|
||||
|
||||
## Drawbacks
|
||||
Why should we *not* do this?
|
||||
|
||||
## Alternatives
|
||||
What other designs have been considered? What is the impact of not doing this?
|
||||
|
||||
### Alternative 1
|
||||
- Description
|
||||
- Trade-offs
|
||||
|
||||
### Alternative 2
|
||||
- Description
|
||||
- Trade-offs
|
||||
|
||||
## Adoption Strategy
|
||||
How will existing users adopt this? Is this a breaking change?
|
||||
|
||||
### Migration Guide
|
||||
```bash
|
||||
# Steps to migrate
|
||||
```
|
||||
|
||||
### Deprecation Timeline
|
||||
- **Announcement**:
|
||||
- **Deprecation**:
|
||||
- **Removal**:
|
||||
|
||||
## Unresolved Questions
|
||||
- Question 1
|
||||
- Question 2
|
||||
|
||||
## Future Possibilities
|
||||
What future work does this enable?
|
||||
|
||||
## Impact Assessment
|
||||
### Performance
|
||||
Expected performance impact.
|
||||
|
||||
### Security
|
||||
Security considerations and implications.
|
||||
|
||||
### Compatibility
|
||||
- **Backward Compatible**: [Yes / No]
|
||||
- **Breaking Changes**: [List]
|
||||
|
||||
### Maintenance
|
||||
Long-term maintenance considerations.
|
||||
|
||||
## Community Input
|
||||
### Stakeholders
|
||||
- [ ] Core team
|
||||
- [ ] Module developers
|
||||
- [ ] End users
|
||||
- [ ] Enterprise customers
|
||||
|
||||
### Feedback Period
|
||||
**Duration**: [e.g., 2 weeks]
|
||||
**Deadline**: [date]
|
||||
|
||||
## Implementation Timeline
|
||||
### Phase 1: Design
|
||||
- [ ] RFC discussion
|
||||
- [ ] Design finalization
|
||||
- [ ] Approval
|
||||
|
||||
### Phase 2: Implementation
|
||||
- [ ] Core implementation
|
||||
- [ ] Tests
|
||||
- [ ] Documentation
|
||||
|
||||
### Phase 3: Release
|
||||
- [ ] Beta release
|
||||
- [ ] Feedback collection
|
||||
- [ ] Stable release
|
||||
|
||||
## Success Metrics
|
||||
How will we measure success?
|
||||
- Metric 1
|
||||
- Metric 2
|
||||
|
||||
## References
|
||||
- Related RFCs:
|
||||
- External documentation:
|
||||
- Prior art:
|
||||
|
||||
## Open Questions for Community
|
||||
1. Question 1?
|
||||
2. Question 2?
|
||||
|
||||
---
|
||||
**Note**: This RFC is open for community discussion. Please provide feedback in the comments below.
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
name: Security Vulnerability Report
|
||||
about: Report a security vulnerability (use only for non-critical issues)
|
||||
title: '[SECURITY] '
|
||||
labels: 'security'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
|
||||
## ⚠️ IMPORTANT: Private Disclosure Required
|
||||
|
||||
**For critical security vulnerabilities, DO NOT use this template.**
|
||||
Follow the process in [SECURITY.md](../SECURITY.md) for responsible disclosure.
|
||||
|
||||
Use this template only for:
|
||||
- Security improvements
|
||||
- Non-critical security suggestions
|
||||
- Security documentation updates
|
||||
|
||||
---
|
||||
|
||||
## Security Issue
|
||||
|
||||
**Severity**:
|
||||
<!-- Low, Medium, or informational only -->
|
||||
|
||||
## Description
|
||||
<!-- Describe the security concern or improvement suggestion -->
|
||||
|
||||
## Affected Components
|
||||
<!-- List the affected files, features, or components -->
|
||||
|
||||
## Suggested Mitigation
|
||||
<!-- Describe how this could be addressed -->
|
||||
|
||||
## Standards Reference
|
||||
Does this relate to security standards in [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoStandards)?
|
||||
- [ ] SPDX license identifiers
|
||||
- [ ] Secret management
|
||||
- [ ] Dependency security
|
||||
- [ ] Access control
|
||||
- [ ] Other: [specify]
|
||||
|
||||
## Additional Context
|
||||
<!-- Add any other context about the security concern -->
|
||||
|
||||
## Checklist
|
||||
- [ ] This is NOT a critical vulnerability requiring private disclosure
|
||||
- [ ] I have reviewed the SECURITY.md policy
|
||||
- [ ] I have provided sufficient detail for evaluation
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
name: Version Bump
|
||||
about: Request or track a version change
|
||||
title: '[VERSION] '
|
||||
labels: 'version, type: version'
|
||||
assignees: 'jmiller'
|
||||
---
|
||||
|
||||
## Version Change
|
||||
|
||||
**Current version**: <!-- e.g., 01.02.03 -->
|
||||
**Requested version**: <!-- e.g., 01.03.00 -->
|
||||
**Change type**: <!-- patch / minor / major -->
|
||||
|
||||
## Reason
|
||||
|
||||
<!-- Why is this version bump needed? -->
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] README.md `VERSION:` field updated
|
||||
- [ ] CHANGELOG.md entry added
|
||||
- [ ] Module descriptor version updated (Dolibarr: `$this->version`, Joomla: `<version>`)
|
||||
- [ ] All file headers will be auto-propagated by `sync-version-on-merge` workflow
|
||||
@@ -1,26 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<mokoplatform xmlns="https://standards.mokoconsulting.tech/mokoplatform/1.0" schema-version="1.0">
|
||||
<identity>
|
||||
<name>MokoSuiteCross</name>
|
||||
<display-name>Package - MokoSuiteCross</display-name>
|
||||
<org>MokoConsulting</org>
|
||||
<description>Cross-posting Joomla content to social media, email marketing, and chat platforms</description>
|
||||
<version>01.06.00</version>
|
||||
<license spdx="GPL-3.0-or-later">GNU General Public License v3</license>
|
||||
</identity>
|
||||
<governance>
|
||||
<platform>joomla</platform>
|
||||
<standards-version>05.00.00</standards-version>
|
||||
<standards-source>https://git.mokoconsulting.tech/MokoConsulting/mokoplatform</standards-source>
|
||||
</governance>
|
||||
<build>
|
||||
<language>PHP</language>
|
||||
<package-type>joomla-extension</package-type>
|
||||
<entry-point>source/</entry-point>
|
||||
</build>
|
||||
<licensing>
|
||||
<enabled>true</enabled>
|
||||
<dlid>true</dlid>
|
||||
<update-server>https://git.mokoconsulting.tech/{org}/{repo}/updates.xml</update-server>
|
||||
</licensing>
|
||||
</mokoplatform>
|
||||
@@ -1,66 +1,66 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: mokocli.Release
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
# PATH: /.mokogitea/workflows/auto-bump.yml
|
||||
# VERSION: 09.02.00
|
||||
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
|
||||
|
||||
name: "Universal: Auto Version Bump"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- rc
|
||||
- 'feature/**'
|
||||
- 'patch/**'
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
bump:
|
||||
name: Version Bump
|
||||
runs-on: release
|
||||
if: >-
|
||||
!contains(github.event.head_commit.message, '[skip ci]') &&
|
||||
!contains(github.event.head_commit.message, '[skip bump]') &&
|
||||
!startsWith(github.event.head_commit.message, 'Merge pull request')
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup mokocli tools
|
||||
run: |
|
||||
if ! command -v composer &> /dev/null; then
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||
fi
|
||||
if [ -d "/opt/mokocli/cli" ]; then
|
||||
echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV"
|
||||
else
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokocli.git" \
|
||||
/tmp/mokocli
|
||||
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
|
||||
echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Bump version
|
||||
run: |
|
||||
php ${MOKO_CLI}/version_auto_bump.php \
|
||||
--path . --branch "${GITHUB_REF_NAME}" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
--repo-url "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: mokocli.Release
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
# PATH: /.mokogitea/workflows/auto-bump.yml
|
||||
# VERSION: 09.02.00
|
||||
# BRIEF: Auto patch-bump version on every push to dev (skips merge commits)
|
||||
|
||||
name: "Universal: Auto Version Bump"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
- rc
|
||||
- 'feature/**'
|
||||
- 'patch/**'
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
bump:
|
||||
name: Version Bump
|
||||
runs-on: release
|
||||
if: >-
|
||||
!contains(github.event.head_commit.message, '[skip ci]') &&
|
||||
!contains(github.event.head_commit.message, '[skip bump]') &&
|
||||
!startsWith(github.event.head_commit.message, 'Merge pull request')
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Setup mokocli tools
|
||||
run: |
|
||||
if ! command -v composer &> /dev/null; then
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||
fi
|
||||
if [ -d "/opt/mokocli/cli" ]; then
|
||||
echo "MOKO_CLI=/opt/mokocli/cli" >> "$GITHUB_ENV"
|
||||
else
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/MokoConsulting/mokocli.git" \
|
||||
/tmp/mokocli
|
||||
cd /tmp/mokocli && composer install --no-dev --no-interaction --quiet
|
||||
echo "MOKO_CLI=/tmp/mokocli/cli" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Bump version
|
||||
run: |
|
||||
php ${MOKO_CLI}/version_auto_bump.php \
|
||||
--path . --branch "${GITHUB_REF_NAME}" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
--repo-url "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||
|
||||
@@ -27,7 +27,7 @@ name: "Universal: Build & Release"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, closed]
|
||||
types: [opened, synchronize, closed]
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
@@ -52,7 +52,7 @@ on:
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||
|
||||
@@ -66,6 +66,7 @@ jobs:
|
||||
runs-on: release
|
||||
if: >-
|
||||
(github.event.action == 'opened' && github.event.pull_request.merged != true) ||
|
||||
(github.event.action == 'synchronize' && github.event.pull_request.merged != true) ||
|
||||
(github.event_name == 'workflow_dispatch' && inputs.action == 'promote-rc')
|
||||
|
||||
steps:
|
||||
@@ -101,7 +102,7 @@ jobs:
|
||||
php ${MOKO_CLI}/branch_rename.php \
|
||||
--from "${{ github.event.pull_request.head.ref || 'dev' }}" --to rc \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
--api-base "${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \
|
||||
--api-base "${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}" \
|
||||
--pr "${{ github.event.pull_request.number }}"
|
||||
|
||||
- name: Checkout rc and configure git
|
||||
@@ -120,7 +121,7 @@ jobs:
|
||||
|
||||
- name: Update RC release notes from CHANGELOG.md
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
# Extract [Unreleased] section from changelog
|
||||
@@ -268,7 +269,7 @@ jobs:
|
||||
!startsWith(steps.platform.outputs.platform, 'joomla')
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
SEMVER_TAG="v${VERSION}"
|
||||
|
||||
@@ -293,7 +294,7 @@ jobs:
|
||||
|
||||
- name: Update release notes and promote changelog
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
# Get the stable release info (version and ID)
|
||||
@@ -362,7 +363,7 @@ jobs:
|
||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
|
||||
GH_REPO="${{ vars.GH_MIRROR_REPO || github.repository }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
php ${MOKO_CLI}/release_mirror.php \
|
||||
--version "$VERSION" --tag "$RELEASE_TAG" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
@@ -391,7 +392,7 @@ jobs:
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
# Delete rc branch (ephemeral — created by promote-rc)
|
||||
@@ -415,7 +416,7 @@ jobs:
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
VERSION="${{ steps.bump.outputs.version || steps.version.outputs.version }}"
|
||||
BRANCH_NAME="version/${VERSION}"
|
||||
@@ -436,7 +437,7 @@ jobs:
|
||||
if: steps.version.outputs.skip != 'true'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
API_BASE="${MOKOGITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
php ${MOKO_CLI}/version_reset_dev.php \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "${API_BASE}" \
|
||||
--branch dev --path . 2>&1 || true
|
||||
@@ -462,5 +463,5 @@ jobs:
|
||||
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Tag | \`${{ steps.version.outputs.tag }}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Release | [View](${GITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Release | [View](${MOKOGITEA_URL}/${GITEA_ORG}/${GITEA_REPO}/releases/tag/${{ steps.version.outputs.tag }}) |" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
@@ -13,6 +13,12 @@
|
||||
name: "Generic: Project CI"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
- dev/**
|
||||
- rc/**
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: mokocli.Universal
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
# PATH: /.mokogitea/workflows/ci-issue-reporter.yml
|
||||
# VERSION: 01.00.00
|
||||
# BRIEF: Reusable workflow — creates/updates a Gitea issue when a CI gate fails.
|
||||
# Clones MokoCLI and runs cli/ci_issue_reporter.sh.
|
||||
|
||||
name: "Universal: CI Issue Reporter"
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
gate:
|
||||
description: "CI gate name (e.g. PR Validation, Repository Health)"
|
||||
required: true
|
||||
type: string
|
||||
details:
|
||||
description: "Human-readable failure description"
|
||||
required: true
|
||||
type: string
|
||||
severity:
|
||||
description: "error or warning"
|
||||
required: false
|
||||
type: string
|
||||
default: "error"
|
||||
workflow:
|
||||
description: "Workflow name for the issue title"
|
||||
required: false
|
||||
type: string
|
||||
default: ""
|
||||
secrets:
|
||||
MOKOGITEA_TOKEN:
|
||||
required: true
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
report:
|
||||
name: "Report: ${{ inputs.gate }}"
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Clone MokoCLI
|
||||
env:
|
||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
run: |
|
||||
MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
|
||||
git clone --depth 1 --filter=blob:none --sparse "${MOKOGITEA_URL}/MokoConsulting/MokoCLI.git" /tmp/mokocli
|
||||
cd /tmp/mokocli && git sparse-checkout set cli/ci_issue_reporter.sh
|
||||
|
||||
- name: Report CI failure
|
||||
env:
|
||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
run: |
|
||||
chmod +x /tmp/mokocli/cli/ci_issue_reporter.sh
|
||||
/tmp/mokocli/cli/ci_issue_reporter.sh \
|
||||
--gate "${{ inputs.gate }}" \
|
||||
--details "${{ inputs.details }}" \
|
||||
--severity "${{ inputs.severity }}" \
|
||||
--workflow "${{ inputs.workflow }}"
|
||||
@@ -164,6 +164,75 @@ jobs:
|
||||
echo "**Manifest validation passed.**" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
- name: Update server & packaging checks
|
||||
continue-on-error: true
|
||||
run: |
|
||||
echo "### Update Server & Packaging" >> $GITHUB_STEP_SUMMARY
|
||||
WARNINGS=0
|
||||
|
||||
# Find the extension manifest
|
||||
MANIFEST=""
|
||||
for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
|
||||
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
|
||||
MANIFEST="$XML_FILE"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$MANIFEST" ]; then
|
||||
echo "No manifest found — skipping." >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
EXT_TYPE=$(grep -oP '<extension[^>]*\btype="\K[^"]+' "$MANIFEST" | head -1)
|
||||
|
||||
# 1. Check <updateservers> exists and uses MokoGitea update server
|
||||
if ! grep -q '<updateservers>' "$MANIFEST" 2>/dev/null; then
|
||||
echo "::warning file=${MANIFEST}::Missing \`<updateservers>\` tag — extension will not receive OTA updates"
|
||||
echo "- **Missing** \`<updateservers>\` — extension will not receive OTA updates" >> $GITHUB_STEP_SUMMARY
|
||||
WARNINGS=$((WARNINGS + 1))
|
||||
else
|
||||
SERVER_URL=$(grep -oP '<server[^>]*>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1)
|
||||
if [ -z "$SERVER_URL" ]; then
|
||||
echo "::warning file=${MANIFEST}::\`<updateservers>\` is empty — no server URL defined"
|
||||
echo "- **Empty** \`<updateservers>\` — no server URL defined" >> $GITHUB_STEP_SUMMARY
|
||||
WARNINGS=$((WARNINGS + 1))
|
||||
elif ! echo "$SERVER_URL" | grep -q 'git\.mokoconsulting\.tech'; then
|
||||
echo "::warning file=${MANIFEST}::Update server does not use MokoGitea engine: ${SERVER_URL}"
|
||||
echo "- **Non-MokoGitea update server:** \`${SERVER_URL}\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo " Expected: \`https://git.mokoconsulting.tech/{org}/{repo}/updates.xml\`" >> $GITHUB_STEP_SUMMARY
|
||||
WARNINGS=$((WARNINGS + 1))
|
||||
else
|
||||
echo "- \`<updateservers>\`: MokoGitea engine ✓" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
fi
|
||||
|
||||
# 2. Check <dlid> tag exists
|
||||
if ! grep -q '<dlid' "$MANIFEST" 2>/dev/null; then
|
||||
echo "::warning file=${MANIFEST}::Missing \`<dlid>\` tag — download ID authentication is not configured"
|
||||
echo "- **Missing** \`<dlid>\` — download ID authentication not configured" >> $GITHUB_STEP_SUMMARY
|
||||
WARNINGS=$((WARNINGS + 1))
|
||||
else
|
||||
echo "- \`<dlid>\`: present ✓" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
# 3. For packages: check <childuninstall> tag
|
||||
if [ "$EXT_TYPE" = "package" ]; then
|
||||
if ! grep -q '<childuninstall>' "$MANIFEST" 2>/dev/null; then
|
||||
echo "::warning file=${MANIFEST}::Package is missing \`<childuninstall>\` — child extensions will not be removed on uninstall"
|
||||
echo "- **Missing** \`<childuninstall>\` — child extensions will remain when package is uninstalled" >> $GITHUB_STEP_SUMMARY
|
||||
WARNINGS=$((WARNINGS + 1))
|
||||
else
|
||||
echo "- \`<childuninstall>\`: present ✓" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
if [ "$WARNINGS" -gt 0 ]; then
|
||||
echo "**${WARNINGS} packaging warning(s).** These won't block CI but should be addressed." >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "**Update server & packaging checks passed.**" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
- name: Check language files referenced in manifest
|
||||
run: |
|
||||
echo "### Language File Check" >> $GITHUB_STEP_SUMMARY
|
||||
@@ -647,6 +716,268 @@ jobs:
|
||||
echo "**Service provider check passed.**" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
- name: Script file reference check
|
||||
run: |
|
||||
echo "### Script File Reference" >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=0
|
||||
|
||||
MANIFEST=""
|
||||
for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
|
||||
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
|
||||
MANIFEST="$XML_FILE"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$MANIFEST" ]; then
|
||||
echo "No manifest found — skipping." >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
MANIFEST_DIR=$(dirname "$MANIFEST")
|
||||
SCRIPT_FILE=$(grep -oP '<scriptfile>\K[^<]+' "$MANIFEST" 2>/dev/null | head -1)
|
||||
if [ -z "$SCRIPT_FILE" ]; then
|
||||
echo "No \`<scriptfile>\` referenced — skipping." >> $GITHUB_STEP_SUMMARY
|
||||
elif [ ! -f "${MANIFEST_DIR}/${SCRIPT_FILE}" ]; then
|
||||
echo "::error file=${MANIFEST}::Manifest references \`<scriptfile>${SCRIPT_FILE}</scriptfile>\` but file does not exist"
|
||||
echo "- **Missing** \`${SCRIPT_FILE}\` — referenced in \`<scriptfile>\` but not found" >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS + 1))
|
||||
else
|
||||
echo "- \`${SCRIPT_FILE}\`: present ✓" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
if [ "${ERRORS}" -gt 0 ]; then
|
||||
echo "**${ERRORS} script file issue(s).**" >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
else
|
||||
echo "**Script file reference check passed.**" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
- name: Media folder validation
|
||||
run: |
|
||||
echo "### Media Folder Validation" >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=0
|
||||
|
||||
MANIFEST=""
|
||||
for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
|
||||
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
|
||||
MANIFEST="$XML_FILE"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$MANIFEST" ]; then
|
||||
echo "No manifest found — skipping." >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
MANIFEST_DIR=$(dirname "$MANIFEST")
|
||||
|
||||
# Check <media> tag and its folder/filename children
|
||||
MEDIA_DEST=$(grep -oP '<media[^>]*\bdestination="\K[^"]+' "$MANIFEST" 2>/dev/null | head -1)
|
||||
MEDIA_FOLDER=$(grep -oP '<media[^>]*\bfolder="\K[^"]+' "$MANIFEST" 2>/dev/null | head -1)
|
||||
|
||||
if [ -z "$MEDIA_DEST" ] && [ -z "$MEDIA_FOLDER" ]; then
|
||||
echo "No \`<media>\` tag found — skipping." >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
if [ -n "$MEDIA_FOLDER" ] && [ ! -d "${MANIFEST_DIR}/${MEDIA_FOLDER}" ]; then
|
||||
echo "::error file=${MANIFEST}::\`<media folder=\"${MEDIA_FOLDER}\">\` references missing directory"
|
||||
echo "- **Missing** media folder \`${MEDIA_FOLDER}\`" >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS + 1))
|
||||
else
|
||||
echo "- Media folder \`${MEDIA_FOLDER:-(inline)}\`: present ✓" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Check child references inside <media> block
|
||||
if [ -n "$MEDIA_FOLDER" ]; then
|
||||
MEDIA_FOLDERS=$(sed -n '/<media /,/<\/media>/p' "$MANIFEST" | grep -oP '<folder>\K[^<]+' 2>/dev/null || true)
|
||||
for F in $MEDIA_FOLDERS; do
|
||||
if [ ! -d "${MANIFEST_DIR}/${MEDIA_FOLDER}/${F}" ]; then
|
||||
echo "- **Missing** media subfolder \`${MEDIA_FOLDER}/${F}\`" >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
MEDIA_FILES=$(sed -n '/<media /,/<\/media>/p' "$MANIFEST" | grep -oP '<filename>\K[^<]+' 2>/dev/null || true)
|
||||
for F in $MEDIA_FILES; do
|
||||
if [ ! -f "${MANIFEST_DIR}/${MEDIA_FOLDER}/${F}" ]; then
|
||||
echo "- **Missing** media file \`${MEDIA_FOLDER}/${F}\`" >> $GITHUB_STEP_SUMMARY
|
||||
ERRORS=$((ERRORS + 1))
|
||||
fi
|
||||
done
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
if [ "${ERRORS}" -gt 0 ]; then
|
||||
echo "**${ERRORS} media reference issue(s).**" >> $GITHUB_STEP_SUMMARY
|
||||
exit 1
|
||||
else
|
||||
echo "**Media folder validation passed.**" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
- name: Target platform check
|
||||
continue-on-error: true
|
||||
run: |
|
||||
echo "### Target Platform Check" >> $GITHUB_STEP_SUMMARY
|
||||
WARNINGS=0
|
||||
|
||||
MANIFEST=""
|
||||
for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
|
||||
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
|
||||
MANIFEST="$XML_FILE"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$MANIFEST" ]; then
|
||||
echo "No manifest found — skipping." >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
# Check updates.xml for targetplatform if it exists
|
||||
if [ -f "updates.xml" ]; then
|
||||
if ! grep -q '<targetplatform' "updates.xml" 2>/dev/null; then
|
||||
echo "::warning file=updates.xml::No \`<targetplatform>\` found — Joomla updater cannot filter by compatible version"
|
||||
echo "- **Missing** \`<targetplatform>\` in updates.xml" >> $GITHUB_STEP_SUMMARY
|
||||
WARNINGS=$((WARNINGS + 1))
|
||||
else
|
||||
echo "- \`<targetplatform>\` in updates.xml: present ✓" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check manifest for minimum PHP/Joomla version hints
|
||||
if ! grep -qP '<php_minimum>|targetplatform|joomla.*version' "$MANIFEST" 2>/dev/null; then
|
||||
echo "::warning file=${MANIFEST}::No minimum Joomla or PHP version constraint found in manifest"
|
||||
echo "- **Missing** version constraints (\`<php_minimum>\` or \`<targetplatform>\`)" >> $GITHUB_STEP_SUMMARY
|
||||
WARNINGS=$((WARNINGS + 1))
|
||||
else
|
||||
echo "- Version constraints in manifest: present ✓" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
if [ "$WARNINGS" -gt 0 ]; then
|
||||
echo "**${WARNINGS} target platform warning(s).**" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "**Target platform check passed.**" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
- name: Changelog URL check
|
||||
continue-on-error: true
|
||||
run: |
|
||||
echo "### Changelog URL Check" >> $GITHUB_STEP_SUMMARY
|
||||
WARNINGS=0
|
||||
|
||||
MANIFEST=""
|
||||
for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
|
||||
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
|
||||
MANIFEST="$XML_FILE"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$MANIFEST" ]; then
|
||||
echo "No manifest found — skipping." >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
if ! grep -q '<changelogurl>' "$MANIFEST" 2>/dev/null; then
|
||||
echo "::warning file=${MANIFEST}::Missing \`<changelogurl>\` — Joomla updater will not display changelogs"
|
||||
echo "- **Missing** \`<changelogurl>\` — Joomla 4+ shows changelogs in the update manager when this is set" >> $GITHUB_STEP_SUMMARY
|
||||
WARNINGS=$((WARNINGS + 1))
|
||||
else
|
||||
CHANGELOG_URL=$(grep -oP '<changelogurl>\K[^<]+' "$MANIFEST" | head -1)
|
||||
echo "- \`<changelogurl>\`: \`${CHANGELOG_URL}\` ✓" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
if [ "$WARNINGS" -gt 0 ]; then
|
||||
echo "**${WARNINGS} changelog URL warning(s).**" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "**Changelog URL check passed.**" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
- name: Duplicate file references check
|
||||
continue-on-error: true
|
||||
run: |
|
||||
echo "### Duplicate File References" >> $GITHUB_STEP_SUMMARY
|
||||
WARNINGS=0
|
||||
|
||||
MANIFEST=""
|
||||
for XML_FILE in $(find . -maxdepth 2 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
|
||||
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
|
||||
MANIFEST="$XML_FILE"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$MANIFEST" ]; then
|
||||
echo "No manifest found — skipping." >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
# Extract all <filename> and <folder> references
|
||||
ALL_REFS=$(grep -oP '<(filename|folder)[^>]*>\K[^<]+' "$MANIFEST" 2>/dev/null | sort || true)
|
||||
if [ -z "$ALL_REFS" ]; then
|
||||
echo "No file/folder references found — skipping." >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
DUPES=$(echo "$ALL_REFS" | uniq -d)
|
||||
if [ -n "$DUPES" ]; then
|
||||
while IFS= read -r DUP; do
|
||||
COUNT=$(echo "$ALL_REFS" | grep -cx "$DUP")
|
||||
echo "::warning file=${MANIFEST}::Duplicate reference: \`${DUP}\` appears ${COUNT} times (may be valid if in different sections)"
|
||||
echo "- **Duplicate:** \`${DUP}\` (${COUNT}x) — check if cross-section" >> $GITHUB_STEP_SUMMARY
|
||||
WARNINGS=$((WARNINGS + 1))
|
||||
done <<< "$DUPES"
|
||||
else
|
||||
TOTAL=$(echo "$ALL_REFS" | wc -l)
|
||||
echo "All ${TOTAL} file/folder references are unique." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
if [ "$WARNINGS" -gt 0 ]; then
|
||||
echo "**${WARNINGS} duplicate reference(s) found.** Review for cross-section validity." >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "**Duplicate file references check passed.**" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
- name: Empty language keys check
|
||||
continue-on-error: true
|
||||
run: |
|
||||
echo "### Empty Language Keys" >> $GITHUB_STEP_SUMMARY
|
||||
WARNINGS=0
|
||||
|
||||
LANG_FILES=$(find . -name "*.ini" -not -path "./.git/*" -not -path "./vendor/*" 2>/dev/null)
|
||||
if [ -z "$LANG_FILES" ]; then
|
||||
echo "No .ini language files found — skipping." >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
TOTAL_FILES=0
|
||||
for FILE in $LANG_FILES; do
|
||||
TOTAL_FILES=$((TOTAL_FILES + 1))
|
||||
# Find lines with KEY= but no value (empty or whitespace-only after =)
|
||||
EMPTY_KEYS=$(grep -nP '^[A-Z_]+=\s*$' "$FILE" 2>/dev/null || true)
|
||||
if [ -n "$EMPTY_KEYS" ]; then
|
||||
COUNT=$(echo "$EMPTY_KEYS" | wc -l)
|
||||
echo "::warning file=${FILE}::${COUNT} empty language key(s)"
|
||||
echo "- \`${FILE}\`: ${COUNT} empty key(s)" >> $GITHUB_STEP_SUMMARY
|
||||
while IFS= read -r LINE; do
|
||||
LINE_NUM=$(echo "$LINE" | cut -d: -f1)
|
||||
KEY=$(echo "$LINE" | cut -d: -f2 | cut -d= -f1)
|
||||
echo " - Line ${LINE_NUM}: \`${KEY}\`" >> $GITHUB_STEP_SUMMARY
|
||||
done <<< "$EMPTY_KEYS"
|
||||
WARNINGS=$((WARNINGS + COUNT))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$WARNINGS" -eq 0 ]; then
|
||||
echo "All ${TOTAL_FILES} language file(s) have populated keys." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
if [ "$WARNINGS" -gt 0 ]; then
|
||||
echo "**${WARNINGS} empty language key(s) across ${TOTAL_FILES} file(s).**" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "**Empty language keys check passed.**" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
release-readiness:
|
||||
name: Release Readiness Check
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -21,7 +21,7 @@ permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
|
||||
jobs:
|
||||
cleanup:
|
||||
@@ -33,17 +33,17 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.GA_TOKEN }}
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
|
||||
- name: Delete merged branches
|
||||
env:
|
||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
run: |
|
||||
echo "=== Merged Branch Cleanup ==="
|
||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||
API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||
|
||||
# List branches via API
|
||||
BRANCHES=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \
|
||||
BRANCHES=$(curl -sS -H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
||||
"${API}/branches?limit=50" | jq -r '.[].name')
|
||||
|
||||
DELETED=0
|
||||
@@ -56,7 +56,7 @@ jobs:
|
||||
# Check if branch is merged into main
|
||||
if git merge-base --is-ancestor "origin/${BRANCH}" origin/main 2>/dev/null; then
|
||||
echo " Deleting merged branch: ${BRANCH}"
|
||||
curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \
|
||||
curl -sS -X DELETE -H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
||||
"${API}/branches/${BRANCH}" 2>/dev/null || true
|
||||
DELETED=$((DELETED + 1))
|
||||
fi
|
||||
@@ -66,20 +66,20 @@ jobs:
|
||||
|
||||
- name: Clean old workflow runs
|
||||
env:
|
||||
GA_TOKEN: ${{ secrets.GA_TOKEN }}
|
||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
run: |
|
||||
echo "=== Workflow Run Cleanup ==="
|
||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||
API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||
CUTOFF=$(date -d "30 days ago" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null || date -v-30d +%Y-%m-%dT%H:%M:%SZ)
|
||||
|
||||
# Get old completed runs
|
||||
RUNS=$(curl -sS -H "Authorization: token ${GA_TOKEN}" \
|
||||
RUNS=$(curl -sS -H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
||||
"${API}/actions/runs?status=completed&limit=50" | \
|
||||
jq -r ".workflow_runs[] | select(.created_at < \"${CUTOFF}\") | .id" 2>/dev/null)
|
||||
|
||||
DELETED=0
|
||||
for RUN_ID in $RUNS; do
|
||||
curl -sS -X DELETE -H "Authorization: token ${GA_TOKEN}" \
|
||||
curl -sS -X DELETE -H "Authorization: token ${MOKOGITEA_TOKEN}" \
|
||||
"${API}/actions/runs/${RUN_ID}" 2>/dev/null || true
|
||||
DELETED=$((DELETED + 1))
|
||||
done
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
name: "Publish to Composer"
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
- '[0-9]*.[0-9]*.[0-9]*'
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish Package
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
!contains(github.event.head_commit.message, '[skip ci]') &&
|
||||
!contains(github.event.head_commit.message, '[skip publish]')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
run: |
|
||||
if ! command -v php &> /dev/null; then
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer install --no-dev --no-interaction --prefer-dist --quiet
|
||||
|
||||
- name: Determine version
|
||||
id: version
|
||||
run: |
|
||||
VERSION=$(php -r "echo json_decode(file_get_contents('composer.json'))->version;")
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "Package version: ${VERSION}"
|
||||
|
||||
# Gitea Composer Registry — auto-publishes from tags
|
||||
# The tag push itself registers the package at:
|
||||
# https://git.mokoconsulting.tech/api/packages/MokoConsulting/composer
|
||||
- name: Verify Gitea registry
|
||||
run: |
|
||||
echo "Gitea Composer registry auto-publishes from tags."
|
||||
echo "Package available at: ${GITEA_URL}/api/packages/MokoConsulting/composer"
|
||||
echo "Install: composer require mokoconsulting/mokocli"
|
||||
|
||||
# Packagist — notify of new version
|
||||
- name: Notify Packagist
|
||||
if: secrets.PACKAGIST_TOKEN != ''
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
echo "Notifying Packagist of version ${VERSION}..."
|
||||
curl -sf -X POST \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"repository":{"url":"https://git.mokoconsulting.tech/MokoConsulting/mokocli"}}' \
|
||||
"https://packagist.org/api/update-package?username=mokoconsulting&apiToken=${{ secrets.PACKAGIST_TOKEN }}" \
|
||||
&& echo "Packagist notified" \
|
||||
|| echo "::warning::Packagist notification failed (package may not be registered yet)"
|
||||
|
||||
- name: Summary
|
||||
run: |
|
||||
VERSION="${{ steps.version.outputs.version }}"
|
||||
echo "## Composer Package Published" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Registry | Status |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|----------|--------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Gitea | \`composer require mokoconsulting/mokocli:${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Packagist | \`composer require mokoconsulting/mokocli\` |" >> $GITHUB_STEP_SUMMARY
|
||||
@@ -42,10 +42,10 @@ jobs:
|
||||
|
||||
- name: Setup MokoStandards tools
|
||||
env:
|
||||
GA_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.GA_TOKEN || secrets.GA_TOKEN || github.token }}
|
||||
MOKO_CLONE_HOST: ${{ secrets.GA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
|
||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GA_TOKEN || github.token }}"}}'
|
||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN || github.token }}
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN || github.token }}
|
||||
MOKO_CLONE_HOST: ${{ secrets.MOKOGITEA_TOKEN && 'git.mokoconsulting.tech/MokoConsulting' || 'github.com/mokoconsulting-tech' }}
|
||||
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.MOKOGITEA_TOKEN || github.token }}"}}'
|
||||
run: |
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/MokoStandards-API.git" \
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: mokocli.Automation
|
||||
# VERSION: 01.06.00
|
||||
# VERSION: 01.08.61
|
||||
# BRIEF: Auto-create feature branch when an issue is opened
|
||||
|
||||
name: "Universal: Issue Branch"
|
||||
@@ -19,7 +19,7 @@ permissions:
|
||||
issues: write
|
||||
|
||||
env:
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
|
||||
jobs:
|
||||
create-branch:
|
||||
@@ -28,8 +28,8 @@ jobs:
|
||||
steps:
|
||||
- name: Create branch and comment
|
||||
run: |
|
||||
TOKEN="${{ secrets.GA_TOKEN }}"
|
||||
API="${GITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
API="${MOKOGITEA_URL}/api/v1/repos/${{ github.repository }}"
|
||||
ISSUE_NUM="${{ github.event.issue.number }}"
|
||||
ISSUE_TITLE="${{ github.event.issue.title }}"
|
||||
|
||||
@@ -58,7 +58,7 @@ jobs:
|
||||
echo "Created branch: ${BRANCH}"
|
||||
|
||||
# Comment on issue with branch link
|
||||
REPO_URL="${GITEA_URL}/${{ github.repository }}"
|
||||
REPO_URL="${MOKOGITEA_URL}/${{ github.repository }}"
|
||||
BODY="Branch created: [\`${BRANCH}\`](${REPO_URL}/src/branch/${BRANCH})\n\n\`\`\`bash\ngit fetch origin\ngit checkout ${BRANCH}\n\`\`\`"
|
||||
|
||||
curl -sf -X POST \
|
||||
|
||||
+521
-534
File diff suppressed because it is too large
Load Diff
@@ -20,7 +20,7 @@ permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
MOKOGITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||
|
||||
@@ -55,14 +55,14 @@ jobs:
|
||||
|
||||
- name: Validate metadata against Joomla manifest
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
run: |
|
||||
php ${MOKO_CLI}/joomla_metadata_validate.php \
|
||||
--path . \
|
||||
--token "${GITEA_TOKEN}" \
|
||||
--token "${MOKOGITEA_TOKEN}" \
|
||||
--org "${GITEA_ORG}" \
|
||||
--repo "${GITEA_REPO}" \
|
||||
--api-base "${GITEA_URL}/api/v1" \
|
||||
--api-base "${MOKOGITEA_URL}/api/v1" \
|
||||
--ci
|
||||
|
||||
if [ $? -ne 0 ]; then
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# INGROUP: mokocli.Release
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/mokocli
|
||||
# PATH: /templates/workflows/universal/pre-release.yml.template
|
||||
# VERSION: 05.01.00
|
||||
# VERSION: 05.02.00
|
||||
# BRIEF: Auto pre-release on push to dev/alpha/beta/rc branches
|
||||
|
||||
name: "Universal: Pre-Release"
|
||||
@@ -59,6 +59,11 @@ jobs:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
ref: ${{ github.ref_name }}
|
||||
submodules: recursive
|
||||
|
||||
- name: Update submodules to main
|
||||
run: |
|
||||
git submodule foreach --quiet 'git checkout main && git pull --quiet origin main' 2>/dev/null || true
|
||||
|
||||
- name: Setup mokocli tools
|
||||
env:
|
||||
@@ -88,8 +93,20 @@ jobs:
|
||||
php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
|
||||
php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
||||
|
||||
- name: Check platform eligibility (Joomla only)
|
||||
id: eligibility
|
||||
run: |
|
||||
PLATFORM="${{ steps.platform.outputs.platform }}"
|
||||
if [[ "$PLATFORM" == joomla* ]] || [[ "$PLATFORM" == "joomla" ]]; then
|
||||
echo "proceed=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "proceed=false" >> "$GITHUB_OUTPUT"
|
||||
echo "::notice::Platform '$PLATFORM' — non-Joomla, skipping pre-release auto-bump"
|
||||
fi
|
||||
|
||||
- name: Resolve metadata and bump version
|
||||
id: meta
|
||||
if: steps.eligibility.outputs.proceed == 'true'
|
||||
run: |
|
||||
# Auto-detect stability from branch name on push, or use input on dispatch
|
||||
if [ "${{ github.event_name }}" = "push" ]; then
|
||||
@@ -166,6 +183,7 @@ jobs:
|
||||
|
||||
- name: Create release
|
||||
id: release
|
||||
if: steps.eligibility.outputs.proceed == 'true'
|
||||
run: |
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
@@ -176,6 +194,7 @@ jobs:
|
||||
--repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease
|
||||
|
||||
- name: Update release notes from CHANGELOG.md
|
||||
if: steps.eligibility.outputs.proceed == 'true'
|
||||
run: |
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
@@ -212,6 +231,7 @@ jobs:
|
||||
|
||||
- name: Build package and upload
|
||||
id: package
|
||||
if: steps.eligibility.outputs.proceed == 'true'
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
@@ -225,6 +245,7 @@ jobs:
|
||||
# No need to build, commit, or sync updates.xml from workflows
|
||||
|
||||
- name: "Delete lesser pre-release channels (cascade)"
|
||||
if: steps.eligibility.outputs.proceed == 'true'
|
||||
continue-on-error: true
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
|
||||
@@ -29,12 +29,20 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Rename branch
|
||||
env:
|
||||
BRANCH: ${{ github.event.pull_request.head.ref }}
|
||||
REPO: ${{ github.repository }}
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
run: |
|
||||
BRANCH="${{ github.event.pull_request.head.ref }}"
|
||||
set -euo pipefail
|
||||
# BRANCH is attacker-controlled (PR head ref). Strict allowlist before ANY use.
|
||||
if ! printf '%s' "$BRANCH" | grep -Eq '^rc/[A-Za-z0-9._/-]+$'; then
|
||||
echo "::error::Refusing unsafe branch name: $BRANCH"; exit 1
|
||||
fi
|
||||
SUFFIX="${BRANCH#rc/}"
|
||||
DEV_BRANCH="dev/${SUFFIX}"
|
||||
API="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}/api/v1/repos/${{ github.repository }}/branches"
|
||||
TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
API="${GITEA_URL}/api/v1/repos/${REPO}/branches"
|
||||
|
||||
# Create dev/ branch from rc/ branch
|
||||
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X POST \
|
||||
@@ -42,25 +50,22 @@ jobs:
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"new_branch_name\": \"${DEV_BRANCH}\", \"old_branch_name\": \"${BRANCH}\"}" \
|
||||
"${API}" 2>/dev/null || true)
|
||||
|
||||
if [ "$STATUS" = "201" ]; then
|
||||
echo "Created branch: ${DEV_BRANCH}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Created branch: ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})"
|
||||
exit 1
|
||||
echo "::error::Failed to create ${DEV_BRANCH} from ${BRANCH} (HTTP ${STATUS})"; exit 1
|
||||
fi
|
||||
|
||||
# Delete rc/ branch
|
||||
ENCODED=$(php -r "echo rawurlencode('${BRANCH}');")
|
||||
# Read BRANCH from the environment inside PHP (getenv, no string interpolation -> no PHP injection)
|
||||
ENCODED=$(php -r 'echo rawurlencode(getenv("BRANCH"));')
|
||||
STATUS=$(curl -sf -o /dev/null -w "%{http_code}" -X DELETE \
|
||||
-H "Authorization: token ${TOKEN}" \
|
||||
"${API}/${ENCODED}" 2>/dev/null || true)
|
||||
|
||||
if [ "$STATUS" = "204" ]; then
|
||||
echo "Deleted branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Deleted branch: ${BRANCH}" >> "$GITHUB_STEP_SUMMARY"
|
||||
else
|
||||
echo "::warning::Failed to delete ${BRANCH} (HTTP ${STATUS})"
|
||||
fi
|
||||
|
||||
echo "### RC Reverted" >> $GITHUB_STEP_SUMMARY
|
||||
echo "${BRANCH} → ${DEV_BRANCH}" >> $GITHUB_STEP_SUMMARY
|
||||
echo "### RC Reverted" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "${BRANCH} → ${DEV_BRANCH}" >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,82 +0,0 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: MokoStandards.Security
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/MokoStandards
|
||||
# PATH: /.gitea/workflows/security-audit.yml
|
||||
# VERSION: 01.00.00
|
||||
# BRIEF: Dependency vulnerability scanning for composer and npm packages
|
||||
|
||||
name: "Universal: Security Audit"
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 6 * * 1' # Weekly on Monday at 06:00 UTC
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'composer.json'
|
||||
- 'composer.lock'
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
env:
|
||||
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
|
||||
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-security' }}
|
||||
|
||||
jobs:
|
||||
audit:
|
||||
name: Dependency Audit
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Composer audit
|
||||
if: hashFiles('composer.lock') != ''
|
||||
run: |
|
||||
echo "=== Composer Security Audit ==="
|
||||
if ! command -v composer &> /dev/null; then
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq php-cli composer >/dev/null 2>&1
|
||||
fi
|
||||
composer audit --format=plain 2>&1 | tee /tmp/composer-audit.txt
|
||||
RESULT=$?
|
||||
if [ $RESULT -ne 0 ]; then
|
||||
echo "::warning::Composer vulnerabilities found"
|
||||
echo "composer_vulnerable=true" >> "$GITHUB_ENV"
|
||||
else
|
||||
echo "No known vulnerabilities in composer dependencies"
|
||||
fi
|
||||
|
||||
- name: NPM audit
|
||||
if: hashFiles('package-lock.json') != ''
|
||||
run: |
|
||||
echo "=== NPM Security Audit ==="
|
||||
npm audit --production 2>&1 | tee /tmp/npm-audit.txt || true
|
||||
if npm audit --production 2>&1 | grep -q "found 0 vulnerabilities"; then
|
||||
echo "No known vulnerabilities in npm dependencies"
|
||||
else
|
||||
echo "::warning::NPM vulnerabilities found"
|
||||
echo "npm_vulnerable=true" >> "$GITHUB_ENV"
|
||||
fi
|
||||
|
||||
- name: Notify on vulnerabilities
|
||||
if: env.composer_vulnerable == 'true' || env.npm_vulnerable == 'true'
|
||||
run: |
|
||||
REPO="${{ github.event.repository.name }}"
|
||||
curl -sS \
|
||||
-H "Title: ${REPO} has vulnerable dependencies" \
|
||||
-H "Tags: lock,warning" \
|
||||
-H "Priority: high" \
|
||||
-d "Security audit found vulnerabilities. Review dependency updates." \
|
||||
"${NTFY_URL}/${NTFY_TOPIC}" || true
|
||||
@@ -1,312 +0,0 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow
|
||||
# INGROUP: moko-platform.Universal
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/moko-platform
|
||||
# PATH: /templates/workflows/update-server.yml
|
||||
# VERSION: 05.00.00
|
||||
# BRIEF: Pre-release build + update server XML for dev/alpha/beta/rc branches
|
||||
#
|
||||
# Thin wrapper around moko-platform CLI tools.
|
||||
# Builds packages, updates updates.xml, and optionally deploys via SFTP.
|
||||
#
|
||||
# Joomla filters update entries by the user's "Minimum Stability" setting.
|
||||
|
||||
name: "Update Server"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'dev'
|
||||
- 'dev/**'
|
||||
- 'alpha/**'
|
||||
- 'beta/**'
|
||||
- 'rc/**'
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'htdocs/**'
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches:
|
||||
- 'dev'
|
||||
- 'dev/**'
|
||||
- 'alpha/**'
|
||||
- 'beta/**'
|
||||
- 'rc/**'
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'htdocs/**'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
stability:
|
||||
description: 'Stability tag'
|
||||
required: true
|
||||
default: 'development'
|
||||
type: choice
|
||||
options:
|
||||
- development
|
||||
- alpha
|
||||
- beta
|
||||
- rc
|
||||
- stable
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
GITEA_URL: ${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}
|
||||
GITEA_ORG: ${{ vars.GITEA_ORG || github.repository_owner }}
|
||||
GITEA_REPO: ${{ vars.GITEA_REPO || github.event.repository.name }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
update-xml:
|
||||
name: Update Server
|
||||
runs-on: release
|
||||
if: >-
|
||||
github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch' || github.event_name == 'push'
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup moko-platform tools
|
||||
env:
|
||||
MOKO_CLONE_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
MOKO_CLONE_HOST: git.mokoconsulting.tech/MokoConsulting
|
||||
COMPOSER_AUTH: '{"http-basic":{"git.mokoconsulting.tech":{"username":"token","password":"${{ secrets.MOKOGITEA_TOKEN }}"}}}'
|
||||
run: |
|
||||
if ! command -v composer &> /dev/null; then
|
||||
sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer >/dev/null 2>&1
|
||||
fi
|
||||
# Always fetch latest CLI tools — never use stale cache from previous runs
|
||||
rm -rf /tmp/moko-platform
|
||||
git clone --depth 1 --branch main --quiet \
|
||||
"https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/moko-platform.git" \
|
||||
/tmp/moko-platform 2>/dev/null || true
|
||||
if [ -d "/tmp/moko-platform" ] && [ -f "/tmp/moko-platform/composer.json" ]; then
|
||||
cd /tmp/moko-platform && composer install --no-dev --no-interaction --quiet 2>/dev/null || true
|
||||
fi
|
||||
echo "MOKO_CLI=/tmp/moko-platform/cli" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Detect platform
|
||||
id: platform
|
||||
run: php ${MOKO_CLI}/manifest_read.php --path . --github-output
|
||||
|
||||
- name: Resolve stability and bump version
|
||||
id: meta
|
||||
run: |
|
||||
BRANCH="${{ github.ref_name }}"
|
||||
|
||||
# Configure git for bot pushes
|
||||
git config --local user.email "gitea-actions[bot]@mokoconsulting.tech"
|
||||
git config --local user.name "gitea-actions[bot]"
|
||||
git remote set-url origin "https://x-access-token:${{ secrets.MOKOGITEA_TOKEN }}@git.mokoconsulting.tech/${{ github.repository }}.git"
|
||||
|
||||
# Auto-bump patch version
|
||||
php ${MOKO_CLI}/version_bump.php --path . 2>/dev/null || true
|
||||
|
||||
VERSION=$(php ${MOKO_CLI}/version_read.php --path . 2>/dev/null || echo "0.0.0")
|
||||
|
||||
# Strip any existing suffix before applying stability
|
||||
VERSION=$(echo "$VERSION" | sed 's/-\(dev\|alpha\|beta\|rc\)$//')
|
||||
|
||||
# Determine stability from branch or manual input
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
STABILITY="${{ inputs.stability }}"
|
||||
elif [[ "$BRANCH" == rc/* ]]; then
|
||||
STABILITY="rc"
|
||||
elif [[ "$BRANCH" == beta/* ]]; then
|
||||
STABILITY="beta"
|
||||
elif [[ "$BRANCH" == alpha/* ]]; then
|
||||
STABILITY="alpha"
|
||||
else
|
||||
STABILITY="development"
|
||||
fi
|
||||
|
||||
# Version suffix per stability stream
|
||||
case "$STABILITY" in
|
||||
development) SUFFIX="-dev"; TAG="development" ;;
|
||||
alpha) SUFFIX="-alpha"; TAG="alpha" ;;
|
||||
beta) SUFFIX="-beta"; TAG="beta" ;;
|
||||
rc) SUFFIX="-rc"; TAG="release-candidate" ;;
|
||||
*) SUFFIX=""; TAG="stable" ;;
|
||||
esac
|
||||
|
||||
# Propagate version with stability suffix to all manifest files
|
||||
php ${MOKO_CLI}/version_set_platform.php \
|
||||
--path . --version "$VERSION" --branch "$BRANCH" --stability "$STABILITY" 2>/dev/null || true
|
||||
php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
|
||||
|
||||
# Re-read version (now includes suffix from version_set_platform)
|
||||
if [ -n "$SUFFIX" ]; then
|
||||
VERSION="${VERSION}${SUFFIX}"
|
||||
fi
|
||||
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
echo "stability=${STABILITY}" >> "$GITHUB_OUTPUT"
|
||||
echo "suffix=${SUFFIX}" >> "$GITHUB_OUTPUT"
|
||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "display_version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Commit version bump if changed
|
||||
git add -A
|
||||
git diff --cached --quiet || {
|
||||
git commit -m "chore(version): auto-bump ${VERSION} [skip ci]" \
|
||||
--author="gitea-actions[bot] <gitea-actions[bot]@mokoconsulting.tech>"
|
||||
git push
|
||||
}
|
||||
|
||||
- name: Create release and upload package
|
||||
id: package
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
TAG="${{ steps.meta.outputs.tag }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
|
||||
# Create or update Gitea release
|
||||
php ${MOKO_CLI}/release_create.php \
|
||||
--path . --version "$VERSION" --tag "$TAG" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--repo "${GITEA_REPO}" --branch "${{ github.ref_name }}" --prerelease
|
||||
|
||||
# Build package and upload
|
||||
php ${MOKO_CLI}/release_package.php \
|
||||
--path . --version "$VERSION" --tag "$TAG" \
|
||||
--token "${{ secrets.MOKOGITEA_TOKEN }}" --api-base "$API_BASE" \
|
||||
--repo "${GITEA_REPO}" --output /tmp || true
|
||||
|
||||
- name: Update updates.xml
|
||||
if: steps.platform.outputs.platform == 'joomla'
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
SHA256="${{ steps.package.outputs.sha256_zip }}"
|
||||
|
||||
if [ ! -f "updates.xml" ]; then
|
||||
echo "No updates.xml — skipping"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
SHA_FLAG=""
|
||||
[ -n "$SHA256" ] && SHA_FLAG="--sha ${SHA256}"
|
||||
|
||||
php ${MOKO_CLI}/updates_xml_build.php \
|
||||
--path . --version "${VERSION}" --stability "${STABILITY}" \
|
||||
--gitea-url "${GITEA_URL}" --org "${GITEA_ORG}" --repo "${GITEA_REPO}" \
|
||||
${SHA_FLAG}
|
||||
|
||||
# Commit and push updates.xml
|
||||
git add updates.xml
|
||||
git diff --cached --quiet || {
|
||||
git commit -m "chore: update ${STABILITY} channel ${VERSION} [skip ci]"
|
||||
git push
|
||||
}
|
||||
|
||||
- name: Sync updates.xml to main
|
||||
if: github.ref_name != 'main' && steps.platform.outputs.platform == 'joomla'
|
||||
run: |
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
GITEA_TOKEN="${{ secrets.MOKOGITEA_TOKEN }}"
|
||||
|
||||
FILE_SHA=$(curl -sf -H "Authorization: token ${GITEA_TOKEN}" \
|
||||
"${API_BASE}/contents/updates.xml?ref=main" | python3 -c "import sys,json; print(json.load(sys.stdin).get('sha',''))" 2>/dev/null || true)
|
||||
|
||||
if [ -n "$FILE_SHA" ] && [ -f "updates.xml" ]; then
|
||||
python3 -c "
|
||||
import base64, json, urllib.request, sys
|
||||
with open('updates.xml', 'rb') as f:
|
||||
content = base64.b64encode(f.read()).decode()
|
||||
payload = json.dumps({
|
||||
'content': content,
|
||||
'sha': '${FILE_SHA}',
|
||||
'message': 'chore: sync updates.xml from ${{ steps.meta.outputs.stability }} [skip ci]',
|
||||
'branch': 'main'
|
||||
}).encode()
|
||||
req = urllib.request.Request(
|
||||
'${API_BASE}/contents/updates.xml',
|
||||
data=payload, method='PUT',
|
||||
headers={
|
||||
'Authorization': 'token ${GITEA_TOKEN}',
|
||||
'Content-Type': 'application/json'
|
||||
})
|
||||
try:
|
||||
urllib.request.urlopen(req)
|
||||
print('updates.xml synced to main')
|
||||
except Exception as e:
|
||||
print(f'WARNING: sync to main failed: {e}', file=sys.stderr)
|
||||
"
|
||||
fi
|
||||
|
||||
- name: SFTP deploy to dev server
|
||||
if: contains(github.ref, 'dev/') || github.ref == 'refs/heads/dev'
|
||||
env:
|
||||
DEV_HOST: ${{ vars.DEV_FTP_HOST }}
|
||||
DEV_PATH: ${{ vars.DEV_FTP_PATH }}
|
||||
DEV_SUFFIX: ${{ vars.DEV_FTP_SUFFIX }}
|
||||
DEV_USER: ${{ vars.DEV_FTP_USERNAME }}
|
||||
DEV_PORT: ${{ vars.DEV_FTP_PORT }}
|
||||
DEV_KEY: ${{ secrets.DEV_FTP_KEY }}
|
||||
DEV_PASS: ${{ secrets.DEV_FTP_PASSWORD }}
|
||||
run: |
|
||||
# Permission check: admin or maintain role required
|
||||
ACTOR="${{ github.actor }}"
|
||||
API_BASE="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
|
||||
|
||||
PERMISSION=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGITEA_TOKEN }}" \
|
||||
"${API_BASE}/collaborators/${ACTOR}/permission" 2>/dev/null | \
|
||||
python3 -c "import sys,json; print(json.load(sys.stdin).get('permission','read'))" 2>/dev/null || echo "read")
|
||||
case "$PERMISSION" in
|
||||
admin|maintain|write) ;;
|
||||
*)
|
||||
echo "Deploy denied: ${ACTOR} has '${PERMISSION}' — requires admin, maintain, or write"
|
||||
exit 0
|
||||
;;
|
||||
esac
|
||||
|
||||
[ -z "$DEV_HOST" ] || [ -z "$DEV_PATH" ] && { echo "DEV FTP not configured — skipping SFTP"; exit 0; }
|
||||
|
||||
SOURCE_DIR="src"
|
||||
[ ! -d "$SOURCE_DIR" ] && SOURCE_DIR="htdocs"
|
||||
[ ! -d "$SOURCE_DIR" ] && exit 0
|
||||
|
||||
PORT="${DEV_PORT:-22}"
|
||||
REMOTE="${DEV_PATH%/}"
|
||||
[ -n "$DEV_SUFFIX" ] && REMOTE="${REMOTE}/${DEV_SUFFIX#/}"
|
||||
|
||||
printf '{"host":"%s","port":%s,"username":"%s","remotePath":"%s"' \
|
||||
"$DEV_HOST" "$PORT" "$DEV_USER" "$REMOTE" > /tmp/sftp-config.json
|
||||
if [ -n "$DEV_KEY" ]; then
|
||||
echo "$DEV_KEY" > /tmp/deploy_key && chmod 600 /tmp/deploy_key
|
||||
printf ',"privateKeyPath":"/tmp/deploy_key"}' >> /tmp/sftp-config.json
|
||||
else
|
||||
printf ',"password":"%s"}' "$DEV_PASS" >> /tmp/sftp-config.json
|
||||
fi
|
||||
|
||||
PLATFORM=$(php ${MOKO_CLI}/platform_detect.php --path . 2>/dev/null || true)
|
||||
if [ "$PLATFORM" = "waas-component" ] && [ -f "${MOKO_CLI}/../deploy/deploy-joomla.php" ]; then
|
||||
php ${MOKO_CLI}/../deploy/deploy-joomla.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
||||
elif [ -f "${MOKO_CLI}/../deploy/deploy-sftp.php" ]; then
|
||||
php ${MOKO_CLI}/../deploy/deploy-sftp.php --path . --src-dir "$SOURCE_DIR" --config /tmp/sftp-config.json
|
||||
fi
|
||||
rm -f /tmp/deploy_key /tmp/sftp-config.json
|
||||
echo "SFTP deploy to dev complete" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Summary
|
||||
if: always()
|
||||
run: |
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
STABILITY="${{ steps.meta.outputs.stability }}"
|
||||
DISPLAY="${{ steps.meta.outputs.display_version }}"
|
||||
echo "## Update Server" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Field | Value |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|-------|-------|" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Stability | \`${STABILITY}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Version | \`${DISPLAY}\` |" >> $GITHUB_STEP_SUMMARY
|
||||
@@ -0,0 +1,130 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# FILE INFORMATION
|
||||
# DEFGROUP: Gitea.Workflow.Template
|
||||
# INGROUP: MokoStandards.CI
|
||||
# REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
|
||||
# PATH: /.mokogitea/workflows/version-set.yml
|
||||
# VERSION: 01.00.00
|
||||
# BRIEF: Set or reset the extension version across all version-bearing files
|
||||
|
||||
name: "Joomla: Set Version"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: "Version number (e.g. 01.00.00)"
|
||||
required: true
|
||||
type: string
|
||||
branch:
|
||||
description: "Branch to update (default: current)"
|
||||
required: false
|
||||
type: string
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
|
||||
|
||||
jobs:
|
||||
set-version:
|
||||
name: Set Version to ${{ inputs.version }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Validate version format
|
||||
run: |
|
||||
VERSION="${{ inputs.version }}"
|
||||
if ! echo "$VERSION" | grep -qP '^\d{2}\.\d{2}\.\d{2}$'; then
|
||||
echo "::error::Invalid version format '${VERSION}' — expected XX.YY.ZZ (e.g. 01.00.00)"
|
||||
exit 1
|
||||
fi
|
||||
echo "VERSION=${VERSION}" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
token: ${{ secrets.MOKOGITEA_TOKEN || github.token }}
|
||||
ref: ${{ inputs.branch || github.ref }}
|
||||
fetch-depth: 1
|
||||
|
||||
- name: Update manifest version
|
||||
run: |
|
||||
MANIFEST=""
|
||||
for XML_FILE in $(find . -maxdepth 3 -name "*.xml" -not -path "./.git/*" -not -path "./vendor/*"); do
|
||||
if grep -q "<extension" "$XML_FILE" 2>/dev/null; then
|
||||
MANIFEST="$XML_FILE"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -z "$MANIFEST" ]; then
|
||||
echo "::warning::No Joomla extension manifest found — skipping manifest update"
|
||||
else
|
||||
OLD_VER=$(grep -oP '<version>\K[^<]+' "$MANIFEST" | head -1)
|
||||
sed -i "s|<version>${OLD_VER}</version>|<version>${VERSION}</version>|" "$MANIFEST"
|
||||
echo "Manifest: ${OLD_VER} → ${VERSION} (${MANIFEST})"
|
||||
fi
|
||||
|
||||
- name: Update README.md version
|
||||
run: |
|
||||
if [ -f "README.md" ]; then
|
||||
if grep -qP '^\s*VERSION:\s*\d' README.md; then
|
||||
sed -i -E "s/(VERSION:\s*)[0-9]{2}\.[0-9]{2}\.[0-9]{2}/\1${VERSION}/" README.md
|
||||
echo "README.md version updated to ${VERSION}"
|
||||
else
|
||||
echo "::warning::No VERSION line found in README.md — skipping"
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Update CHANGELOG.md
|
||||
run: |
|
||||
if [ -f "CHANGELOG.md" ]; then
|
||||
DATE=$(date +%Y-%m-%d)
|
||||
# Check if this version already has an entry
|
||||
if grep -q "^\#\# \[${VERSION}\]" CHANGELOG.md; then
|
||||
echo "CHANGELOG.md already has entry for ${VERSION} — skipping"
|
||||
else
|
||||
# Insert new version entry after [Unreleased] or at the top after header
|
||||
if grep -q '^\#\# \[Unreleased\]' CHANGELOG.md; then
|
||||
sed -i "/^\#\# \[Unreleased\]/a\\\\n## [${VERSION}] --- ${DATE}" CHANGELOG.md
|
||||
else
|
||||
sed -i "/^\# Changelog/a\\\\n## [Unreleased]\n\n## [${VERSION}] --- ${DATE}" CHANGELOG.md
|
||||
fi
|
||||
echo "CHANGELOG.md: added entry for ${VERSION}"
|
||||
fi
|
||||
else
|
||||
echo "::warning::No CHANGELOG.md found — skipping"
|
||||
fi
|
||||
|
||||
- name: Update FILE INFORMATION blocks
|
||||
run: |
|
||||
# Update VERSION in file header blocks (# VERSION: XX.YY.ZZ)
|
||||
find . -maxdepth 1 -type f \( -name "*.yml" -o -name "*.yaml" -o -name "*.php" -o -name "*.md" \) \
|
||||
-not -path "./.git/*" -not -path "./vendor/*" -print0 2>/dev/null | \
|
||||
while IFS= read -r -d '' FILE; do
|
||||
if head -20 "$FILE" | grep -qP '^\s*#?\s*VERSION:\s*\d{2}\.\d{2}\.\d{2}'; then
|
||||
sed -i -E "s/(#?\s*VERSION:\s*)[0-9]{2}\.[0-9]{2}\.[0-9]{2}/\1${VERSION}/" "$FILE"
|
||||
echo "Updated FILE INFORMATION VERSION in ${FILE}"
|
||||
fi
|
||||
done
|
||||
|
||||
- name: Commit and push
|
||||
run: |
|
||||
git config user.name "Moko Consulting [bot]"
|
||||
git config user.email "hello@mokoconsulting.tech"
|
||||
git add -A
|
||||
if git diff --cached --quiet; then
|
||||
echo "No version changes detected — nothing to commit"
|
||||
else
|
||||
git commit -m "chore: set version to ${VERSION} [skip bump]
|
||||
|
||||
Authored-by: Moko Consulting"
|
||||
git push
|
||||
echo "### Version Set" >> $GITHUB_STEP_SUMMARY
|
||||
echo "Version updated to \`${VERSION}\` on branch \`${GITHUB_REF_NAME}\`" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
@@ -13,6 +13,7 @@
|
||||
name: "Universal: Workflow Sync Trigger"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
branches:
|
||||
@@ -26,8 +27,9 @@ jobs:
|
||||
name: Sync workflows to live repos
|
||||
runs-on: ubuntu-latest
|
||||
if: >-
|
||||
github.event.pull_request.merged == true &&
|
||||
!contains(github.event.pull_request.title, '[skip sync]')
|
||||
github.event_name == 'workflow_dispatch' ||
|
||||
(github.event.pull_request.merged == true &&
|
||||
!contains(github.event.pull_request.title, '[skip sync]'))
|
||||
|
||||
steps:
|
||||
- name: Determine platform from repo name
|
||||
@@ -49,8 +51,14 @@ jobs:
|
||||
env:
|
||||
MOKOGITEA_TOKEN: ${{ secrets.MOKOGITEA_TOKEN }}
|
||||
run: |
|
||||
GITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
|
||||
git clone --depth 1 "${GITEA_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
|
||||
MOKOGITEA_URL="${{ vars.GITEA_URL || 'https://git.mokoconsulting.tech' }}"
|
||||
git clone --depth 1 "${MOKOGITEA_URL}/MokoConsulting/mokocli.git" /tmp/mokocli
|
||||
|
||||
- name: Install PHP
|
||||
run: |
|
||||
if ! command -v php &> /dev/null; then
|
||||
apt-get update -qq && apt-get install -y -qq php-cli php-json php-curl > /dev/null 2>&1
|
||||
fi
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
|
||||
+71
-2
@@ -1,9 +1,62 @@
|
||||
# Changelog
|
||||
## [Unreleased]
|
||||
|
||||
## [01.06.00] --- 2026-06-23
|
||||
### Added
|
||||
- **Visual post calendar**: Monthly calendar grid view showing scheduled, queued, and posted cross-posts with status badges (#160)
|
||||
- **Calendar navigation**: Month-by-month navigation with today highlighting (#160)
|
||||
- **Posting analytics**: Best time to post heatmap with day-of-week and hour-of-day breakdown (#165)
|
||||
- **Analytics service filter**: Filter heatmap and stats by service type with configurable date range
|
||||
- **Analytics service breakdown**: Per-service success rate, failure count, and average posts per day
|
||||
- **Analytics AJAX endpoint**: JSON heatmap data for dynamic filtering without page reload
|
||||
- **Social image generator**: Generate Open Graph images with article title overlay using PHP GD library (#157)
|
||||
- **Social image config**: Background color, text color, overlay style, and site name override in component options (#157)
|
||||
- **AI caption generation**: Generate platform-optimized cross-post captions from article content using Claude or OpenAI (#161)
|
||||
- **AI provider config**: New "AI Caption Generation" fieldset in component options with provider, API key, model, and tone settings
|
||||
- **AI Generate button**: One-click AI generation button in the Share Content panel that fills all caption fields
|
||||
- **X/Twitter threads**: Auto-split messages exceeding 280 chars into reply chains at sentence boundaries
|
||||
- **X/Twitter cost-optimized posting**: Optional mode to post text-only tweet first ($0.015) with URL as separate reply ($0.20)
|
||||
- **X/Twitter cost warning**: Language string documenting X API pricing for text vs URL posts
|
||||
- **Instagram carousel**: Multi-image/video posts via Meta carousel container flow (up to 10 items)
|
||||
- **Instagram Reels**: Short-form video publishing via REELS media type
|
||||
- **Instagram Stories**: Image and video story publishing via STORIES media type
|
||||
- **Instagram alt text**: Alt text support for image containers
|
||||
- **Nostr plugin**: Full NIP-01 WebSocket relay publishing with BIP-340 Schnorr signatures (pure PHP, requires ext-gmp)
|
||||
- **Nostr**: Publishes kind-1 text note events to multiple relays with automatic failover
|
||||
- **Nostr**: Raw WebSocket client using stream_socket_client (no external dependencies)
|
||||
- **Nostr**: Public key derivation and event signing via secp256k1 elliptic curve math
|
||||
- **Threads carousel**: Support up to 20-item carousel posts via Threads API multi-container flow
|
||||
- **Threads polls**: Poll creation support via poll_options parameter (2-4 options)
|
||||
- **Threads spoiler tags**: Content warning / spoiler flag support for Threads posts
|
||||
- **Threads text-only optimization**: Simplified single-step flow for text-only posts without media
|
||||
- **Facebook Reels**: Publish video Reels via Graph API video_reels endpoint (#162)
|
||||
- **Facebook Stories**: Publish image and video Stories via photo_stories/video_stories endpoints (#162)
|
||||
- **Facebook scheduled posts**: Schedule feed posts with scheduled_publish_time parameter (#162)
|
||||
- **Facebook draft posts**: Save feed posts as unpublished drafts (#162)
|
||||
- **TikTok video upload**: PULL_FROM_URL video publishing via video/init endpoint with status polling (#164)
|
||||
- **TikTok photo carousel**: Up to 35 image carousel posts via content/init endpoint (#164)
|
||||
- **TikTok posting mode**: Configurable DIRECT_POST or MEDIA_UPLOAD (sends to TikTok inbox for in-app editing) (#164)
|
||||
- **TikTok audit warning**: Language string explaining that unverified apps can only create private posts (#164)
|
||||
- **Link shortening**: Shorten URLs via Bitly, Rebrandly, or YOURLS with {url_short} placeholder (#159)
|
||||
- **Site frontend**: Public-facing cross-post list and detail views for site visitors (#133)
|
||||
- **Social preview**: AJAX platform mockups (Twitter, Facebook, LinkedIn, Instagram, Mastodon, Bluesky) in article editor (#156)
|
||||
- **PHPUnit test suite**: Unit tests for models, helpers, and service plugins (#132)
|
||||
|
||||
## [01.06.00] --- 2026-06-23
|
||||
### Fixed
|
||||
- **PreviewController**: Add ACL check and parameterized query to prevent unauthorized article access (IDOR)
|
||||
- Webservices plugin Joomla 6 compatibility — `onBeforeApiRoute` receives `BeforeApiRouteEvent` object, extract router via `$event->getRouter()`
|
||||
|
||||
## [01.07.00] --- 2026-06-23
|
||||
|
||||
## [01.07.00] --- 2026-06-23
|
||||
|
||||
### Added
|
||||
- **Full ACL system**: 12 granular permissions in access.xml with permissions fieldset in config.xml
|
||||
- **ACL enforcement**: All controllers and views check permissions before allowing actions
|
||||
- **MokoSuiteCrossHelper::getActions()**: Centralized ACL helper for toolbar and view logic
|
||||
|
||||
### Fixed
|
||||
- **License warning**: Removed duplicate from system plugin (install script already shows it)
|
||||
- **Content plugin**: Fixed func_get_arg crash when non-article content is saved (e.g. update sites, installer)
|
||||
|
||||
## [01.05.00] --- 2026-06-23
|
||||
|
||||
@@ -45,3 +98,19 @@
|
||||
|
||||
|
||||
## [01.04.01] --- 2026-06-21
|
||||
|
||||
|
||||
## [01.04.00] --- 2026-06-21
|
||||
|
||||
### Fixed
|
||||
- **Package manifest**: Added missing `plg_system_mokosuitecross_events` and `plg_system_mokosuitecross_gallery` to `pkg_mokosuitecross.xml` — these system plugins were not installed with the package
|
||||
- **Cleanup**: Removed old `src/` directory (pre-rename cruft with `mokojoomcross` files)
|
||||
|
||||
## [01.03.00] --- 2026-06-21
|
||||
|
||||
|
||||
<!-- VERSION: 01.08.61 -->
|
||||
|
||||
All notable changes to MokoSuiteCross will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
<!-- 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 (./LICENSE).
|
||||
|
||||
# FILE INFORMATION
|
||||
DEFGROUP: Template-Joomla
|
||||
INGROUP: Template-Joomla.Documentation
|
||||
REPO: https://github.com/mokoconsulting-tech/Template-Joomla/
|
||||
VERSION: 01.01.00
|
||||
PATH: ./CODE_OF_CONDUCT.md
|
||||
BRIEF: Community expectations and enforcement guidelines
|
||||
NOTE: Adapted with attribution from the Contributor Covenant v2.1
|
||||
-->
|
||||
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone.
|
||||
|
||||
## Our Standards
|
||||
- Be empathetic and kind
|
||||
- Be respectful of differing opinions
|
||||
- Accept constructive feedback
|
||||
- Own mistakes and learn from them
|
||||
|
||||
Unacceptable behavior includes sexualized language/imagery, trolling, harassment, doxing, and other inappropriate conduct.
|
||||
|
||||
## Enforcement
|
||||
Report incidents to **hello@mokoconsulting.tech** or through GitHub Discussions if you prefer a community-visible approach. Private complaints will be reviewed promptly and fairly.
|
||||
|
||||
## Enforcement Guidelines
|
||||
1. **Correction** — Private warning
|
||||
2. **Warning** — Formal warning and limited interaction
|
||||
3. **Temporary Ban** — Time-boxed exclusion
|
||||
4. **Permanent Ban** — Removal from the community
|
||||
|
||||
## Attribution
|
||||
Adapted from the Contributor Covenant v2.1.
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
<!--
|
||||
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 (./LICENSE).
|
||||
|
||||
FILE INFORMATION
|
||||
DEFGROUP: mokoconsulting-tech.Template-Joomla
|
||||
INGROUP: MokoStandards.Governance
|
||||
REPO: https://github.com/mokoconsulting-tech/Template-Joomla
|
||||
VERSION: 01.01.00
|
||||
PATH: /GOVERNANCE.md
|
||||
BRIEF: Project governance rules, roles, and decision process for Template-Joomla
|
||||
-->
|
||||
|
||||
[](https://github.com/mokoconsulting-tech/MokoStandards)
|
||||
|
||||
# Project Governance
|
||||
|
||||
## Overview
|
||||
|
||||
This document defines the governance model for the `Template-Joomla` repository within the
|
||||
`mokoconsulting-tech` organization. It is automatically maintained by
|
||||
[MokoStandards](https://github.com/mokoconsulting-tech/MokoStandards) v04.00.04.
|
||||
|
||||
Full governance policy is defined in the MokoStandards source repository:
|
||||
[docs/policy/GOVERNANCE.md](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/GOVERNANCE.md)
|
||||
|
||||
---
|
||||
|
||||
## Roles and Responsibilities
|
||||
|
||||
### Maintainer
|
||||
|
||||
**GitHub**: @mokoconsulting-tech
|
||||
|
||||
**Authority**: Final decision-making authority on all matters for this repository.
|
||||
|
||||
**Responsibilities**:
|
||||
- Review and merge pull requests
|
||||
- Maintain code quality and standards compliance
|
||||
- Manage releases and versioning
|
||||
- Respond to issues and security reports
|
||||
|
||||
### Contributors
|
||||
|
||||
**Authority**: Submit changes via pull requests.
|
||||
|
||||
**Requirements**:
|
||||
- Read and accept `CODE_OF_CONDUCT.md`
|
||||
- Follow `CONTRIBUTING.md` guidelines
|
||||
|
||||
---
|
||||
|
||||
## Decision-Making
|
||||
|
||||
All changes must be submitted as pull requests. The maintainer (@mokoconsulting-tech)
|
||||
reviews and approves all changes before they are merged.
|
||||
|
||||
### Sole Operator Policy
|
||||
|
||||
This organization operates under a **sole operator** model. The maintainer (@mokoconsulting-tech)
|
||||
is the sole employee and owner and may self-approve pull requests when no second reviewer is
|
||||
available. The following requirements remain mandatory regardless:
|
||||
|
||||
1. **Pull Requests Required** — all changes to protected branches go through a PR.
|
||||
2. **Automated Checks** — all CI checks must pass before merging.
|
||||
3. **Audit Trail** — issues, pull requests, and commit history are preserved.
|
||||
4. **Documentation** — changes are documented in `CHANGELOG.md`.
|
||||
|
||||
See the full policy:
|
||||
[Sole Operator Policy](https://github.com/mokoconsulting-tech/MokoStandards/blob/main/docs/policy/GOVERNANCE.md#sole-operator-policy)
|
||||
|
||||
---
|
||||
|
||||
## Change Management
|
||||
|
||||
| Change Type | Approval | Process |
|
||||
|-------------|----------|---------|
|
||||
| Routine (docs, bug fixes) | Maintainer | PR → CI pass → merge |
|
||||
| Significant (new features) | Maintainer | PR with description → CI pass → merge |
|
||||
| Major (breaking, architecture) | Maintainer | Issue discussion → PR → CI pass → merge |
|
||||
| Emergency (security) | Maintainer | Labelled `EMERGENCY` → immediate merge → post-mortem |
|
||||
|
||||
---
|
||||
|
||||
## Reporting Issues
|
||||
|
||||
- **Bugs / Features**: Open a [GitHub Issue](https://github.com/mokoconsulting-tech/Template-Joomla/issues)
|
||||
- **Security vulnerabilities**: See [SECURITY.md](./SECURITY.md)
|
||||
- **Code of Conduct**: See [CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md)
|
||||
- **Contact**: dev@mokoconsulting.tech
|
||||
|
||||
---
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
| ------------- | ----------------------------------------------- |
|
||||
| Document Type | Policy |
|
||||
| Domain | Governance |
|
||||
| Applies To | mokoconsulting-tech/Template-Joomla |
|
||||
| Jurisdiction | Tennessee, USA |
|
||||
| Maintainer | @mokoconsulting-tech |
|
||||
| Standards | MokoStandards v04.00.04 |
|
||||
| Repo | https://github.com/mokoconsulting-tech/Template-Joomla |
|
||||
| Path | /GOVERNANCE.md |
|
||||
| Status | Active — auto-maintained by MokoStandards |
|
||||
@@ -1,6 +1,6 @@
|
||||
# MokoSuiteCross
|
||||
|
||||
<!-- VERSION: 01.06.00 -->
|
||||
<!-- VERSION: 01.08.61 -->
|
||||
|
||||
Cross-posting Joomla content to social media, email marketing, and chat platforms for Joomla 5/6.
|
||||
|
||||
@@ -16,8 +16,14 @@ MokoSuiteCross automatically publishes your Joomla articles to multiple platform
|
||||
- **Post queue** — Scheduled posting, retry on failure, detailed delivery logs
|
||||
- **Message templates** — Customize post format per platform with placeholders ({title}, {url}, {social}, {short}, {chat}, {email_subject}, {email_body}, {field:xxx})
|
||||
- **Share Content panel** — Per-article fields for platform-optimized text (social, short, chat, email) with image picker
|
||||
- **AI caption generation** — Generate platform-optimized captions using Claude or OpenAI with one click
|
||||
- **Social preview** — AJAX platform mockups (Twitter, Facebook, LinkedIn, Instagram, Mastodon, Bluesky) in the article editor
|
||||
- **Social image generator** — Generate Open Graph images with article title overlay using PHP GD
|
||||
- **Link shortening** — Shorten URLs via Bitly, Rebrandly, or YOURLS with {url_short} placeholder
|
||||
- **Caption rotation** — {random:opt1|opt2|opt3} placeholder for varying evergreen re-shares
|
||||
- **UTM tracking** — Auto-append UTM parameters to shared links with {platform} token
|
||||
- **Post calendar** — Visual monthly calendar view of scheduled and completed cross-posts
|
||||
- **Posting analytics** — Best time to post heatmap with per-service breakdown and recommendations
|
||||
- **Delete from platforms** — Remove cross-posted content when articles are unpublished/trashed (7 platforms)
|
||||
- **Post history** — Track what was posted where, with platform response data
|
||||
- **Evergreen re-sharing** — Automatically re-share articles on a configurable interval
|
||||
@@ -82,7 +88,7 @@ MokoSuiteCross automatically publishes your Joomla articles to multiple platform
|
||||
| RSS Feed | `plg_mokosuitecross_rssfeed` | Implemented |
|
||||
| ActivityPub | `plg_mokosuitecross_activitypub` | Implemented |
|
||||
| Google Business | `plg_mokosuitecross_googlebusiness` | Implemented |
|
||||
| Nostr | `plg_mokosuitecross_nostr` | Stub (WebSocket deferred) |
|
||||
| Nostr | `plg_mokosuitecross_nostr` | Implemented |
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
<!--
|
||||
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: Template-Joomla
|
||||
INGROUP: Template-Joomla.Documentation
|
||||
REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
|
||||
PATH: /SECURITY.md
|
||||
VERSION: 01.01.00
|
||||
BRIEF: Security vulnerability reporting and handling policy
|
||||
-->
|
||||
|
||||
# Security Policy
|
||||
|
||||
## Purpose and Scope
|
||||
|
||||
This document defines the security vulnerability reporting, response, and disclosure policy for this Joomla Plugin template repository. It establishes the authoritative process for responsible disclosure, assessment, remediation, and communication of security issues.
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Security updates are provided for the following versions:
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 01.x.x | :white_check_mark: |
|
||||
| < 01.0 | :x: |
|
||||
|
||||
Only the current major version receives security updates. Users should upgrade to the latest supported version to receive security patches.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
### Where to Report
|
||||
|
||||
**DO NOT** create public GitHub issues for security vulnerabilities.
|
||||
|
||||
Report security vulnerabilities privately to:
|
||||
|
||||
**Email**: `security@mokoconsulting.tech`
|
||||
|
||||
**Subject Line**: `[SECURITY] Template-Joomla - Brief Description`
|
||||
|
||||
### What to Include
|
||||
|
||||
A complete vulnerability report should include:
|
||||
|
||||
1. **Description**: Clear explanation of the vulnerability
|
||||
2. **Impact**: Potential security impact and severity assessment
|
||||
3. **Affected Versions**: Which versions are vulnerable
|
||||
4. **Reproduction Steps**: Detailed steps to reproduce the issue
|
||||
5. **Proof of Concept**: Code, configuration, or demonstration (if applicable)
|
||||
6. **Suggested Fix**: Proposed remediation (if known)
|
||||
7. **Disclosure Timeline**: Your expectations for public disclosure
|
||||
|
||||
### Response Timeline
|
||||
|
||||
* **Initial Response**: Within 3 business days
|
||||
* **Assessment Complete**: Within 7 business days
|
||||
* **Fix Timeline**: Depends on severity (see below)
|
||||
* **Disclosure**: Coordinated with reporter
|
||||
|
||||
## Severity Classification
|
||||
|
||||
Vulnerabilities are classified using the following severity levels:
|
||||
|
||||
### Critical
|
||||
* Remote code execution
|
||||
* Authentication bypass
|
||||
* Data breach or exposure of sensitive information
|
||||
* **Fix Timeline**: 7 days
|
||||
|
||||
### High
|
||||
* Privilege escalation
|
||||
* SQL injection or command injection
|
||||
* Cross-site scripting (XSS) with significant impact
|
||||
* **Fix Timeline**: 14 days
|
||||
|
||||
### Medium
|
||||
* Information disclosure (limited scope)
|
||||
* Denial of service
|
||||
* Security misconfigurations with moderate impact
|
||||
* **Fix Timeline**: 30 days
|
||||
|
||||
### Low
|
||||
* Security best practice violations
|
||||
* Minor information leaks
|
||||
* Issues requiring user interaction or complex preconditions
|
||||
* **Fix Timeline**: 60 days or next release
|
||||
|
||||
## Remediation Process
|
||||
|
||||
1. **Acknowledgment**: Security team confirms receipt and begins investigation
|
||||
2. **Assessment**: Vulnerability is validated, severity assigned, and impact analyzed
|
||||
3. **Development**: Security patch is developed and tested
|
||||
4. **Review**: Patch undergoes security review and validation
|
||||
5. **Release**: Fixed version is released with security advisory
|
||||
6. **Disclosure**: Public disclosure follows coordinated timeline
|
||||
|
||||
## Security Advisories
|
||||
|
||||
Security advisories are published via:
|
||||
|
||||
* GitHub Security Advisories
|
||||
* Release notes and CHANGELOG.md
|
||||
* Email notification to project users (if mailing list is established)
|
||||
|
||||
Advisories include:
|
||||
|
||||
* CVE identifier (if applicable)
|
||||
* Severity rating
|
||||
* Affected versions
|
||||
* Fixed versions
|
||||
* Mitigation steps
|
||||
* Attribution (with reporter consent)
|
||||
|
||||
## Security Best Practices
|
||||
|
||||
For projects using this template:
|
||||
|
||||
### Required Controls
|
||||
|
||||
* Enable GitHub security features (Dependabot, code scanning)
|
||||
* Implement branch protection on `main`
|
||||
* Require code review for all changes
|
||||
* Enforce signed commits (recommended)
|
||||
* Use secrets management (never commit credentials)
|
||||
* Maintain security documentation
|
||||
* Follow secure coding standards defined in MokoStandards
|
||||
|
||||
### Joomla Plugin Security
|
||||
|
||||
* Follow Joomla security best practices
|
||||
* Validate and sanitize all user input
|
||||
* Use Joomla's database API to prevent SQL injection
|
||||
* Properly escape output to prevent XSS
|
||||
* Implement proper access control checks
|
||||
* Use Joomla's session and authentication APIs
|
||||
* Keep Joomla and dependencies up to date
|
||||
|
||||
### CI/CD Security
|
||||
|
||||
* Validate all inputs
|
||||
* Sanitize outputs
|
||||
* Use least privilege access
|
||||
* Pin dependencies with hash verification
|
||||
* Scan for vulnerabilities in dependencies
|
||||
* Audit third-party actions and tools
|
||||
|
||||
#### Automated Security Scanning
|
||||
|
||||
All repositories SHOULD implement:
|
||||
|
||||
**CodeQL Analysis**:
|
||||
* Enabled for PHP and other supported languages
|
||||
* Runs on: push to main, pull requests, weekly schedule
|
||||
* Query sets: `security-extended` and `security-and-quality`
|
||||
* Configuration: `.github/workflows/codeql-analysis.yml`
|
||||
|
||||
**Dependabot Security Updates**:
|
||||
* Weekly scans for vulnerable dependencies
|
||||
* Automated pull requests for security patches
|
||||
* Configuration: `.github/dependabot.yml`
|
||||
|
||||
**Secret Scanning**:
|
||||
* Enabled by default with push protection
|
||||
* Prevents accidental credential commits
|
||||
|
||||
### Dependency Management
|
||||
|
||||
* Keep dependencies up to date
|
||||
* Monitor security advisories for dependencies
|
||||
* Remove unused dependencies
|
||||
* Audit new dependencies before adoption
|
||||
* Document security-critical dependencies
|
||||
|
||||
## Compliance and Governance
|
||||
|
||||
This security policy is aligned with MokoStandards. Deviations require documented justification.
|
||||
|
||||
Security policies are reviewed and updated at least annually or following significant security incidents.
|
||||
|
||||
## Attribution and Recognition
|
||||
|
||||
We acknowledge and appreciate responsible disclosure. With your permission, we will:
|
||||
|
||||
* Credit you in security advisories
|
||||
* List you in CHANGELOG.md for the fix release
|
||||
* Recognize your contribution publicly (if desired)
|
||||
|
||||
## Contact and Escalation
|
||||
|
||||
* **Security Team**: security@mokoconsulting.tech
|
||||
* **Primary Contact**: hello@mokoconsulting.tech
|
||||
* **Escalation**: For urgent matters requiring immediate attention, contact the maintainer directly via GitHub
|
||||
|
||||
## Out of Scope
|
||||
|
||||
The following are explicitly out of scope:
|
||||
|
||||
* Issues in third-party dependencies (report directly to maintainers)
|
||||
* Social engineering attacks
|
||||
* Physical security issues
|
||||
* Denial of service via resource exhaustion without amplification
|
||||
* Issues requiring physical access to systems
|
||||
* Theoretical vulnerabilities without proof of exploitability
|
||||
|
||||
---
|
||||
|
||||
## Metadata
|
||||
|
||||
| Field | Value |
|
||||
| ------------ | ------------------------------------------------------------------------------------------------------------ |
|
||||
| Document | Security Policy |
|
||||
| Path | /SECURITY.md |
|
||||
| Repository | [https://github.com/mokoconsulting-tech/Template-Joomla](https://github.com/mokoconsulting-tech/Template-Joomla) |
|
||||
| Owner | Moko Consulting |
|
||||
| Scope | Security vulnerability handling |
|
||||
| Status | Active |
|
||||
| Effective | 2026-01-16 |
|
||||
|
||||
## Revision History
|
||||
|
||||
| Date | Change Description | Author |
|
||||
| ---------- | ------------------------------------------------- | --------------- |
|
||||
| 2026-01-16 | Initial creation for template repository | Moko Consulting |
|
||||
+15
-1
@@ -15,9 +15,23 @@
|
||||
"php": ">=8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^10.5",
|
||||
"squizlabs/php_codesniffer": "^3.7",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"joomla/coding-standards": "^4.0"
|
||||
"joomla/coding-standards": "dev-3.x-dev"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Joomla\\Component\\MokoSuiteCross\\Administrator\\": "source/packages/com_mokosuitecross/src/",
|
||||
"Joomla\\Component\\MokoSuiteCross\\Site\\": "source/packages/com_mokosuitecross/site/src/",
|
||||
"Joomla\\Plugin\\Content\\MokoSuiteCross\\": "source/packages/plg_content_mokosuitecross/src/",
|
||||
"Joomla\\Plugin\\System\\MokoSuiteCross\\": "source/packages/plg_system_mokosuitecross/src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"MokoSuiteCross\\Tests\\": "tests/"
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"sort-packages": true
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
#
|
||||
# PHPStan configuration for Joomla extension repositories.
|
||||
# Extends the base MokoStandards config and adds Joomla framework class stubs
|
||||
# so PHPStan can resolve Factory, CMSApplication, User, Table, etc.
|
||||
# without requiring a full Joomla installation.
|
||||
|
||||
parameters:
|
||||
level: 5
|
||||
|
||||
paths:
|
||||
- src
|
||||
|
||||
excludePaths:
|
||||
- vendor
|
||||
- node_modules
|
||||
|
||||
# Joomla framework stubs — resolved via the enterprise package from vendor/
|
||||
stubFiles:
|
||||
- vendor/mokoconsulting-tech/enterprise/templates/stubs/joomla.php
|
||||
|
||||
# Suppress errors that are structural in Joomla's service-container architecture
|
||||
ignoreErrors:
|
||||
# Joomla's service-based dependency injection returns mixed from getApplication()
|
||||
- '#Cannot call method .+ on Joomla\\CMS\\Application\\CMSApplication\|null#'
|
||||
# Factory::getX() patterns are safe at runtime even when nullable in stubs
|
||||
- '#Call to static method [a-zA-Z]+\(\) on an interface#'
|
||||
|
||||
reportUnmatchedIgnoredErrors: false
|
||||
checkMissingIterableValueType: false
|
||||
checkGenericClassInNonGenericObjectType: false
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
|
||||
bootstrap="tests/bootstrap.php"
|
||||
colors="true"
|
||||
cacheDirectory=".phpunit.cache"
|
||||
executionOrder="depends,defects"
|
||||
failOnRisky="true"
|
||||
failOnWarning="true">
|
||||
<testsuites>
|
||||
<testsuite name="Unit">
|
||||
<directory>tests/Unit</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<source>
|
||||
<include>
|
||||
<directory>source/packages/com_mokosuitecross/src</directory>
|
||||
<directory>source/packages/plg_content_mokosuitecross/src</directory>
|
||||
<directory>source/packages/plg_system_mokosuitecross/src</directory>
|
||||
</include>
|
||||
</source>
|
||||
</phpunit>
|
||||
@@ -3,6 +3,6 @@
|
||||
; License: GPL-3.0-or-later
|
||||
|
||||
PKG_MOKOSUITECROSS="MokoSuiteCross"
|
||||
PKG_MOKOSUITECROSS_DESCRIPTION="Cross-posting Joomla content to social media, email marketing, and chat platforms. Automatically publish articles to Facebook, X/Twitter, LinkedIn, Mastodon, Bluesky, Mailchimp, Telegram, Discord, and Slack."
|
||||
PKG_MOKOSUITECROSS_DESCRIPTION="Cross-post Joomla articles to 38 platforms including Facebook, Instagram, X/Twitter, LinkedIn, Threads, Mastodon, Bluesky, Nostr, TikTok, YouTube, Pinterest, Reddit, Medium, Telegram, Discord, Slack, Teams, Mailchimp, SendGrid, Brevo, and more. Features scheduled posting, template placeholders, UTM tagging, link shortening, caption rotation, and per-article service selection."
|
||||
PKG_MOKOSUITECROSS_PHP_VERSION_ERROR="MokoSuiteCross requires PHP %s or later."
|
||||
PKG_MOKOSUITECROSS_MIGRATION_DETECTED="Perfect Publisher Pro detected! Navigate to Components → MokoSuiteCross → Dashboard to migrate your settings."
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<access component="com_mokosuitecross">
|
||||
<section name="component">
|
||||
<!-- Joomla core actions -->
|
||||
<action name="core.admin" title="JACTION_ADMIN" />
|
||||
<action name="core.options" title="JACTION_OPTIONS" />
|
||||
<action name="core.manage" title="JACTION_MANAGE" />
|
||||
@@ -8,7 +9,18 @@
|
||||
<action name="core.delete" title="JACTION_DELETE" />
|
||||
<action name="core.edit" title="JACTION_EDIT" />
|
||||
<action name="core.edit.state" title="JACTION_EDITSTATE" />
|
||||
<!-- Component-specific actions -->
|
||||
<action name="mokosuitecross.crosspost" title="COM_MOKOSUITECROSS_ACTION_CROSSPOST" />
|
||||
<action name="mokosuitecross.crosspost.manual" title="COM_MOKOSUITECROSS_ACTION_CROSSPOST_MANUAL" />
|
||||
<action name="mokosuitecross.delete.remote" title="COM_MOKOSUITECROSS_ACTION_DELETE_REMOTE" />
|
||||
<action name="mokosuitecross.services.manage" title="COM_MOKOSUITECROSS_ACTION_SERVICES_MANAGE" />
|
||||
<action name="mokosuitecross.services.credentials" title="COM_MOKOSUITECROSS_ACTION_SERVICES_CREDENTIALS" />
|
||||
<action name="mokosuitecross.templates.manage" title="COM_MOKOSUITECROSS_ACTION_TEMPLATES_MANAGE" />
|
||||
<action name="mokosuitecross.logs.view" title="COM_MOKOSUITECROSS_ACTION_LOGS_VIEW" />
|
||||
<action name="mokosuitecross.logs.purge" title="COM_MOKOSUITECROSS_ACTION_LOGS_PURGE" />
|
||||
<action name="mokosuitecross.queue.manage" title="COM_MOKOSUITECROSS_ACTION_QUEUE_MANAGE" />
|
||||
<action name="mokosuitecross.queue.export" title="COM_MOKOSUITECROSS_ACTION_QUEUE_EXPORT" />
|
||||
<action name="mokosuitecross.dispatch" title="COM_MOKOSUITECROSS_ACTION_DISPATCH" />
|
||||
<action name="mokosuitecross.migrate" title="COM_MOKOSUITECROSS_ACTION_MIGRATE" />
|
||||
</section>
|
||||
</access>
|
||||
|
||||
@@ -120,6 +120,42 @@
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<fieldset name="link_shortening" label="COM_MOKOSUITECROSS_CONFIG_LINK_SHORTENING">
|
||||
<field
|
||||
name="link_shortener"
|
||||
type="list"
|
||||
label="COM_MOKOSUITECROSS_CONFIG_LINK_SHORTENER"
|
||||
description="COM_MOKOSUITECROSS_CONFIG_LINK_SHORTENER_DESC"
|
||||
default="none">
|
||||
<option value="none">COM_MOKOSUITECROSS_CONFIG_LINK_SHORTENER_NONE</option>
|
||||
<option value="bitly">COM_MOKOSUITECROSS_CONFIG_LINK_SHORTENER_BITLY</option>
|
||||
<option value="rebrandly">COM_MOKOSUITECROSS_CONFIG_LINK_SHORTENER_REBRANDLY</option>
|
||||
<option value="yourls">COM_MOKOSUITECROSS_CONFIG_LINK_SHORTENER_YOURLS</option>
|
||||
</field>
|
||||
<field
|
||||
name="link_shortener_api_key"
|
||||
type="text"
|
||||
label="COM_MOKOSUITECROSS_CONFIG_LINK_SHORTENER_API_KEY"
|
||||
description="COM_MOKOSUITECROSS_CONFIG_LINK_SHORTENER_API_KEY_DESC"
|
||||
showon="link_shortener:bitly,rebrandly"
|
||||
/>
|
||||
<field
|
||||
name="link_shortener_yourls_url"
|
||||
type="url"
|
||||
label="COM_MOKOSUITECROSS_CONFIG_LINK_SHORTENER_YOURLS_URL"
|
||||
description="COM_MOKOSUITECROSS_CONFIG_LINK_SHORTENER_YOURLS_URL_DESC"
|
||||
hint="https://short.example.com/yourls-api.php"
|
||||
showon="link_shortener:yourls"
|
||||
/>
|
||||
<field
|
||||
name="link_shortener_yourls_token"
|
||||
type="text"
|
||||
label="COM_MOKOSUITECROSS_CONFIG_LINK_SHORTENER_YOURLS_TOKEN"
|
||||
description="COM_MOKOSUITECROSS_CONFIG_LINK_SHORTENER_YOURLS_TOKEN_DESC"
|
||||
showon="link_shortener:yourls"
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<fieldset name="evergreen" label="COM_MOKOSUITECROSS_CONFIG_EVERGREEN">
|
||||
<field
|
||||
name="evergreen_enabled"
|
||||
@@ -191,6 +227,95 @@
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<fieldset name="ai" label="COM_MOKOSUITECROSS_CONFIG_AI">
|
||||
<field
|
||||
name="ai_provider"
|
||||
type="list"
|
||||
label="COM_MOKOSUITECROSS_CONFIG_AI_PROVIDER"
|
||||
description="COM_MOKOSUITECROSS_CONFIG_AI_PROVIDER_DESC"
|
||||
default="none">
|
||||
<option value="none">COM_MOKOSUITECROSS_CONFIG_AI_PROVIDER_NONE</option>
|
||||
<option value="claude">COM_MOKOSUITECROSS_CONFIG_AI_PROVIDER_CLAUDE</option>
|
||||
<option value="openai">COM_MOKOSUITECROSS_CONFIG_AI_PROVIDER_OPENAI</option>
|
||||
</field>
|
||||
<field
|
||||
name="ai_api_key"
|
||||
type="text"
|
||||
label="COM_MOKOSUITECROSS_CONFIG_AI_API_KEY"
|
||||
description="COM_MOKOSUITECROSS_CONFIG_AI_API_KEY_DESC"
|
||||
showon="ai_provider:claude,openai"
|
||||
/>
|
||||
<field
|
||||
name="ai_model"
|
||||
type="text"
|
||||
label="COM_MOKOSUITECROSS_CONFIG_AI_MODEL"
|
||||
description="COM_MOKOSUITECROSS_CONFIG_AI_MODEL_DESC"
|
||||
hint="claude-haiku-4-5 / gpt-4o-mini"
|
||||
showon="ai_provider:claude,openai"
|
||||
/>
|
||||
<field
|
||||
name="ai_tone"
|
||||
type="list"
|
||||
label="COM_MOKOSUITECROSS_CONFIG_AI_TONE"
|
||||
description="COM_MOKOSUITECROSS_CONFIG_AI_TONE_DESC"
|
||||
default="professional"
|
||||
showon="ai_provider:claude,openai">
|
||||
<option value="professional">COM_MOKOSUITECROSS_CONFIG_AI_TONE_PROFESSIONAL</option>
|
||||
<option value="friendly">COM_MOKOSUITECROSS_CONFIG_AI_TONE_FRIENDLY</option>
|
||||
<option value="casual">COM_MOKOSUITECROSS_CONFIG_AI_TONE_CASUAL</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
|
||||
<fieldset name="social_image" label="COM_MOKOSUITECROSS_CONFIG_SOCIAL_IMAGE">
|
||||
<field
|
||||
name="social_image_enabled"
|
||||
type="radio"
|
||||
label="COM_MOKOSUITECROSS_CONFIG_SOCIAL_IMAGE_ENABLED"
|
||||
description="COM_MOKOSUITECROSS_CONFIG_SOCIAL_IMAGE_ENABLED_DESC"
|
||||
default="0"
|
||||
class="btn-group">
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
<field
|
||||
name="social_image_bg_color"
|
||||
type="text"
|
||||
label="COM_MOKOSUITECROSS_CONFIG_SOCIAL_IMAGE_BG_COLOR"
|
||||
description="COM_MOKOSUITECROSS_CONFIG_SOCIAL_IMAGE_BG_COLOR_DESC"
|
||||
default="#1a1a2e"
|
||||
showon="social_image_enabled:1"
|
||||
/>
|
||||
<field
|
||||
name="social_image_text_color"
|
||||
type="text"
|
||||
label="COM_MOKOSUITECROSS_CONFIG_SOCIAL_IMAGE_TEXT_COLOR"
|
||||
description="COM_MOKOSUITECROSS_CONFIG_SOCIAL_IMAGE_TEXT_COLOR_DESC"
|
||||
default="#ffffff"
|
||||
showon="social_image_enabled:1"
|
||||
/>
|
||||
<field
|
||||
name="social_image_font_size"
|
||||
type="number"
|
||||
label="COM_MOKOSUITECROSS_CONFIG_SOCIAL_IMAGE_FONT_SIZE"
|
||||
description="COM_MOKOSUITECROSS_CONFIG_SOCIAL_IMAGE_FONT_SIZE_DESC"
|
||||
default="48"
|
||||
min="24"
|
||||
max="96"
|
||||
showon="social_image_enabled:1"
|
||||
/>
|
||||
<field
|
||||
name="social_image_show_site_name"
|
||||
type="radio"
|
||||
label="COM_MOKOSUITECROSS_CONFIG_SOCIAL_IMAGE_SHOW_SITE_NAME"
|
||||
description="COM_MOKOSUITECROSS_CONFIG_SOCIAL_IMAGE_SHOW_SITE_NAME_DESC"
|
||||
default="1"
|
||||
class="btn-group"
|
||||
showon="social_image_enabled:1">
|
||||
<option value="1">JYES</option>
|
||||
<option value="0">JNO</option>
|
||||
</field>
|
||||
</fieldset>
|
||||
|
||||
<fieldset name="category_rules" label="COM_MOKOSUITECROSS_CONFIG_CATEGORY_RULES">
|
||||
<field
|
||||
name="category_rules_note"
|
||||
@@ -199,4 +324,19 @@
|
||||
description="COM_MOKOSUITECROSS_CONFIG_CATEGORY_RULES_NOTE_DESC"
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
<fieldset
|
||||
name="permissions"
|
||||
label="JCONFIG_PERMISSIONS_LABEL"
|
||||
description="JCONFIG_PERMISSIONS_DESC">
|
||||
<field
|
||||
name="rules"
|
||||
type="rules"
|
||||
label="JCONFIG_PERMISSIONS_LABEL"
|
||||
component="com_mokosuitecross"
|
||||
filter="rules"
|
||||
validate="rules"
|
||||
section="component"
|
||||
/>
|
||||
</fieldset>
|
||||
</config>
|
||||
|
||||
@@ -5,6 +5,20 @@
|
||||
COM_MOKOSUITECROSS="MokoSuiteCross"
|
||||
COM_MOKOSUITECROSS_DESCRIPTION="Cross-posting Joomla content to social media, email marketing, and chat platforms"
|
||||
|
||||
; ACL Actions
|
||||
COM_MOKOSUITECROSS_ACTION_CROSSPOST="Cross-Post Articles"
|
||||
COM_MOKOSUITECROSS_ACTION_CROSSPOST_MANUAL="Manually Create Posts"
|
||||
COM_MOKOSUITECROSS_ACTION_DELETE_REMOTE="Delete from Remote Platforms"
|
||||
COM_MOKOSUITECROSS_ACTION_SERVICES_MANAGE="Manage Services"
|
||||
COM_MOKOSUITECROSS_ACTION_SERVICES_CREDENTIALS="View Service Credentials"
|
||||
COM_MOKOSUITECROSS_ACTION_TEMPLATES_MANAGE="Manage Templates"
|
||||
COM_MOKOSUITECROSS_ACTION_LOGS_VIEW="View Activity Logs"
|
||||
COM_MOKOSUITECROSS_ACTION_LOGS_PURGE="Purge Activity Logs"
|
||||
COM_MOKOSUITECROSS_ACTION_QUEUE_MANAGE="Manage Post Queue"
|
||||
COM_MOKOSUITECROSS_ACTION_QUEUE_EXPORT="Export Post Queue"
|
||||
COM_MOKOSUITECROSS_ACTION_DISPATCH="Trigger API Dispatch"
|
||||
COM_MOKOSUITECROSS_ACTION_MIGRATE="Run Migration"
|
||||
|
||||
; Submenu
|
||||
COM_MOKOSUITECROSS_SUBMENU_DASHBOARD="Dashboard"
|
||||
COM_MOKOSUITECROSS_SUBMENU_POSTS="Post Queue"
|
||||
@@ -520,7 +534,76 @@ COM_MOKOSUITECROSS_DISPATCH_INVALID_SERVICES="service_ids must be a non-empty ar
|
||||
COM_MOKOSUITECROSS_DISPATCH_ARTICLE_NOT_FOUND="Article not found."
|
||||
COM_MOKOSUITECROSS_DISPATCH_NO_SERVICES="No enabled services found matching the request."
|
||||
|
||||
; Link Shortening
|
||||
COM_MOKOSUITECROSS_CONFIG_LINK_SHORTENING="Link Shortening"
|
||||
COM_MOKOSUITECROSS_CONFIG_LINK_SHORTENER="Link Shortener"
|
||||
COM_MOKOSUITECROSS_CONFIG_LINK_SHORTENER_DESC="Select a link shortening service. Shortened URLs are available via the {url_short} placeholder in templates."
|
||||
COM_MOKOSUITECROSS_CONFIG_LINK_SHORTENER_NONE="None (disabled)"
|
||||
COM_MOKOSUITECROSS_CONFIG_LINK_SHORTENER_BITLY="Bitly"
|
||||
COM_MOKOSUITECROSS_CONFIG_LINK_SHORTENER_REBRANDLY="Rebrandly"
|
||||
COM_MOKOSUITECROSS_CONFIG_LINK_SHORTENER_YOURLS="YOURLS (self-hosted)"
|
||||
COM_MOKOSUITECROSS_CONFIG_LINK_SHORTENER_API_KEY="API Key"
|
||||
COM_MOKOSUITECROSS_CONFIG_LINK_SHORTENER_API_KEY_DESC="API key for Bitly or Rebrandly."
|
||||
COM_MOKOSUITECROSS_CONFIG_LINK_SHORTENER_YOURLS_URL="YOURLS API URL"
|
||||
COM_MOKOSUITECROSS_CONFIG_LINK_SHORTENER_YOURLS_URL_DESC="Full URL to your YOURLS API endpoint (e.g. https://short.example.com/yourls-api.php)."
|
||||
COM_MOKOSUITECROSS_CONFIG_LINK_SHORTENER_YOURLS_TOKEN="YOURLS Signature Token"
|
||||
COM_MOKOSUITECROSS_CONFIG_LINK_SHORTENER_YOURLS_TOKEN_DESC="Secret signature token from your YOURLS installation."
|
||||
|
||||
; AI Caption Generation
|
||||
COM_MOKOSUITECROSS_CONFIG_AI="AI Caption Generation"
|
||||
COM_MOKOSUITECROSS_CONFIG_AI_PROVIDER="AI Provider"
|
||||
COM_MOKOSUITECROSS_CONFIG_AI_PROVIDER_DESC="Select an AI provider to generate cross-post captions from article content. The API key is stored in Joomla component params (encrypted at rest)."
|
||||
COM_MOKOSUITECROSS_CONFIG_AI_PROVIDER_NONE="None (disabled)"
|
||||
COM_MOKOSUITECROSS_CONFIG_AI_PROVIDER_CLAUDE="Anthropic Claude"
|
||||
COM_MOKOSUITECROSS_CONFIG_AI_PROVIDER_OPENAI="OpenAI"
|
||||
COM_MOKOSUITECROSS_CONFIG_AI_API_KEY="API Key"
|
||||
COM_MOKOSUITECROSS_CONFIG_AI_API_KEY_DESC="API key for the selected AI provider."
|
||||
COM_MOKOSUITECROSS_CONFIG_AI_MODEL="Model"
|
||||
COM_MOKOSUITECROSS_CONFIG_AI_MODEL_DESC="AI model to use. Leave blank for the default (Claude Haiku 4.5 or GPT-4o Mini)."
|
||||
COM_MOKOSUITECROSS_CONFIG_AI_TONE="Tone"
|
||||
COM_MOKOSUITECROSS_CONFIG_AI_TONE_DESC="The writing tone for generated captions."
|
||||
COM_MOKOSUITECROSS_CONFIG_AI_TONE_PROFESSIONAL="Professional"
|
||||
COM_MOKOSUITECROSS_CONFIG_AI_TONE_FRIENDLY="Friendly"
|
||||
COM_MOKOSUITECROSS_CONFIG_AI_TONE_CASUAL="Casual"
|
||||
COM_MOKOSUITECROSS_AI_GENERATE="Generate with AI"
|
||||
COM_MOKOSUITECROSS_AI_GENERATE_DESC="Generate platform-optimized captions from the article content using AI."
|
||||
COM_MOKOSUITECROSS_AI_GENERATING="Generating captions..."
|
||||
COM_MOKOSUITECROSS_AI_GENERATED="AI captions generated successfully."
|
||||
COM_MOKOSUITECROSS_AI_ERROR="AI generation failed: %s"
|
||||
COM_MOKOSUITECROSS_AI_NOT_CONFIGURED="AI is not configured. Go to Options to set up a provider and API key."
|
||||
|
||||
; Analytics
|
||||
COM_MOKOSUITECROSS_SUBMENU_ANALYTICS="Analytics"
|
||||
COM_MOKOSUITECROSS_ANALYTICS_PERIOD="Time Period"
|
||||
COM_MOKOSUITECROSS_ANALYTICS_SERVICE_FILTER="Service"
|
||||
COM_MOKOSUITECROSS_ANALYTICS_ALL_SERVICES="All Services"
|
||||
COM_MOKOSUITECROSS_ANALYTICS_BEST_TIMES="Best Times to Post"
|
||||
COM_MOKOSUITECROSS_ANALYTICS_HEATMAP="Engagement Heatmap"
|
||||
COM_MOKOSUITECROSS_ANALYTICS_HOURLY="Hourly Distribution"
|
||||
COM_MOKOSUITECROSS_ANALYTICS_DAILY="Day of Week Distribution"
|
||||
COM_MOKOSUITECROSS_ANALYTICS_NO_DATA="Not enough posting data to generate recommendations. Post at least 3 times per time slot over the selected period."
|
||||
COM_MOKOSUITECROSS_ANALYTICS_POSTS_SUCCESS="%d of %d successful"
|
||||
COM_MOKOSUITECROSS_ANALYTICS_DAY_SUN="Sun"
|
||||
COM_MOKOSUITECROSS_ANALYTICS_DAY_MON="Mon"
|
||||
COM_MOKOSUITECROSS_ANALYTICS_DAY_TUE="Tue"
|
||||
COM_MOKOSUITECROSS_ANALYTICS_DAY_WED="Wed"
|
||||
COM_MOKOSUITECROSS_ANALYTICS_DAY_THU="Thu"
|
||||
COM_MOKOSUITECROSS_ANALYTICS_DAY_FRI="Fri"
|
||||
COM_MOKOSUITECROSS_ANALYTICS_DAY_SAT="Sat"
|
||||
COM_MOKOSUITECROSS_ANALYTICS_LEGEND_HIGH="High success rate"
|
||||
COM_MOKOSUITECROSS_ANALYTICS_LEGEND_MEDIUM="Medium success rate"
|
||||
COM_MOKOSUITECROSS_ANALYTICS_LEGEND_LOW="Low success rate"
|
||||
COM_MOKOSUITECROSS_ANALYTICS_LEGEND_NONE="No data"
|
||||
COM_MOKOSUITECROSS_PERIOD_180_DAYS="Last 180 days"
|
||||
COM_MOKOSUITECROSS_PERIOD_365_DAYS="Last 365 days"
|
||||
|
||||
; Category Rules
|
||||
COM_MOKOSUITECROSS_CONFIG_CATEGORY_RULES="Category Rules"
|
||||
COM_MOKOSUITECROSS_CONFIG_CATEGORY_RULES_NOTE="Category Routing"
|
||||
COM_MOKOSUITECROSS_CONFIG_CATEGORY_RULES_NOTE_DESC="Category routing rules let you map Joomla categories to specific cross-post services. When rules exist for a category, only those services receive posts. When no rules exist, all services are used (default behaviour). Rules are managed in the database table #__mokosuitecross_category_rules. A full admin UI will be added in a future release."
|
||||
|
||||
; Calendar View
|
||||
COM_MOKOSUITECROSS_CALENDAR_PREV_MONTH="Previous"
|
||||
COM_MOKOSUITECROSS_CALENDAR_NEXT_MONTH="Next"
|
||||
COM_MOKOSUITECROSS_CALENDAR_TODAY="Today"
|
||||
COM_MOKOSUITECROSS_SUBMENU_CALENDAR="Post Calendar"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension type="component" method="upgrade">
|
||||
<name>com_mokosuitecross</name>
|
||||
<version>01.06.00</version>
|
||||
<version>01.08.61</version>
|
||||
<creationDate>2026-05-28</creationDate>
|
||||
<author>Moko Consulting</author>
|
||||
<authorEmail>hello@mokoconsulting.tech</authorEmail>
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
; MokoSuiteCross — Site Frontend Language File
|
||||
; MokoSuiteCross -- Site Frontend Language File
|
||||
; Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
; License: GPL-3.0-or-later
|
||||
|
||||
COM_MOKOSUITECROSS="MokoSuiteCross"
|
||||
COM_MOKOSUITECROSS_POSTS_LIST_TITLE="Cross-Posted Content"
|
||||
COM_MOKOSUITECROSS_POST_DETAIL_TITLE="Cross-Post History"
|
||||
COM_MOKOSUITECROSS_COLUMN_ARTICLE="Article"
|
||||
COM_MOKOSUITECROSS_COLUMN_PLATFORMS="Platforms"
|
||||
COM_MOKOSUITECROSS_COLUMN_LAST_POSTED="Last Posted"
|
||||
COM_MOKOSUITECROSS_COLUMN_STATUS="Status"
|
||||
COM_MOKOSUITECROSS_COLUMN_POSTED_DATE="Posted Date"
|
||||
COM_MOKOSUITECROSS_COLUMN_LINK="Platform Link"
|
||||
COM_MOKOSUITECROSS_NO_POSTS="No cross-posted content found."
|
||||
|
||||
@@ -17,5 +17,5 @@ use Joomla\CMS\MVC\Controller\BaseController;
|
||||
|
||||
class DisplayController extends BaseController
|
||||
{
|
||||
protected $default_view = 'post';
|
||||
protected $default_view = 'posts';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package MokoSuiteCross
|
||||
* @subpackage com_mokosuitecross
|
||||
* @author Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\MokoSuiteCross\Site\Model;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\MVC\Model\BaseDatabaseModel;
|
||||
|
||||
class PostModel extends BaseDatabaseModel
|
||||
{
|
||||
public function getArticle(int $articleId): ?object
|
||||
{
|
||||
$db = $this->getDatabase();
|
||||
$user = Factory::getApplication()->getIdentity();
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select('a.id, a.title, a.alias, a.catid, a.access')
|
||||
->from($db->quoteName('#__content', 'a'))
|
||||
->where('a.id = ' . (int) $articleId)
|
||||
->where('a.state = 1');
|
||||
|
||||
$groups = $user->getAuthorisedViewLevels();
|
||||
$query->where('a.access IN (' . implode(',', array_map('intval', $groups)) . ')');
|
||||
|
||||
$db->setQuery($query);
|
||||
|
||||
return $db->loadObject() ?: null;
|
||||
}
|
||||
|
||||
public function getPosts(int $articleId): array
|
||||
{
|
||||
$db = $this->getDatabase();
|
||||
|
||||
$query = $db->getQuery(true)
|
||||
->select([
|
||||
'p.id',
|
||||
'p.status',
|
||||
'p.platform_post_id',
|
||||
'p.posted_at',
|
||||
'p.error_message',
|
||||
'p.created',
|
||||
's.title AS service_title',
|
||||
's.service_type',
|
||||
])
|
||||
->from($db->quoteName('#__mokosuitecross_posts', 'p'))
|
||||
->join('INNER', $db->quoteName('#__mokosuitecross_services', 's') . ' ON s.id = p.service_id')
|
||||
->where('p.article_id = ' . (int) $articleId)
|
||||
->order('p.created DESC');
|
||||
|
||||
$db->setQuery($query, 0, 50);
|
||||
|
||||
return $db->loadObjectList() ?: [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package MokoSuiteCross
|
||||
* @subpackage com_mokosuitecross
|
||||
* @author Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\MokoSuiteCross\Site\Model;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\MVC\Model\ListModel;
|
||||
|
||||
class PostsModel extends ListModel
|
||||
{
|
||||
protected function getListQuery()
|
||||
{
|
||||
$db = $this->getDatabase();
|
||||
$user = Factory::getApplication()->getIdentity();
|
||||
$query = $db->getQuery(true);
|
||||
|
||||
$query->select([
|
||||
'a.id AS article_id',
|
||||
'a.title AS article_title',
|
||||
'a.alias AS article_alias',
|
||||
'a.catid',
|
||||
'MAX(p.posted_at) AS last_posted',
|
||||
'COUNT(p.id) AS post_count',
|
||||
'GROUP_CONCAT(DISTINCT s.service_type ORDER BY s.service_type SEPARATOR \',\') AS service_types',
|
||||
])
|
||||
->from($db->quoteName('#__mokosuitecross_posts', 'p'))
|
||||
->join('INNER', $db->quoteName('#__content', 'a') . ' ON a.id = p.article_id')
|
||||
->join('INNER', $db->quoteName('#__mokosuitecross_services', 's') . ' ON s.id = p.service_id')
|
||||
->where('p.status = ' . $db->quote('posted'))
|
||||
->where('a.state = 1');
|
||||
|
||||
// Access filtering
|
||||
$groups = $user->getAuthorisedViewLevels();
|
||||
$query->where('a.access IN (' . implode(',', array_map('intval', $groups)) . ')');
|
||||
|
||||
$query->group('a.id, a.title, a.alias, a.catid')
|
||||
->order('last_posted DESC');
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package MokoSuiteCross
|
||||
* @subpackage com_mokosuitecross
|
||||
* @author Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\MokoSuiteCross\Site\View\Post;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Factory;
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
protected $article;
|
||||
protected $posts;
|
||||
|
||||
public function display($tpl = null): void
|
||||
{
|
||||
$articleId = Factory::getApplication()->getInput()->getInt('id', 0);
|
||||
$model = $this->getModel();
|
||||
$this->article = $model->getArticle($articleId);
|
||||
$this->posts = $model->getPosts($articleId);
|
||||
|
||||
parent::display($tpl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package MokoSuiteCross
|
||||
* @subpackage com_mokosuitecross
|
||||
* @author Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Joomla\Component\MokoSuiteCross\Site\View\Posts;
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\MVC\View\HtmlView as BaseHtmlView;
|
||||
|
||||
class HtmlView extends BaseHtmlView
|
||||
{
|
||||
protected $items;
|
||||
protected $pagination;
|
||||
|
||||
public function display($tpl = null): void
|
||||
{
|
||||
$this->items = $this->get('Items');
|
||||
$this->pagination = $this->get('Pagination');
|
||||
|
||||
parent::display($tpl);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package MokoSuiteCross
|
||||
* @subpackage com_mokosuitecross
|
||||
* @author Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
$statusClasses = [
|
||||
'posted' => 'bg-success',
|
||||
'failed' => 'bg-danger',
|
||||
'permanently_failed' => 'bg-danger',
|
||||
'queued' => 'bg-warning text-dark',
|
||||
'posting' => 'bg-info',
|
||||
'scheduled' => 'bg-primary',
|
||||
'deleted' => 'bg-secondary',
|
||||
'cancelled' => 'bg-secondary',
|
||||
];
|
||||
|
||||
?>
|
||||
<div class="com-mokosuitecross-post">
|
||||
<?php if (!$this->article) : ?>
|
||||
<div class="alert alert-warning">
|
||||
<?php echo Text::_('COM_MOKOSUITECROSS_NO_POSTS'); ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<h2><?php echo Text::_('COM_MOKOSUITECROSS_POST_DETAIL_TITLE'); ?></h2>
|
||||
<p>
|
||||
<strong><?php echo $this->escape($this->article->title); ?></strong>
|
||||
</p>
|
||||
|
||||
<?php if (empty($this->posts)) : ?>
|
||||
<div class="alert alert-info">
|
||||
<?php echo Text::_('COM_MOKOSUITECROSS_NO_POSTS'); ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php echo Text::_('COM_MOKOSUITECROSS_HEADING_SERVICE'); ?></th>
|
||||
<th><?php echo Text::_('COM_MOKOSUITECROSS_COLUMN_STATUS'); ?></th>
|
||||
<th><?php echo Text::_('COM_MOKOSUITECROSS_COLUMN_POSTED_DATE'); ?></th>
|
||||
<th><?php echo Text::_('COM_MOKOSUITECROSS_COLUMN_LINK'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($this->posts as $post) : ?>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="badge bg-secondary"><?php echo $this->escape($post->service_type); ?></span>
|
||||
<?php echo $this->escape($post->service_title); ?>
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge <?php echo $statusClasses[$post->status] ?? 'bg-secondary'; ?>">
|
||||
<?php echo $this->escape(ucfirst($post->status)); ?>
|
||||
</span>
|
||||
</td>
|
||||
<td><?php echo $post->posted_at ? $this->escape($post->posted_at) : $this->escape($post->created); ?></td>
|
||||
<td>
|
||||
<?php if (!empty($post->platform_post_id)) : ?>
|
||||
<span class="text-muted small"><?php echo $this->escape($post->platform_post_id); ?></span>
|
||||
<?php else : ?>
|
||||
—
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
|
||||
<a href="<?php echo Route::_('index.php?option=com_mokosuitecross&view=posts'); ?>" class="btn btn-secondary">
|
||||
← <?php echo Text::_('COM_MOKOSUITECROSS_POSTS_LIST_TITLE'); ?>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* @package MokoSuiteCross
|
||||
* @subpackage com_mokosuitecross
|
||||
* @author Moko Consulting <hello@mokoconsulting.tech>
|
||||
* @copyright Copyright (C) 2026 Moko Consulting. All rights reserved.
|
||||
* @license GNU General Public License version 3 or later; see LICENSE
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
defined('_JEXEC') or die;
|
||||
|
||||
use Joomla\CMS\Language\Text;
|
||||
use Joomla\CMS\Router\Route;
|
||||
|
||||
?>
|
||||
<div class="com-mokosuitecross-posts">
|
||||
<h2><?php echo Text::_('COM_MOKOSUITECROSS_POSTS_LIST_TITLE'); ?></h2>
|
||||
|
||||
<?php if (empty($this->items)) : ?>
|
||||
<div class="alert alert-info">
|
||||
<?php echo Text::_('COM_MOKOSUITECROSS_NO_POSTS'); ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php echo Text::_('COM_MOKOSUITECROSS_COLUMN_ARTICLE'); ?></th>
|
||||
<th><?php echo Text::_('COM_MOKOSUITECROSS_COLUMN_PLATFORMS'); ?></th>
|
||||
<th><?php echo Text::_('COM_MOKOSUITECROSS_COLUMN_LAST_POSTED'); ?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($this->items as $item) : ?>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="<?php echo Route::_('index.php?option=com_mokosuitecross&view=post&id=' . (int) $item->article_id); ?>">
|
||||
<?php echo $this->escape($item->article_title); ?>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<?php
|
||||
$types = explode(',', $item->service_types ?? '');
|
||||
foreach ($types as $type) :
|
||||
$type = trim($type);
|
||||
if (empty($type)) continue;
|
||||
?>
|
||||
<span class="badge bg-secondary"><?php echo $this->escape($type); ?></span>
|
||||
<?php endforeach; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php echo $item->last_posted ? $this->escape($item->last_posted) : '—'; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<?php if ($this->pagination->pagesTotal > 1) : ?>
|
||||
<div class="com-mokosuitecross-posts__pagination">
|
||||
<?php echo $this->pagination->getListFooter(); ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.05 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.07 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.08 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.09 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.10 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.11 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.12 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.13 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.14 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.15 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.16 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.17 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.19 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.20 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.21 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.22 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.23 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.24 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.25 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.26 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.27 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.28 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.29 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.30 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.31 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.32 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.33 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.34 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.35 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.36 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.37 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.38 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.39 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.40 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.41 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.43 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.44 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.45 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.46 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.47 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.49 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.50 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.51 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.52 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.53 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.54 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.55 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.56 — no schema changes */
|
||||
@@ -0,0 +1 @@
|
||||
/* 01.08.57 — no schema changes */
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user